com.fluxtion.agrona.PrintBufferUtil Maven / Gradle / Ivy
/*
* Copyright 2014-2024 Real Logic Limited.
* Copyright 2012 The Netty Project
*
* Licensed 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
*
* https://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 com.fluxtion.agrona;
/**
* Useful utils to hex dump the Agrona's buffers.
*
* This is code adapted from the Netty project adopted to support {@link DirectBuffer}.
*/
public final class PrintBufferUtil
{
private static final String NEWLINE = System.lineSeparator();
private static final String EMPTY_STRING = "";
private static final String[] BYTE2HEX_PAD = new String[256];
static
{
int i;
for (i = 0; i < 10; i++)
{
BYTE2HEX_PAD[i] = "0" + i;
}
for (; i < 16; i++)
{
final char c = (char)('a' + i - 10);
BYTE2HEX_PAD[i] = "0" + c;
}
for (; i < BYTE2HEX_PAD.length; i++)
{
BYTE2HEX_PAD[i] = Integer.toHexString(i);
}
}
private PrintBufferUtil()
{
}
/**
* 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 DirectBuffer buffer)
{
return hexDump(buffer, 0, buffer.capacity());
}
/**
* Returns a hex dump
* of the specified buffer's subregion.
*
* @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 DirectBuffer 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 subregion.
*
* @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 hexadecimal dump of the specified {@link DirectBuffer} that is easy to read by humans.
*
* @param buffer dumped buffer.
* @return pretty hex dump in a string representation.
*/
public static String prettyHexDump(final DirectBuffer buffer)
{
return prettyHexDump(buffer, 0, buffer.capacity());
}
/**
* Returns a hexadecimal dump of the specified {@link DirectBuffer} 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 DirectBuffer buffer, final int offset, final int length)
{
return HexUtil.prettyHexDump(buffer, offset, length);
}
/**
* Appends the prettified multi-line hexadecimal dump of the specified {@link DirectBuffer} 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 DirectBuffer buffer)
{
appendPrettyHexDump(dump, buffer, 0, buffer.capacity());
}
/**
* Appends the prettified multi-line hexadecimal dump of the specified {@link DirectBuffer} 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 DirectBuffer 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];
}
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[] HEX_PADDING = new String[16];
private static final String[] HEXDUMP_ROW_PREFIXES = new String[65536 >>> 4];
private static final String[] BYTE2HEX = new String[256];
private static final String[] BYTE_PADDING = 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 padding
for (i = 0; i < HEX_PADDING.length; i++)
{
final int padding = HEX_PADDING.length - i;
final StringBuilder buf = new StringBuilder(padding * 3);
for (int j = 0; j < padding; j++)
{
buf.append(" ");
}
HEX_PADDING[i] = buf.toString();
}
// Generate the lookup table for the start-offset header in each row (up to 64 KiB).
for (i = 0; i < HEXDUMP_ROW_PREFIXES.length; i++)
{
final StringBuilder buf = new StringBuilder(12);
buf.append(NEWLINE);
buf.append(Long.toHexString((long)i << 4 & 0xFFFFFFFFL | 0x100000000L));
buf.setCharAt(buf.length() - 9, '|');
buf.append('|');
HEXDUMP_ROW_PREFIXES[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 < BYTE_PADDING.length; i++)
{
final int padding = BYTE_PADDING.length - i;
final StringBuilder buf = new StringBuilder(padding);
for (int j = 0; j < padding; j++)
{
buf.append(' ');
}
BYTE_PADDING[i] = buf.toString();
}
// Generate the lookup table for byte-to-char conversion
for (i = 0; i < BYTE2CHAR.length; i++)
{
BYTE2CHAR[i] = (i <= 0x1f || i >= 0x7f) ? '.' : (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 DirectBuffer buffer, final int index)
{
return (short)(buffer.getByte(index) & 0xFF);
}
static String hexDump(final DirectBuffer 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];
for (int dstIdx = 0, srcIdx = fromIndex; srcIdx < endIndex; srcIdx++, dstIdx += 2)
{
System.arraycopy(
HEXDUMP_TABLE, getUnsignedByte(buffer, srcIdx) << 1,
buf, dstIdx, 2);
}
return new String(buf);
}
static String hexDump(final byte[] array, final int fromIndex, final int length)
{
if (length < 0)
{
throw new IllegalArgumentException("length < 0: " + length);
}
if (length == 0)
{
return "";
}
final int endIndex = fromIndex + length;
final char[] buf = new char[length << 1];
for (int dstIdx = 0, srcIdx = fromIndex; srcIdx < endIndex; srcIdx++, dstIdx += 2)
{
System.arraycopy(
HEXDUMP_TABLE, (array[srcIdx] & 0xFF) << 1,
buf, dstIdx, 2);
}
return new String(buf);
}
static String prettyHexDump(final DirectBuffer 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.
*/
static boolean isOutOfBounds(final int index, final int length, final int capacity)
{
return (index | length | (index + length) | (capacity - (index + length))) < 0;
}
static void appendPrettyHexDump(
final StringBuilder dump, final DirectBuffer 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 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) + offset;
appendHexDumpRowPrefix(dump, row, rowStartIndex);
final int rowEndIndex = rowStartIndex + 16;
for (int j = rowStartIndex; j < rowEndIndex; j++)
{
dump.append(BYTE2HEX[getUnsignedByte(buffer, j)]);
}
dump.append(" |");
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) + offset;
appendHexDumpRowPrefix(dump, fullRows, rowStartIndex);
final int rowEndIndex = rowStartIndex + remainder;
for (int j = rowStartIndex; j < rowEndIndex; j++)
{
dump.append(BYTE2HEX[getUnsignedByte(buffer, j)]);
}
dump.append(HEX_PADDING[remainder]);
dump.append(" |");
for (int j = rowStartIndex; j < rowEndIndex; j++)
{
dump.append(BYTE2CHAR[getUnsignedByte(buffer, j)]);
}
dump.append(BYTE_PADDING[remainder]);
dump.append('|');
}
dump.append(NEWLINE)
.append("+--------+-------------------------------------------------+----------------+");
}
static void appendHexDumpRowPrefix(final StringBuilder dump, final int row, final int rowStartIndex)
{
if (row < HEXDUMP_ROW_PREFIXES.length)
{
dump.append(HEXDUMP_ROW_PREFIXES[row]);
}
else
{
dump.append(NEWLINE);
dump.append(Long.toHexString(rowStartIndex & 0xFFFFFFFFL | 0x100000000L));
dump.setCharAt(dump.length() - 9, '|');
dump.append('|');
}
}
}
}