001/** 002 * Copyright 2010-2014 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 static org.kuali.common.util.base.Exceptions.illegalArgument; 019 020import java.io.UnsupportedEncodingException; 021 022import org.apache.commons.lang3.CharSet; 023import org.apache.commons.lang3.StringUtils; 024 025/** 026 * A few (highly inefficient) methods for converting <code>String's</code> into the hex for a given encoding and back again. 027 */ 028public class HexUtils { 029 030 private static final String ZERO = "0"; 031 private static final int BYTE_MASK = 0x000000ff; 032 private static final String[] HEX_RANGES = new String[] { "0-9", "A-F", "a-f" }; 033 private static final String HEX_RANGES_STRING = toString(HEX_RANGES); 034 private static final CharSet HEX_CHARSET = CharSet.getInstance(HEX_RANGES); 035 036 public static final CharSet getHexCharSet() { 037 return HEX_CHARSET; 038 } 039 040 public static final String[] getHexRanges() { 041 return HEX_RANGES; 042 } 043 044 protected static final String toString(String[] tokens) { 045 StringBuilder sb = new StringBuilder(); 046 sb.append("["); 047 for (int i = 0; i < HEX_RANGES.length; i++) { 048 if (i != 0) { 049 sb.append(","); 050 } 051 sb.append(HEX_RANGES[i]); 052 } 053 sb.append("]"); 054 return sb.toString(); 055 } 056 057 /** 058 * 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. 059 */ 060 public static String toHexString(String string, String encoding) throws UnsupportedEncodingException { 061 byte[] bytes = encoding == null ? string.getBytes() : string.getBytes(encoding); 062 return toHexString(bytes); 063 } 064 065 /** 066 * Convert each <code>byte</code> into its 2 digit hexadecimal form. 067 */ 068 public static String toHexString(byte[] bytes) { 069 StringBuilder sb = new StringBuilder(); 070 for (byte b : bytes) { 071 int masked = BYTE_MASK & b; 072 String hex = Integer.toHexString(masked).toUpperCase(); 073 String padded = StringUtils.leftPad(hex, 2, ZERO); 074 sb.append(padded); 075 } 076 return sb.toString(); 077 } 078 079 /** 080 * Convert each <code>byte</code> into its 2 digit hexadecimal form. 081 */ 082 public static String toHexStringLower(byte[] bytes) { 083 return toHexString(bytes).toLowerCase(); 084 } 085 086 /** 087 * Return true if every character is valid hex <code>0-9</code>, <code>a-f</code>, or <code>A-F</code> 088 */ 089 public static final boolean isHex(char... chars) { 090 for (char c : chars) { 091 if (!HEX_CHARSET.contains(c)) { 092 return false; 093 } 094 } 095 return true; 096 } 097 098 /** 099 * 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}