001 /**
002 * Copyright 2010-2012 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.common.util;
017
018 import java.io.UnsupportedEncodingException;
019
020 import org.apache.commons.lang3.CharSet;
021 import org.apache.commons.lang3.StringUtils;
022
023 /**
024 * A few (highly inefficient) methods for converting <code>String's</code> into the hex for a given encoding and back again.
025 */
026 public class HexUtils {
027
028 private static final String ZERO = "0";
029 private static final int BYTE_MASK = 0x000000ff;
030 private static final String[] HEX_RANGES = new String[] { "0-9", "A-F", "a-f" };
031 private static final String HEX_RANGES_STRING = toString(HEX_RANGES);
032 private static final CharSet HEX_CHARSET = CharSet.getInstance(HEX_RANGES);
033
034 public static final CharSet getHexCharSet() {
035 return HEX_CHARSET;
036 }
037
038 public static final String[] getHexRanges() {
039 return HEX_RANGES;
040 }
041
042 protected static final String toString(String[] tokens) {
043 StringBuilder sb = new StringBuilder();
044 sb.append("[");
045 for (int i = 0; i < HEX_RANGES.length; i++) {
046 if (i != 0) {
047 sb.append(",");
048 }
049 sb.append(HEX_RANGES[i]);
050 }
051 sb.append("]");
052 return sb.toString();
053 }
054
055 /**
056 * Convert <code>string</code> into a <code>byte[]</code> using the specified encoding, then convert each <code>byte</code> into its 2
057 * digit hexadecimal form.
058 */
059 public static String toHexString(String string, String encoding) throws UnsupportedEncodingException {
060 byte[] bytes = encoding == null ? string.getBytes() : string.getBytes(encoding);
061 return toHexString(bytes);
062 }
063
064 /**
065 * Convert each <code>byte</code> into its 2 digit hexadecimal form.
066 */
067 public static String toHexString(byte[] bytes) {
068 StringBuilder sb = new StringBuilder();
069 for (byte b : bytes) {
070 int masked = BYTE_MASK & b;
071 String hex = Integer.toHexString(masked).toUpperCase();
072 String padded = StringUtils.leftPad(hex, 2, ZERO);
073 sb.append(padded);
074 }
075 return sb.toString();
076 }
077
078 /**
079 * Return true if every character is valid hex <code>0-9</code>, <code>a-f</code>, or <code>A-F</code>
080 */
081 public static final boolean isHex(char... chars) {
082 for (char c : chars) {
083 if (!HEX_CHARSET.contains(c)) {
084 return false;
085 }
086 }
087 return true;
088 }
089
090 /**
091 * Given a string in <code>strictly hex</code> format, return the corresponding <code>byte[]</code>. <code>strictly hex</code> in the
092 * context of this method means that the string:<br>
093 * 1 - Contains only the characters <code>a-f</code>, <code>A-F</code>, and <code>0-9</code><br>
094 * 2 - Its length is an even number.
095 */
096 public static final byte[] getBytesFromHexString(String hex) {
097 char[] chars = hex.toCharArray();
098 int length = chars.length;
099 if (length % 2 != 0) {
100 throw new IllegalArgumentException("Invalid hex string [" + hex + "]. String must contain an even number of characters. " + length + " is not an even number!");
101 }
102 byte[] bytes = new byte[length / 2];
103 int byteIndex = 0;
104 for (int i = 0; i < length; i += 2) {
105 char c1 = chars[i];
106 char c2 = chars[i + 1];
107 String s = c1 + "" + c2;
108 if (!isHex(c1, c2)) {
109 int byteNumber = i / 2 + 1;
110 throw new IllegalArgumentException("Invalid hex string [" + hex + "]. Invalid hex detected at byte " + byteNumber + " [" + s
111 + "]. Both characters must be in the range " + HEX_RANGES_STRING);
112 }
113 int integer = Integer.parseInt(s, 16);
114 int masked = integer & BYTE_MASK;
115 byte b = (byte) masked;
116 bytes[byteIndex++] = b;
117 }
118 return bytes;
119 }
120
121 /**
122 * 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
123 * a Java <code>String</code>. <code>strictly hex</code> in the context of this method means that the string:<br>
124 * 1 - Contains only the characters <code>a-f</code>, <code>A-F</code>, and <code>0-9</code><br>
125 * 2 - Its length is an even number.
126 */
127 public static final String toStringFromHex(String hex, String encoding) throws UnsupportedEncodingException {
128 byte[] bytes = getBytesFromHexString(hex);
129 return StringUtils.toString(bytes, encoding);
130 }
131
132 }