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.io.OutputStreamWriter;
22  import java.nio.charset.Charset;
23  import java.util.Objects;
24  
25  import org.apache.commons.io.output.CloseShieldOutputStream;
26  
27  /**
28   * Dumps data in hexadecimal format.
29   * <p>
30   * Provides a single function to take an array of bytes and display it
31   * in hexadecimal form.
32   * </p>
33   * <p>
34   * Provenance: POI.
35   * </p>
36   */
37  public class HexDump {
38  
39      /**
40       * The line-separator (initializes to "line.separator" system property).
41       *
42       * @deprecated Use {@link System#lineSeparator()}.
43       */
44      @Deprecated
45      public static final String EOL = System.lineSeparator();
46  
47      private static final char[] HEX_CODES =
48              {
49                  '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
50                  'A', 'B', 'C', 'D', 'E', 'F'
51              };
52  
53      private static final int[] SHIFTS =
54              {
55                  28, 24, 20, 16, 12, 8, 4, 0
56              };
57  
58      /**
59       * Dumps an array of bytes to an Appendable. The output is formatted
60       * for human inspection, with a hexadecimal offset followed by the
61       * hexadecimal values of the next 16 bytes of data and the printable ASCII
62       * characters (if any) that those bytes represent printed per each line
63       * of output.
64       *
65       * @param data  the byte array to be dumped
66       * @param appendable  the Appendable to which the data is to be written
67       * @throws IOException is thrown if anything goes wrong writing
68       *         the data to appendable
69       * @throws NullPointerException if the output appendable is null
70       * @since 2.12.0
71       */
72      public static void dump(final byte[] data, final Appendable appendable)
73              throws IOException {
74          dump(data, 0, appendable, 0, data.length);
75      }
76  
77      /**
78       * Dumps an array of bytes to an Appendable. The output is formatted
79       * for human inspection, with a hexadecimal offset followed by the
80       * hexadecimal values of the next 16 bytes of data and the printable ASCII
81       * characters (if any) that those bytes represent printed per each line
82       * of output.
83       * <p>
84       * The offset argument specifies the start offset of the data array
85       * within a larger entity like a file or an incoming stream. For example,
86       * if the data array contains the third kibibyte of a file, then the
87       * offset argument should be set to 2048. The offset value printed
88       * at the beginning of each line indicates where in that larger entity
89       * the first byte on that line is located.
90       * </p>
91       *
92       * @param data  the byte array to be dumped
93       * @param offset  offset of the byte array within a larger entity
94       * @param appendable  the Appendable to which the data is to be written
95       * @param index initial index into the byte array
96       * @param length number of bytes to dump from the array
97       * @throws IOException is thrown if anything goes wrong writing
98       *         the data to appendable
99       * @throws ArrayIndexOutOfBoundsException if the index or length is
100      *         outside the data array's bounds
101      * @throws NullPointerException if the output appendable is null
102      * @since 2.12.0
103      */
104     public static void dump(final byte[] data, final long offset,
105                             final Appendable appendable, final int index,
106                             final int length)
107             throws IOException, ArrayIndexOutOfBoundsException {
108         Objects.requireNonNull(appendable, "appendable");
109         if (index < 0 || index >= data.length) {
110             throw new ArrayIndexOutOfBoundsException(
111                     "illegal index: " + index + " into array of length "
112                     + data.length);
113         }
114         long display_offset = offset + index;
115         final StringBuilder buffer = new StringBuilder(74);
116 
117         // TODO Use Objects.checkFromIndexSize(index, length, data.length) when upgrading to JDK9
118         if (length < 0 || index + length > data.length) {
119             throw new ArrayIndexOutOfBoundsException(String.format("Range [%s, %<s + %s) out of bounds for length %s", index, length, data.length));
120         }
121 
122         final int endIndex = index + length;
123 
124         for (int j = index; j < endIndex; j += 16) {
125             int chars_read = endIndex - j;
126 
127             if (chars_read > 16) {
128                 chars_read = 16;
129             }
130             dump(buffer, display_offset).append(' ');
131             for (int k = 0; k < 16; k++) {
132                 if (k < chars_read) {
133                     dump(buffer, data[k + j]);
134                 } else {
135                     buffer.append("  ");
136                 }
137                 buffer.append(' ');
138             }
139             for (int k = 0; k < chars_read; k++) {
140                 if (data[k + j] >= ' ' && data[k + j] < 127) {
141                     buffer.append((char) data[k + j]);
142                 } else {
143                     buffer.append('.');
144                 }
145             }
146             buffer.append(System.lineSeparator());
147             appendable.append(buffer);
148             buffer.setLength(0);
149             display_offset += chars_read;
150         }
151     }
152 
153     /**
154      * Dumps an array of bytes to an OutputStream. The output is formatted
155      * for human inspection, with a hexadecimal offset followed by the
156      * hexadecimal values of the next 16 bytes of data and the printable ASCII
157      * characters (if any) that those bytes represent printed per each line
158      * of output.
159      * <p>
160      * The offset argument specifies the start offset of the data array
161      * within a larger entity like a file or an incoming stream. For example,
162      * if the data array contains the third kibibyte of a file, then the
163      * offset argument should be set to 2048. The offset value printed
164      * at the beginning of each line indicates where in that larger entity
165      * the first byte on that line is located.
166      * </p>
167      * <p>
168      * All bytes between the given index (inclusive) and the end of the
169      * data array are dumped.
170      * </p>
171      * <p>
172      * This method uses the virtual machine's {@link Charset#defaultCharset() default charset}.
173      * </p>
174      *
175      * @param data  the byte array to be dumped
176      * @param offset  offset of the byte array within a larger entity
177      * @param stream  the OutputStream to which the data is to be
178      *               written
179      * @param index initial index into the byte array
180      * @throws IOException is thrown if anything goes wrong writing
181      *         the data to stream
182      * @throws ArrayIndexOutOfBoundsException if the index is
183      *         outside the data array's bounds
184      * @throws NullPointerException if the output stream is null
185      */
186     @SuppressWarnings("resource") // Caller closes stream
187     public static void dump(final byte[] data, final long offset,
188                             final OutputStream stream, final int index)
189             throws IOException, ArrayIndexOutOfBoundsException {
190         Objects.requireNonNull(stream, "stream");
191 
192         try (OutputStreamWriter out = new OutputStreamWriter(CloseShieldOutputStream.wrap(stream), Charset.defaultCharset())) {
193             dump(data, offset, out, index, data.length - index);
194         }
195     }
196 
197     /**
198      * Dumps a byte value into a StringBuilder.
199      *
200      * @param builder the StringBuilder to dump the value in
201      * @param value  the byte value to be dumped
202      * @return StringBuilder containing the dumped value.
203      */
204     private static StringBuilder dump(final StringBuilder builder, final byte value) {
205         for (int j = 0; j < 2; j++) {
206             builder.append(HEX_CODES[value >> SHIFTS[j + 6] & 15]);
207         }
208         return builder;
209     }
210 
211     /**
212      * Dumps a long value into a StringBuilder.
213      *
214      * @param builder the StringBuilder to dump the value in
215      * @param value  the long value to be dumped
216      * @return StringBuilder containing the dumped value.
217      */
218     private static StringBuilder dump(final StringBuilder builder, final long value) {
219         for (int j = 0; j < 8; j++) {
220             builder.append(HEX_CODES[(int) (value >> SHIFTS[j]) & 15]);
221         }
222         return builder;
223     }
224 
225     /**
226      * Instances should NOT be constructed in standard programming.
227      */
228     public HexDump() {
229     }
230 
231 }