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 * https://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 }