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    }