--- /dev/null 2018-10-27 20:27:18.925084831 -0700 +++ new/src/java.base/share/classes/java/util/HexFormat.java 2018-12-11 12:52:38.424224957 -0800 @@ -0,0 +1,803 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.util; + +import java.io.*; +import java.nio.*; +import java.util.*; +import java.util.stream.*; +import static java.nio.charset.StandardCharsets.*; + +/** + * Converts binary data to and from its hexadecimal (base 16) string + * representation. It can also generate the classic Unix {@code hexdump(1)} + * format. + *

+ * Example usages: + *

{@code    // Initialize a 16-byte array from a hexadecimal string
+ *   byte[] bytes = HexFormat.fromString("a1a2a3a4a5a6a7a8a9aaabacadaeaf");
+ *
+ *   // Display the hexadecimal representation of a file's 256-bit hash code
+ *   MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
+ *   System.out.println(
+ *       HexFormat.toString(sha256.digest(Files.readAllBytes(Paths.get("mydata")))));
+ *
+ *   // Write the printable representation of a file to the standard output stream
+ *   // in 64-byte chunks formatted according to the supplied Formatter function
+ *   try (InputStream is = Files.newInputStream(Paths.get("mydata"))) {
+ *       HexFormat.dumpAsStream(is, 64,
+ *           (offset, chunk, fromIndex, toIndex) ->
+ *               String.format("%d %s",
+ *                   offset / 64 + 1,
+ *                   HexFormat.toPrintableString(chunk, fromIndex, toIndex)))
+ *           .forEachOrdered(System.out::println);
+ *   } catch (IOException ioe) {
+ *       ...
+ *   }
+ *
+ *   // Write the standard input stream to the standard output stream in hexdump format
+ *   HexFormat.dump(System.in, System.out);
+ * }
+ * + * @since 12 + */ +public final class HexFormat { + + private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray(); + private static final String NEWLINE = System.lineSeparator(); + private static final int NEWLINE_LENGTH = NEWLINE.length(); + private static final int DEFAULT_CHUNK_SIZE = 16; + + /** + * A formatter that generates the classic Unix {@code hexdump(1)} format. + * It behaves as if: + *
{@code
+     *     String.format("%08x  %s  |%s|",
+     *         offset,
+     *         HexFormat.toFormattedString(chunk, from, to),
+     *         HexFormat.toPrintableString(chunk, from, to));
+     * }
+ */ + public static final Formatter HEXDUMP_FORMATTER = new Formatter() { + public String format(long offset, byte[] chunk, int fromIndex, int toIndex) { + return String.format("%08x %s |%s|", + offset, + HexFormat.toFormattedString(chunk, fromIndex, toIndex), + HexFormat.toPrintableString(chunk, fromIndex, toIndex)); + } + }; + + private HexFormat() {} + + /** + * Returns a hexadecimal string representation of the contents of the + * provided byte array, with no additional formatting. + *

+ * The binary value is converted to a string comprising pairs of + * hexadecimal digits that use only the following ASCII characters: + *

+ * {@code 0123456789abcdef} + *
+ * + * @param bytes a byte array + * @return a hexadecimal string representation of the byte array. + * The string length is twice the array length. + * @throws NullPointerException if {@code bytes} is {@code null} + */ + public static String toString(byte[] bytes) { + Objects.requireNonNull(bytes, "bytes"); + return toString(bytes, 0, bytes.length); + } + + /** + * Returns a hexadecimal string representation of a range within the + * provided byte array, with no additional formatting. + *

+ * The binary value is converted to a string comprising pairs of + * hexadecimal digits that use only the following ASCII characters: + *

+ * {@code 0123456789abcdef} + *
+ * The range to be converted extends from index {@code fromIndex}, + * inclusive, to index {@code toIndex}, exclusive. + * If {@code fromIndex==toIndex}, the range to be converted is empty. + * + * @param bytes a byte array + * @param fromIndex the index of the first byte (inclusive) to be converted + * @param toIndex the index of the last byte (exclusive) to be converted + * @return a hexadecimal string representation of the byte array. + * The string length is twice the number of bytes converted. + * @throws NullPointerException if {@code bytes} is {@code null} + * @throws IllegalArgumentException if {@code fromIndex > toIndex} + * @throws ArrayIndexOutOfBoundsException + * if {@code fromIndex < 0} or {@code toIndex > bytes.length} + */ + public static String toString(byte[] bytes, int fromIndex, int toIndex) { + Objects.requireNonNull(bytes, "bytes"); + Arrays.rangeCheck(bytes.length, fromIndex, toIndex); + return toChunkedString(bytes, fromIndex, toIndex, toIndex - fromIndex, + 1, false); + } + + /** + * Returns a formatted hexadecimal string representation of the contents of + * the provided byte array. + *

+ * The binary value is converted to a string in the canonical hexdump + * format of two columns of eight space-separated pairs of hexadecimal + * digits that use only the following ASCII characters: + *

+ * {@code 0123456789abcdef} + *
+ *

+ * If the number of bytes to be converted is greater than 16 then + * {@link System#lineSeparator()} characters are inserted after each 16-byte chunk. + * If the final chunk is less than 16 bytes then the result is padded with spaces + * to match the length of the preceding chunks. + * The general output format is as follows: + *

+     * 00 11 22 33 44 55 66 77  88 99 aa bb cc dd ee ff
+     * 
+ * + * @param bytes a byte array + * @return a formatted hexadecimal string representation of the byte array + * @throws NullPointerException if {@code bytes} is {@code null} + */ + public static String toFormattedString(byte[] bytes) { + Objects.requireNonNull(bytes, "bytes"); + return toFormattedString(bytes, 0, bytes.length); + } + + /** + * Returns a formatted hexadecimal string representation of the contents of + * a range within the provided byte array. + *

+ * The binary value is converted to a string in the canonical hexdump + * format of two columns of eight space-separated pairs of hexadecimal + * digits that use only the following ASCII characters: + *

+ * {@code 0123456789abcdef} + *
+ *

+ * The range to be converted extends from index {@code fromIndex}, + * inclusive, to index {@code toIndex}, exclusive. + * If {@code fromIndex==toIndex}, the range to be converted is empty. + *

+ * If the number of bytes to be converted is greater than 16 then + * {@link System#lineSeparator()} characters are inserted after each 16-byte chunk. + * If the final chunk is less than 16 bytes then the result is padded with spaces + * to match the length of the preceding chunks. + * The general output format is as follows: + *

+     * 00 11 22 33 44 55 66 77  88 99 aa bb cc dd ee ff
+     * 
+ * + * @param bytes a byte array + * @param fromIndex the index of the first byte (inclusive) to be converted + * @param toIndex the index of the last byte (exclusive) to be converted + * @return a formatted hexadecimal string representation of the byte array + * @throws NullPointerException if {@code bytes} is {@code null} + * @throws IllegalArgumentException if {@code fromIndex > toIndex} + * @throws ArrayIndexOutOfBoundsException + * if {@code fromIndex < 0} or {@code toIndex > bytes.length} + */ + public static String toFormattedString(byte[] bytes, int fromIndex, + int toIndex) { + Objects.requireNonNull(bytes, "bytes"); + Arrays.rangeCheck(bytes.length, fromIndex, toIndex); + return toChunkedString(bytes, fromIndex, toIndex, DEFAULT_CHUNK_SIZE, 2, true); + } + + /** + * Returns a printable representation of the contents of the + * provided byte array. + *

+ * The binary value is converted to a string comprising printable + * {@link java.nio.charset.StandardCharsets#ISO_8859_1} + * characters, or {@code '.'} if the byte maps to a non-printable character. + * A non-printable character is one outside of the range + * {@code '\u005Cu0020'} through {@code '\u005Cu007E'} and + * {@code '\u005Cu00A0'} through {@code '\u005Cu00FF'}. + * + * @param bytes a byte array + * @return a printable representation of the byte array + * @throws NullPointerException if {@code bytes} is {@code null} + */ + public static String toPrintableString(byte[] bytes) { + Objects.requireNonNull(bytes, "bytes"); + return toPrintableString(bytes, 0, bytes.length); + } + + /** + * Returns a printable representation of the contents of a + * range within the provided byte array. + *

+ * The binary value is converted to a string comprising printable + * {@link java.nio.charset.StandardCharsets#ISO_8859_1} + * characters, or {@code '.'} if the byte maps to a non-printable character. + * A non-printable character is one outside of the range + * {@code '\u005Cu0020'} through {@code '\u005Cu007E'} and + * {@code '\u005Cu00A0'} through {@code '\u005Cu00FF'}. + * + * @param bytes a byte array + * @param fromIndex the index of the first byte (inclusive) to be converted + * @param toIndex the index of the last byte (exclusive) to be converted + * @return a printable representation of the byte array + * @throws NullPointerException if {@code bytes} is {@code null} + * @throws IllegalArgumentException if {@code fromIndex > toIndex} + * @throws ArrayIndexOutOfBoundsException + * if {@code fromIndex < 0} or {@code toIndex > bytes.length} + */ + public static String toPrintableString(byte[] bytes, int fromIndex, + int toIndex) { + Objects.requireNonNull(bytes, "bytes"); + Arrays.rangeCheck(bytes.length, fromIndex, toIndex); + + StringBuilder printable = new StringBuilder(toIndex - fromIndex); + for (int i = fromIndex; i < toIndex; i++) { + if (bytes[i] > 0x1F && bytes[i] < 0x7F) { + printable.append((char) bytes[i]); + } else if (bytes[i] > (byte)0x9F && bytes[i] <= (byte)0xFF) { + printable.append(new String(new byte[]{bytes[i]}, ISO_8859_1)); + + } else { + printable.append('.'); + } + } + + return printable.toString(); + } + + /** + * Returns a byte array containing the provided sequence of hexadecimal + * digits. The sequence may be prefixed with the hexadecimal indicator + * {@code "0x"}. + * The optional prefix of {@code "0x"} is ignored. + *

+ * The binary value is generated from pairs of hexadecimal digits that use + * only the following ASCII characters: + *

+ * {@code 0123456789abcdefABCDEF} + *
+ * + * @param hexString an even numbered sequence of hexadecimal digits. + * If this is a {@link CharBuffer} then its position does not get + * advanced. + * @return a byte array + * @throws IllegalArgumentException if {@code hexString} has an odd number + * of digits or contains an illegal hexadecimal character + * @throws NullPointerException if {@code hexString} is {@code null} + */ + public static byte[] fromString(CharSequence hexString) { + Objects.requireNonNull(hexString, "hexString"); + return hexToBytes(hexString, 0, hexString.length()); + } + + /** + * Returns a byte array containing a range within the provided + * sequence of hexadecimal digits. The sequence may be prefixed with the + * hexadecimal indicator {@code "0x"}. + * The optional prefix of {@code "0x"} is ignored. + *

+ * The binary value is generated from pairs of hexadecimal digits that use + * only the following ASCII characters: + *

+ * {@code 0123456789abcdefABCDEF} + *
+ * + * @param hexString an even numbered sequence of hexadecimal digits. + * If this is a {@link CharBuffer} then its position does not get + * advanced. + * @param fromIndex the index of the first digit (inclusive) to be converted + * @param toIndex the index of the last digit (exclusive) to be converted + * @return a byte array + * @throws IllegalArgumentException if {@code hexString} has an odd number + * of digits or contains an illegal hexadecimal character, + * or if {@code fromIndex > toIndex} + * @throws NullPointerException if {@code hexString} is {@code null} + * @throws ArrayIndexOutOfBoundsException + * if {@code fromIndex < 0} or {@code toIndex > hexString.length()} + */ + public static byte[] fromString(CharSequence hexString, int fromIndex, + int toIndex) { + Objects.requireNonNull(hexString, "hexString"); + Arrays.rangeCheck(hexString.length(), fromIndex, toIndex); + return hexToBytes(hexString, fromIndex, toIndex); + } + + /** + * Generates a dump of the contents of the provided input stream, as a + * stream of hexadecimal strings in hexdump format. + * This method outputs the same format as + * {@link #dump(byte[],PrintStream)}, + * without the {@link System#lineSeparator()} characters. + *

+ * If the input is not a multiple of 16 bytes then the final chunk will + * be shorter than the preceding chunks. The result will be padded with + * spaces to match the length of the preceding chunks. + *

+ * On return, the generated stream lazily consumes the input stream. + * This method does not close the input stream and may block indefinitely + * reading from it. The behavior for the case where it is + * asynchronously closed, or the thread interrupted, + * is highly input stream specific, and therefore not specified. + *

+ * If an I/O error occurs reading from the input stream then it may not be + * at end-of-stream and may be in an inconsistent state. It is strongly + * recommended that the input stream be promptly closed if an I/O error + * occurs. + * + * @param in the input stream, non-null + * @return a new infinite sequential ordered stream of hexadecimal strings + * @throws NullPointerException if {@code in} is {@code null} + */ + public static Stream dumpAsStream(InputStream in) { + return dumpAsStream(in, DEFAULT_CHUNK_SIZE, null); + } + + /** + * Generates a dump of the contents of the provided input stream, as a + * stream of formatted hexadecimal strings. Each string is formatted + * according to the {@code formatter} function, if not {@code null}. + * Otherwise, this method outputs the same format as + * {@link #dump(byte[],PrintStream)}, + * without the {@link System#lineSeparator()} characters. + *

+ * On return, the generated stream lazily consumes the input stream. + * This method does not close the input stream and may block indefinitely + * reading from it. The behavior for the case where it is + * asynchronously closed, or the thread interrupted, + * is highly input stream specific, and therefore not specified. + *

+ * If an I/O error occurs reading from the input stream then it may not be + * at end-of-stream and may be in an inconsistent state. It is strongly + * recommended that the input stream be promptly closed if an I/O error + * occurs. + *

+ * If an error occurs in the {@code formatter} then an unchecked exception + * will be thrown from the underlying {@code Stream} method. + * + * @param in the input stream, non-null + * @param chunkSize the number of bytes-per-chunk (typically 16) + * @param formatter a hexdump formatting function, or {@code null} + * @return a new infinite sequential ordered stream of hexadecimal strings + * @throws IllegalArgumentException if {@code chunkSize <= 0} + * @throws NullPointerException if {@code in} is {@code null} + */ + public static Stream dumpAsStream(InputStream in, int chunkSize, + Formatter formatter) { + Objects.requireNonNull(in, "in"); + if (chunkSize <= 0) { + throw new IllegalArgumentException("chunkSize(" + chunkSize + ") <= 0"); + } + final Formatter f = formatter == null ? HEXDUMP_FORMATTER : formatter; + + Iterator iterator = new Iterator<>() { + byte[] nextChunk = null; + int counter = 0; + + @Override + public boolean hasNext() { + if (nextChunk != null) { + return true; + } else { + try { + nextChunk = readChunk(in, chunkSize); + return (nextChunk != null); + + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + } + + @Override + public String next() { + if (nextChunk != null || hasNext()) { + String formattedChunk = + f.format(counter * chunkSize, nextChunk, 0, + nextChunk.length); + nextChunk = null; + counter++; + return formattedChunk; + + } else { + throw new NoSuchElementException(); + } + } + }; + + return StreamSupport.stream( + Spliterators.spliteratorUnknownSize( + iterator, Spliterator.ORDERED | Spliterator.NONNULL), + false); + } + + /** + * Generates a dump of the contents of the provided byte array, as a stream + * of hexadecimal strings in hexdump format. + * This method outputs the same format as + * {@link #dump(byte[],PrintStream)}, + * without the {@link System#lineSeparator()} characters. + *

+ * If the input is not a multiple of 16 bytes then the final chunk will + * be shorter than the preceding chunks. The result will be padded with + * spaces to match the length of the preceding chunks. + * + * @param bytes a byte array, assumed to be unmodified during use + * @return a new sequential ordered stream of hexadecimal strings + * @throws NullPointerException if {@code bytes} is {@code null} + */ + public static Stream dumpAsStream(byte[] bytes) { + Objects.requireNonNull(bytes, "bytes"); + return dumpAsStream(bytes, 0, bytes.length, DEFAULT_CHUNK_SIZE, null); + } + + /** + * Generates a dump of the contents of a range within the provided + * byte array, as a stream of formatted hexadecimal strings. Each string is + * formatted according to the {@code formatter} function, if not {@code null}. + * Otherwise, this method outputs the same format as + * {@link #dump(byte[],PrintStream)}, + * without the {@link System#lineSeparator()} characters. + *

+ * The range to be converted extends from index {@code fromIndex}, + * inclusive, to index {@code toIndex}, exclusive. + * If {@code fromIndex==toIndex}, the range to be converted is empty. + * If the input is not a multiple of {@code chunkSize} then the final chunk + * will be shorter than the preceding chunks. The result may be padded with + * spaces to match the length of the preceding chunks. + *

+ * If an error occurs in the {@code formatter} then an unchecked exception + * will be thrown from the underlying {@code Stream} method. + * + * @param bytes a byte array, assumed to be unmodified during use + * @param fromIndex the index of the first byte (inclusive) to be converted + * @param toIndex the index of the last byte (exclusive) to be converted + * @param chunkSize the number of bytes-per-chunk (typically 16) + * @param formatter a hexdump formatting function, or {@code null} + * @return a new sequential ordered stream of hexadecimal strings + * @throws NullPointerException if {@code bytes} is {@code null} + * @throws IllegalArgumentException if {@code fromIndex > toIndex} + * or {@code chunkSize <= 0} + * @throws ArrayIndexOutOfBoundsException + * if {@code fromIndex < 0} or {@code toIndex > bytes.length} + */ + public static Stream dumpAsStream(byte[] bytes, int fromIndex, + int toIndex, int chunkSize, Formatter formatter) { + Objects.requireNonNull(bytes, "bytes"); + Arrays.rangeCheck(bytes.length, fromIndex, toIndex); + if (chunkSize <= 0) { + throw new IllegalArgumentException("chunkSize(" + chunkSize + ") <= 0"); + } + final Formatter f = formatter == null ? HEXDUMP_FORMATTER : formatter; + + int range = toIndex - fromIndex; + if (range == 0) { + return Stream.empty(); + } + final int length = chunkSize > range ? range : chunkSize; + + return IntStream.range(0, roundUp(range, length)) + .mapToObj(i -> { + int from = fromIndex + (i * length); + int to = from + length; + if (to > toIndex) { + to = toIndex; + } + return f.format(i * chunkSize, bytes, from, to); + }); + } + + /** + * Generates a dump of the contents of the provided ByteBuffer, + * as a stream of formatted hexadecimal strings. Each string is + * formatted according to the {@code formatter} function, if not {@code null}. + * Otherwise, this method outputs the same format as + * {@link #dump(byte[],PrintStream)}, + * without the {@link System#lineSeparator()} characters. + *

+ * If the input is not a multiple of {@code chunkSize} then the final chunk + * will be shorter than the preceding chunks. The result may be padded with + * spaces to match the length of the preceding chunks. + *

+ * Access to the ByteBuffer is relative and its position gets advanced to + * the buffer's limit. + *

+ * If an error occurs in the {@code formatter} then an unchecked exception + * will be thrown from the underlying {@code Stream} method. + * + * @param buffer a byte buffer, assumed to be unmodified during use + * @param chunkSize the number of bytes-per-chunk (typically 16) + * @param formatter a hexdump formatting function, or {@code null} + * @return a new sequential ordered stream of hexadecimal strings + * @throws IllegalArgumentException if {@code chunkSize <= 0} + * @throws NullPointerException if {@code buffer} is {@code null} + */ + public static Stream dumpAsStream(ByteBuffer buffer, int chunkSize, + Formatter formatter) { + Objects.requireNonNull(buffer, "buffer"); + if (chunkSize <= 0) { + throw new IllegalArgumentException("chunkSize(" + chunkSize + ") <= 0"); + } + byte[] bytes = new byte[buffer.remaining()]; + try { + buffer.get(bytes); + } catch (BufferUnderflowException e) { + // Safe to ignore + } + + return dumpAsStream(bytes, 0, bytes.length, chunkSize, formatter); + } + + /** + * Generates a hexadecimal dump of the contents of the provided byte array + * and writes it to the provided output stream. + * This method behaves as if: + *

{@code
+     *     byte[] bytes = ...
+     *     PrintStream out = ...
+     *     HexFormat.dumpAsStream(bytes, 16,
+     *         (offset, chunk, from, to) ->
+     *             String.format("%08x  %s  |%s|",
+     *                 offset,
+     *                 HexFormat.toFormattedString(chunk, from, to),
+     *                 HexFormat.toPrintableString(chunk, from, to)))
+     *         .forEachOrdered(out::println);
+     * }
+ *

+ * This method does not close the output stream and may block indefinitely + * writing to it. The behavior for the case where it is + * asynchronously closed, or the thread interrupted, + * is highly output stream specific, and therefore not specified. + *

+ * If an I/O error occurs writing to the output stream, then it may be + * in an inconsistent state. It is strongly recommended that the output + * stream be promptly closed if an I/O error occurs. + * + * @param bytes the byte array, assumed to be unmodified during use + * @param out the output stream, non-null + * @throws IOException if an I/O error occurs when writing + * @throws NullPointerException if {@code bytes} or {@code out} is + * {@code null} + */ + public static void dump(byte[] bytes, PrintStream out) throws IOException { + Objects.requireNonNull(bytes, "bytes"); + dump(bytes, 0, bytes.length, out); + } + + /** + * Generates a hexadecimal dump of the contents of a range within the + * provided byte array and writes it to the provided output stream. + * This method outputs the same format as + * {@link #dump(byte[],PrintStream)}. + *

+ * The range to be converted extends from index {@code fromIndex}, + * inclusive, to index {@code toIndex}, exclusive. + * If {@code fromIndex==toIndex}, the range to be converted is empty. + *

+ * This method does not close the output stream and may block indefinitely + * writing to it. The behavior for the case where it is + * asynchronously closed, or the thread interrupted, + * is highly output stream specific, and therefore not specified. + *

+ * If an I/O error occurs writing to the output stream, then it may be + * in an inconsistent state. It is strongly recommended that the output + * stream be promptly closed if an I/O error occurs. + * + * @param bytes the byte array, assumed to be unmodified during use + * @param fromIndex the index of the first byte (inclusive) to be converted + * @param toIndex the index of the last byte (exclusive) to be converted + * @param out the output stream, non-null + * @throws IOException if an I/O error occurs when writing + * @throws NullPointerException if {@code bytes} or {@code out} is + * {@code null} + * @throws IllegalArgumentException if {@code fromIndex > toIndex} + * @throws ArrayIndexOutOfBoundsException + * if {@code fromIndex < 0} or {@code toIndex > bytes.length} + */ + public static void dump(byte[] bytes, int fromIndex, int toIndex, + PrintStream out) throws IOException { + + dumpAsStream(bytes, fromIndex, toIndex, DEFAULT_CHUNK_SIZE, null) + .forEachOrdered(getPrintStream(out)::println); + } + + /** + * Generates a hexadecimal dump of the contents of the provided input stream + * and writes it to the provided output stream. + * This method outputs the same format as + * {@link #dump(byte[],PrintStream)}. + *

+ * Reads all bytes from the input stream. + * On return, the input stream will be at end-of-stream. This method does + * not close either stream and may block indefinitely reading from the + * input stream, or writing to the output stream. The behavior for the case + * where the input and/or output stream is asynchronously closed, + * or the thread interrupted, is highly input stream and output stream + * specific, and therefore not specified. + *

+ * If an I/O error occurs reading from the input stream or writing to the + * output stream, then it may do so after some bytes have been read or + * written. Consequently the input stream may not be at end-of-stream and + * one, or both, streams may be in an inconsistent state. It is strongly + * recommended that both streams be promptly closed if an I/O error occurs. + * + * @param in the input stream, non-null + * @param out the output stream, non-null + * @throws IOException if an I/O error occurs when reading or writing + * @throws NullPointerException if {@code in} or {@code out} is {@code null} + */ + public static void dump(InputStream in, PrintStream out) + throws IOException { + dumpAsStream(in, DEFAULT_CHUNK_SIZE, null) + .forEachOrdered(getPrintStream(out)::println); + } + + // Returns a hexadecimal string formatted according to the specified number + // of columns and with/without space separators between pairs of hexadecimal + // digits. Newlines are added when the chunkSize is exceeded. If the final + // line is less than chunkSize then it is padded with spaces. + private static String toChunkedString(byte[] bytes, int fromIndex, + int toIndex, int chunkSize, int columns, boolean useSeparators) { + + int range = toIndex - fromIndex; + if (range == 0) { + return ""; + } + int columnWidth = chunkSize / columns; + int lineLength = useSeparators + ? chunkSize * 3 + (columns - 1) - 1 + : chunkSize * 2 + (columns - 1); + + StringBuilder hexString = + new StringBuilder(lineLength + lineLength * (range / chunkSize)); + int position = 1; + int newlineCount = 0; + for (int i = fromIndex; i < toIndex; i++, position++) { + // add the pair of hex. digits + hexString.append(HEX_DIGITS[(bytes[i] >> 4) & 0xF]); + hexString.append(HEX_DIGITS[(bytes[i] & 0xF)]); + // add a space between pairs of hex. digits + if (useSeparators && position != chunkSize) { + hexString.append(' '); + } + // add a space between columns + if (position % columnWidth == 0 && position != chunkSize) { + hexString.append(' '); + } + // handle end-of-line + if (position == chunkSize && (i + 1 < toIndex)) { + hexString.append(NEWLINE); + newlineCount++; + position = 0; + } + } + // add final line padding, if needed + if (position <= chunkSize) { + int len = hexString.length() - (newlineCount * NEWLINE_LENGTH); + for (int i = len % lineLength; i < lineLength; i++) { + hexString.append(' '); + } + } + + return hexString.toString(); + } + + private static byte[] hexToBytes(CharSequence hexString, int fromIndex, + int toIndex) { + + int len = toIndex - fromIndex; + if (len % 2 != 0) { + throw new IllegalArgumentException( + "contains an odd number of digits: " + hexString); + } + // Skip the '0x' prefix, if present + if (len > 2 && + hexString.charAt(fromIndex) == '0' && + hexString.charAt(fromIndex + 1) == 'x') { + fromIndex += 2; + len -= 2; + } + byte[] bytes = new byte[len / 2]; + + for (int i = 0; i < len; i += 2) { + int hexIndex = fromIndex + i; + int high = Character.digit(hexString.charAt(hexIndex), 16); + int low = Character.digit(hexString.charAt(hexIndex + 1), 16); + if (high == -1 || low == -1) { + throw new IllegalArgumentException( + "contains an illegal hexadecimal character: " + hexString); + } + + bytes[i / 2] = (byte) (high * 16 + low); + } + return bytes; + } + + private static int roundUp(int total, int chunkSize) { + return (total + chunkSize - 1) / chunkSize; + } + + private static byte[] readChunk(InputStream inStream, int chunkSize) + throws IOException { + byte[] buffer = new byte[chunkSize]; + + int n = inStream.readNBytes(buffer, 0, buffer.length); + if (n == 0) { + return null; + } else if (n < chunkSize) { + return Arrays.copyOf(buffer, n); + } else { + return buffer; + } + } + + private static PrintStream getPrintStream(OutputStream out) + throws IOException { + Objects.requireNonNull(out, "out"); + PrintStream ps = null; + if (out instanceof PrintStream) { + ps = (PrintStream) out; + } else { + ps = new PrintStream(out, true); // auto flush + } + return ps; + } + + /** + * Represents a function that formats a byte array as a hexadecimal + * string. + * + *

This is a functional interface + * whose functional method is + * {@link #format}. + * + * @see java.util.function.Function + * @since 12 + */ + @FunctionalInterface + public interface Formatter { + /** + * Returns a formatted hexadecimal string representation of the contents + * of a chunk within a byte array. + * + * @param offsetField is the offset into the byte array + * @param chunk a byte array + * @param fromIndex the index of the first byte (inclusive) of the + * chunk to be converted + * @param toIndex the index of the last byte (exclusive) of the + * chunk to be converted + * @return a hexadecimal string representation of a chunk of the byte + * array + * @throws NullPointerException if {@code chunk} is {@code null} + * @throws IllegalArgumentException if {@code fromIndex > toIndex} + * @throws ArrayIndexOutOfBoundsException + * if {@code fromIndex < 0} or {@code toIndex > chunk.length} + */ + String format(long offsetField, byte[] chunk, int fromIndex, int toIndex); + } +} --- /dev/null 2018-10-27 20:27:18.925084831 -0700 +++ new/test/jdk/java/util/HexFormat/HexdumpTest.java 2018-12-11 12:52:39.107287353 -0800 @@ -0,0 +1,491 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.*; +import java.nio.*; +import java.util.*; +import java.util.stream.*; +import static java.nio.charset.StandardCharsets.*; +import static java.util.HexFormat.*; + +/** + * @test + * @bug 8170769 + * @summary test hexadecimal conversions to/from binary data. + */ + +public class HexdumpTest { + private static final String LINE_SEPARATOR = String.format("%n"); + + /** + * Formatter that generates a custom hexdump format (8-byte chunks). + */ + public static final HexFormat.Formatter CUSTOM_8_HEXDUMP_FORMATTER = new HexFormat.Formatter() { + public String format(long offset, byte[] chunk, int fromIndex, int toIndex) { + return String.format("%04d %-16s %s", + offset, + HexFormat.toString(chunk, fromIndex, toIndex), + HexFormat.toPrintableString(chunk, fromIndex, toIndex)); + } + }; + + /** + * Formatter that generates a custom hexdump format (32-byte chunks). + */ + public static final HexFormat.Formatter CUSTOM_32_HEXDUMP_FORMATTER = new HexFormat.Formatter() { + public String format(long offset, byte[] chunk, int fromIndex, int toIndex) { + return String.format("%04d %-64s %s", + offset, + HexFormat.toString(chunk, fromIndex, toIndex), + HexFormat.toPrintableString(chunk, fromIndex, toIndex)); + } + }; + + /** + * Formatter that generates a custom hexdump format (supports Latin-1). + */ + public static final HexFormat.Formatter CUSTOM_LATIN1_HEXDUMP_FORMATTER = new HexFormat.Formatter() { + + public String format(long offset, byte[] chunk, int fromIndex, int toIndex) { + return String.format("%04d %s %s", + offset, + HexFormat.toFormattedString(chunk, fromIndex, toIndex), + new String(chunk, fromIndex, toIndex - fromIndex, ISO_8859_1).replaceAll("[\\x00-\\x1F\\x7F]", ".")); + } + }; + + public static final void main(String[] args) throws Exception { + + // Test data for byte array + List byteArrayData = new ArrayList<>() {{ + add(new byte[]{ 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18 }); + add(new byte[]{ 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 }); + add(new byte[]{ 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14 }); + add(new byte[]{ (byte)0, (byte)0, (byte)134, (byte)0, (byte)61 }); + add(new byte[]{ (byte)0x00, (byte)0x01, (byte)0x02 }); + add(new byte[]{ (byte)0x00, (byte)0x01 }); + add(new byte[]{ (byte)0x00 }); + add(new byte[0]); + add(new byte[]{ 32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48, + 49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69, + 70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90, + 91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108, + 109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124, + 125,126,127 }); + }}; + + // Test data for byte array (Latin1) + List latin1ByteArrayData = new ArrayList<>() {{ + add(new byte[]{ (byte)192, (byte)193, (byte)194, (byte)195, (byte)196, (byte)197, (byte)198, (byte)199, + (byte)200, (byte)201, (byte)202, (byte)203, (byte)204, (byte)205, (byte)206, (byte)207 }); + add(new byte[]{ (byte)192, 1, (byte)193, 2, (byte)194, 3, (byte)195, 4, (byte)196, 5, (byte)197, 6 }); + add(new byte[]{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, + (byte)127, (byte)128, (byte)129, (byte)130, (byte)131, (byte)132, (byte)133, (byte)134, + (byte)135, (byte)136, (byte)137, (byte)138, (byte)139, (byte)140, (byte)141, (byte)142, + (byte)143, (byte)144, (byte)145, (byte)146, (byte)147, (byte)148, (byte)149, (byte)150, + (byte)151, (byte)152, (byte)153, (byte)154, (byte)155, (byte)156, (byte)157, (byte)158, + (byte)159, (byte)160, (byte)161, (byte)162, (byte)163, (byte)164, (byte)165, (byte)166, + (byte)167, (byte)168, (byte)169, (byte)170, (byte)171, (byte)172, (byte)173, (byte)174, + (byte)175, (byte)176, (byte)177, (byte)178, (byte)179, (byte)180, (byte)181, (byte)182, + (byte)183, (byte)184, (byte)185, (byte)186, (byte)187, (byte)188, (byte)189, (byte)190, + (byte)191, (byte)192, (byte)193, (byte)194, (byte)195, (byte)196, (byte)197, (byte)198, + (byte)199, (byte)200, (byte)201, (byte)202, (byte)203, (byte)204, (byte)205, (byte)206, + (byte)207, (byte)208, (byte)209, (byte)210, (byte)211, (byte)212, (byte)213, (byte)214, + (byte)215, (byte)216, (byte)217, (byte)218, (byte)219, (byte)220, (byte)221, (byte)222, + (byte)223, (byte)224, (byte)225, (byte)226, (byte)227, (byte)228, (byte)229, (byte)230, + (byte)231, (byte)232, (byte)233, (byte)234, (byte)235, (byte)236, (byte)237, (byte)238, + (byte)239, (byte)240, (byte)241, (byte)242, (byte)243, (byte)244, (byte)245, (byte)246, + (byte)247, (byte)248, (byte)249, (byte)250, (byte)251, (byte)252, (byte)253, (byte)254, + (byte)255 }); + }}; + + // Test data for String + List stringData = new ArrayList<>() {{ + add("000102030405060708090a0b0c0d0e0f101112"); + add("000102030405060708090a0b0c0d0e0f"); + add("000102030405060708090a0b0c0d0e"); + add("000086003d"); + add("000102"); + add("0001"); + add("00"); + add(""); + add("202122232425262728292a2b2c2d2e2f" + + "303132333435363738393a3b3c3d3e3f" + + "404142434445464748494a4b4c4d4e4f" + + "505152535455565758595a5b5c5d5e5f" + + "606162636465666768696a6b6c6d6e6f" + + "707172737475767778797a7b7c7d7e7f"); + }}; + + // Test data for Stream of String + List> streamData = new ArrayList<>() {{ + add(List.of( + "00000000 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|", + "00000010 10 11 12 |...|")); + add(List.of( + "00000000 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|" + )); + add(List.of( + "00000000 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e |...............|" + )); + add(List.of( + "00000000 00 00 86 00 3d |....=|")); + add(List.of( + "00000000 00 01 02 |...|")); + add(List.of( + "00000000 00 01 |..|")); + add(List.of( + "00000000 00 |.|")); + add(Collections.emptyList()); + add(List.of( + "00000000 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f | !\"#$%&'()*+,-./|", + "00000010 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f |0123456789:;<=>?|", + "00000020 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f |@ABCDEFGHIJKLMNO|", + "00000030 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f |PQRSTUVWXYZ[\\]^_|", + "00000040 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f |`abcdefghijklmno|", + "00000050 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f |pqrstuvwxyz{|}~.|" + )); + }}; + + // Test data for Stream of String (subarray) + List> subarrayStreamData = new ArrayList<>() {{ + add(List.of( + "00000000 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 |................|", + "00000010 11 |.|")); + add(List.of( + "00000000 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e |..............|" + )); + add(List.of( + "00000000 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d |.............|" + )); + add(List.of( + "00000000 00 86 00 |...|")); + add(List.of( + "00000000 01 |.|")); + add(Collections.emptyList()); // skipped, too short + add(Collections.emptyList()); // skipped, too short + add(Collections.emptyList()); // skipped, too short + add(List.of( + "00000000 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!\"#$%&'()*+,-./0|", + "00000010 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 |123456789:;<=>?@|", + "00000020 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 |ABCDEFGHIJKLMNOP|", + "00000030 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 |QRSTUVWXYZ[\\]^_`|", + "00000040 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 |abcdefghijklmnop|", + "00000050 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e |qrstuvwxyz{|}~|" + )); + }}; + + // Test data for Stream of custom String + List> customStreamData = new ArrayList<>() {{ + add(List.of( + "0000 000102030405060708090a0b0c0d0e0f101112 ...................")); + add(List.of( + "0000 000102030405060708090a0b0c0d0e0f ................")); + add(List.of( + "0000 000102030405060708090a0b0c0d0e ...............")); + add(List.of( + "0000 000086003d ....=")); + add(List.of( + "0000 000102 ...")); + add(List.of( + "0000 0001 ..")); + add(List.of( + "0000 00 .")); + add(Collections.emptyList()); + add(List.of( + "0000 202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f !\"#$%&'()*+,-./0123456789:;<=>?", + "0032 404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_", + "0064 606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f `abcdefghijklmnopqrstuvwxyz{|}~." + )); + }}; + + // Test data for Stream of Latin-1 String + List> customLatin1StreamData = new ArrayList<>() {{ + add(List.of( + "00000000 c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf |ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ|" + )); + add(List.of( + "00000000 c0 01 c1 02 c2 03 c3 04 c4 05 c5 06 |À.Á.Â.Ã.Ä.Å.|" + )); + add(List.of( + "00000000 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|", + "00000010 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f |................|", + "00000020 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f | !\"#$%&'()*+,-./|", + "00000030 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f |0123456789:;<=>?|", + "00000040 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f |@ABCDEFGHIJKLMNO|", + "00000050 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f |PQRSTUVWXYZ[\\]^_|", + "00000060 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f |`abcdefghijklmno|", + "00000070 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f |pqrstuvwxyz{|}~.|", + "00000080 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f |................|", + "00000090 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f |................|", + "000000a0 a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af |\u00A0¡¢£¤¥¦§¨©ª«¬­®¯|", + "000000b0 b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf |°±²³´µ¶·¸¹º»¼½¾¿|", + "000000c0 c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf |ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ|", + "000000d0 d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df |ÐÑÒÓÔÕÖרÙÚÛÜÝÞß|", + "000000e0 e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef |àáâãäåæçèéêëìíîï|", + "000000f0 f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff |ðñòóôõö÷øùúûüýþÿ|" + )); + }}; + + // Test data for Stream of custom String (byteBuffer) + List> byteBufferStreamData = new ArrayList<>() {{ + add(List.of( + "0000 0001020304050607 ........", + "0008 08090a0b0c0d0e0f ........", + "0016 101112 ..." + )); + add(List.of( + "0000 0001020304050607 ........", + "0008 08090a0b0c0d0e0f ........" + )); + add(List.of( + "0000 0001020304050607 ........", + "0008 08090a0b0c0d0e ......." + )); + add(List.of( + "0000 000086003d ....=" + )); + add(List.of( + "0000 000102 ..." + )); + add(List.of( + "0000 0001 .." + )); + add(List.of( + "0000 00 ." + )); + add(Collections.emptyList()); + add(List.of( + "0000 2021222324252627 !\"#$%&'", + "0008 28292a2b2c2d2e2f ()*+,-./", + "0016 3031323334353637 01234567", + "0024 38393a3b3c3d3e3f 89:;<=>?", + "0032 4041424344454647 @ABCDEFG", + "0040 48494a4b4c4d4e4f HIJKLMNO", + "0048 5051525354555657 PQRSTUVW", + "0056 58595a5b5c5d5e5f XYZ[\\]^_", + "0064 6061626364656667 `abcdefg", + "0072 68696a6b6c6d6e6f hijklmno", + "0080 7071727374757677 pqrstuvw", + "0088 78797a7b7c7d7e7f xyz{|}~." + )); + }}; + + // Testing byte array conversions to hex string + System.out.println("----------"); + for (int i = 0; i < byteArrayData.size(); i++) { + byte[] input = byteArrayData.get(i); + String expected = stringData.get(i); + String output = HexFormat.toString(input); + if (expected.equals(output)) { + System.out.println((i + 1) + ") Generated hex string: \"" + output + "\""); + } else { + throw new Exception("ERROR: expected: \"" + expected + + "\" but received: \"" + output + "\""); + } + } + + // Testing subarray conversions to hex string + System.out.println("----------"); + for (int i = 0; i < byteArrayData.size(); i++) { + byte[] input = byteArrayData.get(i); + if (input.length < 2) { + System.out.println((i + 1) + ") Input too short - skipping..."); + continue; + } + String expected = stringData.get(i).toLowerCase(); + expected = expected.substring(2, expected.length() - 2); + String output = HexFormat.toString(input, 1, input.length - 1); + if (expected.equals(output)) { + System.out.println((i + 1) + + ") Generated subarray hex string: \"" + output + "\""); + } else { + throw new Exception("ERROR: expected: \"" + expected + + "\" but received: \"" + output + "\""); + } + } + + // Testing conversions from hex string + System.out.println("----------"); + for (int i = 0; i < stringData.size(); i++) { + String input = stringData.get(i); + byte[] expected = byteArrayData.get(i); + byte[] output = HexFormat.fromString(input); + if (Arrays.equals(expected, output)) { + System.out.println((i + 1) + ") Parsed hex string: \"" + input + "\""); + } else { + throw new Exception("ERROR: expected: " + + Arrays.toString(expected) + " but received: " + + Arrays.toString(output)); + } + } + + // Testing conversions to stream of hexdump string + System.out.println("----------"); + for (int i = 0; i < byteArrayData.size(); i++) { + byte[] input = byteArrayData.get(i); + Stream expected = + Stream.of(streamData.get(i).toArray(new String[0])); + Stream output = HexFormat.dumpAsStream(input); + Object[] expectedArray = expected.toArray(); + System.out.println((i + 1) + ") Generating stream of hexdump string: (from byte array)"); + if (Arrays.equals(expectedArray, output.toArray())) { + HexFormat.dumpAsStream(input).forEach(System.out::println); + } else { + throw new Exception( + "ERROR: expected this stream of hexdump string: " + + Arrays.toString(expectedArray) + " but received: " + + Arrays.toString(HexFormat.dumpAsStream(input).toArray())); + } + } + + // Testing subarray conversions to stream of hexdump string + System.out.println("----------"); + for (int i = 0; i < byteArrayData.size(); i++) { + byte[] input = byteArrayData.get(i); + if (input.length < 2) { + System.out.println((i + 1) + ") Input too short - skipping..."); + continue; + } + Stream expected = + Stream.of(subarrayStreamData.get(i).toArray(new String[0])); + Stream output = + HexFormat.dumpAsStream(input, 1, input.length - 1, 16, null); + Object[] expectedArray = expected.toArray(); + System.out.println((i + 1) + ") Generating stream of hexdump string: (from byte subarray)"); + if (Arrays.equals(expectedArray, output.toArray())) { + HexFormat.dumpAsStream(input, 1, input.length - 1, 16, null) + .forEach(System.out::println); + } else { + throw new Exception( + "ERROR: expected this stream of hexdump string: " + + Arrays.toString(expectedArray) + " but received: " + + Arrays.toString( + HexFormat.dumpAsStream(input, 1, input.length - 1, 16, null) + .toArray())); + } + } + + // Testing subarray conversions to stream of hexdump string + System.out.println("----------"); + for (int i = 0; i < byteArrayData.size(); i++) { + byte[] input = byteArrayData.get(i); + if (input.length < 2) { + System.out.println((i + 1) + ") Input too short - skipping..."); + continue; + } + Stream expected = + Stream.of(subarrayStreamData.get(i).toArray(new String[0])); + Stream output = + HexFormat.dumpAsStream(Arrays.copyOfRange(input, 1, input.length - 1)); + Object[] expectedArray = expected.toArray(); + System.out.println((i + 1) + ") Generating stream of hexdump string: (from byte subarray)"); + if (Arrays.equals(expectedArray, output.toArray())) { + HexFormat.dumpAsStream(Arrays.copyOfRange(input, 1, input.length - 1)) + .forEach(System.out::println); + } else { + throw new Exception( + "ERROR: expected this stream of hexdump string: " + + Arrays.toString(expectedArray) + " but received: " + + Arrays.toString( + HexFormat.dumpAsStream(Arrays.copyOfRange(input, 1, input.length - 1)) + .toArray())); + } + } + + // Testing conversions to stream of custom hexdump string using 32-byte chunks + System.out.println("----------"); + for (int i = 0; i < byteArrayData.size(); i++) { + byte[] input = byteArrayData.get(i); + Stream expected = + Stream.of(customStreamData.get(i).toArray(new String[0])); + Stream output = HexFormat.dumpAsStream(input, 0, input.length, 32, CUSTOM_32_HEXDUMP_FORMATTER); + Object[] expectedArray = expected.toArray(); + System.out.println((i + 1) + ") Generating stream of custom hexdump string: (from byte array)"); + if (Arrays.equals(expectedArray, output.toArray())) { + HexFormat.dumpAsStream(input, 0, input.length, 32, CUSTOM_32_HEXDUMP_FORMATTER) + .forEach(System.out::println); + } else { + throw new Exception( + "ERROR: expected this stream of hexdump string: " + + Arrays.toString(expectedArray) + " but received: " + + Arrays.toString(HexFormat.dumpAsStream(input, 0, input.length, 32, CUSTOM_32_HEXDUMP_FORMATTER) + .toArray())); + } + } + + // Testing conversions to stream of custom hexdump string using Latin-1 + System.out.println("----------"); + for (int i = 0; i < latin1ByteArrayData.size(); i++) { + byte[] input = latin1ByteArrayData.get(i); + Stream expected = + Stream.of(customLatin1StreamData.get(i).toArray(new String[0])); + //VR Stream output = HexFormat.dumpAsStream(input, 0, input.length, 16, CUSTOM_LATIN1_HEXDUMP_FORMATTER); + Stream output = HexFormat.dumpAsStream(input, 0, input.length, 16, null); + Object[] expectedArray = expected.toArray(); + System.out.println((i + 1) + ") Generating stream of custom Latin-1 hexdump string: (from byte array)"); + if (Arrays.equals(expectedArray, output.toArray())) { + //VR HexFormat.dumpAsStream(input, 0, input.length, 16, CUSTOM_LATIN1_HEXDUMP_FORMATTER) + HexFormat.dumpAsStream(input, 0, input.length, 16, null) + .forEach(System.out::println); + } else { + System.out.println("VR: error at byte["+i+"]"); + throw new Exception( + "ERROR: expected this stream of hexdump string: " + + Arrays.toString(expectedArray) + " but received: " + + //VR Arrays.toString(HexFormat.dumpAsStream(input, 0, input.length, 16, CUSTOM_LATIN1_HEXDUMP_FORMATTER) + Arrays.toString(HexFormat.dumpAsStream(input, 0, input.length, 16, null) + .toArray())); + } + } + + // Testing ByteBuffer conversions to stream of custom hexdump string using 8-byte chunks + System.out.println("----------"); + for (int i = 0; i < byteArrayData.size(); i++) { + byte[] input = byteArrayData.get(i); + Stream expected = + Stream.of(byteBufferStreamData.get(i).toArray(new String[0])); + Stream output = + HexFormat.dumpAsStream(ByteBuffer.wrap(input), 0, input.length, 8, CUSTOM_8_HEXDUMP_FORMATTER); + Object[] expectedArray = expected.toArray(); + System.out.println((i + 1) + ") Generating stream of custom hexdump string: (from ByteBuffer)"); + if (Arrays.equals(expectedArray, output.toArray())) { + HexFormat.dumpAsStream(ByteBuffer.wrap(input), 0, input.length, 8, CUSTOM_8_HEXDUMP_FORMATTER) + .forEach(System.out::println); + } else { + throw new Exception( + "ERROR: expected this stream of custom hexdump string: " + + Arrays.toString(expectedArray) + " but received: " + + Arrays.toString( + HexFormat.dumpAsStream(ByteBuffer.wrap(input), 0, input.length, 8, CUSTOM_8_HEXDUMP_FORMATTER).toArray())); + } + } + } +}