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}