--- /dev/null Mon Jul 24 10:17:39 2017 +++ new/src/java.base/share/classes/java/util/Hex.java Mon Jul 24 10:17:38 2017 @@ -0,0 +1,570 @@ +/* + * Copyright (c) 2017, 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.util.*; +import java.util.stream.*; + +/** + * Converts hexadecimal (base 16) string representations to and from + * binary data. It can also generate the classic Unix hexdump(1) format. + *
+ * Example usage: + *
{@code + * // Display the hexadecimal representation of the local Internet Protocol (IP) address + * System.out.println(Hex.toString(InetAddress.getLocalHost().getAddress())); + * + * // Initialize a 16-byte array from a hexadecimal string + * byte[] bytes = Hex.fromString("a1a2a3a4a5a6a7a8a9aaabacadaeaf"); + * + * // Dump a Java class file to the standard output stream + * Hex.dump(Files.readAllBytes(Paths.get("MyApp.class")), System.out); + * + * // Dump a file to the standard output stream, skipping blocks of content comprising all zeros + * try (InputStream in = new FileInputStream("mydata.bin")) { + * Hex.dumpAsStream(in) + * .filter(s -> + * !s.contains("00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00")) + * .forEachOrdered(System.out::println); + * } + * + * // Write the hexadecimal representation of a file to the standard output stream in 32-byte chunks + * Hex.stream(Files.readAllBytes(Paths.get("mydata.bin")), 32) + * .forEachOrdered(System.out::println); + * }+ * + * @since 10 + */ +public final class Hex { + + private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray(); + private static final int CHUNK_SIZE = 16; + + private Hex() {} + + /** + * Returns a hexadecimal string representation of the contents of a + * binary buffer. + *
+ * The binary value is converted to a string comprising pairs of + * hexadecimal digits using 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 the contents of a + * range within a binary buffer. + *
+ * The binary value is converted to a string comprising pairs of + * hexadecimal digits using 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); + + StringBuilder hexString = new StringBuilder((toIndex - fromIndex) * 2); + for (int i = fromIndex; i < toIndex; i++) { + hexString.append(HEX_DIGITS[(bytes[i] >> 4) & 0xF]); + hexString.append(HEX_DIGITS[(bytes[i] & 0xF)]); + } + return hexString.toString(); + } + + /** + * Returns a stream of hexadecimal string representations of the contents + * of a binary buffer. + *
+ * The binary values are converted to strings comprising pairs of + * hexadecimal digits using only the following ASCII characters: + *
+ * {@code 0123456789abcdef} + *+ * + * @param bytes a binary buffer + * @param chunkSize the number of bytes per hexadecimal string + * (defaults to 16 if omitted) + * @return a stream of hexadecimal strings representating the binary buffer. + * Each string length is twice the {@code chunkSize} + * (but the final string may be shorter). + * @throws NullPointerException if {@code bytes} is {@code null} + */ + public static Stream
+ * The binary values are converted to strings comprising pairs of + * hexadecimal digits using 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.) + * + * TBD + * @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 + * @param chunkSize the number of bytes per hexadecimal string + * (defaults to 16 if omitted) + * @return a stream of hexadecimal strings representating the binary buffer. + * Each string length is twice the {@code chunkSize} + * (but the final string may be shorter). + * @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
+ * The binary value is generated from pairs of hexadecimal digits that use + * only the following ASCII characters: + *
+ * {@code 0123456789abcdef} + *+ * + * @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"); + int len = hexString.length(); + if (len % 2 != 0) { + throw new IllegalArgumentException( + "contains an odd number of digits: " + hexString); + } + byte[] bytes = new byte[len / 2]; + + for (int i = 0; i < len; i += 2) { + int high = hexToBinary(hexString.charAt(i)); + int low = hexToBinary(hexString.charAt(i + 1)); + if (high == -1 || low == -1) { + throw new IllegalArgumentException( + "contains an illegal hexadecimal character: " + hexString); + } + + bytes[i / 2] = (byte) (high * 16 + low); + } + return bytes; + } + + /** + * Returns a binary buffer coresponding to a range within the sequence of + * hexadecimal digits. + *
+ * The binary value is generated from pairs of hexadecimal digits that use + * only the following ASCII characters: + *
+ * {@code 0123456789abcdef} + *+ * + * @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 + * @throws NullPointerException if {@code hexString} is {@code null} + * @throws IllegalArgumentException if {@code fromIndex > toIndex} + * @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"); + return fromString(hexString, 0, hexString.length()); + } + + /** + * Generates a hexadecimal dump of the contents of a binary buffer, + * as a stream of hexadecimal strings. + *
+ * It outputs the same format as {@link #dump(byte[],OutputStream)},
+ * without the line separator characters.
+ * When the input is not a multiple of 16-bytes then the final chunk
+ * is shorter than 16-bytes.
+ *
+ * @param bytes a binary buffer
+ * @return a stream of hexadecimal strings
+ * @throws NullPointerException if {@code bytes} is {@code null}
+ */
+ public static Stream
+ * It outputs the same format as {@link #dump(byte[],OutputStream)},
+ * without the line separator characters.
+ * When the input is not a multiple of 16-bytes then the final chunk
+ * is shorter than 16-bytes.
+ * 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 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
+ * It outputs the same format as {@link #dump(byte[],OutputStream)},
+ * without the line separator characters.
+ * When the input is not a multiple of 16-bytes then the final chunk
+ * is shorter than 16-bytes.
+ *
+ * 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
+ * @throws NullPointerException if {@code in} is {@code null}
+ */
+ public static Stream
+ * This is useful when analyzing binary data.
+ * The general output format is as follows:
+ *
+ * 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
+ * @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 a
+ * binary buffer and writes it to the output stream.
+ * It 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
+ * @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)
+ .forEachOrdered(getPrintStream(out)::println);
+ }
+
+ /**
+ * Generates a hexadecimal dump of the contents of an input stream and
+ * writes it to the output stream.
+ * It 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)
+ .forEachOrdered(getPrintStream(out)::println);
+ }
+
+//VR: TBD: check for (total + chunk - 1) > Integer.MAX_VALUE ??
+ private static int roundUp(int total, int chunk) {
+ return (total + chunk - 1) / chunk;
+ }
+
+ private static String readChunk(InputStream inStream, int counter)
+ throws IOException {
+ byte[] buffer = new byte[CHUNK_SIZE];
+
+ int n = inStream.readNBytes(buffer, 0, buffer.length);
+ if (n == 0) {
+ return null;
+ }
+ return chunk(counter, buffer, 0, n);
+ }
+
+ private static String chunk(int counter, byte[] bytes) {
+ int fromIndex = counter * CHUNK_SIZE;
+ int toIndex = fromIndex + CHUNK_SIZE;
+ if (toIndex > bytes.length) {
+ toIndex = bytes.length;
+ }
+ return chunk(counter, bytes, fromIndex, toIndex);
+ }
+
+ private static String chunk(int counter, byte[] bytes, int fromIndex,
+ int toIndex) {
+ StringBuilder hex = new StringBuilder(CHUNK_SIZE * 3 + 1);
+ StringBuilder ascii = new StringBuilder(CHUNK_SIZE);
+ boolean hasDivider = false;
+
+ for (int j = fromIndex; j < toIndex; j++) {
+ // Hex digits
+ hex.append(HEX_DIGITS[(bytes[j] >> 4) & 0xF]);
+ hex.append(HEX_DIGITS[(bytes[j] & 0xF)]);
+ if (j == fromIndex + (CHUNK_SIZE / 2 - 1)) {
+ hex.append(" ");
+ hasDivider = true;
+ } else {
+ hex.append(" ");
+ }
+
+ // Printable ASCII
+ if (bytes[j] < ' ' || bytes[j] > '~') {
+ ascii.append('.');
+ } else {
+ ascii.append((char) bytes[j]);
+ }
+ }
+
+ // Pad the final chunk, if shorter
+ int chunkLength = toIndex - fromIndex;
+ if (chunkLength < CHUNK_SIZE) {
+ int padding = CHUNK_SIZE - chunkLength;
+ for (int k = 0; k < padding; k++) {
+ hex.append(" ");
+ ascii.append(' ');
+ }
+ if (!hasDivider) {
+ hex.append(' ');
+ }
+ }
+
+ return String.format("%08x %s |%s|",
+ counter * CHUNK_SIZE, hex.toString(), ascii.toString());
+ }
+
+ 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;
+ }
+}
+ * xxxxxxxx 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff |................|
+ *
+ * where '{@code xxxxxxxx}' is the offset into the buffer in 16-byte chunks,
+ * followed by ASCII coded hexadecimal bytes, followed by the ASCII
+ * representation of the bytes, or {@code '.'} if it is non-printable.
+ * A non-printable character is one outside the ASCII range
+ * {@code ' '} through {@code '~'}
+ * ({@code '\u005Cu0020'} through {@code '\u005Cu007E'}).
+ * Output lines are separated by the platform-specific line separator.
+ * When the input is not a multiple of 16-bytes then the final line is
+ * shorter than normal.
+ *