View Javadoc
1   /**
2    * Copyright 2010-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.common.util;
17  
18  import static org.kuali.common.util.base.Exceptions.illegalArgument;
19  
20  import java.io.UnsupportedEncodingException;
21  
22  import org.apache.commons.lang3.CharSet;
23  import org.apache.commons.lang3.StringUtils;
24  
25  /**
26   * A few (highly inefficient) methods for converting <code>String's</code> into the hex for a given encoding and back again.
27   */
28  public class HexUtils {
29  
30  	private static final String ZERO = "0";
31  	private static final int BYTE_MASK = 0x000000ff;
32  	private static final String[] HEX_RANGES = new String[] { "0-9", "A-F", "a-f" };
33  	private static final String HEX_RANGES_STRING = toString(HEX_RANGES);
34  	private static final CharSet HEX_CHARSET = CharSet.getInstance(HEX_RANGES);
35  
36  	public static final CharSet getHexCharSet() {
37  		return HEX_CHARSET;
38  	}
39  
40  	public static final String[] getHexRanges() {
41  		return HEX_RANGES;
42  	}
43  
44  	protected static final String toString(String[] tokens) {
45  		StringBuilder sb = new StringBuilder();
46  		sb.append("[");
47  		for (int i = 0; i < HEX_RANGES.length; i++) {
48  			if (i != 0) {
49  				sb.append(",");
50  			}
51  			sb.append(HEX_RANGES[i]);
52  		}
53  		sb.append("]");
54  		return sb.toString();
55  	}
56  
57  	/**
58  	 * Convert <code>string</code> into a <code>byte[]</code> using the specified encoding, then convert each <code>byte</code> into its 2 digit hexadecimal form.
59  	 */
60  	public static String toHexString(String string, String encoding) throws UnsupportedEncodingException {
61  		byte[] bytes = encoding == null ? string.getBytes() : string.getBytes(encoding);
62  		return toHexString(bytes);
63  	}
64  
65  	/**
66  	 * Convert each <code>byte</code> into its 2 digit hexadecimal form.
67  	 */
68  	public static String toHexString(byte[] bytes) {
69  		StringBuilder sb = new StringBuilder();
70  		for (byte b : bytes) {
71  			int masked = BYTE_MASK & b;
72  			String hex = Integer.toHexString(masked).toUpperCase();
73  			String padded = StringUtils.leftPad(hex, 2, ZERO);
74  			sb.append(padded);
75  		}
76  		return sb.toString();
77  	}
78  
79  	/**
80  	 * Convert each <code>byte</code> into its 2 digit hexadecimal form.
81  	 */
82  	public static String toHexStringLower(byte[] bytes) {
83  		return toHexString(bytes).toLowerCase();
84  	}
85  
86  	/**
87  	 * Return true if every character is valid hex <code>0-9</code>, <code>a-f</code>, or <code>A-F</code>
88  	 */
89  	public static final boolean isHex(char... chars) {
90  		for (char c : chars) {
91  			if (!HEX_CHARSET.contains(c)) {
92  				return false;
93  			}
94  		}
95  		return true;
96  	}
97  
98  	/**
99  	 * Given a string in <code>strictly hex</code> format, return the corresponding <code>byte[]</code>. <code>strictly hex</code> in the context of this method means that the
100 	 * string:<br>
101 	 * 1 - Contains only the characters <code>a-f</code>, <code>A-F</code>, and <code>0-9</code><br>
102 	 * 2 - Its length is an even number.
103 	 */
104 	public static final byte[] getBytesFromHexString(String hex) {
105 		char[] chars = hex.toCharArray();
106 		int length = chars.length;
107 		if (length % 2 != 0) {
108 			throw illegalArgument("Invalid hex string [%s].  String must contain an even number of characters.  %s is not an even number!", hex, length);
109 		}
110 		byte[] bytes = new byte[length / 2];
111 		int byteIndex = 0;
112 		for (int i = 0; i < length; i += 2) {
113 			char c1 = chars[i];
114 			char c2 = chars[i + 1];
115 			String s = c1 + "" + c2;
116 			if (!isHex(c1, c2)) {
117 				int byteNumber = i / 2 + 1;
118 				throw illegalArgument("Invalid hex string [%s].  Invalid hex detected at byte %s [%s].  Both characters must be in the range %s", hex, byteNumber, s,
119 						HEX_RANGES_STRING);
120 			}
121 			int integer = Integer.parseInt(s, 16);
122 			int masked = integer & BYTE_MASK;
123 			byte b = (byte) masked;
124 			bytes[byteIndex++] = b;
125 		}
126 		return bytes;
127 	}
128 
129 	/**
130 	 * Given a string in <code>strictly hex</code> format and the <code>encoding</code> that was used to produce the hex, convert it back to a Java <code>String</code>.
131 	 * <code>strictly hex</code> in the context of this method means that the string:<br>
132 	 * 1 - Contains only the characters <code>a-f</code>, <code>A-F</code>, and <code>0-9</code><br>
133 	 * 2 - Its length is an even number.
134 	 */
135 	public static final String toStringFromHex(String hex, String encoding) throws UnsupportedEncodingException {
136 		byte[] bytes = getBytesFromHexString(hex);
137 		return StringUtils.toString(bytes, encoding);
138 	}
139 
140 }