001/** 002 * Copyright 2010-2013 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 */ 016package org.kuali.common.util; 017 018import java.io.UnsupportedEncodingException; 019 020import org.apache.commons.lang3.CharSet; 021import 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 */ 026public 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}