aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFredrik Kjellberg <fredrik.kjellberg@gmail.com>2023-01-18 20:58:28 +0100
committerGitHub <noreply@github.com>2023-01-18 14:58:28 -0500
commitfcf8752f87aebbd5fa729e042a1bfa48882972da (patch)
tree55dd957d37cd8c8649bdd9369011495c14fb9ddc /src
parentf71d338bf9e4e564f1fcb655252c94623c592e0e (diff)
downloadapache-commons-io-fcf8752f87aebbd5fa729e042a1bfa48882972da.tar.gz
[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
Diffstat (limited to 'src')
-rw-r--r--src/main/java/org/apache/commons/io/HexDump.java103
-rw-r--r--src/test/java/org/apache/commons/io/HexDumpTest.java70
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,15 +148,54 @@ 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.
*
* @param _cbuffer the StringBuilder to dump the value in
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) {