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
018package org.apache.commons.codec.binary;
019
020import java.io.UnsupportedEncodingException;
021
022import org.apache.commons.codec.BinaryDecoder;
023import org.apache.commons.codec.BinaryEncoder;
024import org.apache.commons.codec.CharEncoding;
025import org.apache.commons.codec.DecoderException;
026import 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 */
036public 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}