View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.miloss.fgsms.common.codec;
19  import java.nio.ByteBuffer;
20  import java.nio.charset.Charset;
21  
22  /**
23   * Converts hexadecimal Strings. The charset used for certain operation can be set, the default is set in
24   * {@link #DEFAULT_CHARSET_NAME}
25   *
26   * This class is thread-safe.
27   *
28   * @since 1.1
29   * @version $Id$
30   */
31  public class Hex implements BinaryEncoder, BinaryDecoder {
32  
33      /**
34       * Default charset is {@link Charsets#UTF_8}
35       *
36       * @since 1.7
37       */
38      public static final Charset DEFAULT_CHARSET = Charsets.UTF_8;
39  
40      /**
41       * Default charset name is {@link CharEncoding#UTF_8}
42       *
43       * @since 1.4
44       */
45      public static final String DEFAULT_CHARSET_NAME = CharEncoding.UTF_8;
46  
47      /**
48       * Used to build output as Hex
49       */
50      private static final char[] DIGITS_LOWER =
51          {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
52  
53      /**
54       * Used to build output as Hex
55       */
56      private static final char[] DIGITS_UPPER =
57          {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
58  
59      /**
60       * Converts a String representing hexadecimal values into an array of bytes of those same values. The
61       * returned array will be half the length of the passed String, as it takes two characters to represent any given
62       * byte. An exception is thrown if the passed String has an odd number of elements.
63       *
64       * @param data
65       *            A String containing hexadecimal digits
66       * @return A byte array containing binary data decoded from the supplied char array.
67       * @throws DecoderException
68       *             Thrown if an odd number or illegal of characters is supplied
69       * @since 1.11
70       */
71      public static byte[] decodeHex(String data) throws DecoderException {
72          return decodeHex(data.toCharArray());
73      }
74  
75      /**
76       * Converts an array of characters representing hexadecimal values into an array of bytes of those same values. The
77       * returned array will be half the length of the passed array, as it takes two characters to represent any given
78       * byte. An exception is thrown if the passed char array has an odd number of elements.
79       *
80       * @param data
81       *            An array of characters containing hexadecimal digits
82       * @return A byte array containing binary data decoded from the supplied char array.
83       * @throws DecoderException
84       *             Thrown if an odd number or illegal of characters is supplied
85       */
86      public static byte[] decodeHex(final char[] data) throws DecoderException {
87  
88          final int len = data.length;
89  
90          if ((len & 0x01) != 0) {
91              throw new DecoderException("Odd number of characters.");
92          }
93  
94          final byte[] out = new byte[len >> 1];
95  
96          // two characters form the hex value.
97          for (int i = 0, j = 0; j < len; i++) {
98              int f = toDigit(data[j], j) << 4;
99              j++;
100             f = f | toDigit(data[j], j);
101             j++;
102             out[i] = (byte) (f & 0xFF);
103         }
104 
105         return out;
106     }
107 
108     /**
109      * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
110      * The returned array will be double the length of the passed array, as it takes two characters to represent any
111      * given byte.
112      *
113      * @param data
114      *            a byte[] to convert to Hex characters
115      * @return A char[] containing lower-case hexadecimal characters
116      */
117     public static char[] encodeHex(final byte[] data) {
118         return encodeHex(data, true);
119     }
120 
121     /**
122      * Converts a byte buffer into an array of characters representing the hexadecimal values of each byte in order.
123      * The returned array will be double the length of the passed array, as it takes two characters to represent any
124      * given byte.
125      *
126      * @param data
127      *            a byte buffer to convert to Hex characters
128      * @return A char[] containing lower-case hexadecimal characters
129      * @since 1.11
130      */
131     public static char[] encodeHex(final ByteBuffer data) {
132         return encodeHex(data, true);
133     }
134 
135     /**
136      * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
137      * The returned array will be double the length of the passed array, as it takes two characters to represent any
138      * given byte.
139      *
140      * @param data
141      *            a byte[] to convert to Hex characters
142      * @param toLowerCase
143      *            <code>true</code> converts to lowercase, <code>false</code> to uppercase
144      * @return A char[] containing hexadecimal characters in the selected case
145      * @since 1.4
146      */
147     public static char[] encodeHex(final byte[] data, final boolean toLowerCase) {
148         return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
149     }
150 
151     /**
152      * Converts a byte buffer into an array of characters representing the hexadecimal values of each byte in order.
153      * The returned array will be double the length of the passed array, as it takes two characters to represent any
154      * given byte.
155      *
156      * @param data
157      *            a byte buffer to convert to Hex characters
158      * @param toLowerCase
159      *            <code>true</code> converts to lowercase, <code>false</code> to uppercase
160      * @return A char[] containing hexadecimal characters in the selected case
161      * @since 1.11
162      */
163     public static char[] encodeHex(final ByteBuffer data, final boolean toLowerCase) {
164         return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
165     }
166 
167     /**
168      * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
169      * The returned array will be double the length of the passed array, as it takes two characters to represent any
170      * given byte.
171      *
172      * @param data
173      *            a byte[] to convert to Hex characters
174      * @param toDigits
175      *            the output alphabet (must contain at least 16 chars)
176      * @return A char[] containing the appropriate characters from the alphabet
177      *         For best results, this should be either upper- or lower-case hex.
178      * @since 1.4
179      */
180     protected static char[] encodeHex(final byte[] data, final char[] toDigits) {
181         final int l = data.length;
182         final char[] out = new char[l << 1];
183         // two characters form the hex value.
184         for (int i = 0, j = 0; i < l; i++) {
185             out[j++] = toDigits[(0xF0 & data[i]) >>> 4];
186             out[j++] = toDigits[0x0F & data[i]];
187         }
188         return out;
189     }
190 
191     /**
192      * Converts a byte buffer into an array of characters representing the hexadecimal values of each byte in order.
193      * The returned array will be double the length of the passed array, as it takes two characters to represent any
194      * given byte.
195      *
196      * @param data
197      *            a byte buffer to convert to Hex characters
198      * @param toDigits
199      *            the output alphabet (must be at least 16 characters)
200      * @return A char[] containing the appropriate characters from the alphabet
201      *         For best results, this should be either upper- or lower-case hex.
202      * @since 1.11
203      */
204     protected static char[] encodeHex(final ByteBuffer data, final char[] toDigits) {
205         return encodeHex(data.array(), toDigits);
206     }
207 
208     /**
209      * Converts an array of bytes into a String representing the hexadecimal values of each byte in order. The returned
210      * String will be double the length of the passed array, as it takes two characters to represent any given byte.
211      *
212      * @param data
213      *            a byte[] to convert to Hex characters
214      * @return A String containing lower-case hexadecimal characters
215      * @since 1.4
216      */
217     public static String encodeHexString(final byte[] data) {
218         return new String(encodeHex(data));
219     }
220 
221     /**
222      * Converts an array of bytes into a String representing the hexadecimal values of each byte in order. The returned
223      * String will be double the length of the passed array, as it takes two characters to represent any given byte.
224      *
225      * @param data
226      *            a byte[] to convert to Hex characters
227      * @param toLowerCase
228      *            <code>true</code> converts to lowercase, <code>false</code> to uppercase
229      * @return A String containing lower-case hexadecimal characters
230      * @since 1.11
231      */
232     public static String encodeHexString(final byte[] data, boolean toLowerCase) {
233         return new String(encodeHex(data, toLowerCase));
234     }
235 
236     /**
237      * Converts a byte buffer into a String representing the hexadecimal values of each byte in order. The returned
238      * String will be double the length of the passed array, as it takes two characters to represent any given byte.
239      *
240      * @param data
241      *            a byte buffer to convert to Hex characters
242      * @return A String containing lower-case hexadecimal characters
243      * @since 1.11
244      */
245     public static String encodeHexString(final ByteBuffer data) {
246         return new String(encodeHex(data));
247     }
248 
249     /**
250      * Converts a byte buffer into a String representing the hexadecimal values of each byte in order. The returned
251      * String will be double the length of the passed array, as it takes two characters to represent any given byte.
252      *
253      * @param data
254      *            a byte buffer to convert to Hex characters
255      * @param toLowerCase
256      *            <code>true</code> converts to lowercase, <code>false</code> to uppercase
257      * @return A String containing lower-case hexadecimal characters
258      * @since 1.11
259      */
260     public static String encodeHexString(final ByteBuffer data, boolean toLowerCase) {
261         return new String(encodeHex(data, toLowerCase));
262     }
263 
264     /**
265      * Converts a hexadecimal character to an integer.
266      *
267      * @param ch
268      *            A character to convert to an integer digit
269      * @param index
270      *            The index of the character in the source
271      * @return An integer
272      * @throws DecoderException
273      *             Thrown if ch is an illegal hex character
274      */
275     protected static int toDigit(final char ch, final int index) throws DecoderException {
276         final int digit = Character.digit(ch, 16);
277         if (digit == -1) {
278             throw new DecoderException("Illegal hexadecimal character " + ch + " at index " + index);
279         }
280         return digit;
281     }
282 
283     private final Charset charset;
284 
285     /**
286      * Creates a new codec with the default charset name {@link #DEFAULT_CHARSET}
287      */
288     public Hex() {
289         // use default encoding
290         this.charset = DEFAULT_CHARSET;
291     }
292 
293     /**
294      * Creates a new codec with the given Charset.
295      *
296      * @param charset
297      *            the charset.
298      * @since 1.7
299      */
300     public Hex(final Charset charset) {
301         this.charset = charset;
302     }
303 
304     /**
305      * Creates a new codec with the given charset name.
306      *
307      * @param charsetName
308      *            the charset name.
309      * @throws java.nio.charset.UnsupportedCharsetException
310      *             If the named charset is unavailable
311      * @since 1.4
312      * @since 1.7 throws UnsupportedCharsetException if the named charset is unavailable
313      */
314     public Hex(final String charsetName) {
315         this(Charset.forName(charsetName));
316     }
317 
318     /**
319      * Converts an array of character bytes representing hexadecimal values into an array of bytes of those same values.
320      * The returned array will be half the length of the passed array, as it takes two characters to represent any given
321      * byte. An exception is thrown if the passed char array has an odd number of elements.
322      *
323      * @param array
324      *            An array of character bytes containing hexadecimal digits
325      * @return A byte array containing binary data decoded from the supplied byte array (representing characters).
326      * @throws DecoderException
327      *             Thrown if an odd number of characters is supplied to this function
328      * @see #decodeHex(char[])
329      */
330     @Override
331     public byte[] decode(final byte[] array) throws DecoderException {
332         return decodeHex(new String(array, getCharset()).toCharArray());
333     }
334 
335     /**
336      * Converts a buffer of character bytes representing hexadecimal values into an array of bytes of those same values.
337      * The returned array will be half the length of the passed array, as it takes two characters to represent any given
338      * byte. An exception is thrown if the passed char array has an odd number of elements.
339      *
340      * @param buffer
341      *            An array of character bytes containing hexadecimal digits
342      * @return A byte array containing binary data decoded from the supplied byte array (representing characters).
343      * @throws DecoderException
344      *             Thrown if an odd number of characters is supplied to this function
345      * @see #decodeHex(char[])
346      * @since 1.11
347      */
348     public byte[] decode(final ByteBuffer buffer) throws DecoderException {
349         return decodeHex(new String(buffer.array(), getCharset()).toCharArray());
350     }
351 
352     /**
353      * Converts a String or an array of character bytes representing hexadecimal values into an array of bytes of those
354      * same values. The returned array will be half the length of the passed String or array, as it takes two characters
355      * to represent any given byte. An exception is thrown if the passed char array has an odd number of elements.
356      *
357      * @param object
358      *            A String, ByteBuffer, byte[], or an array of character bytes containing hexadecimal digits
359      * @return A byte array containing binary data decoded from the supplied byte array (representing characters).
360      * @throws DecoderException
361      *             Thrown if an odd number of characters is supplied to this function or the object is not a String or
362      *             char[]
363      * @see #decodeHex(char[])
364      */
365     @Override
366     public Object decode(final Object object) throws DecoderException {
367         if (object instanceof String) {
368             return decode(((String) object).toCharArray());
369         } else if (object instanceof byte[]) {
370             return decode((byte[]) object);
371         } else if (object instanceof ByteBuffer) {
372             return decode((ByteBuffer) object);
373         } else {
374             try {
375                 return decodeHex((char[]) object);
376             } catch (final ClassCastException e) {
377                 throw new DecoderException(e.getMessage(), e);
378             }
379         }
380     }
381 
382     /**
383      * Converts an array of bytes into an array of bytes for the characters representing the hexadecimal values of each
384      * byte in order. The returned array will be double the length of the passed array, as it takes two characters to
385      * represent any given byte.
386      * <p>
387      * The conversion from hexadecimal characters to the returned bytes is performed with the charset named by
388      * {@link #getCharset()}.
389      * </p>
390      *
391      * @param array
392      *            a byte[] to convert to Hex characters
393      * @return A byte[] containing the bytes of the lower-case hexadecimal characters
394      * @since 1.7 No longer throws IllegalStateException if the charsetName is invalid.
395      * @see #encodeHex(byte[])
396      */
397     @Override
398     public byte[] encode(final byte[] array) {
399         return encodeHexString(array).getBytes(this.getCharset());
400     }
401 
402     /**
403      * Converts byte buffer into an array of bytes for the characters representing the hexadecimal values of each
404      * byte in order. The returned array will be double the length of the passed array, as it takes two characters to
405      * represent any given byte.
406      * <p>
407      * The conversion from hexadecimal characters to the returned bytes is performed with the charset named by
408      * {@link #getCharset()}.
409      * </p>
410      *
411      * @param array
412      *            a byte buffer to convert to Hex characters
413      * @return A byte[] containing the bytes of the lower-case hexadecimal characters
414      * @see #encodeHex(byte[])
415      * @since 1.11
416      */
417     public byte[] encode(final ByteBuffer array) {
418         return encodeHexString(array).getBytes(this.getCharset());
419     }
420 
421     /**
422      * Converts a String or an array of bytes into an array of characters representing the hexadecimal values of each
423      * byte in order. The returned array will be double the length of the passed String or array, as it takes two
424      * characters to represent any given byte.
425      * <p>
426      * The conversion from hexadecimal characters to bytes to be encoded to performed with the charset named by
427      * {@link #getCharset()}.
428      * </p>
429      *
430      * @param object
431      *            a String, ByteBuffer, or byte[] to convert to Hex characters
432      * @return A char[] containing lower-case hexadecimal characters
433      * @throws EncoderException
434      *             Thrown if the given object is not a String or byte[]
435      * @see #encodeHex(byte[])
436      */
437     @Override
438     public Object encode(final Object object) throws EncoderException {
439         byte[] byteArray;
440         if (object instanceof String) {
441             byteArray = ((String) object).getBytes(this.getCharset());
442         } else if (object instanceof ByteBuffer) {
443             byteArray = ((ByteBuffer) object).array();
444         } else {
445             try {
446                 byteArray = (byte[]) object;
447             } catch (final ClassCastException e) {
448                 throw new EncoderException(e.getMessage(), e);
449             }
450         }
451         return encodeHex(byteArray);
452     }
453 
454     /**
455      * Gets the charset.
456      *
457      * @return the charset.
458      * @since 1.7
459      */
460     public Charset getCharset() {
461         return this.charset;
462     }
463 
464     /**
465      * Gets the charset name.
466      *
467      * @return the charset name.
468      * @since 1.4
469      */
470     public String getCharsetName() {
471         return this.charset.name();
472     }
473 
474     /**
475      * Returns a string representation of the object, which includes the charset name.
476      *
477      * @return a string representation of the object.
478      */
479     @Override
480     public String toString() {
481         return super.toString() + "[charsetName=" + this.charset + "]";
482     }
483 }