/* * 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.
*