--- /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
+ * 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
+ * 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
+ * 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
+ * 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
+ * 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{@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);
+ * }
+ * > 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