View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.io;
18  
19  import java.io.IOException;
20  import java.io.OutputStream;
21  import java.nio.charset.Charset;
22  
23  /**
24   * Dumps data in hexadecimal format.
25   * <p>
26   * Provides a single function to take an array of bytes and display it
27   * in hexadecimal form.
28   * <p>
29   * Origin of code: POI.
30   *
31   */
32  public class HexDump {
33  
34      /**
35       * Instances should NOT be constructed in standard programming.
36       */
37      public HexDump() {
38          super();
39      }
40  
41      /**
42       * Dump an array of bytes to an OutputStream. The output is formatted
43       * for human inspection, with a hexadecimal offset followed by the
44       * hexadecimal values of the next 16 bytes of data and the printable ASCII
45       * characters (if any) that those bytes represent printed per each line
46       * of output.
47       * <p>
48       * The offset argument specifies the start offset of the data array
49       * within a larger entity like a file or an incoming stream. For example,
50       * if the data array contains the third kibibyte of a file, then the
51       * offset argument should be set to 2048. The offset value printed
52       * at the beginning of each line indicates where in that larger entity
53       * the first byte on that line is located.
54       * <p>
55       * All bytes between the given index (inclusive) and the end of the
56       * data array are dumped.
57       *
58       * @param data  the byte array to be dumped
59       * @param offset  offset of the byte array within a larger entity
60       * @param stream  the OutputStream to which the data is to be
61       *               written
62       * @param index initial index into the byte array
63       *
64       * @throws IOException is thrown if anything goes wrong writing
65       *         the data to stream
66       * @throws ArrayIndexOutOfBoundsException if the index is
67       *         outside the data array's bounds
68       * @throws IllegalArgumentException if the output stream is null
69       */
70  
71      public static void dump(final byte[] data, final long offset,
72                              final OutputStream stream, final int index)
73              throws IOException, ArrayIndexOutOfBoundsException,
74              IllegalArgumentException {
75  
76          if (index < 0 || index >= data.length) {
77              throw new ArrayIndexOutOfBoundsException(
78                      "illegal index: " + index + " into array of length "
79                      + data.length);
80          }
81          if (stream == null) {
82              throw new IllegalArgumentException("cannot write to nullstream");
83          }
84          long display_offset = offset + index;
85          final StringBuilder buffer = new StringBuilder(74);
86  
87          for (int j = index; j < data.length; j += 16) {
88              int chars_read = data.length - j;
89  
90              if (chars_read > 16) {
91                  chars_read = 16;
92              }
93              dump(buffer, display_offset).append(' ');
94              for (int k = 0; k < 16; k++) {
95                  if (k < chars_read) {
96                      dump(buffer, data[k + j]);
97                  } else {
98                      buffer.append("  ");
99                  }
100                 buffer.append(' ');
101             }
102             for (int k = 0; k < chars_read; k++) {
103                 if (data[k + j] >= ' ' && data[k + j] < 127) {
104                     buffer.append((char) data[k + j]);
105                 } else {
106                     buffer.append('.');
107                 }
108             }
109             buffer.append(EOL);
110             // make explicit the dependency on the default encoding
111             stream.write(buffer.toString().getBytes(Charset.defaultCharset()));
112             stream.flush();
113             buffer.setLength(0);
114             display_offset += chars_read;
115         }
116     }
117 
118     /**
119      * The line-separator (initializes to "line.separator" system property.
120      */
121     public static final String EOL =
122             System.getProperty("line.separator");
123     private static final char[] _hexcodes =
124             {
125                 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
126                 'A', 'B', 'C', 'D', 'E', 'F'
127             };
128     private static final int[] _shifts =
129             {
130                 28, 24, 20, 16, 12, 8, 4, 0
131             };
132 
133     /**
134      * Dump a long value into a StringBuilder.
135      *
136      * @param _lbuffer the StringBuilder to dump the value in
137      * @param value  the long value to be dumped
138      * @return StringBuilder containing the dumped value.
139      */
140     private static StringBuilder dump(final StringBuilder _lbuffer, final long value) {
141         for (int j = 0; j < 8; j++) {
142             _lbuffer
143                     .append(_hexcodes[(int) (value >> _shifts[j]) & 15]);
144         }
145         return _lbuffer;
146     }
147 
148     /**
149      * Dump a byte value into a StringBuilder.
150      *
151      * @param _cbuffer the StringBuilder to dump the value in
152      * @param value  the byte value to be dumped
153      * @return StringBuilder containing the dumped value.
154      */
155     private static StringBuilder dump(final StringBuilder _cbuffer, final byte value) {
156         for (int j = 0; j < 2; j++) {
157             _cbuffer.append(_hexcodes[value >> _shifts[j + 6] & 15]);
158         }
159         return _cbuffer;
160     }
161 
162 }