root/trunk/src/java/org/jcoderz/commons/util/HexUtil.java

Revision 1011, 10.4 kB (checked in by amandel, 2 years ago)

Aligned svn keyword settings.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 *
4 * Copyright 2006, The jCoderZ.org Project. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are
8 * met:
9 *
10 *    * Redistributions of source code must retain the above copyright
11 *      notice, this list of conditions and the following disclaimer.
12 *    * Redistributions in binary form must reproduce the above
13 *      copyright notice, this list of conditions and the following
14 *      disclaimer in the documentation and/or other materials
15 *      provided with the distribution.
16 *    * Neither the name of the jCoderZ.org Project nor the names of
17 *      its contributors may be used to endorse or promote products
18 *      derived from this software without specific prior written
19 *      permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS
25 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
28 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
29 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
30 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
31 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 */
33package org.jcoderz.commons.util;
34
35import java.util.Arrays;
36
37/**
38 * This class converts byte array data from and to hex and also provides
39 * a hexdump method.
40 *
41 * @author Albrecht Messner
42 */
43public final class HexUtil
44{
45   private static final int BYTE_UNSIGNED_MAX = 255;
46   private static final int HALF_BYTE = 4;
47   private static final byte LOW_HALF_MASK = (byte) 0x0F;
48   private static final byte HIGH_HALF_MASK = (byte) 0xF0;
49   private static final int BYTE_MASK = 0xFF;
50   private static final int CHARS_PER_BYTE = 2;
51   private static final int DECIMAL_OFFSET = 10;
52
53
54   private static final char[] HEX_CHARS = {
55      '0', '1', '2', '3', '4', '5', '6', '7',
56      '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
57   };
58
59   private static final String[] BYTE_AS_HEX
60         = new String[BYTE_UNSIGNED_MAX + 1];
61
62   private static final int[] CHAR_TO_NIBBLE_LOW
63         = new int[BYTE_UNSIGNED_MAX + 1];
64   private static final int[] CHAR_TO_NIBBLE_HIGH
65         = new int[BYTE_UNSIGNED_MAX + 1];
66
67   private static final int DUMP_BYTES_PER_LINE = 16;
68   private static final int DUMP_BYTES_PER_COLUMN = 8;
69   private static final int DUMP_ADDR_LEN = 8;
70   private static final int DUMP_BUFFER_SIZE = DUMP_ADDR_LEN // address
71         + DUMP_BYTES_PER_LINE * CHARS_PER_BYTE // bytes
72         + DUMP_BYTES_PER_LINE // whitespaces between bytes
73         + 1 + 1; // extra whitespace after address and to separate columns
74
75   static
76   {
77      // set up tables/arrays for byte to hex conversion
78      final StringBuffer sb = new StringBuffer();
79      for (int i = 0; i < BYTE_AS_HEX.length; i++)
80      {
81         sb.setLength(0);
82         final int lowerFourBits = i & LOW_HALF_MASK;
83         final int highFourBits = (i & HIGH_HALF_MASK) >> HALF_BYTE;
84         sb.append(HEX_CHARS[highFourBits]);
85         sb.append(HEX_CHARS[lowerFourBits]);
86         BYTE_AS_HEX[i] = sb.toString();
87      }
88
89      // set up tables/arrays for hex to byte conversion
90      Arrays.fill(CHAR_TO_NIBBLE_LOW, -1);
91      Arrays.fill(CHAR_TO_NIBBLE_HIGH, -1);
92      for (int i = '0'; i <= '9'; i++)
93      {
94         CHAR_TO_NIBBLE_LOW[i] = i - '0';
95         CHAR_TO_NIBBLE_HIGH[i] = (i - '0') << HALF_BYTE;
96      }
97      for (int i = 'a'; i <= 'f'; i++)
98      {
99         CHAR_TO_NIBBLE_LOW[i] = i - 'a' + DECIMAL_OFFSET;
100         CHAR_TO_NIBBLE_HIGH[i] = (i - 'a' + DECIMAL_OFFSET) << HALF_BYTE;
101      }
102      for (int i = 'A'; i <= 'F'; i++)
103      {
104         CHAR_TO_NIBBLE_LOW[i] = i - 'A' + DECIMAL_OFFSET;
105         CHAR_TO_NIBBLE_HIGH[i] = (i - 'A' + DECIMAL_OFFSET) << HALF_BYTE;
106      }
107   }
108
109   private HexUtil ()
110   {
111       // utility class that provides only static methods
112   }
113
114   /**
115    * Converts a byte array into an upper-case hex string, starting at the
116    * given offset and converting the given number of bytes.
117    * @param data the byte data to convert to hex
118    * @param offset the start offset in the byte array
119    * @param length the number of bytes to convert
120    * @return null if data was null, an empty string if data.length == 0,
121    *       and the hex representation of the byte array otherwise
122    * @throws IndexOutOfBoundsException if offset + length > data.lenght
123    */
124   public static String bytesToHex (
125         final byte[] data, final int offset, final int length)
126         throws IndexOutOfBoundsException
127   {
128      final String result;
129      if (data == null)
130      {
131         result = null;
132      }
133      else
134      {
135         final StringBuffer sbuf = new StringBuffer();
136         for (int i = offset; i < offset + length; i++)
137         {
138            sbuf.append(BYTE_AS_HEX[data[i] & BYTE_MASK]);
139         }
140         result = sbuf.toString();
141      }
142      return result;
143   }
144
145   /**
146    * Converts a byte array into an upper-case hex string, starting at the
147    * first byte and converting the whole array.
148    * @param data the byte data to convert to hex
149    * @return null if data was null, an empty string if data.length == 0,
150    *       and the hex representation of the byte array otherwise
151    */
152   public static String bytesToHex (final byte[] data)
153   {
154      final String result;
155      if (data != null)
156      {
157         result = bytesToHex(data, 0, data.length);
158      }
159      else
160      {
161         result = null;
162      }
163      return result;
164   }
165
166   /**
167    * Convert the given hex string to a byte array.
168    * @param s the string to convert
169    * @return null if the string is null, an empty byte array if s.length == 0
170    *       and a byte array representing the hex string otherwise
171    * @throws IllegalArgumentException if the string is not a multiple of 2
172    *       characters long, or if the string contains an invalid hex char
173    */
174   public static byte[] stringToBytes (final String s)
175         throws IllegalArgumentException
176   {
177      final byte[] result;
178      if (s == null)
179      {
180         result = null;
181      }
182      else if (s.length() == 0)
183      {
184         result = new byte[0];
185      }
186      else
187      {
188         // string must be a multiple of 2 chars
189         if (s.length() % CHARS_PER_BYTE != 0)
190         {
191            throw new IllegalArgumentException(
192                  "The length of a hex string must be a multiple of 2 (was "
193                  + s.length() + ")");
194         }
195         int count = 0;
196         result = new byte[s.length() / CHARS_PER_BYTE];
197         try
198         {
199            for (int i = 0; i < s.length(); i++)
200            {
201               final char c1 = s.charAt(i);
202               final char c2 = s.charAt(++i);
203               final int b = CHAR_TO_NIBBLE_HIGH[c1] | CHAR_TO_NIBBLE_LOW[c2];
204               if (b == -1)
205               {
206                  throw new IllegalArgumentException(
207                        "'" + c1 + c2
208                        + "' is not a valid hex representation of a byte");
209               }
210               result[count] = (byte) b;
211               ++count;
212            }
213         }
214         catch (IndexOutOfBoundsException ex)
215         {
216            final char c1 = s.charAt(count * CHARS_PER_BYTE);
217            final char c2 = s.charAt(count * CHARS_PER_BYTE + 1);
218            final IllegalArgumentException e = new IllegalArgumentException(
219                  "'" + c1 + c2
220                  + "' is not a valid hex representation of a byte");
221            e.initCause(ex);
222            throw e;
223         }
224      }
225      return result;
226   }
227
228   /**
229    * Produces a hexdump of the given byte array with a formatting
230    * as in "hexdump -C" (canonical hex + ASCII display). This formatting
231    * contains an address column, sixteen bytes of hex separated by spaces,
232    * with an extra space after eight bytes, and an ascii print-out of the
233    * bytes enclosed in pipe symbols. Non-ASCII characters are replaced by
234    * dots.
235    *
236    * @param data the byte data to dump
237    * @return a string containing the hexdump, or null if data was null
238    */
239   public static String dump (final byte[] data)
240   {
241      final String result;
242      if (data == null)
243      {
244         result = null;
245      }
246      else
247      {
248         int offset = 0;
249         final StringBuffer dumpBuffer = new StringBuffer();
250         final StringBuffer lineBuffer = new StringBuffer();
251         final StringBuffer charBuffer = new StringBuffer();
252         while (offset < data.length)
253         {
254            lineBuffer.setLength(0);
255            charBuffer.setLength(0);
256            charBuffer.append('|');
257            lineBuffer.append(offsetToHex(offset));
258            lineBuffer.append(' ');
259            final int end = (offset + DUMP_BYTES_PER_LINE < data.length
260                  ? offset + DUMP_BYTES_PER_LINE
261                  : data.length);
262            for (int i = offset; i < end; i++)
263            {
264               final byte b = data[i];
265               lineBuffer.append(bytesToHex(new byte[] {b}));
266               lineBuffer.append(' ');
267
268               if (i - offset == DUMP_BYTES_PER_COLUMN - 1)
269               {
270                  lineBuffer.append(' ');
271               }
272               final char c = (char) b;
273               if ((! Character.isISOControl(c))
274                     && StringUtil.isAscii(c))
275               {
276                  charBuffer.append(c);
277               }
278               else
279               {
280                  charBuffer.append('.');
281               }
282            }
283            padBuffer(lineBuffer);
284            charBuffer.append('|');
285            dumpBuffer.append(lineBuffer);
286            dumpBuffer.append(charBuffer);
287            dumpBuffer.append(Constants.LINE_SEPARATOR);
288            offset += DUMP_BYTES_PER_LINE;
289         }
290         result = dumpBuffer.toString();
291      }
292      return result;
293   }
294
295   private static void padBuffer (final StringBuffer sbuf)
296   {
297      while (sbuf.length() < DUMP_BUFFER_SIZE)
298      {
299         sbuf.append(' ');
300      }
301   }
302
303   private static String offsetToHex (int offset)
304   {
305      String s = Integer.toHexString(offset);
306      while (s.length() < DUMP_ADDR_LEN)
307      {
308         s = "0" + s;
309      }
310      return s;
311   }
312}
Note: See TracBrowser for help on using the browser.