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       *
68       * @throws IOException is thrown if anything goes wrong writing
69       *         the data to appendable
70       * @throws NullPointerException if the output appendable is null
71       *
72       * @since 2.12.0
73       */
74      public static void dump(final byte[] data, final Appendable appendable)
75              throws IOException {
76          dump(data, 0, appendable, 0, data.length);
77      }
78  
79      /**
80       * Dumps an array of bytes to an Appendable. The output is formatted
81       * for human inspection, with a hexadecimal offset followed by the
82       * hexadecimal values of the next 16 bytes of data and the printable ASCII
83       * characters (if any) that those bytes represent printed per each line
84       * of output.
85       * <p>
86       * The offset argument specifies the start offset of the data array
87       * within a larger entity like a file or an incoming stream. For example,
88       * if the data array contains the third kibibyte of a file, then the
89       * offset argument should be set to 2048. The offset value printed
90       * at the beginning of each line indicates where in that larger entity
91       * the first byte on that line is located.
92       * </p>
93       *
94       * @param data  the byte array to be dumped
95       * @param offset  offset of the byte array within a larger entity
96       * @param appendable  the Appendable to which the data is to be written
97       * @param index initial index into the byte array
98       * @param length number of bytes to dump from the array
99       *
100      * @throws IOException is thrown if anything goes wrong writing
101      *         the data to appendable
102      * @throws ArrayIndexOutOfBoundsException if the index or length is
103      *         outside the data array's bounds
104      * @throws NullPointerException if the output appendable is null
105      *
106      * @since 2.12.0
107      */
108     public static void dump(final byte[] data, final long offset,
109                             final Appendable appendable, final int index,
110                             final int length)
111             throws IOException, ArrayIndexOutOfBoundsException {
112         Objects.requireNonNull(appendable, "appendable");
113         if (index < 0 || index >= data.length) {
114             throw new ArrayIndexOutOfBoundsException(
115                     "illegal index: " + index + " into array of length "
116                     + data.length);
117         }
118         long display_offset = offset + index;
119         final StringBuilder buffer = new StringBuilder(74);
120 
121         // TODO Use Objects.checkFromIndexSize(index, length, data.length) when upgrading to JDK9
122         if (length < 0 || index + length > data.length) {
123             throw new ArrayIndexOutOfBoundsException(String.format("Range [%s, %<s + %s) out of bounds for length %s", index, length, data.length));
124         }
125 
126         final int endIndex = index + length;
127 
128         for (int j = index; j < endIndex; j += 16) {
129             int chars_read = endIndex - j;
130 
131             if (chars_read > 16) {
132                 chars_read = 16;
133             }
134             dump(buffer, display_offset).append(' ');
135             for (int k = 0; k < 16; k++) {
136                 if (k < chars_read) {
137                     dump(buffer, data[k + j]);
138                 } else {
139                     buffer.append("  ");
140                 }
141                 buffer.append(' ');
142             }
143             for (int k = 0; k < chars_read; k++) {
144                 if (data[k + j] >= ' ' && data[k + j] < 127) {
145                     buffer.append((char) data[k + j]);
146                 } else {
147                     buffer.append('.');
148                 }
149             }
150             buffer.append(System.lineSeparator());
151             appendable.append(buffer);
152             buffer.setLength(0);
153             display_offset += chars_read;
154         }
155     }
156 
157     /**
158      * Dumps an array of bytes to an OutputStream. The output is formatted
159      * for human inspection, with a hexadecimal offset followed by the
160      * hexadecimal values of the next 16 bytes of data and the printable ASCII
161      * characters (if any) that those bytes represent printed per each line
162      * of output.
163      * <p>
164      * The offset argument specifies the start offset of the data array
165      * within a larger entity like a file or an incoming stream. For example,
166      * if the data array contains the third kibibyte of a file, then the
167      * offset argument should be set to 2048. The offset value printed
168      * at the beginning of each line indicates where in that larger entity
169      * the first byte on that line is located.
170      * </p>
171      * <p>
172      * All bytes between the given index (inclusive) and the end of the
173      * data array are dumped.
174      * </p>
175      *
176      * @param data  the byte array to be dumped
177      * @param offset  offset of the byte array within a larger entity
178      * @param stream  the OutputStream to which the data is to be
179      *               written
180      * @param index initial index into the byte array
181      *
182      * @throws IOException is thrown if anything goes wrong writing
183      *         the data to stream
184      * @throws ArrayIndexOutOfBoundsException if the index is
185      *         outside the data array's bounds
186      * @throws NullPointerException if the output stream is null
187      */
188     @SuppressWarnings("resource") // Caller closes stream
189     public static void dump(final byte[] data, final long offset,
190                             final OutputStream stream, final int index)
191             throws IOException, ArrayIndexOutOfBoundsException {
192         Objects.requireNonNull(stream, "stream");
193 
194         try (OutputStreamWriter out = new OutputStreamWriter(CloseShieldOutputStream.wrap(stream), Charset.defaultCharset())) {
195             dump(data, offset, out, index, data.length - index);
196         }
197     }
198 
199     /**
200      * Dumps a byte value into a StringBuilder.
201      *
202      * @param _cbuffer the StringBuilder to dump the value in
203      * @param value  the byte value to be dumped
204      * @return StringBuilder containing the dumped value.
205      */
206     private static StringBuilder dump(final StringBuilder _cbuffer, final byte value) {
207         for (int j = 0; j < 2; j++) {
208             _cbuffer.append(HEX_CODES[value >> SHIFTS[j + 6] & 15]);
209         }
210         return _cbuffer;
211     }
212 
213     /**
214      * Dumps a long value into a StringBuilder.
215      *
216      * @param _lbuffer the StringBuilder to dump the value in
217      * @param value  the long value to be dumped
218      * @return StringBuilder containing the dumped value.
219      */
220     private static StringBuilder dump(final StringBuilder _lbuffer, final long value) {
221         for (int j = 0; j < 8; j++) {
222             _lbuffer
223                     .append(HEX_CODES[(int) (value >> SHIFTS[j]) & 15]);
224         }
225         return _lbuffer;
226     }
227 
228     /**
229      * Instances should NOT be constructed in standard programming.
230      */
231     public HexDump() {
232     }
233 
234 }