001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     * 
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     * 
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    package org.apache.commons.codec.binary;
019    
020    import java.io.UnsupportedEncodingException;
021    
022    import org.apache.commons.codec.BinaryDecoder;
023    import org.apache.commons.codec.BinaryEncoder;
024    import org.apache.commons.codec.CharEncoding;
025    import org.apache.commons.codec.DecoderException;
026    import org.apache.commons.codec.EncoderException;
027    
028    /**
029     * Hex encoder and decoder. The charset used for certain operation can be set, the default is set in
030     * {@link #DEFAULT_CHARSET_NAME}
031     * 
032     * @since 1.1
033     * @author Apache Software Foundation
034     * @version $Id: Hex.java 801639 2009-08-06 13:15:10Z niallp $
035     */
036    public class Hex implements BinaryEncoder, BinaryDecoder {
037    
038        /**
039         * Default charset name is {@link CharEncoding#UTF_8}
040         */
041        public static final String DEFAULT_CHARSET_NAME = CharEncoding.UTF_8;
042    
043        /**
044         * Used to build output as Hex
045         */
046        private static final char[] DIGITS_LOWER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
047    
048        /**
049         * Used to build output as Hex
050         */
051        private static final char[] DIGITS_UPPER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
052    
053        /**
054         * Converts an array of characters representing hexadecimal values into an array of bytes of those same values. The
055         * returned array will be half the length of the passed array, as it takes two characters to represent any given
056         * byte. An exception is thrown if the passed char array has an odd number of elements.
057         * 
058         * @param data
059         *            An array of characters containing hexadecimal digits
060         * @return A byte array containing binary data decoded from the supplied char array.
061         * @throws DecoderException
062         *             Thrown if an odd number or illegal of characters is supplied
063         */
064        public static byte[] decodeHex(char[] data) throws DecoderException {
065    
066            int len = data.length;
067    
068            if ((len & 0x01) != 0) {
069                throw new DecoderException("Odd number of characters.");
070            }
071    
072            byte[] out = new byte[len >> 1];
073    
074            // two characters form the hex value.
075            for (int i = 0, j = 0; j < len; i++) {
076                int f = toDigit(data[j], j) << 4;
077                j++;
078                f = f | toDigit(data[j], j);
079                j++;
080                out[i] = (byte) (f & 0xFF);
081            }
082    
083            return out;
084        }
085    
086        /**
087         * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
088         * The returned array will be double the length of the passed array, as it takes two characters to represent any
089         * given byte.
090         * 
091         * @param data
092         *            a byte[] to convert to Hex characters
093         * @return A char[] containing hexadecimal characters
094         */
095        public static char[] encodeHex(byte[] data) {
096            return encodeHex(data, true);
097        }
098    
099        /**
100         * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
101         * The returned array will be double the length of the passed array, as it takes two characters to represent any
102         * given byte.
103         * 
104         * @param data
105         *            a byte[] to convert to Hex characters
106         * @param toLowerCase
107         *            <code>true</code> converts to lowercase, <code>false</code> to uppercase
108         * @return A char[] containing hexadecimal characters
109         * @since 1.4
110         */
111        public static char[] encodeHex(byte[] data, boolean toLowerCase) {
112            return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
113        }
114    
115        /**
116         * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
117         * The returned array will be double the length of the passed array, as it takes two characters to represent any
118         * given byte.
119         * 
120         * @param data
121         *            a byte[] to convert to Hex characters
122         * @param toDigits
123         *            the output alphabet
124         * @return A char[] containing hexadecimal characters
125         * @since 1.4
126         */
127        protected static char[] encodeHex(byte[] data, char[] toDigits) {
128            int l = data.length;
129            char[] out = new char[l << 1];
130            // two characters form the hex value.
131            for (int i = 0, j = 0; i < l; i++) {
132                out[j++] = toDigits[(0xF0 & data[i]) >>> 4];
133                out[j++] = toDigits[0x0F & data[i]];
134            }
135            return out;
136        }
137    
138        /**
139         * Converts an array of bytes into a String representing the hexadecimal values of each byte in order. The returned
140         * String will be double the length of the passed array, as it takes two characters to represent any given byte.
141         * 
142         * @param data
143         *            a byte[] to convert to Hex characters
144         * @return A String containing hexadecimal characters
145         * @since 1.4
146         */
147        public static String encodeHexString(byte[] data) {
148            return new String(encodeHex(data));
149        }
150    
151        /**
152         * Converts a hexadecimal character to an integer.
153         * 
154         * @param ch
155         *            A character to convert to an integer digit
156         * @param index
157         *            The index of the character in the source
158         * @return An integer
159         * @throws DecoderException
160         *             Thrown if ch is an illegal hex character
161         */
162        protected static int toDigit(char ch, int index) throws DecoderException {
163            int digit = Character.digit(ch, 16);
164            if (digit == -1) {
165                throw new DecoderException("Illegal hexadecimal charcter " + ch + " at index " + index);
166            }
167            return digit;
168        }
169    
170        private final String charsetName;
171    
172        /**
173         * Creates a new codec with the default charset name {@link #DEFAULT_CHARSET_NAME}
174         */
175        public Hex() {
176            // use default encoding
177            this.charsetName = DEFAULT_CHARSET_NAME;
178        }
179    
180        /**
181         * Creates a new codec with the given charset name.
182         * 
183         * @param csName
184         *            the charset name.
185         * @since 1.4
186         */
187        public Hex(String csName) {
188            this.charsetName = csName;
189        }
190    
191        /**
192         * Converts an array of character bytes representing hexadecimal values into an array of bytes of those same values.
193         * The returned array will be half the length of the passed array, as it takes two characters to represent any given
194         * byte. An exception is thrown if the passed char array has an odd number of elements.
195         * 
196         * @param array
197         *            An array of character bytes containing hexadecimal digits
198         * @return A byte array containing binary data decoded from the supplied byte array (representing characters).
199         * @throws DecoderException
200         *             Thrown if an odd number of characters is supplied to this function
201         * @see #decodeHex(char[])
202         */
203        public byte[] decode(byte[] array) throws DecoderException {
204            try {
205                return decodeHex(new String(array, getCharsetName()).toCharArray());
206            } catch (UnsupportedEncodingException e) {
207                throw new DecoderException(e.getMessage(), e);
208            }
209        }
210    
211        /**
212         * Converts a String or an array of character bytes representing hexadecimal values into an array of bytes of those
213         * same values. The returned array will be half the length of the passed String or array, as it takes two characters
214         * to represent any given byte. An exception is thrown if the passed char array has an odd number of elements.
215         * 
216         * @param object
217         *            A String or, an array of character bytes containing hexadecimal digits
218         * @return A byte array containing binary data decoded from the supplied byte array (representing characters).
219         * @throws DecoderException
220         *             Thrown if an odd number of characters is supplied to this function or the object is not a String or
221         *             char[]
222         * @see #decodeHex(char[])
223         */
224        public Object decode(Object object) throws DecoderException {
225            try {
226                char[] charArray = object instanceof String ? ((String) object).toCharArray() : (char[]) object;
227                return decodeHex(charArray);
228            } catch (ClassCastException e) {
229                throw new DecoderException(e.getMessage(), e);
230            }
231        }
232    
233        /**
234         * Converts an array of bytes into an array of bytes for the characters representing the hexadecimal values of each
235         * byte in order. The returned array will be double the length of the passed array, as it takes two characters to
236         * represent any given byte.
237         * <p>
238         * The conversion from hexadecimal characters to the returned bytes is performed with the charset named by
239         * {@link #getCharsetName()}.
240         * </p>
241         * 
242         * @param array
243         *            a byte[] to convert to Hex characters
244         * @return A byte[] containing the bytes of the hexadecimal characters
245         * @throws IllegalStateException
246         *             if the charsetName is invalid. This API throws {@link IllegalStateException} instead of
247         *             {@link UnsupportedEncodingException} for backward compatibility.
248         * @see #encodeHex(byte[])
249         */
250        public byte[] encode(byte[] array) {
251            return StringUtils.getBytesUnchecked(encodeHexString(array), getCharsetName());
252        }
253    
254        /**
255         * Converts a String or an array of bytes into an array of characters representing the hexadecimal values of each
256         * byte in order. The returned array will be double the length of the passed String or array, as it takes two
257         * characters to represent any given byte.
258         * <p>
259         * The conversion from hexadecimal characters to bytes to be encoded to performed with the charset named by
260         * {@link #getCharsetName()}.
261         * </p>
262         * 
263         * @param object
264         *            a String, or byte[] to convert to Hex characters
265         * @return A char[] containing hexadecimal characters
266         * @throws EncoderException
267         *             Thrown if the given object is not a String or byte[]
268         * @see #encodeHex(byte[])
269         */
270        public Object encode(Object object) throws EncoderException {
271            try {
272                byte[] byteArray = object instanceof String ? ((String) object).getBytes(getCharsetName()) : (byte[]) object;
273                return encodeHex(byteArray);
274            } catch (ClassCastException e) {
275                throw new EncoderException(e.getMessage(), e);
276            } catch (UnsupportedEncodingException e) {
277                throw new EncoderException(e.getMessage(), e);
278            }
279        }
280    
281        /**
282         * Gets the charset name.
283         * 
284         * @return the charset name.
285         * @since 1.4
286         */
287        public String getCharsetName() {
288            return this.charsetName;
289        }
290    
291        /**
292         * Returns a string representation of the object, which includes the charset name.
293         * 
294         * @return a string representation of the object.
295         */
296        public String toString() {
297            return super.toString() + "[charsetName=" + this.charsetName + "]";
298        }
299    }