/* * $Id$ * * Copyright 2006, The jCoderZ.org Project. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * Neither the name of the jCoderZ.org Project nor the names of * its contributors may be used to endorse or promote products * derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.jcoderz.commons.util; import java.util.Arrays; /** * This class converts byte array data from and to hex and also provides * a hexdump method. * * @author Albrecht Messner */ public final class HexUtil { private static final int BYTE_UNSIGNED_MAX = 255; private static final int HALF_BYTE = 4; private static final byte LOW_HALF_MASK = (byte) 0x0F; private static final byte HIGH_HALF_MASK = (byte) 0xF0; private static final int BYTE_MASK = 0xFF; private static final int CHARS_PER_BYTE = 2; private static final int DECIMAL_OFFSET = 10; private static final char[] HEX_CHARS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; private static final String[] BYTE_AS_HEX = new String[BYTE_UNSIGNED_MAX + 1]; private static final int[] CHAR_TO_NIBBLE_LOW = new int[BYTE_UNSIGNED_MAX + 1]; private static final int[] CHAR_TO_NIBBLE_HIGH = new int[BYTE_UNSIGNED_MAX + 1]; private static final int DUMP_BYTES_PER_LINE = 16; private static final int DUMP_BYTES_PER_COLUMN = 8; private static final int DUMP_ADDR_LEN = 8; private static final int DUMP_BUFFER_SIZE = DUMP_ADDR_LEN // address + DUMP_BYTES_PER_LINE * CHARS_PER_BYTE // bytes + DUMP_BYTES_PER_LINE // whitespaces between bytes + 1 + 1; // extra whitespace after address and to separate columns static { // set up tables/arrays for byte to hex conversion final StringBuffer sb = new StringBuffer(); for (int i = 0; i < BYTE_AS_HEX.length; i++) { sb.setLength(0); final int lowerFourBits = i & LOW_HALF_MASK; final int highFourBits = (i & HIGH_HALF_MASK) >> HALF_BYTE; sb.append(HEX_CHARS[highFourBits]); sb.append(HEX_CHARS[lowerFourBits]); BYTE_AS_HEX[i] = sb.toString(); } // set up tables/arrays for hex to byte conversion Arrays.fill(CHAR_TO_NIBBLE_LOW, -1); Arrays.fill(CHAR_TO_NIBBLE_HIGH, -1); for (int i = '0'; i <= '9'; i++) { CHAR_TO_NIBBLE_LOW[i] = i - '0'; CHAR_TO_NIBBLE_HIGH[i] = (i - '0') << HALF_BYTE; } for (int i = 'a'; i <= 'f'; i++) { CHAR_TO_NIBBLE_LOW[i] = i - 'a' + DECIMAL_OFFSET; CHAR_TO_NIBBLE_HIGH[i] = (i - 'a' + DECIMAL_OFFSET) << HALF_BYTE; } for (int i = 'A'; i <= 'F'; i++) { CHAR_TO_NIBBLE_LOW[i] = i - 'A' + DECIMAL_OFFSET; CHAR_TO_NIBBLE_HIGH[i] = (i - 'A' + DECIMAL_OFFSET) << HALF_BYTE; } } private HexUtil () { // utility class that provides only static methods } /** * Converts a byte array into an upper-case hex string, starting at the * given offset and converting the given number of bytes. * @param data the byte data to convert to hex * @param offset the start offset in the byte array * @param length the number of bytes to convert * @return null if data was null, an empty string if data.length == 0, * and the hex representation of the byte array otherwise * @throws IndexOutOfBoundsException if offset + length > data.lenght */ public static String bytesToHex ( final byte[] data, final int offset, final int length) throws IndexOutOfBoundsException { final String result; if (data == null) { result = null; } else { final StringBuffer sbuf = new StringBuffer(); for (int i = offset; i < offset + length; i++) { sbuf.append(BYTE_AS_HEX[data[i] & BYTE_MASK]); } result = sbuf.toString(); } return result; } /** * Converts a byte array into an upper-case hex string, starting at the * first byte and converting the whole array. * @param data the byte data to convert to hex * @return null if data was null, an empty string if data.length == 0, * and the hex representation of the byte array otherwise */ public static String bytesToHex (final byte[] data) { final String result; if (data != null) { result = bytesToHex(data, 0, data.length); } else { result = null; } return result; } /** * Convert the given hex string to a byte array. * @param s the string to convert * @return null if the string is null, an empty byte array if s.length == 0 * and a byte array representing the hex string otherwise * @throws IllegalArgumentException if the string is not a multiple of 2 * characters long, or if the string contains an invalid hex char */ public static byte[] stringToBytes (final String s) throws IllegalArgumentException { final byte[] result; if (s == null) { result = null; } else if (s.length() == 0) { result = new byte[0]; } else { // string must be a multiple of 2 chars if (s.length() % CHARS_PER_BYTE != 0) { throw new IllegalArgumentException( "The length of a hex string must be a multiple of 2 (was " + s.length() + ")"); } int count = 0; result = new byte[s.length() / CHARS_PER_BYTE]; try { for (int i = 0; i < s.length(); i++) { final char c1 = s.charAt(i); final char c2 = s.charAt(++i); final int b = CHAR_TO_NIBBLE_HIGH[c1] | CHAR_TO_NIBBLE_LOW[c2]; if (b == -1) { throw new IllegalArgumentException( "'" + c1 + c2 + "' is not a valid hex representation of a byte"); } result[count] = (byte) b; ++count; } } catch (IndexOutOfBoundsException ex) { final char c1 = s.charAt(count * CHARS_PER_BYTE); final char c2 = s.charAt(count * CHARS_PER_BYTE + 1); final IllegalArgumentException e = new IllegalArgumentException( "'" + c1 + c2 + "' is not a valid hex representation of a byte"); e.initCause(ex); throw e; } } return result; } /** * Produces a hexdump of the given byte array with a formatting * as in "hexdump -C" (canonical hex + ASCII display). This formatting * contains an address column, sixteen bytes of hex separated by spaces, * with an extra space after eight bytes, and an ascii print-out of the * bytes enclosed in pipe symbols. Non-ASCII characters are replaced by * dots. * * @param data the byte data to dump * @return a string containing the hexdump, or null if data was null */ public static String dump (final byte[] data) { final String result; if (data == null) { result = null; } else { int offset = 0; final StringBuffer dumpBuffer = new StringBuffer(); final StringBuffer lineBuffer = new StringBuffer(); final StringBuffer charBuffer = new StringBuffer(); while (offset < data.length) { lineBuffer.setLength(0); charBuffer.setLength(0); charBuffer.append('|'); lineBuffer.append(offsetToHex(offset)); lineBuffer.append(' '); final int end = (offset + DUMP_BYTES_PER_LINE < data.length ? offset + DUMP_BYTES_PER_LINE : data.length); for (int i = offset; i < end; i++) { final byte b = data[i]; lineBuffer.append(bytesToHex(new byte[] {b})); lineBuffer.append(' '); if (i - offset == DUMP_BYTES_PER_COLUMN - 1) { lineBuffer.append(' '); } final char c = (char) b; if ((! Character.isISOControl(c)) && StringUtil.isAscii(c)) { charBuffer.append(c); } else { charBuffer.append('.'); } } padBuffer(lineBuffer); charBuffer.append('|'); dumpBuffer.append(lineBuffer); dumpBuffer.append(charBuffer); dumpBuffer.append(Constants.LINE_SEPARATOR); offset += DUMP_BYTES_PER_LINE; } result = dumpBuffer.toString(); } return result; } private static void padBuffer (final StringBuffer sbuf) { while (sbuf.length() < DUMP_BUFFER_SIZE) { sbuf.append(' '); } } private static String offsetToHex (int offset) { String s = Integer.toHexString(offset); while (s.length() < DUMP_ADDR_LEN) { s = "0" + s; } return s; } }