HexDump.java

  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. import java.io.IOException;
  19. import java.io.OutputStream;
  20. import java.io.OutputStreamWriter;
  21. import java.nio.charset.Charset;
  22. import java.util.Objects;

  23. import org.apache.commons.io.output.CloseShieldOutputStream;

  24. /**
  25.  * Dumps data in hexadecimal format.
  26.  * <p>
  27.  * Provides a single function to take an array of bytes and display it
  28.  * in hexadecimal form.
  29.  * </p>
  30.  * <p>
  31.  * Provenance: POI.
  32.  * </p>
  33.  */
  34. public class HexDump {

  35.     /**
  36.      * The line-separator (initializes to "line.separator" system property).
  37.      *
  38.      * @deprecated Use {@link System#lineSeparator()}.
  39.      */
  40.     @Deprecated
  41.     public static final String EOL = System.lineSeparator();

  42.     private static final char[] HEX_CODES =
  43.             {
  44.                 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
  45.                 'A', 'B', 'C', 'D', 'E', 'F'
  46.             };

  47.     private static final int[] SHIFTS =
  48.             {
  49.                 28, 24, 20, 16, 12, 8, 4, 0
  50.             };

  51.     /**
  52.      * Dumps an array of bytes to an Appendable. The output is formatted
  53.      * for human inspection, with a hexadecimal offset followed by the
  54.      * hexadecimal values of the next 16 bytes of data and the printable ASCII
  55.      * characters (if any) that those bytes represent printed per each line
  56.      * of output.
  57.      *
  58.      * @param data  the byte array to be dumped
  59.      * @param appendable  the Appendable to which the data is to be written
  60.      * @throws IOException is thrown if anything goes wrong writing
  61.      *         the data to appendable
  62.      * @throws NullPointerException if the output appendable is null
  63.      * @since 2.12.0
  64.      */
  65.     public static void dump(final byte[] data, final Appendable appendable)
  66.             throws IOException {
  67.         dump(data, 0, appendable, 0, data.length);
  68.     }

  69.     /**
  70.      * Dumps an array of bytes to an Appendable. The output is formatted
  71.      * for human inspection, with a hexadecimal offset followed by the
  72.      * hexadecimal values of the next 16 bytes of data and the printable ASCII
  73.      * characters (if any) that those bytes represent printed per each line
  74.      * of output.
  75.      * <p>
  76.      * The offset argument specifies the start offset of the data array
  77.      * within a larger entity like a file or an incoming stream. For example,
  78.      * if the data array contains the third kibibyte of a file, then the
  79.      * offset argument should be set to 2048. The offset value printed
  80.      * at the beginning of each line indicates where in that larger entity
  81.      * the first byte on that line is located.
  82.      * </p>
  83.      *
  84.      * @param data  the byte array to be dumped
  85.      * @param offset  offset of the byte array within a larger entity
  86.      * @param appendable  the Appendable to which the data is to be written
  87.      * @param index initial index into the byte array
  88.      * @param length number of bytes to dump from the array
  89.      * @throws IOException is thrown if anything goes wrong writing
  90.      *         the data to appendable
  91.      * @throws ArrayIndexOutOfBoundsException if the index or length is
  92.      *         outside the data array's bounds
  93.      * @throws NullPointerException if the output appendable is null
  94.      * @since 2.12.0
  95.      */
  96.     public static void dump(final byte[] data, final long offset,
  97.                             final Appendable appendable, final int index,
  98.                             final int length)
  99.             throws IOException, ArrayIndexOutOfBoundsException {
  100.         Objects.requireNonNull(appendable, "appendable");
  101.         if (index < 0 || index >= data.length) {
  102.             throw new ArrayIndexOutOfBoundsException(
  103.                     "illegal index: " + index + " into array of length "
  104.                     + data.length);
  105.         }
  106.         long display_offset = offset + index;
  107.         final StringBuilder buffer = new StringBuilder(74);

  108.         // TODO Use Objects.checkFromIndexSize(index, length, data.length) when upgrading to JDK9
  109.         if (length < 0 || index + length > data.length) {
  110.             throw new ArrayIndexOutOfBoundsException(String.format("Range [%s, %<s + %s) out of bounds for length %s", index, length, data.length));
  111.         }

  112.         final int endIndex = index + length;

  113.         for (int j = index; j < endIndex; j += 16) {
  114.             int chars_read = endIndex - j;

  115.             if (chars_read > 16) {
  116.                 chars_read = 16;
  117.             }
  118.             dump(buffer, display_offset).append(' ');
  119.             for (int k = 0; k < 16; k++) {
  120.                 if (k < chars_read) {
  121.                     dump(buffer, data[k + j]);
  122.                 } else {
  123.                     buffer.append("  ");
  124.                 }
  125.                 buffer.append(' ');
  126.             }
  127.             for (int k = 0; k < chars_read; k++) {
  128.                 if (data[k + j] >= ' ' && data[k + j] < 127) {
  129.                     buffer.append((char) data[k + j]);
  130.                 } else {
  131.                     buffer.append('.');
  132.                 }
  133.             }
  134.             buffer.append(System.lineSeparator());
  135.             appendable.append(buffer);
  136.             buffer.setLength(0);
  137.             display_offset += chars_read;
  138.         }
  139.     }

  140.     /**
  141.      * Dumps an array of bytes to an OutputStream. The output is formatted
  142.      * for human inspection, with a hexadecimal offset followed by the
  143.      * hexadecimal values of the next 16 bytes of data and the printable ASCII
  144.      * characters (if any) that those bytes represent printed per each line
  145.      * of output.
  146.      * <p>
  147.      * The offset argument specifies the start offset of the data array
  148.      * within a larger entity like a file or an incoming stream. For example,
  149.      * if the data array contains the third kibibyte of a file, then the
  150.      * offset argument should be set to 2048. The offset value printed
  151.      * at the beginning of each line indicates where in that larger entity
  152.      * the first byte on that line is located.
  153.      * </p>
  154.      * <p>
  155.      * All bytes between the given index (inclusive) and the end of the
  156.      * data array are dumped.
  157.      * </p>
  158.      * <p>
  159.      * This method uses the virtual machine's {@link Charset#defaultCharset() default charset}.
  160.      * </p>
  161.      *
  162.      * @param data  the byte array to be dumped
  163.      * @param offset  offset of the byte array within a larger entity
  164.      * @param stream  the OutputStream to which the data is to be
  165.      *               written
  166.      * @param index initial index into the byte array
  167.      * @throws IOException is thrown if anything goes wrong writing
  168.      *         the data to stream
  169.      * @throws ArrayIndexOutOfBoundsException if the index is
  170.      *         outside the data array's bounds
  171.      * @throws NullPointerException if the output stream is null
  172.      */
  173.     @SuppressWarnings("resource") // Caller closes stream
  174.     public static void dump(final byte[] data, final long offset,
  175.                             final OutputStream stream, final int index)
  176.             throws IOException, ArrayIndexOutOfBoundsException {
  177.         Objects.requireNonNull(stream, "stream");

  178.         try (OutputStreamWriter out = new OutputStreamWriter(CloseShieldOutputStream.wrap(stream), Charset.defaultCharset())) {
  179.             dump(data, offset, out, index, data.length - index);
  180.         }
  181.     }

  182.     /**
  183.      * Dumps a byte value into a StringBuilder.
  184.      *
  185.      * @param builder the StringBuilder to dump the value in
  186.      * @param value  the byte value to be dumped
  187.      * @return StringBuilder containing the dumped value.
  188.      */
  189.     private static StringBuilder dump(final StringBuilder builder, final byte value) {
  190.         for (int j = 0; j < 2; j++) {
  191.             builder.append(HEX_CODES[value >> SHIFTS[j + 6] & 15]);
  192.         }
  193.         return builder;
  194.     }

  195.     /**
  196.      * Dumps a long value into a StringBuilder.
  197.      *
  198.      * @param builder the StringBuilder to dump the value in
  199.      * @param value  the long value to be dumped
  200.      * @return StringBuilder containing the dumped value.
  201.      */
  202.     private static StringBuilder dump(final StringBuilder builder, final long value) {
  203.         for (int j = 0; j < 8; j++) {
  204.             builder.append(HEX_CODES[(int) (value >> SHIFTS[j]) & 15]);
  205.         }
  206.         return builder;
  207.     }

  208.     /**
  209.      * Instances should NOT be constructed in standard programming.
  210.      */
  211.     public HexDump() {
  212.     }

  213. }