This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-io.git


The following commit(s) were added to refs/heads/master by this push:
     new fcf8752f [IO-784] Add support for Appendable to HexDump util (#418)
fcf8752f is described below

commit fcf8752f87aebbd5fa729e042a1bfa48882972da
Author: Fredrik Kjellberg <fredrik.kjellb...@gmail.com>
AuthorDate: Wed Jan 18 20:58:28 2023 +0100

    [IO-784] Add support for Appendable to HexDump util (#418)
    
    * Add support for Appendable to HexDump util
    
    * Added since annotations and some minor code cleanup
    
    * Remove flush call
    
    * Add test to verify that OutputStream is not closed by the dump method
    
    * Use ThrowOnCloseOutputStream to make sure that the output stream is not 
closed
---
 src/main/java/org/apache/commons/io/HexDump.java   | 103 +++++++++++++++++----
 .../java/org/apache/commons/io/HexDumpTest.java    |  70 +++++++++++++-
 2 files changed, 155 insertions(+), 18 deletions(-)

diff --git a/src/main/java/org/apache/commons/io/HexDump.java 
b/src/main/java/org/apache/commons/io/HexDump.java
index 5482b851..0360a494 100644
--- a/src/main/java/org/apache/commons/io/HexDump.java
+++ b/src/main/java/org/apache/commons/io/HexDump.java
@@ -18,9 +18,12 @@ package org.apache.commons.io;
 
 import java.io.IOException;
 import java.io.OutputStream;
+import java.io.OutputStreamWriter;
 import java.nio.charset.Charset;
 import java.util.Objects;
 
+import org.apache.commons.io.output.CloseShieldOutputStream;
+
 /**
  * Dumps data in hexadecimal format.
  * <p>
@@ -53,7 +56,28 @@ public class HexDump {
             };
 
     /**
-     * Dumps an array of bytes to an OutputStream. The output is formatted
+     * Dumps an array of bytes to an Appendable. The output is formatted
+     * for human inspection, with a hexadecimal offset followed by the
+     * hexadecimal values of the next 16 bytes of data and the printable ASCII
+     * characters (if any) that those bytes represent printed per each line
+     * of output.
+     *
+     * @param data  the byte array to be dumped
+     * @param appendable  the Appendable to which the data is to be written
+     *
+     * @throws IOException is thrown if anything goes wrong writing
+     *         the data to appendable
+     * @throws NullPointerException if the output appendable is null
+     *
+     * @since 2.12.0
+     */
+    public static void dump(final byte[] data, final Appendable appendable)
+            throws IOException {
+        dump(data, 0, appendable, 0, data.length);
+    }
+
+    /**
+     * Dumps an array of bytes to an Appendable. The output is formatted
      * for human inspection, with a hexadecimal offset followed by the
      * hexadecimal values of the next 16 bytes of data and the printable ASCII
      * characters (if any) that those bytes represent printed per each line
@@ -66,27 +90,26 @@ public class HexDump {
      * at the beginning of each line indicates where in that larger entity
      * the first byte on that line is located.
      * </p>
-     * <p>
-     * All bytes between the given index (inclusive) and the end of the
-     * data array are dumped.
-     * </p>
      *
      * @param data  the byte array to be dumped
      * @param offset  offset of the byte array within a larger entity
-     * @param stream  the OutputStream to which the data is to be
-     *               written
+     * @param appendable  the Appendable to which the data is to be written
      * @param index initial index into the byte array
+     * @param length number of bytes to dump from the array
      *
      * @throws IOException is thrown if anything goes wrong writing
-     *         the data to stream
-     * @throws ArrayIndexOutOfBoundsException if the index is
+     *         the data to appendable
+     * @throws ArrayIndexOutOfBoundsException if the index or length is
      *         outside the data array's bounds
-     * @throws NullPointerException if the output stream is null
+     * @throws NullPointerException if the output appendable is null
+     *
+     * @since 2.12.0
      */
     public static void dump(final byte[] data, final long offset,
-                            final OutputStream stream, final int index)
+                            final Appendable appendable, final int index,
+                            final int length)
             throws IOException, ArrayIndexOutOfBoundsException {
-        Objects.requireNonNull(stream, "stream");
+        Objects.requireNonNull(appendable, "appendable");
         if (index < 0 || index >= data.length) {
             throw new ArrayIndexOutOfBoundsException(
                     "illegal index: " + index + " into array of length "
@@ -95,8 +118,15 @@ public class HexDump {
         long display_offset = offset + index;
         final StringBuilder buffer = new StringBuilder(74);
 
-        for (int j = index; j < data.length; j += 16) {
-            int chars_read = data.length - j;
+        // TODO Use Objects.checkFromIndexSize(index, length, data.length) 
when upgrading to JDK9
+        if (length < 0 || (index + length) > data.length) {
+            throw new ArrayIndexOutOfBoundsException(String.format("Range [%s, 
%<s + %s) out of bounds for length %s", index, length, data.length));
+        }
+
+        final int endIndex = index + length;
+
+        for (int j = index; j < endIndex; j += 16) {
+            int chars_read = endIndex - j;
 
             if (chars_read > 16) {
                 chars_read = 16;
@@ -118,14 +148,53 @@ public class HexDump {
                 }
             }
             buffer.append(System.lineSeparator());
-            // make explicit the dependency on the default encoding
-            stream.write(buffer.toString().getBytes(Charset.defaultCharset()));
-            stream.flush();
+            appendable.append(buffer);
             buffer.setLength(0);
             display_offset += chars_read;
         }
     }
 
+    /**
+     * Dumps an array of bytes to an OutputStream. The output is formatted
+     * for human inspection, with a hexadecimal offset followed by the
+     * hexadecimal values of the next 16 bytes of data and the printable ASCII
+     * characters (if any) that those bytes represent printed per each line
+     * of output.
+     * <p>
+     * The offset argument specifies the start offset of the data array
+     * within a larger entity like a file or an incoming stream. For example,
+     * if the data array contains the third kibibyte of a file, then the
+     * offset argument should be set to 2048. The offset value printed
+     * at the beginning of each line indicates where in that larger entity
+     * the first byte on that line is located.
+     * </p>
+     * <p>
+     * All bytes between the given index (inclusive) and the end of the
+     * data array are dumped.
+     * </p>
+     *
+     * @param data  the byte array to be dumped
+     * @param offset  offset of the byte array within a larger entity
+     * @param stream  the OutputStream to which the data is to be
+     *               written
+     * @param index initial index into the byte array
+     *
+     * @throws IOException is thrown if anything goes wrong writing
+     *         the data to stream
+     * @throws ArrayIndexOutOfBoundsException if the index is
+     *         outside the data array's bounds
+     * @throws NullPointerException if the output stream is null
+     */
+    public static void dump(final byte[] data, final long offset,
+                            final OutputStream stream, final int index)
+            throws IOException, ArrayIndexOutOfBoundsException {
+        Objects.requireNonNull(stream, "stream");
+
+        try (OutputStreamWriter out = new 
OutputStreamWriter(CloseShieldOutputStream.wrap(stream), 
Charset.defaultCharset())) {
+            dump(data, offset, out, index, data.length - index);
+        }
+    }
+
     /**
      * Dumps a byte value into a StringBuilder.
      *
diff --git a/src/test/java/org/apache/commons/io/HexDumpTest.java 
b/src/test/java/org/apache/commons/io/HexDumpTest.java
index 6e8e0046..a7da7f15 100644
--- a/src/test/java/org/apache/commons/io/HexDumpTest.java
+++ b/src/test/java/org/apache/commons/io/HexDumpTest.java
@@ -22,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 import java.io.IOException;
 
 import org.apache.commons.io.output.ByteArrayOutputStream;
+import org.apache.commons.io.test.ThrowOnCloseOutputStream;
 import org.junit.jupiter.api.Test;
 
 
@@ -31,7 +32,71 @@ import org.junit.jupiter.api.Test;
 public class HexDumpTest {
 
     @Test
-    public void testDump() throws IOException {
+    public void testDumpAppendable() throws IOException {
+        final byte[] testArray = new byte[256];
+
+        for (int j = 0; j < 256; j++) {
+            testArray[j] = (byte) j;
+        }
+
+        // verify proper behavior dumping the entire array
+        StringBuilder out = new StringBuilder();
+        HexDump.dump(testArray, out);
+        assertEquals(
+            "00000000 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 
................" + System.lineSeparator() +
+            "00000010 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 
................" + System.lineSeparator() +
+            "00000020 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F  
!\"#$%&'()*+,-./" + System.lineSeparator() +
+            "00000030 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 
0123456789:;<=>?" + System.lineSeparator() +
+            "00000040 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 
@ABCDEFGHIJKLMNO" + System.lineSeparator() +
+            "00000050 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F 
PQRSTUVWXYZ[\\]^_" + System.lineSeparator() +
+            "00000060 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 
`abcdefghijklmno" + System.lineSeparator() +
+            "00000070 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F 
pqrstuvwxyz{|}~." + System.lineSeparator() +
+            "00000080 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F 
................" + System.lineSeparator() +
+            "00000090 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F 
................" + System.lineSeparator() +
+            "000000A0 A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF 
................" + System.lineSeparator() +
+            "000000B0 B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF 
................" + System.lineSeparator() +
+            "000000C0 C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF 
................" + System.lineSeparator() +
+            "000000D0 D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF 
................" + System.lineSeparator() +
+            "000000E0 E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF 
................" + System.lineSeparator() +
+            "000000F0 F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF 
................" + System.lineSeparator(),
+            out.toString());
+
+        // verify proper behavior with non-zero offset, non-zero index and 
length shorter than array size
+        out = new StringBuilder();
+        HexDump.dump(testArray, 0x10000000, out, 0x28, 32);
+        assertEquals(
+            "10000028 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 
()*+,-./01234567" + System.lineSeparator() +
+            "10000038 38 39 3A 3B 3C 3D 3E 3F 40 41 42 43 44 45 46 47 
89:;<=>?@ABCDEFG" + System.lineSeparator(),
+            out.toString());
+
+        // verify proper behavior with non-zero index and length shorter than 
array size
+        out = new StringBuilder();
+        HexDump.dump(testArray, 0, out, 0x40, 24);
+        assertEquals(
+            "00000040 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 
@ABCDEFGHIJKLMNO" + System.lineSeparator() +
+            "00000050 50 51 52 53 54 55 56 57                         
PQRSTUVW" + System.lineSeparator(),
+            out.toString());
+
+        // verify proper behavior with negative index
+        assertThrows(ArrayIndexOutOfBoundsException.class, () -> 
HexDump.dump(testArray, 0x10000000, new StringBuilder(), -1, testArray.length));
+
+        // verify proper behavior with index that is too large
+        assertThrows(ArrayIndexOutOfBoundsException.class, () -> 
HexDump.dump(testArray, 0x10000000, new StringBuilder(), testArray.length, 
testArray.length));
+
+        // verify proper behavior with length that is negative
+        assertThrows(ArrayIndexOutOfBoundsException.class, () -> 
HexDump.dump(testArray, 0, new StringBuilder(), 0, -1));
+
+        // verify proper behavior with length that is too large
+        final Exception exception = 
assertThrows(ArrayIndexOutOfBoundsException.class, () -> 
HexDump.dump(testArray, 0, new StringBuilder(), 1,
+            testArray.length));
+        assertEquals("Range [1, 1 + 256) out of bounds for length 256", 
exception.getMessage());
+
+        // verify proper behavior with null appendable
+        assertThrows(NullPointerException.class, () -> HexDump.dump(testArray, 
0x10000000, null, 0, testArray.length));
+    }
+
+    @Test
+    public void testDumpOutputStream() throws IOException {
         final byte[] testArray = new byte[256];
 
         for (int j = 0; j < 256; j++) {
@@ -189,6 +254,9 @@ public class HexDumpTest {
 
         // verify proper behavior with null stream
         assertThrows(NullPointerException.class, () -> HexDump.dump(testArray, 
0x10000000, null, 0));
+
+        // verify output stream is not closed by the dump method
+        HexDump.dump(testArray, 0, new ThrowOnCloseOutputStream(new 
ByteArrayOutputStream()), 0);
     }
 
     private char toAscii(final int c) {

Reply via email to