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 }