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.nio.ByteBuffer;
021import java.nio.charset.Charset;
022
023import org.apache.commons.codec.BinaryDecoder;
024import org.apache.commons.codec.BinaryEncoder;
025import org.apache.commons.codec.CharEncoding;
026import org.apache.commons.codec.Charsets;
027import org.apache.commons.codec.DecoderException;
028import org.apache.commons.codec.EncoderException;
029
030/**
031 * Converts hexadecimal Strings. The charset used for certain operation can be set, the default is set in
032 * {@link #DEFAULT_CHARSET_NAME}
033 *
034 * This class is thread-safe.
035 *
036 * @since 1.1
037 */
038public class Hex implements BinaryEncoder, BinaryDecoder {
039
040    /**
041     * Default charset is {@link Charsets#UTF_8}
042     *
043     * @since 1.7
044     */
045    public static final Charset DEFAULT_CHARSET = Charsets.UTF_8;
046
047    /**
048     * Default charset name is {@link CharEncoding#UTF_8}
049     *
050     * @since 1.4
051     */
052    public static final String DEFAULT_CHARSET_NAME = CharEncoding.UTF_8;
053
054    /**
055     * Used to build output as Hex
056     */
057    private static final char[] DIGITS_LOWER = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',
058            'e', 'f' };
059
060    /**
061     * Used to build output as Hex
062     */
063    private static final char[] DIGITS_UPPER = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
064            'E', 'F' };
065
066    /**
067     * Converts a String representing hexadecimal values into an array of bytes of those same values. The returned array
068     * will be half the length of the passed String, as it takes two characters to represent any given byte. An
069     * exception is thrown if the passed String has an odd number of elements.
070     *
071     * @param data A String containing hexadecimal digits
072     * @return A byte array containing binary data decoded from the supplied char array.
073     * @throws DecoderException Thrown if an odd number or illegal of characters is supplied
074     * @since 1.11
075     */
076    public static byte[] decodeHex(final String data) throws DecoderException {
077        return decodeHex(data.toCharArray());
078    }
079
080    /**
081     * Converts an array of characters representing hexadecimal values into an array of bytes of those same values. The
082     * returned array will be half the length of the passed array, as it takes two characters to represent any given
083     * byte. An exception is thrown if the passed char array has an odd number of elements.
084     *
085     * @param data An array of characters containing hexadecimal digits
086     * @return A byte array containing binary data decoded from the supplied char array.
087     * @throws DecoderException Thrown if an odd number or illegal of characters is supplied
088     */
089    public static byte[] decodeHex(final char[] data) throws DecoderException {
090
091        final int len = data.length;
092
093        if ((len & 0x01) != 0) {
094            throw new DecoderException("Odd number of characters.");
095        }
096
097        final byte[] out = new byte[len >> 1];
098
099        // two characters form the hex value.
100        for (int i = 0, j = 0; j < len; i++) {
101            int f = toDigit(data[j], j) << 4;
102            j++;
103            f = f | toDigit(data[j], j);
104            j++;
105            out[i] = (byte) (f & 0xFF);
106        }
107
108        return out;
109    }
110
111    /**
112     * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
113     * The returned array will be double the length of the passed array, as it takes two characters to represent any
114     * given byte.
115     *
116     * @param data a byte[] to convert to Hex characters
117     * @return A char[] containing lower-case hexadecimal characters
118     */
119    public static char[] encodeHex(final byte[] data) {
120        return encodeHex(data, true);
121    }
122
123    /**
124     * Converts a byte buffer into an array of characters representing the hexadecimal values of each byte in order. The
125     * returned array will be double the length of the passed array, as it takes two characters to represent any given
126     * byte.
127     *
128     * @param data a byte buffer to convert to Hex characters
129     * @return A char[] containing lower-case hexadecimal characters
130     * @since 1.11
131     */
132    public static char[] encodeHex(final ByteBuffer data) {
133        return encodeHex(data, true);
134    }
135
136    /**
137     * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
138     * The returned array will be double the length of the passed array, as it takes two characters to represent any
139     * given byte.
140     *
141     * @param data        a byte[] to convert to Hex characters
142     * @param toLowerCase <code>true</code> converts to lowercase, <code>false</code> to uppercase
143     * @return A char[] containing hexadecimal characters in the selected case
144     * @since 1.4
145     */
146    public static char[] encodeHex(final byte[] data, final boolean toLowerCase) {
147        return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
148    }
149
150    /**
151     * Converts a byte buffer into an array of characters representing the hexadecimal values of each byte in order. The
152     * returned array will be double the length of the passed array, as it takes two characters to represent any given
153     * byte.
154     *
155     * @param data        a byte buffer to convert to Hex characters
156     * @param toLowerCase <code>true</code> converts to lowercase, <code>false</code> to uppercase
157     * @return A char[] containing hexadecimal characters in the selected case
158     * @since 1.11
159     */
160    public static char[] encodeHex(final ByteBuffer data, final boolean toLowerCase) {
161        return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
162    }
163
164    /**
165     * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
166     * The returned array will be double the length of the passed array, as it takes two characters to represent any
167     * given byte.
168     *
169     * @param data     a byte[] to convert to Hex characters
170     * @param toDigits the output alphabet (must contain at least 16 chars)
171     * @return A char[] containing the appropriate characters from the alphabet For best results, this should be either
172     *         upper- or lower-case hex.
173     * @since 1.4
174     */
175    protected static char[] encodeHex(final byte[] data, final char[] toDigits) {
176        final int l = data.length;
177        final char[] out = new char[l << 1];
178        // two characters form the hex value.
179        for (int i = 0, j = 0; i < l; i++) {
180            out[j++] = toDigits[(0xF0 & data[i]) >>> 4];
181            out[j++] = toDigits[0x0F & data[i]];
182        }
183        return out;
184    }
185
186    /**
187     * Converts a byte buffer into an array of characters representing the hexadecimal values of each byte in order. The
188     * returned array will be double the length of the passed array, as it takes two characters to represent any given
189     * byte.
190     *
191     * @param byteBuffer a byte buffer to convert to Hex characters
192     * @param toDigits   the output alphabet (must be at least 16 characters)
193     * @return A char[] containing the appropriate characters from the alphabet For best results, this should be either
194     *         upper- or lower-case hex.
195     * @since 1.11
196     */
197    protected static char[] encodeHex(final ByteBuffer byteBuffer, final char[] toDigits) {
198        return encodeHex(toByteArray(byteBuffer), toDigits);
199    }
200
201    /**
202     * Converts an array of bytes into a String representing the hexadecimal values of each byte in order. The returned
203     * String will be double the length of the passed array, as it takes two characters to represent any given byte.
204     *
205     * @param data a byte[] to convert to Hex characters
206     * @return A String containing lower-case hexadecimal characters
207     * @since 1.4
208     */
209    public static String encodeHexString(final byte[] data) {
210        return new String(encodeHex(data));
211    }
212
213    /**
214     * Converts an array of bytes into a String representing the hexadecimal values of each byte in order. The returned
215     * String will be double the length of the passed array, as it takes two characters to represent any given byte.
216     *
217     * @param data        a byte[] to convert to Hex characters
218     * @param toLowerCase <code>true</code> converts to lowercase, <code>false</code> to uppercase
219     * @return A String containing lower-case hexadecimal characters
220     * @since 1.11
221     */
222    public static String encodeHexString(final byte[] data, final boolean toLowerCase) {
223        return new String(encodeHex(data, toLowerCase));
224    }
225
226    /**
227     * Converts a byte buffer into a String representing the hexadecimal values of each byte in order. The returned
228     * String will be double the length of the passed array, as it takes two characters to represent any given byte.
229     *
230     * @param data a byte buffer to convert to Hex characters
231     * @return A String containing lower-case hexadecimal characters
232     * @since 1.11
233     */
234    public static String encodeHexString(final ByteBuffer data) {
235        return new String(encodeHex(data));
236    }
237
238    /**
239     * Converts a byte buffer into a String representing the hexadecimal values of each byte in order. The returned
240     * String will be double the length of the passed array, as it takes two characters to represent any given byte.
241     *
242     * @param data        a byte buffer to convert to Hex characters
243     * @param toLowerCase <code>true</code> converts to lowercase, <code>false</code> to uppercase
244     * @return A String containing lower-case hexadecimal characters
245     * @since 1.11
246     */
247    public static String encodeHexString(final ByteBuffer data, final boolean toLowerCase) {
248        return new String(encodeHex(data, toLowerCase));
249    }
250
251    private static byte[] toByteArray(final ByteBuffer byteBuffer) {
252        if (byteBuffer.hasArray()) {
253            return byteBuffer.array();
254        }
255        final byte[] byteArray = new byte[byteBuffer.remaining()];
256        byteBuffer.get(byteArray);
257        return byteArray;
258    }
259
260    /**
261     * Converts a hexadecimal character to an integer.
262     *
263     * @param ch    A character to convert to an integer digit
264     * @param index The index of the character in the source
265     * @return An integer
266     * @throws DecoderException Thrown if ch is an illegal hex character
267     */
268    protected static int toDigit(final char ch, final int index) throws DecoderException {
269        final int digit = Character.digit(ch, 16);
270        if (digit == -1) {
271            throw new DecoderException("Illegal hexadecimal character " + ch + " at index " + index);
272        }
273        return digit;
274    }
275
276    private final Charset charset;
277
278    /**
279     * Creates a new codec with the default charset name {@link #DEFAULT_CHARSET}
280     */
281    public Hex() {
282        // use default encoding
283        this.charset = DEFAULT_CHARSET;
284    }
285
286    /**
287     * Creates a new codec with the given Charset.
288     *
289     * @param charset the charset.
290     * @since 1.7
291     */
292    public Hex(final Charset charset) {
293        this.charset = charset;
294    }
295
296    /**
297     * Creates a new codec with the given charset name.
298     *
299     * @param charsetName the charset name.
300     * @throws java.nio.charset.UnsupportedCharsetException If the named charset is unavailable
301     * @since 1.4
302     * @since 1.7 throws UnsupportedCharsetException if the named charset is unavailable
303     */
304    public Hex(final String charsetName) {
305        this(Charset.forName(charsetName));
306    }
307
308    /**
309     * Converts an array of character bytes representing hexadecimal values into an array of bytes of those same values.
310     * The returned array will be half the length of the passed array, as it takes two characters to represent any given
311     * byte. An exception is thrown if the passed char array has an odd number of elements.
312     *
313     * @param array An array of character bytes containing hexadecimal digits
314     * @return A byte array containing binary data decoded from the supplied byte array (representing characters).
315     * @throws DecoderException Thrown if an odd number of characters is supplied to this function
316     * @see #decodeHex(char[])
317     */
318    @Override
319    public byte[] decode(final byte[] array) throws DecoderException {
320        return decodeHex(new String(array, getCharset()).toCharArray());
321    }
322
323    /**
324     * Converts a buffer of character bytes representing hexadecimal values into an array of bytes of those same values.
325     * The returned array will be half the length of the passed array, as it takes two characters to represent any given
326     * byte. An exception is thrown if the passed char array has an odd number of elements.
327     *
328     * @param buffer An array of character bytes containing hexadecimal digits
329     * @return A byte array containing binary data decoded from the supplied byte array (representing characters).
330     * @throws DecoderException Thrown if an odd number of characters is supplied to this function
331     * @see #decodeHex(char[])
332     * @since 1.11
333     */
334    public byte[] decode(final ByteBuffer buffer) throws DecoderException {
335        return decodeHex(new String(toByteArray(buffer), getCharset()).toCharArray());
336    }
337
338    /**
339     * Converts a String or an array of character bytes representing hexadecimal values into an array of bytes of those
340     * same values. The returned array will be half the length of the passed String or array, as it takes two characters
341     * to represent any given byte. An exception is thrown if the passed char array has an odd number of elements.
342     *
343     * @param object A String, ByteBuffer, byte[], or an array of character bytes containing hexadecimal digits
344     * @return A byte array containing binary data decoded from the supplied byte array (representing characters).
345     * @throws DecoderException Thrown if an odd number of characters is supplied to this function or the object is not
346     *                          a String or char[]
347     * @see #decodeHex(char[])
348     */
349    @Override
350    public Object decode(final Object object) throws DecoderException {
351        if (object instanceof String) {
352            return decode(((String) object).toCharArray());
353        } else if (object instanceof byte[]) {
354            return decode((byte[]) object);
355        } else if (object instanceof ByteBuffer) {
356            return decode((ByteBuffer) object);
357        } else {
358            try {
359                return decodeHex((char[]) object);
360            } catch (final ClassCastException e) {
361                throw new DecoderException(e.getMessage(), e);
362            }
363        }
364    }
365
366    /**
367     * Converts an array of bytes into an array of bytes for the characters representing the hexadecimal values of each
368     * byte in order. The returned array will be double the length of the passed array, as it takes two characters to
369     * represent any given byte.
370     * <p>
371     * The conversion from hexadecimal characters to the returned bytes is performed with the charset named by
372     * {@link #getCharset()}.
373     * </p>
374     *
375     * @param array a byte[] to convert to Hex characters
376     * @return A byte[] containing the bytes of the lower-case hexadecimal characters
377     * @since 1.7 No longer throws IllegalStateException if the charsetName is invalid.
378     * @see #encodeHex(byte[])
379     */
380    @Override
381    public byte[] encode(final byte[] array) {
382        return encodeHexString(array).getBytes(this.getCharset());
383    }
384
385    /**
386     * Converts byte buffer into an array of bytes for the characters representing the hexadecimal values of each byte
387     * in order. The returned array will be double the length of the passed array, as it takes two characters to
388     * represent any given byte.
389     * <p>
390     * The conversion from hexadecimal characters to the returned bytes is performed with the charset named by
391     * {@link #getCharset()}.
392     * </p>
393     *
394     * @param array a byte buffer to convert to Hex characters
395     * @return A byte[] containing the bytes of the lower-case hexadecimal characters
396     * @see #encodeHex(byte[])
397     * @since 1.11
398     */
399    public byte[] encode(final ByteBuffer array) {
400        return encodeHexString(array).getBytes(this.getCharset());
401    }
402
403    /**
404     * Converts a String or an array of bytes into an array of characters representing the hexadecimal values of each
405     * byte in order. The returned array will be double the length of the passed String or array, as it takes two
406     * characters to represent any given byte.
407     * <p>
408     * The conversion from hexadecimal characters to bytes to be encoded to performed with the charset named by
409     * {@link #getCharset()}.
410     * </p>
411     *
412     * @param object a String, ByteBuffer, or byte[] to convert to Hex characters
413     * @return A char[] containing lower-case hexadecimal characters
414     * @throws EncoderException Thrown if the given object is not a String or byte[]
415     * @see #encodeHex(byte[])
416     */
417    @Override
418    public Object encode(final Object object) throws EncoderException {
419        byte[] byteArray;
420        if (object instanceof String) {
421            byteArray = ((String) object).getBytes(this.getCharset());
422        } else if (object instanceof ByteBuffer) {
423            byteArray = toByteArray((ByteBuffer) object);
424        } else {
425            try {
426                byteArray = (byte[]) object;
427            } catch (final ClassCastException e) {
428                throw new EncoderException(e.getMessage(), e);
429            }
430        }
431        return encodeHex(byteArray);
432    }
433
434    /**
435     * Gets the charset.
436     *
437     * @return the charset.
438     * @since 1.7
439     */
440    public Charset getCharset() {
441        return this.charset;
442    }
443
444    /**
445     * Gets the charset name.
446     *
447     * @return the charset name.
448     * @since 1.4
449     */
450    public String getCharsetName() {
451        return this.charset.name();
452    }
453
454    /**
455     * Returns a string representation of the object, which includes the charset name.
456     *
457     * @return a string representation of the object.
458     */
459    @Override
460    public String toString() {
461        return super.toString() + "[charsetName=" + this.charset + "]";
462    }
463}