HexConverter.java |
/* * $Id: HexConverter.java,v 1.35 2011/02/12 08:22:46 agoubard Exp $ * * See the COPYRIGHT file for redistribution and use restrictions. */ package org.xins.common.text; import org.xins.common.MandatoryArgumentChecker; /** * Utility class for converting numbers to unsigned hex strings and vice * versa. * * @version $Revision: 1.35 $ $Date: 2011/02/12 08:22:46 $ * @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a> * * @since XINS 1.0.0 */ public class HexConverter { /** * The number of characters written when converting a <code>byte</code> to * an unsigned hex string. */ private static final int BYTE_LENGTH = 2; /** * The number of characters written when converting a <code>short</code> to * an unsigned hex string. */ private static final int SHORT_LENGTH = 4; /** * The number of characters written when converting a <code>char</code> to * an unsigned hex string. */ private static final int CHAR_LENGTH = 4; /** * The number of characters written when converting a <code>int</code> to * an unsigned hex string. */ private static final int INT_LENGTH = 8; /** * The number of characters written when converting a <code>long</code> to * an unsigned hex string. */ private static final int LONG_LENGTH = 16; /** * The radix when converting (16). */ private static final byte RADIX = 16; /** * The radix mask as an <code>int</code>. Equal to {@link #RADIX}<code> - * 1</code>. */ private static final int INT_MASK = RADIX - 1; /** * The radix mask as a <code>long</code>. Equal to {@link #RADIX}<code> - * 1L</code>. */ private static final long LONG_MASK = RADIX - 1L; /** * Array of 2 zero characters. */ private static final char[] TWO_ZEROES = { '0', '0' }; /** * Array of 4 zero characters. */ private static final char[] FOUR_ZEROES = { '0', '0', '0', '0' }; /** * Array of 8 zero characters. */ private static final char[] EIGHT_ZEROES = { '0', '0', '0', '0', '0', '0', '0', '0' }; /** * Array of 16 zero characters. */ private static final char[] SIXTEEN_ZEROES = { '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0' }; /** * Array that contains the hexadecimal digits, from 0 to 9 and from a to z. */ private static final char[] DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7' , '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; /** * The '0' character. */ private static final int CHAR_ZERO = (int) '0'; /** * The '9' character. */ private static final int CHAR_NINE = (int) '9'; /** * The 'a' character. */ private static final int CHAR_A = (int) 'a'; /** * The 'f' character. */ private static final int CHAR_F = (int) 'f'; /** * The 'A' character. */ private static final int CHAR_UP_A = (int) 'A'; /** * The 'f' character. */ private static final int CHAR_UP_F = (int) 'F'; /** * The 'a' character lowered by 0xA. */ private static final int CHAR_A_FACTOR = CHAR_A - 10; /** * The 'A' character lowered by 0xA. */ private static final int CHAR_UP_A_FACTOR = CHAR_UP_A - 10; /** * Creates a new <code>HexConverter</code> object. */ private HexConverter() { // empty } /** * Checks if the specified character is a hexadecimal digit. The following * ranges of characters are considered hexadecimal digits: * * <ul> * <li><code>'0'</code> to <code>'9'</code> * <li><code>'a'</code> to <code>'f'</code> * <li><code>'A'</code> to <code>'F'</code> * </ul> * * @param c * the character to check. * * @return * <code>true</code> if the specified character is a hexadecimal digit, * <code>false</code> otherwise. */ public static final boolean isHexDigit(char c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); } /** * Converts the specified <code>byte</code> array to an unsigned number hex * string. The number of characters in the returned string will always be * equal to the number of input bytes times 2. * * @param input * the <code>byte[]</code> array to be converted to a hex string. * * @return * the hex string, cannot be <code>null</code>, the length is always 2 * times the length of the input array * (i.e. <code><em>return</em>.</code>{@link String#length() length()}<code> == (input.length * 2)</code>). * * @throws IllegalArgumentException * if <code>n == null || n.length < 1</code>. */ public static String toHexString(byte[] input) throws IllegalArgumentException { // Check preconditions MandatoryArgumentChecker.check("input", input); // Empty input is empty output if (input.length < 1) { return ""; } // Construct a new char array to store the hex digits in int length = input.length; char[] chars = new char[length * 2]; int pos = 0; for (int i = 0; i < length; i++) { int n = (int) input[i]; chars[pos++] = DIGITS[(n & 0x000000f0) >> 4]; chars[pos++] = DIGITS[(n & 0x0000000f)]; } return new String(chars, 0, length * 2); } /** * Converts the specified <code>byte</code> to an unsigned number hex * string. The returned string will always consist of 2 hex characters, * a zero will be prepended if necessary. * * @param n * the number to be converted to a hex string. * * @return * the hex string, cannot be <code>null</code>, the length is always 2 * (i.e. <code><em>return</em>.</code>{@link String#length() length()}<code> == 2</code>). */ public static String toHexString(byte n) { // First convert to int, since there are no Java opcodes for bytes byte i = (byte) n; char[] chars = new char[BYTE_LENGTH]; chars[0] = DIGITS[(i & 0x000000f0) >> 4]; chars[1] = DIGITS[(i & 0x0000000f)]; return new String(chars); } /** * Converts the specified <code>short</code> to an unsigned number hex * string. The returned string will always consist of 4 hex characters, * zeroes will be prepended as necessary. * * @param n * the number to be converted to a hex string. * * @return * the hex string, cannot be <code>null</code>, the length is always 4 * (i.e. <code><em>return</em>.</code>{@link String#length() length()}<code> == 4</code>). */ public static String toHexString(short n) { // First convert to int, since there are no Java opcodes for shorts int i = ((int) n) & 0x0000ffff; char[] chars = new char[SHORT_LENGTH]; int pos = SHORT_LENGTH - 1; // Convert the number to a hex string until the remainder is 0 for (; i != 0; i >>>= 4) { chars[pos--] = DIGITS[i & INT_MASK]; } // Fill the rest with '0' characters for (; pos >= 0; pos--) { chars[pos] = '0'; } return new String(chars, 0, SHORT_LENGTH); } /** * Converts the specified <code>char</code> to an unsigned number hex * string. The returned string will always consist of 4 hex characters, * zeroes will be prepended as necessary. * * @param n * the character to be converted to a hex string. * * @return * the hex string, cannot be <code>null</code>, the length is always 4 * (i.e. <code><em>return</em>.</code>{@link String#length() length()}<code> == 4</code>). */ public static String toHexString(char n) { // First convert to int, since there are no Java opcodes for shorts int i = (int) n; char[] chars = new char[CHAR_LENGTH]; int pos = CHAR_LENGTH - 1; // Convert the number to a hex string until the remainder is 0 for (; i != 0; i >>>= 4) { chars[pos--] = DIGITS[i & INT_MASK]; } // Fill the rest with '0' characters for (; pos >= 0; pos--) { chars[pos] = '0'; } return new String(chars, 0, CHAR_LENGTH); } /** * Converts the specified <code>int</code> to an unsigned number hex * string. The returned string will always consist of 8 hex characters, * zeroes will be prepended as necessary. * * @param n * the number to be converted to a hex string. * * @return * the hex string, cannot be <code>null</code>, the length is always 8 * (i.e. <code><em>return</em>.</code>{@link String#length() length()}<code> == 8</code>). */ public static String toHexString(int n) { char[] chars = new char[INT_LENGTH]; int pos = INT_LENGTH - 1; // Convert the int to a hex string until the remainder is 0 for (; n != 0; n >>>= 4) { chars[pos--] = DIGITS[n & INT_MASK]; } // Fill the rest with '0' characters for (; pos >= 0; pos--) { chars[pos] = '0'; } return new String(chars, 0, INT_LENGTH); } /** * Convert the specified <code>long</code> to an unsigned number hex * string. The returned string will always consist of 16 hex characters, * zeroes will be prepended as necessary. * * @param n * the number to be converted to a hex string. * * @return * the hex string, cannot be <code>null</code>, the length is always 16 * (i.e. <code><em>return</em>.</code>{@link String#length() length()}<code> == 16</code>). */ public static String toHexString(long n) { char[] chars = new char[LONG_LENGTH]; int pos = LONG_LENGTH - 1; // Convert the long to a hex string until the remainder is 0 for (; n != 0; n >>>= 4) { chars[pos--] = DIGITS[(int) (n & LONG_MASK)]; } // Fill the rest with '0' characters for (; pos >= 0; pos--) { chars[pos] = '0'; } return new String(chars, 0, LONG_LENGTH); } /** * Converts the specified <code>byte</code> to unsigned number and appends * it to the specified string buffer. Exactly 2 characters will be * appended, both between <code>'0'</code> to <code>'9'</code> or between * <code>'a'</code> and <code>'f'</code>. * * @param buffer * the string buffer to append to, cannot be <code>null</code>. * * @param n * the number to be converted to a hex string. * * @throws IllegalArgumentException * if <code>buffer == null</code>. * * @since XINS 1.3.0 * @deprecated since XINS 2.0, use toHexString(byte). */ public static void toHexString(FastStringBuffer buffer, byte n) throws IllegalArgumentException { // Check preconditions if (buffer == null) { throw new IllegalArgumentException("buffer == null"); } // Store the starting position where the buffer should write the value. int initPos = buffer.getLength(); // Append 2 zero characters to the buffer buffer.append(TWO_ZEROES); int pos = initPos + BYTE_LENGTH - 1; // Convert the short to a hex string until the remainder is 0 int x = ((int) n) & 0x000000ff; for (; x != 0; x >>>= 4) { buffer.setChar(pos--, DIGITS[x & INT_MASK]); } } /** * Converts the specified <code>short</code> to unsigned number and appends * it to the specified string buffer. Exactly 4 characters will be * appended, all between <code>'0'</code> to <code>'9'</code> or between * <code>'a'</code> and <code>'f'</code>. * * @param buffer * the string buffer to append to, cannot be <code>null</code>. * * @param n * the number to be converted to a hex string. * * @throws IllegalArgumentException * if <code>buffer == null</code>. * * @since XINS 1.3.0 * @deprecated since XINS 2.0, use toHexString(short). */ public static void toHexString(FastStringBuffer buffer, short n) throws IllegalArgumentException { // Check preconditions if (buffer == null) { throw new IllegalArgumentException("buffer == null"); } // Store the starting position where the buffer should write the value. int initPos = buffer.getLength(); // Append 4 zero characters to the buffer buffer.append(FOUR_ZEROES); int pos = initPos + SHORT_LENGTH - 1; // Convert the short to a hex string until the remainder is 0 int x = ((int) n) & 0x0000ffff; for (; x != 0; x >>>= 4) { buffer.setChar(pos--, DIGITS[x & INT_MASK]); } } /** * Converts the specified <code>char</code> to unsigned number and appends * it to the specified string buffer. Exactly 4 characters will be * appended, all between <code>'0'</code> to <code>'9'</code> or between * <code>'a'</code> and <code>'f'</code>. * * @param buffer * the string buffer to append to, cannot be <code>null</code>. * * @param n * the number to be converted to a hex string. * * @throws IllegalArgumentException * if <code>buffer == null</code>. * * @since XINS 1.3.0 * @deprecated since XINS 2.0, use toHexString(char). */ public static void toHexString(FastStringBuffer buffer, char n) throws IllegalArgumentException { // Check preconditions if (buffer == null) { throw new IllegalArgumentException("buffer == null"); } // Store the starting position where the buffer should write the value. int initPos = buffer.getLength(); // Append 4 zero characters to the buffer buffer.append(FOUR_ZEROES); int pos = initPos + SHORT_LENGTH - 1; // Convert the char to a hex string until the remainder is 0 int x = ((int) n) & 0x0000ffff; for (; x != 0; x >>>= 4) { buffer.setChar(pos--, DIGITS[x & INT_MASK]); } } /** * Converts the specified <code>int</code> to unsigned number and appends * it to the specified string buffer. Exactly 8 characters will be * appended, all between <code>'0'</code> to <code>'9'</code> or between * <code>'a'</code> and <code>'f'</code>. * * @param buffer * the string buffer to append to, cannot be <code>null</code>. * * @param n * the number to be converted to a hex string. * * @throws IllegalArgumentException * if <code>buffer == null</code>. * @deprecated since XINS 2.0, use toHexString(int). */ public static void toHexString(FastStringBuffer buffer, int n) throws IllegalArgumentException { // Check preconditions if (buffer == null) { throw new IllegalArgumentException("buffer == null"); } // Store the starting position where the buffer should write the value. int initPos = buffer.getLength(); // Append 8 zero characters to the buffer buffer.append(EIGHT_ZEROES); int pos = initPos + INT_LENGTH - 1; // Convert the int to a hex string until the remainder is 0 for (; n != 0; n >>>= 4) { buffer.setChar(pos--, DIGITS[n & INT_MASK]); } } /** * Converts the specified <code>long</code> to unsigned number and appends * it to the specified string buffer. Exactly 16 characters will be * appended, all between <code>'0'</code> to <code>'9'</code> or between * <code>'a'</code> and <code>'f'</code>. * * @param buffer * the string buffer to append to, cannot be <code>null</code>. * * @param n * the number to be converted to a hex string. * * @throws IllegalArgumentException * if <code>buffer == null</code>. * @deprecated since XINS 2.0, use toHexString(long). */ public static void toHexString(FastStringBuffer buffer, long n) throws IllegalArgumentException { // Check preconditions if (buffer == null) { throw new IllegalArgumentException("buffer == null"); } // Store the starting position where the buffer should write the value. int initPos = buffer.getLength(); // Append 16 zero characters to the buffer buffer.append(SIXTEEN_ZEROES); int pos = initPos + LONG_LENGTH - 1; // Convert the long to a hex string until the remainder is 0 for (; n != 0; n >>>= 4) { buffer.setChar(pos--, DIGITS[(int) (n & LONG_MASK)]); } } /** * Parses the specified string as a set of hex digits and converts it to a * byte array. * * @param s * the hexadecimal string, cannot be <code>null</code>. * * @param index * the starting index in the string, must be >= 0. * * @param length * the number of characters to convert in the string, must be >= 0. * * @return * the value of the parsed unsigned hexadecimal string, as an array of * bytes. * * @throws IllegalArgumentException * if <code>s == null || index < 0 || length < 1 || s.{@link String#length() length()} < index + length</code>). * * @throws NumberFormatException * if any of the characters in the specified range of the string is not * a hex digit (<code>'0'</code> to <code>'9'</code>, * <code>'a'</code> to <code>'f'</code> and * <code>'A'</code> to <code>'F'</code>). */ public static byte[] parseHexBytes(String s, int index, int length) throws IllegalArgumentException, NumberFormatException { // Check preconditions if (s == null) { throw new IllegalArgumentException("s == null"); } else if (index < 0) { throw new IllegalArgumentException("index (" + index + ") < 0"); } else if (length < 1) { throw new IllegalArgumentException("length (" + length + ") < 1"); } else if (s.length() < index + length) { throw new IllegalArgumentException("s.length() (" + s.length() + ") < index (" + index + ") + length (" + length + ')'); } byte[] bytes = new byte[(length / 2) + (length % 2)]; // Loop through all characters int top = index + length; int pos = 0; for (int i = index; i < top; i++) { int c = (int) s.charAt(i); int upper; if (c >= CHAR_ZERO && c <= CHAR_NINE) { upper = (c - CHAR_ZERO); } else if (c >= CHAR_A && c <= CHAR_F) { upper = (c - CHAR_A_FACTOR); } else if (c >= CHAR_UP_A && c <= CHAR_UP_F) { upper = (c - CHAR_UP_A_FACTOR); } else { throw new NumberFormatException("s.charAt(" + i + ") == '" + s.charAt(i) + '\''); } // Proceed to next char, which is the lower nibble of the byte i++; int lower = 0; if (i < top) { c = (int) s.charAt(i); if (c >= CHAR_ZERO && c <= CHAR_NINE) { lower = (c - CHAR_ZERO); } else if (c >= CHAR_A && c <= CHAR_F) { lower = (c - CHAR_A_FACTOR); } else if (c >= CHAR_UP_A && c <= CHAR_UP_F) { lower = (c - CHAR_UP_A_FACTOR); } else { throw new NumberFormatException("s.charAt(" + i + ") == '" + s.charAt(i) + '\''); } } upper <<= 4; bytes[pos++] = (byte) (upper | lower); } return bytes; } /** * Parses the 8-digit unsigned hex number in the specified string. * * @param s * the hexadecimal string, cannot be <code>null</code>. * * @param index * the starting index in the string, must be >= 0. * * @return * the value of the parsed unsigned hexadecimal string. * * @throws IllegalArgumentException * if <code>s == null * || index < 0 * || s.{@link String#length() length()} < index + 8</code>). * * @throws NumberFormatException * if any of the characters in the specified range of the string is not * a hex digit (<code>'0'</code> to <code>'9'</code> and * <code>'a'</code> to <code>'f'</code>). */ public static int parseHexInt(String s, int index) throws IllegalArgumentException, NumberFormatException { // Check preconditions if (s == null) { throw new IllegalArgumentException("s == null"); } else if (s.length() < index + 8) { throw new IllegalArgumentException("s.length() (" + s.length() + ") < index (" + index + ") + 8 (" + (index + 8) + ')'); } int n = 0; // Loop through all characters int last = index + 8; for (int i = index; i < last; i++) { int c = (int) s.charAt(i); n <<= 4; if (c >= CHAR_ZERO && c <= CHAR_NINE) { n |= (c - CHAR_ZERO); } else if (c >= CHAR_A && c <= CHAR_F) { n |= (c - CHAR_A_FACTOR); } else if (c >= CHAR_UP_A && c <= CHAR_UP_F) { n |= (c - CHAR_UP_A_FACTOR); } else { throw new NumberFormatException("s.charAt(" + i + ") == '" + s.charAt(i) + '\''); } } return n; } /** * Parses the specified 8-digit unsigned hex string. * * @param s * the hexadecimal string, cannot be <code>null</code> and must have * size 8 * (i.e. <code>s.</code>{@link String#length() length()}<code> == 8</code>). * * @return * the value of the parsed unsigned hexadecimal string. * * @throws IllegalArgumentException * if <code>s == null || s.</code>{@link String#length() length()}<code> != 8</code>. * * @throws NumberFormatException * if any of the characters in the specified string is not a hex digit * (<code>'0'</code> to <code>'9'</code> and <code>'a'</code> to * <code>'f'</code>). */ public static int parseHexInt(String s) throws IllegalArgumentException, NumberFormatException { // Check preconditions if (s == null) { throw new IllegalArgumentException("s == null"); } else if (s.length() != 8) { throw new IllegalArgumentException("s.length() != 8"); } return parseHexInt(s, 0); } /** * Parses the 16-digit unsigned hex number in the specified string. * * @param s * the hexadecimal string, cannot be <code>null</code>. * * @param index * the starting index in the string, must be >= 0. * * @return * the value of the parsed unsigned hexadecimal string. * * @throws IllegalArgumentException * if <code>s == null * || index < 0 * || s.{@link String#length() length()} < index + 16</code>). * * @throws NumberFormatException * if any of the characters in the specified range of the string is not * a hex digit (<code>'0'</code> to <code>'9'</code> and * <code>'a'</code> to <code>'f'</code>). */ public static long parseHexLong(String s, int index) throws IllegalArgumentException, NumberFormatException { // Check preconditions if (s == null) { throw new IllegalArgumentException("s == null"); } else if (s.length() < index + 16) { throw new IllegalArgumentException("s.length() (" + s.length() + ") < index (" + index + ") + 16 (" + (index + 16) + ')'); } long n = 0L; // Loop through all characters int last = index + 16; for (int i = index; i < last; i++) { int c = (int) s.charAt(i); n <<= 4; if (c >= CHAR_ZERO && c <= CHAR_NINE) { n |= (c - CHAR_ZERO); } else if (c >= CHAR_A && c <= CHAR_F) { n |= (c - CHAR_A_FACTOR); } else if (c >= CHAR_UP_A && c <= CHAR_UP_F) { n |= (c - CHAR_UP_A_FACTOR); } else { throw new NumberFormatException("s.charAt(" + i + ") == '" + s.charAt(i) + '\''); } } return n; } /** * Parses the specified 16-digit unsigned hex string. * * @param s * the hexadecimal string, cannot be <code>null</code> and must have * size 16 * (i.e. <code>s.</code>{@link String#length() length()}<code> == 16</code>). * * @return * the value of the parsed unsigned hexadecimal string. * * @throws IllegalArgumentException * if <code>s == null || s.</code>{@link String#length() length()}<code> != 16</code>. * * @throws NumberFormatException * if any of the characters in the specified string is not a hex digit * (<code>'0'</code> to <code>'9'</code> and <code>'a'</code> to * <code>'f'</code>). */ public static long parseHexLong(String s) throws IllegalArgumentException, NumberFormatException { // Check preconditions if (s == null) { throw new IllegalArgumentException("s == null"); } else if (s.length() != 16) { throw new IllegalArgumentException("s.length() != 16"); } return parseHexLong(s, 0); } }