/* * 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.*; /** * 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 = Hex.fromString("a1a2a3a4a5a6a7a8a9aaabacadaeaf");
 *
 *   // Display the hexadecimal representation of a file's 256-bit hash code
 *   MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
 *   System.out.println(
 *       Hex.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
 *   Hex.dumpAsStream(Files.newInputStream(Paths.get("mydata")), 64,
 *       (offset, chunk, fromIndex, toIndex) ->
 *           String.format("%d %s",
 *               offset / 64 + 1,
 *               Hex.toPrintableString(chunk, fromIndex, toIndex)))
 *       .forEachOrdered(System.out::println);
 *
 *   // Write the standard input stream to the standard output stream in hexdump format
 *   Hex.dump(System.in, System.out);
 * }
* * @since 12 */ public final class Hex { private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray(); private static final int NEWLINE_LENGTH = System.lineSeparator().length(); private static final int DEFAULT_CHUNK_SIZE = 16; /** * Formatter that generates the classic Unix {@code hexdump(1)} format. */ 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, Hex.toFormattedHexString(chunk, fromIndex, toIndex), Hex.toPrintableString(chunk, fromIndex, toIndex)); } }; private Hex() {} /** * 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 binary buffer * @return a hexadecimal string representation of the binary buffer. * The string length is twice the buffer 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 binary buffer * @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 binary buffer. * 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 toFormattedString(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 * line-separator characters are inserted after each 16-byte chunk. * If the final chunk is less than 16 then it 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 binary buffer * @return a formatted hexadecimal string representation of the binary buffer * @throws NullPointerException if {@code bytes} is {@code null} */ public static String toFormattedHexString(byte[] bytes) { Objects.requireNonNull(bytes, "bytes"); return toFormattedHexString(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 * line-separator characters are inserted after each 16-byte chunk. * If the final chunk is less than 16 then it 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 binary buffer * @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 binary buffer * @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 toFormattedHexString(byte[] bytes, int fromIndex, int toIndex) { Objects.requireNonNull(bytes, "bytes"); Arrays.rangeCheck(bytes.length, fromIndex, toIndex); return toFormattedString(bytes, fromIndex, toIndex, DEFAULT_CHUNK_SIZE, 2, true); } /** * Returns the printable ASCII representation of the contents of the * provided byte array. *

* The binary value is converted to a string comprising printable ASCII * characters, or {@code '.'} if the byte maps to a non-printable character. * A non-printable character is one outside of the ASCII range * {@code ' '} through {@code '~'} * ({@code '\u005Cu0020'} through {@code '\u005Cu007E'}). * * @param bytes a binary buffer * @return a printable ASCII representation of the binary buffer * @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 the printable ASCII representation of the contents of a * range within the provided byte array. *

* The binary value is converted to a string comprising printable ASCII * characters, or {@code '.'} if the byte maps to a non-printable character. * A non-printable character is one outside of the ASCII range * {@code ' '} through {@code '~'} * ({@code '\u005Cu0020'} through {@code '\u005Cu007E'}). * * @param bytes a binary buffer * @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 ASCII representation of the binary buffer * @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 ascii = new StringBuilder(toIndex - fromIndex); // Printable ASCII for (int i = fromIndex; i < toIndex; i++) { if (bytes[i] < ' ' || bytes[i] > '~') { ascii.append('.'); } else { ascii.append((char) bytes[i]); } } return ascii.toString(); } /** * Returns a byte array containing the provided sequence of hexadecimal * digits. The sequence may be prefixed with the hexadecimal indicator * {@code "0x"}. *

* 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 * @return a binary buffer * @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 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 * @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 binary buffer * @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[],OutputStream)}, * without the line-separator characters. *

* If the input is not a multiple of 16 bytes then the final chunk will * be shorter than the preceding chunks. *

* On return, the input stream will be at end-of-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 stream of hexadecimal strings */ 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 present. Otherwise, * this method outputs the same format as * {@link #dump(byte[],OutputStream)}, * without the line separator characters. *

* On return, the input stream will be at end-of-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 stream of hexadecimal strings * @throws NullPointerException if {@code in} is {@code null} */ public static Stream dumpAsStream(InputStream in, int chunkSize, Formatter formatter) { Objects.requireNonNull(in, "in"); 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[],OutputStream)}, * without the line separator characters. *

* If the input is not a multiple of 16 bytes then the final chunk will * be shorter than the preceding chunks. * * @param bytes a binary buffer, assumed to be unmodified during use * @return a 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 present. * Otherwise, this method outputs the same format as * {@link #dump(byte[],OutputStream)}, * without the line separator 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. *

* If an error occurs in the {@code formatter} then an unchecked exception * will be thrown from the underlying {@code Stream} method. * * @param bytes a binary buffer, 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 stream of hexadecimal strings * @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 Stream dumpAsStream(byte[] bytes, int fromIndex, int toIndex, int chunkSize, Formatter formatter) { Objects.requireNonNull(bytes, "bytes"); Arrays.rangeCheck(bytes.length, fromIndex, toIndex); 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 a range within the provided * ByteBuffer, as a stream of formatted hexadecimal strings. Each string is * formatted according to the {@code formatter} function, if present. * Otherwise, this method outputs the same format as * {@link #dump(byte[],OutputStream)}, * without the line separator 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. *

* If an error occurs in the {@code formatter} then an unchecked exception * will be thrown from the underlying {@code Stream} method. * * @param buffer a binary buffer, 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 stream of hexadecimal strings * @throws NullPointerException if {@code buffer} is {@code null} * @throws IllegalArgumentException if {@code fromIndex > toIndex} * @throws ArrayIndexOutOfBoundsException * if {@code fromIndex < 0} or {@code toIndex > buffer.remaining()} */ public static Stream dumpAsStream(ByteBuffer buffer, int fromIndex, int toIndex, int chunkSize, Formatter formatter) { Objects.requireNonNull(buffer, "buffer"); byte[] bytes = new byte[Math.max(0, buffer.limit() - buffer.position())]; try { buffer.get(bytes); } catch (BufferUnderflowException e) { // Safe to ignore } return dumpAsStream(bytes, fromIndex, toIndex, 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
     *     Hex.dumpAsStream(bytes, 16,
     *         (offset, chunk, from, to) ->
     *             String.format("%08x  %s  |%s|",
     *                 offset,
     *                 Hex.toFormattedHexString(chunk, from, to),
     *                 Hex.toPrintableString(chunk, from, to)))
     *         .forEachOrdered(PrintStream::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 binary buffer, 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, OutputStream 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[],OutputStream)}. *

* 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 binary buffer, 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, OutputStream 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[],OutputStream)}. *

* 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, OutputStream 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 toFormattedString(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('\n'); 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 = hexToBinary(hexString.charAt(hexIndex)); int low = hexToBinary(hexString.charAt(hexIndex + 1)); if (high == -1 || low == -1) { throw new IllegalArgumentException( "contains an illegal hexadecimal character: " + hexString); } bytes[i / 2] = (byte) (high * 16 + low); } return bytes; } //VR: TBD: check for (total + chunkSize - 1) > Integer.MAX_VALUE ?? 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; } private static int hexToBinary(char ch) { if ('0' <= ch && ch <= '9') { return ch - '0'; } if ('A' <= ch && ch <= 'F') { return ch - 'A' + 10; } if ('a' <= ch && ch <= 'f') { return ch - 'a' + 10; } return -1; } /** * Represents a function that formats a binary buffer as a hexadecimal * string. * *

This is a functional interface * whose functional method is * {@link #format}. * * @see Function * @since 12 */ @FunctionalInterface public interface Formatter { /** * Returns a formatted hexadecimal string representation of the contents * of a chunk within a binary buffer. * * @param offset is the offset into the byte buffer * @param chunk a binary buffer * @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 binary * buffer * @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 offset, byte[] chunk, int fromIndex, int toIndex); } }