From 7c81414930feb73f36e72781e219517faa3f18f4 Mon Sep 17 00:00:00 2001 From: QIvan Date: Fri, 26 Apr 2019 20:36:24 +0300 Subject: [PATCH] [Java] add an util method for buffer's pretty hex dump --- .../main/java/org/agrona/PrintBufferUtil.java | 459 ++++++++++++++++++ build.gradle | 19 +- 2 files changed, 472 insertions(+), 6 deletions(-) create mode 100644 agrona/src/main/java/org/agrona/PrintBufferUtil.java diff --git a/agrona/src/main/java/org/agrona/PrintBufferUtil.java b/agrona/src/main/java/org/agrona/PrintBufferUtil.java new file mode 100644 index 000000000..399569df4 --- /dev/null +++ b/agrona/src/main/java/org/agrona/PrintBufferUtil.java @@ -0,0 +1,459 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package org.agrona; + + +import java.util.Formatter; + + +/** + * Useful utils for hex dump of the agrona's buffers. + * + * This is code from the Netty project adopted to {@link MutableDirectBuffer} + */ +public final class PrintBufferUtil +{ + public static final String NEWLINE; + public static final String EMPTY_STRING = ""; + private static final String[] BYTE2HEX_PAD = new String[256]; + + static + { + // Determine the newline character of the current platform. + String newLine; + + try (Formatter formatter = new Formatter()) + { + newLine = formatter.format("%n").toString(); + } + catch (final Exception e) + { + // Should not reach here, but just in case. + newLine = "\n"; + } + + NEWLINE = newLine; + + // Generate the lookup table that converts a byte into a 2-digit hexadecimal integer. + int i; + for (i = 0; i < 10; i++) + { + final StringBuilder builder = new StringBuilder(2); + builder.append('0'); + builder.append(i); + BYTE2HEX_PAD[i] = builder.toString(); + } + for (; i < 16; i++) + { + final StringBuilder builder = new StringBuilder(2); + final char c = (char)('a' + i - 10); + builder.append('0'); + builder.append(c); + BYTE2HEX_PAD[i] = builder.toString(); + } + for (; i < BYTE2HEX_PAD.length; i++) + { + final StringBuilder builder = new StringBuilder(2); + builder.append(Integer.toHexString(i)); + final String str = builder.toString(); + BYTE2HEX_PAD[i] = str; + } + } + + + /** + * Returns a hex dump + * of the specified buffer's readable bytes. + * + * @param buffer dumped buffer + * @return hex dump in a string representation + */ + public static String hexDump(final MutableDirectBuffer buffer) + { + return hexDump(buffer, 0, buffer.capacity()); + } + + + /** + * Returns a hex dump + * of the specified buffer's sub-region. + * + * @param buffer dumped buffer + * @param fromIndex where should we start to print + * @param length how much should we print + * @return hex dump in a string representation + */ + public static String hexDump(final MutableDirectBuffer buffer, final int fromIndex, final int length) + { + return HexUtil.hexDump(buffer, fromIndex, length); + } + + /** + * Returns a hex dump + * of the specified byte array. + * + * @param array dumped array + * @return hex dump in a string representation + */ + public static String hexDump(final byte[] array) + { + return hexDump(array, 0, array.length); + } + + /** + * Returns a hex dump + * of the specified byte array's sub-region. + * + * @param array dumped array + * @param fromIndex where should we start to print + * @param length how much should we print + * @return hex dump in a string representation + */ + public static String hexDump(final byte[] array, final int fromIndex, final int length) + { + return HexUtil.hexDump(array, fromIndex, length); + } + + + /** + * Returns a multi-line hexadecimal dump of the specified {@link MutableDirectBuffer} that is easy to read by humans. + * + * @param buffer dumped buffer + * @return pretty hex dump in a string representation + */ + public static String prettyHexDump(final MutableDirectBuffer buffer) + { + return prettyHexDump(buffer, 0, buffer.capacity()); + } + + /** + * Returns a multi-line hexadecimal dump of the specified {@link MutableDirectBuffer} that is easy to read by humans, + * starting at the given {@code offset} using the given {@code length}. + * + * @param buffer dumped buffer + * @param offset where should we start to print + * @param length how much should we print + * @return pretty hex dump in a string representation + */ + public static String prettyHexDump(final MutableDirectBuffer buffer, final int offset, final int length) + { + return HexUtil.prettyHexDump(buffer, offset, length); + } + + /** + * Appends the prettified multi-line hexadecimal dump of the specified {@link MutableDirectBuffer} to the specified + * {@link StringBuilder} that is easy to read by humans. + * + * @param dump where should we append string representation of the buffer + * @param buffer dumped buffer + */ + public static void appendPrettyHexDump(final StringBuilder dump, final MutableDirectBuffer buffer) + { + appendPrettyHexDump(dump, buffer, 0, buffer.capacity()); + } + + /** + * Appends the prettified multi-line hexadecimal dump of the specified {@link MutableDirectBuffer} to the specified + * {@link StringBuilder} that is easy to read by humans, starting at the given {@code offset} using + * the given {@code length}. + * + * @param dump where should we append string representation of the buffer + * @param buffer dumped buffer + * @param offset where should we start to print + * @param length how much should we print + */ + public static void appendPrettyHexDump(final StringBuilder dump, final MutableDirectBuffer buffer, final int offset, + final int length) + { + HexUtil.appendPrettyHexDump(dump, buffer, offset, length); + } + + /** + * Converts the specified byte value into a 2-digit hexadecimal integer. + * + * @param value converted value + * @return hex representation of the value + */ + public static String byteToHexStringPadded(final int value) + { + return BYTE2HEX_PAD[value & 0xff]; + } + + /* Separate class so that the expensive static initialization is only done when needed */ + private static final class HexUtil + { + + private static final char[] BYTE2CHAR = new char[256]; + private static final char[] HEXDUMP_TABLE = new char[256 * 4]; + private static final String[] HEXPADDING = new String[16]; + private static final String[] HEXDUMP_ROWPREFIXES = new String[65536 >>> 4]; + private static final String[] BYTE2HEX = new String[256]; + private static final String[] BYTEPADDING = new String[16]; + + static + { + final char[] digits = "0123456789abcdef".toCharArray(); + for (int i = 0; i < 256; i++) + { + HEXDUMP_TABLE[i << 1] = digits[i >>> 4 & 0x0F]; + HEXDUMP_TABLE[(i << 1) + 1] = digits[i & 0x0F]; + } + + int i; + + // Generate the lookup table for hex dump paddings + for (i = 0; i < HEXPADDING.length; i++) + { + final int padding = HEXPADDING.length - i; + final StringBuilder buf = new StringBuilder(padding * 3); + for (int j = 0; j < padding; j++) + { + buf.append(" "); + } + HEXPADDING[i] = buf.toString(); + } + + // Generate the lookup table for the start-offset header in each row (up to 64KiB). + for (i = 0; i < HEXDUMP_ROWPREFIXES.length; i++) + { + final StringBuilder buf = new StringBuilder(12); + buf.append(NEWLINE); + buf.append(Long.toHexString(i << 4 & 0xFFFFFFFFL | 0x100000000L)); + buf.setCharAt(buf.length() - 9, '|'); + buf.append('|'); + HEXDUMP_ROWPREFIXES[i] = buf.toString(); + } + + // Generate the lookup table for byte-to-hex-dump conversion + for (i = 0; i < BYTE2HEX.length; i++) + { + BYTE2HEX[i] = ' ' + byteToHexStringPadded(i); + } + + // Generate the lookup table for byte dump paddings + for (i = 0; i < BYTEPADDING.length; i++) + { + final int padding = BYTEPADDING.length - i; + final StringBuilder buf = new StringBuilder(padding); + for (int j = 0; j < padding; j++) + { + buf.append(' '); + } + BYTEPADDING[i] = buf.toString(); + } + + // Generate the lookup table for byte-to-char conversion + for (i = 0; i < BYTE2CHAR.length; i++) + { + if (i <= 0x1f || i >= 0x7f) + { + BYTE2CHAR[i] = '.'; + } + else + { + BYTE2CHAR[i] = (char)i; + } + } + } + + /** + * Gets an unsigned byte at the specified absolute {@code index} in a buffer. + * + * @param buffer the source of value + * @param index the absolute {@code index} in the buffer. + * @return unsigned byte value + */ + static short getUnsignedByte(final MutableDirectBuffer buffer, final int index) + { + return (short)(buffer.getByte(index) & 0xFF); + } + + private static String hexDump(final MutableDirectBuffer buffer, final int fromIndex, final int length) + { + if (length < 0) + { + throw new IllegalArgumentException("length: " + length); + } + if (length == 0) + { + return ""; + } + + final int endIndex = fromIndex + length; + final char[] buf = new char[length << 1]; + + int srcIdx = fromIndex; + int dstIdx = 0; + for (; srcIdx < endIndex; srcIdx++, dstIdx += 2) + { + System.arraycopy( + HEXDUMP_TABLE, getUnsignedByte(buffer, srcIdx) << 1, + buf, dstIdx, 2); + } + + return new String(buf); + } + + private static String hexDump(final byte[] array, final int fromIndex, final int length) + { + if (length < 0) + { + throw new IllegalArgumentException("length: " + length); + } + if (length == 0) + { + return ""; + } + + final int endIndex = fromIndex + length; + final char[] buf = new char[length << 1]; + + int srcIdx = fromIndex; + int dstIdx = 0; + for (; srcIdx < endIndex; srcIdx++, dstIdx += 2) + { + System.arraycopy( + HEXDUMP_TABLE, (array[srcIdx] & 0xFF) << 1, + buf, dstIdx, 2); + } + + return new String(buf); + } + + private static String prettyHexDump(final MutableDirectBuffer buffer, final int offset, final int length) + { + if (length == 0) + { + return EMPTY_STRING; + } + else + { + final int rows = length / 16 + (length % 15 == 0 ? 0 : 1) + 4; + final StringBuilder stringBuilder = new StringBuilder(rows * 80); + appendPrettyHexDump(stringBuilder, buffer, offset, length); + return stringBuilder.toString(); + } + } + + /** + * Determine if the requested {@code index} and {@code length} will fit within {@code capacity}. + * + * @param index The starting index. + * @param length The length which will be utilized (starting from {@code index}). + * @param capacity The capacity that {@code index + length} is allowed to be within. + * @return {@code true} if the requested {@code index} and {@code length} will fit within {@code capacity}. + * {@code false} if this would result in an index out of bounds exception. + */ + public static boolean isOutOfBounds(final int index, final int length, final int capacity) + { + return (index | length | (index + length) | (capacity - (index + length))) < 0; + } + + private static void appendPrettyHexDump(final StringBuilder dump, final MutableDirectBuffer buffer, + final int offset, + final int length) + { + if (isOutOfBounds(offset, length, buffer.capacity())) + { + throw new IndexOutOfBoundsException( + "expected: " + "0 <= offset(" + offset + ") <= offset + length(" + length + ") <= " + + "buffer.capacity(" + buffer.capacity() + ')'); + } + if (length == 0) + { + return; + } + dump.append(" +-------------------------------------------------+") + .append(NEWLINE) + .append(" | 0 1 2 3 4 5 6 7 8 9 a b c d e f |") + .append(NEWLINE) + .append("+--------+-------------------------------------------------+----------------+"); + + final int startIndex = offset; + final int fullRows = length >>> 4; + final int remainder = length & 0xF; + + // Dump the rows which have 16 bytes. + for (int row = 0; row < fullRows; row++) + { + final int rowStartIndex = (row << 4) + startIndex; + + // Per-row prefix. + appendHexDumpRowPrefix(dump, row, rowStartIndex); + + // Hex dump + final int rowEndIndex = rowStartIndex + 16; + for (int j = rowStartIndex; j < rowEndIndex; j++) + { + dump.append(BYTE2HEX[getUnsignedByte(buffer, j)]); + } + dump.append(" |"); + + // ASCII dump + for (int j = rowStartIndex; j < rowEndIndex; j++) + { + dump.append(BYTE2CHAR[getUnsignedByte(buffer, j)]); + } + dump.append('|'); + } + + // Dump the last row which has less than 16 bytes. + if (remainder != 0) + { + final int rowStartIndex = (fullRows << 4) + startIndex; + appendHexDumpRowPrefix(dump, fullRows, rowStartIndex); + + // Hex dump + final int rowEndIndex = rowStartIndex + remainder; + for (int j = rowStartIndex; j < rowEndIndex; j++) + { + dump.append(BYTE2HEX[getUnsignedByte(buffer, j)]); + } + dump.append(HEXPADDING[remainder]); + dump.append(" |"); + + // Ascii dump + for (int j = rowStartIndex; j < rowEndIndex; j++) + { + dump.append(BYTE2CHAR[getUnsignedByte(buffer, j)]); + } + dump.append(BYTEPADDING[remainder]); + dump.append('|'); + } + + dump.append(NEWLINE + "+--------+-------------------------------------------------+----------------+"); + } + + private static void appendHexDumpRowPrefix(final StringBuilder dump, final int row, final int rowStartIndex) + { + if (row < HEXDUMP_ROWPREFIXES.length) + { + dump.append(HEXDUMP_ROWPREFIXES[row]); + } + else + { + dump.append(NEWLINE); + dump.append(Long.toHexString(rowStartIndex & 0xFFFFFFFFL | 0x100000000L)); + dump.setCharAt(dump.length() - 9, '|'); + dump.append('|'); + } + } + } + + private PrintBufferUtil() + { + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 6f4ba6490..573309ce0 100644 --- a/build.gradle +++ b/build.gradle @@ -47,6 +47,13 @@ ext { if (!project.hasProperty('ossrhPassword')) { ossrhPassword = '' } + + if (null == System.getenv("JAVA_HOME")) { + logger.warn("JAVA_HOME is not set! It will be used a default jdk"); + externalJdk = false + } else { + externalJdk = true + } } def projectPom = { @@ -135,8 +142,12 @@ subprojects { sourceCompatibility = agronaJavaVersion targetCompatibility = agronaJavaVersion options.compilerArgs << '-XDignore.symbol.file' // Suppress warnings about using Unsafe - options.fork = true - options.forkOptions.javaHome = file("${System.env.JAVA_HOME}") + if (externalJdk) { + options.fork = true + options.forkOptions.javaHome = file("${System.env.JAVA_HOME}") + } else { + logger.warn("fork options for compilation are disabled because JAVA_HOME is not set") + } options.encoding = 'UTF-8' options.deprecation = true } @@ -175,10 +186,6 @@ subprojects { } project(':agrona') { - if (null == System.getenv("JAVA_HOME")) { - throw new GradleException('JAVA_HOME environment variable required') - } - dependencies { testImplementation 'com.google.guava:guava-testlib:27.0.1-jre' }