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.apache.commons.codec.binary;
19
20 import java.io.UnsupportedEncodingException;
21
22 import org.apache.commons.codec.BinaryDecoder;
23 import org.apache.commons.codec.BinaryEncoder;
24 import org.apache.commons.codec.CharEncoding;
25 import org.apache.commons.codec.DecoderException;
26 import org.apache.commons.codec.EncoderException;
27
28 /**
29 * Converts hexadecimal Strings. The charset used for certain operation can be set, the default is set in
30 * {@link #DEFAULT_CHARSET_NAME}
31 *
32 * @since 1.1
33 * @author Apache Software Foundation
34 * @version $Id: Hex.java 1157192 2011-08-12 17:27:38Z ggregory $
35 */
36 public class Hex implements BinaryEncoder, BinaryDecoder {
37
38 /**
39 * Default charset name is {@link CharEncoding#UTF_8}
40 *
41 * @since 1.4
42 */
43 public static final String DEFAULT_CHARSET_NAME = CharEncoding.UTF_8;
44
45 /**
46 * Used to build output as Hex
47 */
48 private static final char[] DIGITS_LOWER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
49
50 /**
51 * Used to build output as Hex
52 */
53 private static final char[] DIGITS_UPPER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
54
55 /**
56 * Converts an array of characters representing hexadecimal values into an array of bytes of those same values. The
57 * returned array will be half the length of the passed array, as it takes two characters to represent any given
58 * byte. An exception is thrown if the passed char array has an odd number of elements.
59 *
60 * @param data
61 * An array of characters containing hexadecimal digits
62 * @return A byte array containing binary data decoded from the supplied char array.
63 * @throws DecoderException
64 * Thrown if an odd number or illegal of characters is supplied
65 */
66 public static byte[] decodeHex(char[] data) throws DecoderException {
67
68 int len = data.length;
69
70 if ((len & 0x01) != 0) {
71 throw new DecoderException("Odd number of characters.");
72 }
73
74 byte[] out = new byte[len >> 1];
75
76 // two characters form the hex value.
77 for (int i = 0, j = 0; j < len; i++) {
78 int f = toDigit(data[j], j) << 4;
79 j++;
80 f = f | toDigit(data[j], j);
81 j++;
82 out[i] = (byte) (f & 0xFF);
83 }
84
85 return out;
86 }
87
88 /**
89 * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
90 * The returned array will be double the length of the passed array, as it takes two characters to represent any
91 * given byte.
92 *
93 * @param data
94 * a byte[] to convert to Hex characters
95 * @return A char[] containing hexadecimal characters
96 */
97 public static char[] encodeHex(byte[] data) {
98 return encodeHex(data, true);
99 }
100
101 /**
102 * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
103 * The returned array will be double the length of the passed array, as it takes two characters to represent any
104 * given byte.
105 *
106 * @param data
107 * a byte[] to convert to Hex characters
108 * @param toLowerCase
109 * <code>true</code> converts to lowercase, <code>false</code> to uppercase
110 * @return A char[] containing hexadecimal characters
111 * @since 1.4
112 */
113 public static char[] encodeHex(byte[] data, boolean toLowerCase) {
114 return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
115 }
116
117 /**
118 * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
119 * The returned array will be double the length of the passed array, as it takes two characters to represent any
120 * given byte.
121 *
122 * @param data
123 * a byte[] to convert to Hex characters
124 * @param toDigits
125 * the output alphabet
126 * @return A char[] containing hexadecimal characters
127 * @since 1.4
128 */
129 protected static char[] encodeHex(byte[] data, char[] toDigits) {
130 int l = data.length;
131 char[] out = new char[l << 1];
132 // two characters form the hex value.
133 for (int i = 0, j = 0; i < l; i++) {
134 out[j++] = toDigits[(0xF0 & data[i]) >>> 4];
135 out[j++] = toDigits[0x0F & data[i]];
136 }
137 return out;
138 }
139
140 /**
141 * Converts an array of bytes into a String representing the hexadecimal values of each byte in order. The returned
142 * String will be double the length of the passed array, as it takes two characters to represent any given byte.
143 *
144 * @param data
145 * a byte[] to convert to Hex characters
146 * @return A String containing hexadecimal characters
147 * @since 1.4
148 */
149 public static String encodeHexString(byte[] data) {
150 return new String(encodeHex(data));
151 }
152
153 /**
154 * Converts a hexadecimal character to an integer.
155 *
156 * @param ch
157 * A character to convert to an integer digit
158 * @param index
159 * The index of the character in the source
160 * @return An integer
161 * @throws DecoderException
162 * Thrown if ch is an illegal hex character
163 */
164 protected static int toDigit(char ch, int index) throws DecoderException {
165 int digit = Character.digit(ch, 16);
166 if (digit == -1) {
167 throw new DecoderException("Illegal hexadecimal character " + ch + " at index " + index);
168 }
169 return digit;
170 }
171
172 private final String charsetName;
173
174 /**
175 * Creates a new codec with the default charset name {@link #DEFAULT_CHARSET_NAME}
176 */
177 public Hex() {
178 // use default encoding
179 this.charsetName = DEFAULT_CHARSET_NAME;
180 }
181
182 /**
183 * Creates a new codec with the given charset name.
184 *
185 * @param csName
186 * the charset name.
187 * @since 1.4
188 */
189 public Hex(String csName) {
190 this.charsetName = csName;
191 }
192
193 /**
194 * Converts an array of character bytes representing hexadecimal values into an array of bytes of those same values.
195 * The returned array will be half the length of the passed array, as it takes two characters to represent any given
196 * byte. An exception is thrown if the passed char array has an odd number of elements.
197 *
198 * @param array
199 * An array of character bytes containing hexadecimal digits
200 * @return A byte array containing binary data decoded from the supplied byte array (representing characters).
201 * @throws DecoderException
202 * Thrown if an odd number of characters is supplied to this function
203 * @see #decodeHex(char[])
204 */
205 public byte[] decode(byte[] array) throws DecoderException {
206 try {
207 return decodeHex(new String(array, getCharsetName()).toCharArray());
208 } catch (UnsupportedEncodingException e) {
209 throw new DecoderException(e.getMessage(), e);
210 }
211 }
212
213 /**
214 * Converts a String or an array of character bytes representing hexadecimal values into an array of bytes of those
215 * same values. The returned array will be half the length of the passed String or array, as it takes two characters
216 * to represent any given byte. An exception is thrown if the passed char array has an odd number of elements.
217 *
218 * @param object
219 * A String or, an array of character bytes containing hexadecimal digits
220 * @return A byte array containing binary data decoded from the supplied byte array (representing characters).
221 * @throws DecoderException
222 * Thrown if an odd number of characters is supplied to this function or the object is not a String or
223 * char[]
224 * @see #decodeHex(char[])
225 */
226 public Object decode(Object object) throws DecoderException {
227 try {
228 char[] charArray = object instanceof String ? ((String) object).toCharArray() : (char[]) object;
229 return decodeHex(charArray);
230 } catch (ClassCastException e) {
231 throw new DecoderException(e.getMessage(), e);
232 }
233 }
234
235 /**
236 * Converts an array of bytes into an array of bytes for the characters representing the hexadecimal values of each
237 * byte in order. The returned array will be double the length of the passed array, as it takes two characters to
238 * represent any given byte.
239 * <p>
240 * The conversion from hexadecimal characters to the returned bytes is performed with the charset named by
241 * {@link #getCharsetName()}.
242 * </p>
243 *
244 * @param array
245 * a byte[] to convert to Hex characters
246 * @return A byte[] containing the bytes of the hexadecimal characters
247 * @throws IllegalStateException
248 * if the charsetName is invalid. This API throws {@link IllegalStateException} instead of
249 * {@link UnsupportedEncodingException} for backward compatibility.
250 * @see #encodeHex(byte[])
251 */
252 public byte[] encode(byte[] array) {
253 return StringUtils.getBytesUnchecked(encodeHexString(array), getCharsetName());
254 }
255
256 /**
257 * Converts a String or an array of bytes into an array of characters representing the hexadecimal values of each
258 * byte in order. The returned array will be double the length of the passed String or array, as it takes two
259 * characters to represent any given byte.
260 * <p>
261 * The conversion from hexadecimal characters to bytes to be encoded to performed with the charset named by
262 * {@link #getCharsetName()}.
263 * </p>
264 *
265 * @param object
266 * a String, or byte[] to convert to Hex characters
267 * @return A char[] containing hexadecimal characters
268 * @throws EncoderException
269 * Thrown if the given object is not a String or byte[]
270 * @see #encodeHex(byte[])
271 */
272 public Object encode(Object object) throws EncoderException {
273 try {
274 byte[] byteArray = object instanceof String ? ((String) object).getBytes(getCharsetName()) : (byte[]) object;
275 return encodeHex(byteArray);
276 } catch (ClassCastException e) {
277 throw new EncoderException(e.getMessage(), e);
278 } catch (UnsupportedEncodingException e) {
279 throw new EncoderException(e.getMessage(), e);
280 }
281 }
282
283 /**
284 * Gets the charset name.
285 *
286 * @return the charset name.
287 * @since 1.4
288 */
289 public String getCharsetName() {
290 return this.charsetName;
291 }
292
293 /**
294 * Returns a string representation of the object, which includes the charset name.
295 *
296 * @return a string representation of the object.
297 */
298 @Override
299 public String toString() {
300 return super.toString() + "[charsetName=" + this.charsetName + "]";
301 }
302 }