1 /*
   2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package java.util;
  27 
  28 import java.io.*;
  29 import java.nio.*;
  30 import java.util.*;
  31 import java.util.stream.*;
  32 import static java.nio.charset.StandardCharsets.*;
  33 
  34 /**
  35  * Converts binary data to and from its hexadecimal (base 16) string
  36  * representation. It can also generate the classic Unix {@code hexdump(1)}
  37  * format.
  38  * <p>
  39  * <b>Example usages:</b>
  40  * <pre>{@code    // Initialize a 16-byte array from a hexadecimal string
  41  *   byte[] bytes = HexFormat.fromString("a1a2a3a4a5a6a7a8a9aaabacadaeaf");
  42  *
  43  *   // Display the hexadecimal representation of a file's 256-bit hash code
  44  *   MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
  45  *   System.out.println(
  46  *       HexFormat.toString(sha256.digest(Files.readAllBytes(Paths.get("mydata")))));
  47  *
  48  *   // Write the printable representation of a file to the standard output stream
  49  *   // in 64-byte chunks formatted according to the supplied Formatter function
  50  *   try (InputStream is = Files.newInputStream(Paths.get("mydata"))) {
  51  *       HexFormat.dumpAsStream(is, 64,
  52  *           (offset, chunk, fromIndex, toIndex) ->
  53  *               String.format("%d %s",
  54  *                   offset / 64 + 1,
  55  *                   HexFormat.toPrintableString(chunk, fromIndex, toIndex)))
  56  *           .forEachOrdered(System.out::println);
  57  *   } catch (IOException ioe) {
  58  *       ...
  59  *   }
  60  *
  61  *   // Write the standard input stream to the standard output stream in hexdump format
  62  *   HexFormat.dump(System.in, System.out);
  63  * }</pre>
  64  *
  65  * @since 12
  66  */
  67 public final class HexFormat {
  68 
  69     private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray();
  70     private static final String NEWLINE = System.lineSeparator();
  71     private static final int NEWLINE_LENGTH = NEWLINE.length();
  72     private static final int DEFAULT_CHUNK_SIZE = 16;
  73 
  74     /**
  75      * A formatter that generates the classic Unix {@code hexdump(1)} format.
  76      * It behaves <i>as if</i>:
  77      * <pre>{@code
  78      *     String.format("%08x  %s  |%s|",
  79      *         offset,
  80      *         HexFormat.toFormattedString(chunk, from, to),
  81      *         HexFormat.toPrintableString(chunk, from, to));
  82      * }</pre>
  83      */
  84     public static final Formatter HEXDUMP_FORMATTER = new Formatter() {
  85         public String format(long offset, byte[] chunk, int fromIndex, int toIndex) {
  86             return String.format("%08x  %s  |%s|",
  87                 offset,
  88                 HexFormat.toFormattedString(chunk, fromIndex, toIndex),
  89                 HexFormat.toPrintableString(chunk, fromIndex, toIndex));
  90         }
  91     };
  92 
  93     private HexFormat() {}
  94 
  95     /**
  96      * Returns a hexadecimal string representation of the contents of the
  97      * provided byte array, with no additional formatting.
  98      * <p>
  99      * The binary value is converted to a string comprising pairs of
 100      * hexadecimal digits that use only the following ASCII characters:
 101      * <blockquote>
 102      *  {@code 0123456789abcdef}
 103      * </blockquote>
 104      *
 105      * @param bytes a byte array
 106      * @return a hexadecimal string representation of the byte array.
 107      *         The string length is twice the array length.
 108      * @throws NullPointerException if {@code bytes} is {@code null}
 109      */
 110     public static String toString(byte[] bytes) {
 111         Objects.requireNonNull(bytes, "bytes");
 112         return toString(bytes, 0, bytes.length);
 113     }
 114 
 115     /**
 116      * Returns a hexadecimal string representation of a <i>range</i> within the
 117      * provided byte array, with no additional formatting.
 118      * <p>
 119      * The binary value is converted to a string comprising pairs of
 120      * hexadecimal digits that use only the following ASCII characters:
 121      * <blockquote>
 122      *  {@code 0123456789abcdef}
 123      * </blockquote>
 124      * The range to be converted extends from index {@code fromIndex},
 125      * inclusive, to index {@code toIndex}, exclusive.
 126      * If {@code fromIndex==toIndex}, the range to be converted is empty.
 127      *
 128      * @param bytes a byte array
 129      * @param fromIndex the index of the first byte (inclusive) to be converted
 130      * @param toIndex the index of the last byte (exclusive) to be converted
 131      * @return a hexadecimal string representation of the byte array.
 132      *         The string length is twice the number of bytes converted.
 133      * @throws NullPointerException if {@code bytes} is {@code null}
 134      * @throws IllegalArgumentException if {@code fromIndex > toIndex}
 135      * @throws ArrayIndexOutOfBoundsException
 136      *     if {@code fromIndex < 0} or {@code toIndex > bytes.length}
 137      */
 138     public static String toString(byte[] bytes, int fromIndex, int toIndex) {
 139         Objects.requireNonNull(bytes, "bytes");
 140         Arrays.rangeCheck(bytes.length, fromIndex, toIndex);
 141         return toChunkedString(bytes, fromIndex, toIndex, toIndex - fromIndex,
 142             1, false);
 143     }
 144 
 145     /**
 146      * Returns a formatted hexadecimal string representation of the contents of
 147      * the provided byte array.
 148      * <p>
 149      * The binary value is converted to a string in the canonical hexdump
 150      * format of two columns of eight space-separated pairs of hexadecimal
 151      * digits that use only the following ASCII characters:
 152      * <blockquote>
 153      *  {@code 0123456789abcdef}
 154      * </blockquote>
 155      * <p>
 156      * If the number of bytes to be converted is greater than 16 then
 157      * {@link System#lineSeparator()} characters are inserted after each 16-byte chunk.
 158      * If the final chunk is less than 16 bytes then the result is padded with spaces
 159      * to match the length of the preceding chunks.
 160      * The general output format is as follows:
 161      * <pre>
 162      * 00 11 22 33 44 55 66 77  88 99 aa bb cc dd ee ff
 163      * </pre>
 164      *
 165      * @param bytes a byte array
 166      * @return a formatted hexadecimal string representation of the byte array
 167      * @throws NullPointerException if {@code bytes} is {@code null}
 168      */
 169     public static String toFormattedString(byte[] bytes) {
 170         Objects.requireNonNull(bytes, "bytes");
 171         return toFormattedString(bytes, 0, bytes.length);
 172     }
 173 
 174     /**
 175      * Returns a formatted hexadecimal string representation of the contents of
 176      * a <i>range</i> within the provided byte array.
 177      * <p>
 178      * The binary value is converted to a string in the canonical hexdump
 179      * format of two columns of eight space-separated pairs of hexadecimal
 180      * digits that use only the following ASCII characters:
 181      * <blockquote>
 182      *  {@code 0123456789abcdef}
 183      * </blockquote>
 184      * <p>
 185      * The range to be converted extends from index {@code fromIndex},
 186      * inclusive, to index {@code toIndex}, exclusive.
 187      * If {@code fromIndex==toIndex}, the range to be converted is empty.
 188      * <p>
 189      * If the number of bytes to be converted is greater than 16 then
 190      * {@link System#lineSeparator()} characters are inserted after each 16-byte chunk.
 191      * If the final chunk is less than 16 bytes then the result is padded with spaces
 192      * to match the length of the preceding chunks.
 193      * The general output format is as follows:
 194      * <pre>
 195      * 00 11 22 33 44 55 66 77  88 99 aa bb cc dd ee ff
 196      * </pre>
 197      *
 198      * @param bytes a byte array
 199      * @param fromIndex the index of the first byte (inclusive) to be converted
 200      * @param toIndex the index of the last byte (exclusive) to be converted
 201      * @return a formatted hexadecimal string representation of the byte array
 202      * @throws NullPointerException if {@code bytes} is {@code null}
 203      * @throws IllegalArgumentException if {@code fromIndex > toIndex}
 204      * @throws ArrayIndexOutOfBoundsException
 205      *     if {@code fromIndex < 0} or {@code toIndex > bytes.length}
 206      */
 207     public static String toFormattedString(byte[] bytes, int fromIndex,
 208         int toIndex) {
 209         Objects.requireNonNull(bytes, "bytes");
 210         Arrays.rangeCheck(bytes.length, fromIndex, toIndex);
 211         return toChunkedString(bytes, fromIndex, toIndex, DEFAULT_CHUNK_SIZE, 2, true);
 212     }
 213 
 214     /**
 215      * Returns a printable representation of the contents of the
 216      * provided byte array.
 217      * <p>
 218      * The binary value is converted to a string comprising printable
 219      * {@link java.nio.charset.StandardCharsets#ISO_8859_1}
 220      * characters, or {@code '.'} if the byte maps to a non-printable character.
 221      * A non-printable character is one outside of the range
 222      * {@code '\u005Cu0020'} through {@code '\u005Cu007E'} and
 223      * {@code '\u005Cu00A0'} through {@code '\u005Cu00FF'}.
 224      *
 225      * @param bytes a byte array
 226      * @return a printable representation of the byte array
 227      * @throws NullPointerException if {@code bytes} is {@code null}
 228      */
 229     public static String toPrintableString(byte[] bytes) {
 230         Objects.requireNonNull(bytes, "bytes");
 231         return toPrintableString(bytes, 0, bytes.length);
 232     }
 233 
 234     /**
 235      * Returns a printable representation of the contents of a
 236      * <i>range</i> within the provided byte array.
 237      * <p>
 238      * The binary value is converted to a string comprising printable
 239      * {@link java.nio.charset.StandardCharsets#ISO_8859_1}
 240      * characters, or {@code '.'} if the byte maps to a non-printable character.
 241      * A non-printable character is one outside of the range
 242      * {@code '\u005Cu0020'} through {@code '\u005Cu007E'} and
 243      * {@code '\u005Cu00A0'} through {@code '\u005Cu00FF'}.
 244      *
 245      * @param bytes a byte array
 246      * @param fromIndex the index of the first byte (inclusive) to be converted
 247      * @param toIndex the index of the last byte (exclusive) to be converted
 248      * @return a printable representation of the byte array
 249      * @throws NullPointerException if {@code bytes} is {@code null}
 250      * @throws IllegalArgumentException if {@code fromIndex > toIndex}
 251      * @throws ArrayIndexOutOfBoundsException
 252      *     if {@code fromIndex < 0} or {@code toIndex > bytes.length}
 253      */
 254     public static String toPrintableString(byte[] bytes, int fromIndex,
 255             int toIndex) {
 256         Objects.requireNonNull(bytes, "bytes");
 257         Arrays.rangeCheck(bytes.length, fromIndex, toIndex);
 258 
 259         StringBuilder printable = new StringBuilder(toIndex - fromIndex);
 260         for (int i = fromIndex; i < toIndex; i++) {
 261             if (bytes[i] > 0x1F && bytes[i] < 0x7F) {
 262                 printable.append((char) bytes[i]);
 263             } else if (bytes[i] > (byte)0x9F && bytes[i] <= (byte)0xFF) {
 264                 printable.append(new String(new byte[]{bytes[i]}, ISO_8859_1));
 265 
 266             } else {
 267                 printable.append('.');
 268             }
 269         }
 270 
 271         return printable.toString();
 272     }
 273 
 274     /**
 275      * Returns a byte array containing the provided sequence of hexadecimal
 276      * digits. The sequence may be prefixed with the hexadecimal indicator
 277      * {@code "0x"}.
 278      * The optional prefix of {@code "0x"} is ignored.
 279      * <p>
 280      * The binary value is generated from pairs of hexadecimal digits that use
 281      * only the following ASCII characters:
 282      * <blockquote>
 283      *  {@code 0123456789abcdefABCDEF}
 284      * </blockquote>
 285      *
 286      * @param hexString an even numbered sequence of hexadecimal digits.
 287      *         If this is a {@link CharBuffer} then its position does not get
 288      *         advanced.
 289      * @return a byte array
 290      * @throws IllegalArgumentException if {@code hexString} has an odd number
 291      *         of digits or contains an illegal hexadecimal character
 292      * @throws NullPointerException if {@code hexString} is {@code null}
 293      */
 294     public static byte[] fromString(CharSequence hexString) {
 295         Objects.requireNonNull(hexString, "hexString");
 296         return hexToBytes(hexString, 0, hexString.length());
 297     }
 298 
 299     /**
 300      * Returns a byte array containing a <i>range</i> within the provided
 301      * sequence of hexadecimal digits. The sequence may be prefixed with the
 302      * hexadecimal indicator {@code "0x"}.
 303      * The optional prefix of {@code "0x"} is ignored.
 304      * <p>
 305      * The binary value is generated from pairs of hexadecimal digits that use
 306      * only the following ASCII characters:
 307      * <blockquote>
 308      *  {@code 0123456789abcdefABCDEF}
 309      * </blockquote>
 310      *
 311      * @param hexString an even numbered sequence of hexadecimal digits.
 312      *         If this is a {@link CharBuffer} then its position does not get
 313      *         advanced.
 314      * @param fromIndex the index of the first digit (inclusive) to be converted
 315      * @param toIndex the index of the last digit (exclusive) to be converted
 316      * @return a byte array
 317      * @throws IllegalArgumentException if {@code hexString} has an odd number
 318      *         of digits or contains an illegal hexadecimal character,
 319      *         or if {@code fromIndex > toIndex}
 320      * @throws NullPointerException if {@code hexString} is {@code null}
 321      * @throws ArrayIndexOutOfBoundsException
 322      *     if {@code fromIndex < 0} or {@code toIndex > hexString.length()}
 323      */
 324     public static byte[] fromString(CharSequence hexString, int fromIndex,
 325             int toIndex) {
 326         Objects.requireNonNull(hexString, "hexString");
 327         Arrays.rangeCheck(hexString.length(), fromIndex, toIndex);
 328         return hexToBytes(hexString, fromIndex, toIndex);
 329     }
 330 
 331     /**
 332      * Generates a dump of the contents of the provided input stream, as a
 333      * stream of hexadecimal strings in hexdump format.
 334      * This method outputs the same format as
 335      * {@link #dump(byte[],PrintStream)},
 336      * without the {@link System#lineSeparator()} characters.
 337      * <p>
 338      * If the input is not a multiple of 16 bytes then the final chunk will
 339      * be shorter than the preceding chunks. The result will be padded with
 340      * spaces to match the length of the preceding chunks.
 341      * <p>
 342      * On return, the generated stream lazily consumes the input stream.
 343      * This method does not close the input stream and may block indefinitely
 344      * reading from it. The behavior for the case where it is
 345      * <i>asynchronously closed</i>, or the thread interrupted,
 346      * is highly input stream specific, and therefore not specified.
 347      * <p>
 348      * If an I/O error occurs reading from the input stream then it may not be
 349      * at end-of-stream and may be in an inconsistent state. It is strongly
 350      * recommended that the input stream be promptly closed if an I/O error
 351      * occurs.
 352      *
 353      * @param in the input stream, non-null
 354      * @return a new infinite sequential ordered stream of hexadecimal strings
 355      * @throws NullPointerException if {@code in} is {@code null}
 356      */
 357     public static Stream<String> dumpAsStream(InputStream in) {
 358         return dumpAsStream(in, DEFAULT_CHUNK_SIZE, null);
 359     }
 360 
 361     /**
 362      * Generates a dump of the contents of the provided input stream, as a
 363      * stream of formatted hexadecimal strings. Each string is formatted
 364      * according to the {@code formatter} function, if not {@code null}.
 365      * Otherwise, this method outputs the same format as
 366      * {@link #dump(byte[],PrintStream)},
 367      * without the {@link System#lineSeparator()} characters.
 368      * <p>
 369      * On return, the generated stream lazily consumes the input stream.
 370      * This method does not close the input stream and may block indefinitely
 371      * reading from it. The behavior for the case where it is
 372      * <i>asynchronously closed</i>, or the thread interrupted,
 373      * is highly input stream specific, and therefore not specified.
 374      * <p>
 375      * If an I/O error occurs reading from the input stream then it may not be
 376      * at end-of-stream and may be in an inconsistent state. It is strongly
 377      * recommended that the input stream be promptly closed if an I/O error
 378      * occurs.
 379      * <p>
 380      * If an error occurs in the {@code formatter} then an unchecked exception
 381      * will be thrown from the underlying {@code Stream} method.
 382      *
 383      * @param in the input stream, non-null
 384      * @param chunkSize the number of bytes-per-chunk (typically 16)
 385      * @param formatter a hexdump formatting function, or {@code null}
 386      * @return a new infinite sequential ordered stream of hexadecimal strings
 387      * @throws IllegalArgumentException if {@code chunkSize <= 0}
 388      * @throws NullPointerException if {@code in} is {@code null}
 389      */
 390     public static Stream<String> dumpAsStream(InputStream in, int chunkSize,
 391             Formatter formatter) {
 392         Objects.requireNonNull(in, "in");
 393         if (chunkSize <= 0) {
 394             throw new IllegalArgumentException("chunkSize(" + chunkSize + ") <= 0");
 395         }
 396         final Formatter f = formatter == null ? HEXDUMP_FORMATTER : formatter;
 397 
 398         Iterator<String> iterator = new Iterator<>() {
 399             byte[] nextChunk = null;
 400             int counter = 0;
 401 
 402             @Override
 403             public boolean hasNext() {
 404                 if (nextChunk != null) {
 405                     return true;
 406                 } else {
 407                     try {
 408                         nextChunk = readChunk(in, chunkSize);
 409                         return (nextChunk != null);
 410 
 411                     } catch (IOException e) {
 412                         throw new UncheckedIOException(e);
 413                     }
 414                 }
 415             }
 416 
 417             @Override
 418             public String next() {
 419                 if (nextChunk != null || hasNext()) {
 420                     String formattedChunk =
 421                         f.format(counter * chunkSize, nextChunk, 0,
 422                             nextChunk.length);
 423                     nextChunk = null;
 424                     counter++;
 425                     return formattedChunk;
 426 
 427                 } else {
 428                     throw new NoSuchElementException();
 429                 }
 430             }
 431         };
 432 
 433         return StreamSupport.stream(
 434             Spliterators.spliteratorUnknownSize(
 435                 iterator, Spliterator.ORDERED | Spliterator.NONNULL),
 436             false);
 437     }
 438 
 439     /**
 440      * Generates a dump of the contents of the provided byte array, as a stream
 441      * of hexadecimal strings in hexdump format.
 442      * This method outputs the same format as
 443      * {@link #dump(byte[],PrintStream)},
 444      * without the {@link System#lineSeparator()} characters.
 445      * <p>
 446      * If the input is not a multiple of 16 bytes then the final chunk will
 447      * be shorter than the preceding chunks. The result will be padded with
 448      * spaces to match the length of the preceding chunks.
 449      *
 450      * @param bytes a byte array, assumed to be unmodified during use
 451      * @return a new sequential ordered stream of hexadecimal strings
 452      * @throws NullPointerException if {@code bytes} is {@code null}
 453      */
 454     public static Stream<String> dumpAsStream(byte[] bytes) {
 455         Objects.requireNonNull(bytes, "bytes");
 456         return dumpAsStream(bytes, 0, bytes.length, DEFAULT_CHUNK_SIZE, null);
 457     }
 458 
 459     /**
 460      * Generates a dump of the contents of a <i>range</i> within the provided
 461      * byte array, as a stream of formatted hexadecimal strings. Each string is
 462      * formatted according to the {@code formatter} function, if not {@code null}.
 463      * Otherwise, this method outputs the same format as
 464      * {@link #dump(byte[],PrintStream)},
 465      * without the {@link System#lineSeparator()} characters.
 466      * <p>
 467      * The range to be converted extends from index {@code fromIndex},
 468      * inclusive, to index {@code toIndex}, exclusive.
 469      * If {@code fromIndex==toIndex}, the range to be converted is empty.
 470      * If the input is not a multiple of {@code chunkSize} then the final chunk
 471      * will be shorter than the preceding chunks. The result may be padded with
 472      * spaces to match the length of the preceding chunks.
 473      * <p>
 474      * If an error occurs in the {@code formatter} then an unchecked exception
 475      * will be thrown from the underlying {@code Stream} method.
 476      *
 477      * @param bytes a byte array, assumed to be unmodified during use
 478      * @param fromIndex the index of the first byte (inclusive) to be converted
 479      * @param toIndex the index of the last byte (exclusive) to be converted
 480      * @param chunkSize the number of bytes-per-chunk (typically 16)
 481      * @param formatter a hexdump formatting function, or {@code null}
 482      * @return a new sequential ordered stream of hexadecimal strings
 483      * @throws NullPointerException if {@code bytes} is {@code null}
 484      * @throws IllegalArgumentException if {@code fromIndex > toIndex}
 485      *     or {@code chunkSize <= 0}
 486      * @throws ArrayIndexOutOfBoundsException
 487      *     if {@code fromIndex < 0} or {@code toIndex > bytes.length}
 488      */
 489     public static Stream<String> dumpAsStream(byte[] bytes, int fromIndex,
 490             int toIndex, int chunkSize, Formatter formatter) {
 491         Objects.requireNonNull(bytes, "bytes");
 492         Arrays.rangeCheck(bytes.length, fromIndex, toIndex);
 493         if (chunkSize <= 0) {
 494             throw new IllegalArgumentException("chunkSize(" + chunkSize + ") <= 0");
 495         }
 496         final Formatter f = formatter == null ? HEXDUMP_FORMATTER : formatter;
 497 
 498         int range = toIndex - fromIndex;
 499         if (range == 0) {
 500             return Stream.empty();
 501         }
 502         final int length = chunkSize > range ? range : chunkSize;
 503 
 504         return IntStream.range(0, roundUp(range, length))
 505             .mapToObj(i -> {
 506                 int from = fromIndex + (i * length);
 507                 int to = from + length;
 508                 if (to > toIndex) {
 509                     to = toIndex;
 510                 }
 511                 return f.format(i * chunkSize, bytes, from, to);
 512             });
 513     }
 514 
 515     /**
 516      * Generates a dump of the contents of the provided ByteBuffer,
 517      * as a stream of formatted hexadecimal strings. Each string is
 518      * formatted according to the {@code formatter} function, if not {@code null}.
 519      * Otherwise, this method outputs the same format as
 520      * {@link #dump(byte[],PrintStream)},
 521      * without the {@link System#lineSeparator()} characters.
 522      * <p>
 523      * If the input is not a multiple of {@code chunkSize} then the final chunk
 524      * will be shorter than the preceding chunks. The result may be padded with
 525      * spaces to match the length of the preceding chunks.
 526      * <p>
 527      * Access to the ByteBuffer is relative and its position gets advanced to
 528      * the buffer's limit.
 529      * <p>
 530      * If an error occurs in the {@code formatter} then an unchecked exception
 531      * will be thrown from the underlying {@code Stream} method.
 532      *
 533      * @param buffer a byte buffer, assumed to be unmodified during use
 534      * @param chunkSize the number of bytes-per-chunk (typically 16)
 535      * @param formatter a hexdump formatting function, or {@code null}
 536      * @return a new sequential ordered stream of hexadecimal strings
 537      * @throws IllegalArgumentException if {@code chunkSize <= 0}
 538      * @throws NullPointerException if {@code buffer} is {@code null}
 539      */
 540     public static Stream<String> dumpAsStream(ByteBuffer buffer, int chunkSize,
 541             Formatter formatter) {
 542         Objects.requireNonNull(buffer, "buffer");
 543         if (chunkSize <= 0) {
 544             throw new IllegalArgumentException("chunkSize(" + chunkSize + ") <= 0");
 545         }
 546         byte[] bytes = new byte[buffer.remaining()];
 547         try {
 548             buffer.get(bytes);
 549         } catch (BufferUnderflowException e) {
 550             // Safe to ignore
 551         }
 552 
 553         return dumpAsStream(bytes, 0, bytes.length, chunkSize, formatter);
 554     }
 555 
 556     /**
 557      * Generates a hexadecimal dump of the contents of the provided byte array
 558      * and writes it to the provided output stream.
 559      * This method behaves <i>as if</i>:
 560      * <pre>{@code
 561      *     byte[] bytes = ...
 562      *     PrintStream out = ...
 563      *     HexFormat.dumpAsStream(bytes, 16,
 564      *         (offset, chunk, from, to) ->
 565      *             String.format("%08x  %s  |%s|",
 566      *                 offset,
 567      *                 HexFormat.toFormattedString(chunk, from, to),
 568      *                 HexFormat.toPrintableString(chunk, from, to)))
 569      *         .forEachOrdered(out::println);
 570      * }</pre>
 571      * <p>
 572      * This method does not close the output stream and may block indefinitely
 573      * writing to it. The behavior for the case where it is
 574      * <i>asynchronously closed</i>, or the thread interrupted,
 575      * is highly output stream specific, and therefore not specified.
 576      * <p>
 577      * If an I/O error occurs writing to the output stream, then it may be
 578      * in an inconsistent state. It is strongly recommended that the output
 579      * stream be promptly closed if an I/O error occurs.
 580      *
 581      * @param bytes the byte array, assumed to be unmodified during use
 582      * @param out the output stream, non-null
 583      * @throws IOException if an I/O error occurs when writing
 584      * @throws NullPointerException if {@code bytes} or {@code out} is
 585      *     {@code null}
 586      */
 587     public static void dump(byte[] bytes, PrintStream out) throws IOException {
 588         Objects.requireNonNull(bytes, "bytes");
 589         dump(bytes, 0, bytes.length, out);
 590     }
 591 
 592     /**
 593      * Generates a hexadecimal dump of the contents of a <i>range</i> within the
 594      * provided byte array and writes it to the provided output stream.
 595      * This method outputs the same format as
 596      * {@link #dump(byte[],PrintStream)}. 
 597      * <p>
 598      * The range to be converted extends from index {@code fromIndex},
 599      * inclusive, to index {@code toIndex}, exclusive.
 600      * If {@code fromIndex==toIndex}, the range to be converted is empty.
 601      * <p>
 602      * This method does not close the output stream and may block indefinitely
 603      * writing to it. The behavior for the case where it is
 604      * <i>asynchronously closed</i>, or the thread interrupted,
 605      * is highly output stream specific, and therefore not specified.
 606      * <p>
 607      * If an I/O error occurs writing to the output stream, then it may be
 608      * in an inconsistent state. It is strongly recommended that the output
 609      * stream be promptly closed if an I/O error occurs.
 610      *
 611      * @param bytes the byte array, assumed to be unmodified during use
 612      * @param fromIndex the index of the first byte (inclusive) to be converted
 613      * @param toIndex the index of the last byte (exclusive) to be converted
 614      * @param out the output stream, non-null
 615      * @throws IOException if an I/O error occurs when writing
 616      * @throws NullPointerException if {@code bytes} or {@code out} is
 617      *     {@code null}
 618      * @throws IllegalArgumentException if {@code fromIndex > toIndex}
 619      * @throws ArrayIndexOutOfBoundsException
 620      *     if {@code fromIndex < 0} or {@code toIndex > bytes.length}
 621      */
 622     public static void dump(byte[] bytes, int fromIndex, int toIndex,
 623         PrintStream out) throws IOException {
 624 
 625         dumpAsStream(bytes, fromIndex, toIndex, DEFAULT_CHUNK_SIZE, null)
 626             .forEachOrdered(getPrintStream(out)::println);
 627     }
 628 
 629     /**
 630      * Generates a hexadecimal dump of the contents of the provided input stream
 631      * and writes it to the provided output stream.
 632      * This method outputs the same format as
 633      * {@link #dump(byte[],PrintStream)}.
 634      * <p>
 635      * Reads all bytes from the input stream.
 636      * On return, the input stream will be at end-of-stream. This method does
 637      * not close either stream and may block indefinitely reading from the
 638      * input stream, or writing to the output stream. The behavior for the case
 639      * where the input and/or output stream is <i>asynchronously closed</i>,
 640      * or the thread interrupted, is highly input stream and output stream
 641      * specific, and therefore not specified.
 642      * <p>
 643      * If an I/O error occurs reading from the input stream or writing to the
 644      * output stream, then it may do so after some bytes have been read or
 645      * written. Consequently the input stream may not be at end-of-stream and
 646      * one, or both, streams may be in an inconsistent state. It is strongly
 647      * recommended that both streams be promptly closed if an I/O error occurs.
 648      *
 649      * @param  in the input stream, non-null
 650      * @param  out the output stream, non-null
 651      * @throws IOException if an I/O error occurs when reading or writing
 652      * @throws NullPointerException if {@code in} or {@code out} is {@code null}
 653      */
 654     public static void dump(InputStream in, PrintStream out)
 655             throws IOException {
 656         dumpAsStream(in, DEFAULT_CHUNK_SIZE, null)
 657             .forEachOrdered(getPrintStream(out)::println);
 658     }
 659 
 660     // Returns a hexadecimal string formatted according to the specified number
 661     // of columns and with/without space separators between pairs of hexadecimal
 662     // digits. Newlines are added when the chunkSize is exceeded. If the final
 663     // line is less than chunkSize then it is padded with spaces.
 664     private static String toChunkedString(byte[] bytes, int fromIndex,
 665         int toIndex, int chunkSize, int columns, boolean useSeparators) {
 666 
 667         int range = toIndex - fromIndex;
 668         if (range == 0) {
 669             return "";
 670         }
 671         int columnWidth = chunkSize / columns;
 672         int lineLength = useSeparators
 673             ? chunkSize * 3 + (columns - 1) - 1
 674             : chunkSize * 2 + (columns - 1);
 675 
 676         StringBuilder hexString =
 677             new StringBuilder(lineLength + lineLength * (range / chunkSize));
 678         int position = 1;
 679         int newlineCount = 0;
 680         for (int i = fromIndex; i < toIndex; i++, position++) {
 681             // add the pair of hex. digits
 682             hexString.append(HEX_DIGITS[(bytes[i] >> 4) & 0xF]);
 683             hexString.append(HEX_DIGITS[(bytes[i] & 0xF)]);
 684             // add a space between pairs of hex. digits
 685             if (useSeparators && position != chunkSize) {
 686                 hexString.append(' ');
 687             }
 688             // add a space between columns
 689             if (position % columnWidth == 0 && position != chunkSize) {
 690                 hexString.append(' ');
 691             }
 692             // handle end-of-line
 693             if (position == chunkSize && (i + 1 < toIndex)) {
 694                 hexString.append(NEWLINE);
 695                 newlineCount++;
 696                 position = 0;
 697             }
 698         }
 699         // add final line padding, if needed
 700         if (position <= chunkSize) {
 701             int len = hexString.length() - (newlineCount * NEWLINE_LENGTH);
 702             for (int i = len % lineLength; i < lineLength; i++) {
 703                 hexString.append(' ');
 704             }
 705         }
 706 
 707         return hexString.toString();
 708     }
 709 
 710     private static byte[] hexToBytes(CharSequence hexString, int fromIndex,
 711             int toIndex) {
 712 
 713         int len = toIndex - fromIndex;
 714         if (len % 2 != 0) {
 715             throw new IllegalArgumentException(
 716                 "contains an odd number of digits: " + hexString);
 717         }
 718         // Skip the '0x' prefix, if present
 719         if (len > 2 &&
 720             hexString.charAt(fromIndex) == '0' &&
 721             hexString.charAt(fromIndex + 1) == 'x') {
 722             fromIndex += 2;
 723             len -= 2;
 724         }
 725         byte[] bytes = new byte[len / 2];
 726 
 727         for (int i = 0; i < len; i += 2) {
 728             int hexIndex = fromIndex + i;
 729             int high = Character.digit(hexString.charAt(hexIndex), 16);
 730             int low = Character.digit(hexString.charAt(hexIndex + 1), 16);
 731             if (high == -1 || low == -1) {
 732                 throw new IllegalArgumentException(
 733                    "contains an illegal hexadecimal character: " + hexString);
 734             }
 735 
 736             bytes[i / 2] = (byte) (high * 16 + low);
 737         }
 738         return bytes;
 739     }
 740 
 741     private static int roundUp(int total, int chunkSize) {
 742         return (total + chunkSize - 1) / chunkSize;
 743     }
 744 
 745     private static byte[] readChunk(InputStream inStream, int chunkSize)
 746             throws IOException {
 747         byte[] buffer = new byte[chunkSize];
 748 
 749         int n = inStream.readNBytes(buffer, 0, buffer.length);
 750         if (n == 0) {
 751             return null;
 752         } else if (n < chunkSize) {
 753             return Arrays.copyOf(buffer, n);
 754         } else {
 755             return buffer;
 756         }
 757     }
 758 
 759     private static PrintStream getPrintStream(OutputStream out)
 760             throws IOException {
 761         Objects.requireNonNull(out, "out");
 762         PrintStream ps = null;
 763         if (out instanceof PrintStream) {
 764             ps = (PrintStream) out;
 765         } else {
 766             ps = new PrintStream(out, true); // auto flush
 767         }
 768         return ps;
 769     }
 770 
 771     /**
 772      * Represents a function that formats a byte array as a hexadecimal
 773      * string.
 774      * 
 775      * <p>This is a <a href="package-summary.html">functional interface</a>
 776      * whose functional method is
 777      * {@link #format}.
 778      *
 779      * @see java.util.function.Function
 780      * @since 12
 781      */
 782     @FunctionalInterface
 783     public interface Formatter {
 784         /**
 785          * Returns a formatted hexadecimal string representation of the contents
 786          * of a chunk within a byte array.
 787          *
 788          * @param offsetField is the offset into the byte array
 789          * @param chunk a byte array
 790          * @param fromIndex the index of the first byte (inclusive) of the
 791          *     chunk to be converted
 792          * @param toIndex the index of the last byte (exclusive) of the
 793          *     chunk to be converted
 794          * @return a hexadecimal string representation of a chunk of the byte
 795          *     array
 796          * @throws NullPointerException if {@code chunk} is {@code null}
 797          * @throws IllegalArgumentException if {@code fromIndex > toIndex}
 798          * @throws ArrayIndexOutOfBoundsException
 799          *     if {@code fromIndex < 0} or {@code toIndex > chunk.length}
 800          */
 801         String format(long offsetField, byte[] chunk, int fromIndex, int toIndex);
 802     }
 803 }