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 }