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