001
002 /*
003 * Copyright (C) 2011 Archie L. Cobbs. All rights reserved.
004 *
005 * $Id: ByteArrayEncoder.java 2 2011-02-05 21:51:43Z archie.cobbs $
006 */
007
008 package org.dellroad.stuff.string;
009
010 /**
011 * Encodes {@code byte[]} arrays to and from hexadecimal strings.
012 */
013 public final class ByteArrayEncoder {
014
015 private ByteArrayEncoder() {
016 }
017
018 /**
019 * Encode a {@code byte[]} array as a {@link String}.
020 * Equivalent to:
021 * <blockquote>
022 * <code>encode(array, 0, array.length)</code>
023 * </blockquote>
024 *
025 * @param array byte array, or {@code null}
026 * @return hexadecimal string (or {@code null} if {@code array} was {@code null})
027 */
028 public static String encode(byte[] array) {
029 return encode(array, 0, array.length);
030 }
031
032 /**
033 * Encode a {@code byte[]} array as a {@link String}.
034 *
035 * @param array byte array, or {@code null}
036 * @param off offset into the array
037 * @param len number of bytes to convert
038 * @return hexadecimal string (or {@code null} if {@code array} was {@code null})
039 * @throws IndexOutOfBoundsException if array bounds are exceeded
040 * @throws IllegalArgumentException if {@code len} is greater than {@code Integer.MAX_VALUE / 2}
041 */
042 public static String encode(byte[] array, int off, int len) {
043
044 // Check for null
045 if (array == null)
046 return null;
047
048 // Check bounds
049 if (off < 0 || len < 0 || off + len < 0 || off + len > array.length)
050 throw new IndexOutOfBoundsException("array bounds exceeded");
051
052 // Encode bytes
053 if (len > Integer.MAX_VALUE / 2)
054 throw new IllegalArgumentException("array is too long");
055 char[] buf = new char[len * 2];
056 for (int i = 0; i < len; i++) {
057 int value = array[off + i] & 0xff;
058 buf[i * 2] = Character.forDigit(value >> 4, 16);
059 buf[i * 2 + 1] = Character.forDigit(value & 0xf, 16);
060 }
061
062 // Done
063 return new String(buf);
064 }
065
066 /**
067 * Decode a {@link String} back into a {@code byte[]} array.
068 * Any extra whitespace in the string is ignored.
069 *
070 * @param text string previously encoded by {@link #encode}, or {@code null}
071 * @return original {@code byte[]} array (or {@code null} if {@code text} was {@code null})
072 * @throws IllegalArgumentException if any invalid non-whitespace characters are seen, or the number of hex digits is odd
073 */
074 public static byte[] decode(String text) {
075
076 // Check for null
077 if (text == null)
078 return null;
079
080 // Allocate array
081 byte[] array = new byte[text.length() / 2];
082
083 // Parse bytes
084 int len = 0;
085 boolean flipflop = false;
086 int prevNibble = 0;
087 final int limit = text.length();
088 for (int pos = 0; pos < limit; pos++) {
089 char ch = text.charAt(pos);
090 if (Character.isWhitespace(ch))
091 continue;
092 int nibble = Character.digit(ch, 16);
093 if (nibble == -1)
094 throw new IllegalArgumentException("invalid character '" + ch + "' in byte array");
095 if (flipflop)
096 array[len++] = (byte)((prevNibble << 4) | nibble);
097 else
098 prevNibble = nibble;
099
100 // bitwise inversion of boolean
101 flipflop ^= true;
102 }
103 if (flipflop)
104 throw new IllegalArgumentException("byte array has an odd number of digits");
105
106 // Account for any squeezed-out whitespace
107 if (len < array.length) {
108 byte[] temp = new byte[len];
109 System.arraycopy(array, 0, temp, 0, len);
110 array = temp;
111 }
112
113 // Done
114 return array;
115 }
116 }
117