001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.commons.utils; 018 019import static org.apache.juneau.commons.utils.Exceptions.*; 020import static org.apache.juneau.commons.utils.ObjectUtils.*; 021import static org.apache.juneau.commons.utils.Shorts.f; 022import static org.apache.juneau.commons.utils.Shorts.eq; 023import static org.apache.juneau.commons.utils.Shorts.ioex; 024import static org.apache.juneau.commons.utils.SystemUtils.*; 025import static org.apache.juneau.commons.utils.ThrowableUtils.*; 026 027import java.io.*; 028import java.nio.charset.*; 029import java.nio.file.*; 030import java.util.*; 031import java.util.concurrent.atomic.*; 032import java.util.function.*; 033import java.util.zip.*; 034 035import org.apache.juneau.commons.io.*; 036import org.apache.juneau.commons.logging.*; 037 038/** 039 * Various I/O related utility methods. 040 */ 041@SuppressWarnings({ 042 "java:S5164", // Cleanup method provided: cleanupThreadLocals() 043 "null", // Eclipse null analysis does not recognise isNotNull()/isNotEmpty() guards on the optional buffer caches and the saved IOException. 044 "resource" // Wrapper readers/writers (InputStreamReader, OutputStreamWriter, BufferedReader) are built over caller-owned streams and must not close them. 045}) 046public class IoUtils { 047 048 /** UTF-8 charset */ 049 public static final Charset UTF8 = StandardCharsets.UTF_8; 050 051 /** Reusable empty input stream. */ 052 public static final InputStream EMPTY_INPUT_STREAM = new InputStream() { 053 @Override 054 public int read() { 055 return -1; // end of stream 056 } 057 }; 058 059 private static final int BUFF_SIZE = 1024; 060 061 private static final ThreadLocal<byte[]> BYTE_BUFFER_CACHE = (isTrue(env("juneau.disableIoBufferReuse", false)) ? null : new ThreadLocal<>()); 062 private static final ThreadLocal<char[]> CHAR_BUFFER_CACHE = (isTrue(env("juneau.disableIoBufferReuse", false)) ? null : new ThreadLocal<>()); 063 static final AtomicInteger BYTE_BUFFER_CACHE_HITS = new AtomicInteger(); 064 065 static final AtomicInteger BYTE_BUFFER_CACHE_MISSES = new AtomicInteger(); 066 static final AtomicInteger CHAR_BUFFER_CACHE_HITS = new AtomicInteger(); 067 static final AtomicInteger CHAR_BUFFER_CACHE_MISSES = new AtomicInteger(); 068 static { 069 shutdownMessage(() -> "Byte buffer cache: hits=" + BYTE_BUFFER_CACHE_HITS.get() + ", misses=" + BYTE_BUFFER_CACHE_MISSES); 070 shutdownMessage(() -> "Char buffer cache: hits=" + CHAR_BUFFER_CACHE_HITS.get() + ", misses=" + CHAR_BUFFER_CACHE_MISSES); 071 } 072 073 /** 074 * Cleans up thread-local buffer caches for the current thread. 075 * 076 * <p> 077 * This method should be called when a thread is finished using the buffer caches to prevent memory leaks 078 * in thread pool environments where threads are reused. 079 */ 080 public static void cleanupThreadLocals() { 081 if (BYTE_BUFFER_CACHE != null) { 082 BYTE_BUFFER_CACHE.remove(); 083 } 084 if (CHAR_BUFFER_CACHE != null) { 085 CHAR_BUFFER_CACHE.remove(); 086 } 087 } 088 089 /** Reusable empty reader. */ 090 public static final Reader EMPTY_READER = new Reader() { 091 @Override 092 public void close() throws IOException { /* no-op */ } 093 094 @Override 095 public int read() { 096 return -1; // end of stream 097 } 098 099 @Override 100 public int read(char[] cbuf, int off, int len) throws IOException { 101 return -1; // end of stream 102 } 103 }; 104 105 /** 106 * Close all specified input streams, output streams, readers, and writers. 107 * 108 * @param o 109 * The list of all objects to close. 110 * <jk>null</jk> entries are ignored. 111 * @throws IOException Thrown by underlying stream. 112 */ 113 public static void close(Object...o) throws IOException { 114 IOException ex = null; 115 for (var o2 : o) { 116 try { 117 if (o2 instanceof InputStream o3) 118 o3.close(); 119 if (o2 instanceof OutputStream o3) 120 o3.close(); 121 if (o2 instanceof Reader o3) 122 o3.close(); 123 if (o2 instanceof Writer o3) 124 o3.close(); 125 } catch (IOException e) { 126 ex = e; 127 } 128 } 129 if (isNotNull(ex)) 130 throw ex; 131 } 132 133 /** 134 * Reads and discards the remainder of a reader or input stream, ignoring any exceptions. 135 * 136 * <p> 137 * Reads until end-of-stream using a 256-unit buffer. Any {@link IOException} stops the drain silently. 138 * No-op if the object is <jk>null</jk> or is neither a {@link Reader} nor an {@link InputStream}. 139 * The stream is not closed. 140 * 141 * @param in The reader or input stream to drain. 142 */ 143 public static void drainQuietly(Closeable in) { 144 try { 145 if (in instanceof Reader r) { 146 var buf = new char[256]; 147 while (r.read(buf) != -1) { /* discard */ } 148 } else if (in instanceof InputStream is) { 149 var buf = new byte[256]; 150 while (is.read(buf) != -1) { /* discard */ } 151 } 152 } catch (IOException ignored) { 153 // best-effort 154 } 155 } 156 157 /** 158 * Close input stream and ignore any exceptions. 159 * 160 * <p> 161 * No-op if input stream is <jk>null</jk>. 162 * 163 * @param is The input stream to close. 164 */ 165 public static void closeQuietly(InputStream is) { 166 if (isNotNull(is)) 167 safe(is::close); 168 } 169 170 /** 171 * Quietly close all specified input streams, output streams, readers, and writers. 172 * 173 * @param o The list of all objects to quietly close. 174 */ 175 public static void closeQuietly(Object...o) { 176 for (var o2 : o) { 177 if (o2 instanceof InputStream o3) 178 closeQuietly(o3); 179 if (o2 instanceof OutputStream o3) 180 closeQuietly(o3); 181 if (o2 instanceof Reader o3) 182 closeQuietly(o3); 183 if (o2 instanceof Writer o3) 184 closeQuietly(o3); 185 } 186 } 187 188 /** 189 * Close output stream and ignore any exceptions. 190 * 191 * <p> 192 * No-op if output stream is <jk>null</jk>. 193 * 194 * @param os The output stream to close. 195 */ 196 public static void closeQuietly(OutputStream os) { 197 if (isNotNull(os)) 198 runQuietly(os::close); 199 } 200 201 /** 202 * Close reader and ignore any exceptions. 203 * 204 * <p> 205 * No-op if reader is <jk>null</jk>. 206 * 207 * @param r The reader to close. 208 */ 209 public static void closeQuietly(Reader r) { 210 if (isNotNull(r)) 211 runQuietly(r::close); 212 } 213 214 /** 215 * Close writer and ignore any exceptions. 216 * 217 * <p> 218 * No-op if writer is <jk>null</jk>. 219 * 220 * @param w The writer to close. 221 */ 222 public static void closeQuietly(Writer w) { 223 if (isNotNull(w)) 224 runQuietly(w::close); 225 } 226 227 /** 228 * Counts the number of bytes in the input stream and then closes the stream. 229 * 230 * @param is The input stream to read from. Can be <jk>null</jk> (returns <c>0</c>). 231 * @return The number of bytes read. 232 * @throws IOException Thrown by underlying stream. 233 */ 234 public static long count(InputStream is) throws IOException { 235 if (is == null) 236 return 0; 237 var c = 0l; 238 var i = 0l; 239 try { 240 while ((i = is.skip(1024)) != 0) 241 c += i; 242 } finally { 243 is.close(); 244 } 245 return c; 246 } 247 248 /** 249 * Counts the number of characters in the reader and then closes the reader. 250 * 251 * @param r The reader to read from. Can be <jk>null</jk> (returns <c>0</c>). 252 * @return The number of characters read. 253 * @throws IOException Thrown by underlying stream. 254 */ 255 public static long count(Reader r) throws IOException { 256 if (r == null) 257 return 0; 258 var c = 0l; 259 var i = 0l; 260 try { 261 while ((i = r.skip(1024)) != 0) 262 c += i; 263 } finally { 264 r.close(); 265 } 266 return c; 267 } 268 269 /** 270 * Flushes multiple output streams and writers in a single call. 271 * 272 * @param o 273 * The objects to flush. 274 * <jk>null</jk> entries are ignored. 275 * @throws IOException Thrown by underlying stream. 276 */ 277 public static void flush(Object...o) throws IOException { 278 IOException ex = null; 279 for (var o2 : o) { 280 try { 281 if (o2 instanceof OutputStream o3) 282 o3.flush(); 283 if (o2 instanceof Writer o3) 284 o3.flush(); 285 } catch (IOException e) { 286 ex = e; 287 } 288 } 289 if (isNotNull(ex)) 290 throw ex; 291 } 292 293 /** 294 * Loads a text file from either the file system or classpath. 295 * 296 * @param name The file name. Must not be <jk>null</jk>. 297 * @param paths The paths to search. 298 * @return The file contents, or <jk>null</jk> if not found. 299 * @throws IOException Thrown by underlying stream. 300 */ 301 public static String loadSystemResourceAsString(String name, String...paths) throws IOException { 302 for (var path : paths) { 303 var p = new File(path); 304 if (p.exists()) { 305 var f = new File(p, name); 306 if (f.exists() && f.canRead()) 307 return read(f); 308 } 309 } 310 var cl = Thread.currentThread().getContextClassLoader(); 311 if (cl == null) 312 cl = ClassLoader.getSystemClassLoader(); 313 for (var path : paths) { 314 var n = eq(path, ".") ? name : path + '/' + name; 315 try (var is = cl.getResourceAsStream(n)) { 316 if (isNotNull(is)) 317 return read(is); 318 } 319 try (var is = ClassLoader.getSystemResourceAsStream(n)) { 320 if (isNotNull(is)) 321 return read(is); 322 } 323 } 324 return null; 325 } 326 327 /** 328 * Pipes the contents of the specified <c>Reader</c> to the specified <c>Writer</c> a line at a time. 329 * 330 * <p> 331 * Writer is flushed after every line. Typically useful when writing to consoles. 332 * 333 * @param in 334 * The reader to pipe from. 335 * <br>Can be <jk>null</jk> (returns <c>0</c>). 336 * <br>Reader is automatically closed. 337 * @param out 338 * The file to write the output to. 339 * <br>Can be <jk>null</jk> (returns <c>0</c>). 340 * <br>Writer is flushed but not automatically closed. 341 * @return 342 * The number of characters piped. 343 * @throws IOException Thrown by underlying stream. 344 */ 345 public static long pipeLines(Reader in, Writer out) throws IOException { 346 if (in == null || out == null) 347 return 0; 348 var total = 0l; 349 try (var in2 = in) { 350 try (var s = new Scanner(in2)) { 351 while (s.hasNextLine()) { 352 var l = s.nextLine(); 353 if (isNotNull(l)) { 354 out.write(l); 355 out.write("\n"); 356 out.flush(); 357 total += l.length() + 1; 358 } 359 } 360 } 361 } 362 return total; 363 } 364 365 /** 366 * Reads the specified byte array containing UTF-8 into a string. 367 * 368 * @param in 369 * The input. 370 * <br>Can be <jk>null</jk>. 371 * @return The new string, or <jk>null</jk> if the input was null. 372 */ 373 public static String read(byte[] in) { 374 return read(in, UTF8); 375 } 376 377 /** 378 * Reads the specified byte array into a string. 379 * 380 * @param in 381 * The input. 382 * <br>Can be <jk>null</jk>. 383 * @param charset The character set to use for decoding. Must not be <jk>null</jk> when the input is non-<jk>null</jk>. 384 * @return The new string, or <jk>null</jk> if the input was null. 385 */ 386 public static String read(byte[] in, Charset charset) { 387 if (in == null) 388 return null; 389 return new String(in, charset); 390 } 391 392 /** 393 * Reads the contents of a file into a string. 394 * 395 * <p> 396 * Assumes default character encoding. 397 * 398 * @param in 399 * The file to read. 400 * <br>Can be <jk>null</jk>. 401 * @return 402 * The contents of the reader as a string, or <jk>null</jk> if file does not exist. 403 * @throws IOException If a problem occurred trying to read from the reader. 404 */ 405 public static String read(File in) throws IOException { 406 if (in == null || ! in.exists()) 407 return null; 408 try (var r = FileReaderBuilder.create(in).build()) { 409 return read(r, in.length()); 410 } 411 } 412 413 /** 414 * Reads the contents of an input stream into a string. 415 * 416 * <p> 417 * Assumes UTF-8 encoding. 418 * 419 * @param in 420 * The input stream. 421 * <br>Can be <jk>null</jk>. 422 * <br>Stream is automatically closed. 423 * @return 424 * The contents of the reader as a string, or <jk>null</jk> if the input stream was <jk>null</jk>. 425 * @throws IOException If a problem occurred trying to read from the input stream. 426 */ 427 public static String read(InputStream in) throws IOException { 428 return read(in, UTF8); 429 } 430 431 /** 432 * Reads the contents of an input stream into a string using the specified charset. 433 * 434 * @param in 435 * The input stream. 436 * <br>Can be <jk>null</jk>. 437 * <br>Stream is automatically closed. 438 * @param cs 439 * The charset of the contents of the input stream. Must not be <jk>null</jk> when the input stream is non-<jk>null</jk>. 440 * @return 441 * The contents of the reader as a string or <jk>null</jk> if input stream was <jk>null</jk>. 442 * @throws IOException If a problem occurred trying to read from the input stream. 443 */ 444 public static String read(InputStream in, Charset cs) throws IOException { 445 if (in == null) 446 return null; 447 try (var isr = new InputStreamReader(in, cs)) { 448 return read(isr); 449 } 450 } 451 452 /** 453 * Reads the contents of an input stream into a string using the specified charset. 454 * 455 * @param in 456 * The input stream. 457 * <br>Can be <jk>null</jk>. 458 * <br>Stream is automatically closed. 459 * @param cs 460 * The charset of the contents of the input stream. Must not be <jk>null</jk> when the input stream is non-<jk>null</jk>. 461 * @param onException Consumer of any {@link IOException I/O exceptions}. Must not be <jk>null</jk> (invoked on I/O error). 462 * @return 463 * The contents of the reader as a string or <jk>null</jk> if input stream was <jk>null</jk>. 464 */ 465 public static String read(InputStream in, Charset cs, Consumer<IOException> onException) { 466 if (in == null) 467 return null; 468 try (var isr = new InputStreamReader(in, cs)) { 469 return read(isr); 470 } catch (IOException e) { 471 onException.accept(e); 472 return null; 473 } 474 } 475 476 /** 477 * Reads the contents of an input stream into a string. 478 * 479 * <p> 480 * Assumes UTF-8 encoding. 481 * 482 * @param in 483 * The input stream. 484 * <br>Can be <jk>null</jk>. 485 * <br>Stream is automatically closed. 486 * @param onException Consumer of any {@link IOException I/O exceptions}. Must not be <jk>null</jk> (invoked on I/O error). 487 * @return 488 * The contents of the reader as a string, or <jk>null</jk> if the input stream was <jk>null</jk>. 489 */ 490 public static String read(InputStream in, Consumer<IOException> onException) { 491 return read(in, UTF8, onException); 492 } 493 494 /** 495 * Reads the contents of an input stream into a string, reading up to the specified maximum number of bytes. 496 * 497 * <p> 498 * Assumes UTF-8 encoding. 499 * 500 * @param in 501 * The input stream. 502 * <br>Can be <jk>null</jk>. 503 * <br>Stream is automatically closed. 504 * @param maxBytes 505 * The maximum number of bytes to read, or <c>-1</c> to read all bytes. 506 * @return 507 * The contents of the input stream as a string, or <jk>null</jk> if the input stream was <jk>null</jk>. 508 * @throws IOException If a problem occurred trying to read from the input stream. 509 */ 510 public static String read(InputStream in, int maxBytes) throws IOException { 511 return read(in, maxBytes, UTF8); 512 } 513 514 /** 515 * Reads the contents of an input stream into a string using the specified charset, reading up to the specified maximum number of bytes. 516 * 517 * @param in 518 * The input stream. 519 * <br>Can be <jk>null</jk>. 520 * <br>Stream is automatically closed. 521 * @param maxBytes 522 * The maximum number of bytes to read, or <c>-1</c> to read all bytes. 523 * @param cs 524 * The charset of the contents of the input stream. Must not be <jk>null</jk> when the input stream is non-<jk>null</jk>. 525 * @return 526 * The contents of the input stream as a string, or <jk>null</jk> if the input stream was <jk>null</jk>. 527 * @throws IOException If a problem occurred trying to read from the input stream. 528 */ 529 public static String read(InputStream in, int maxBytes, Charset cs) throws IOException { 530 if (in == null) 531 return null; 532 try (var in2 = in) { 533 var bytes = readBytes(in2, maxBytes); 534 return new String(bytes, cs); 535 } 536 } 537 538 /** 539 * Reads the contents of an input stream into a string, reading up to the specified maximum number of bytes. 540 * 541 * <p> 542 * Assumes UTF-8 encoding. 543 * 544 * @param in 545 * The input stream. 546 * <br>Can be <jk>null</jk>. 547 * <br>Stream is automatically closed. 548 * @param maxBytes 549 * The maximum number of bytes to read, or <c>-1</c> to read all bytes. 550 * @return 551 * The contents of the input stream as a string, or <jk>null</jk> if the input stream was <jk>null</jk>. 552 * @throws IOException If a problem occurred trying to read from the input stream. 553 */ 554 public static String read(InputStream in, long maxBytes) throws IOException { 555 if (maxBytes > Integer.MAX_VALUE) 556 maxBytes = Integer.MAX_VALUE; 557 return read(in, (int)maxBytes, UTF8); 558 } 559 560 /** 561 * Pipes the specified object to the specified output stream. 562 * 563 * @param in 564 * The input byte array. 565 * <br>Can be <jk>null</jk> or any of the following types: 566 * <ul> 567 * <li>{@link Reader} 568 * <li>{@link InputStream} 569 * <li>{@link File} 570 * <li>byte array. 571 * </ul> 572 * @return The input converted to a string, or <jk>null</jk> if the input was <jk>null</jk>. 573 * @throws IOException If thrown from output stream. 574 * @throws IllegalArgumentException If the input is non-<jk>null</jk> but not one of the supported types. 575 */ 576 public static String read(Object in) throws IOException { 577 if (in == null) 578 return null; 579 if (in instanceof Reader in2) 580 return read(in2); 581 if (in instanceof InputStream in2) 582 return read(in2); 583 if (in instanceof File in2) 584 return read(in2); 585 if (in instanceof byte[] in2) 586 return read(in2); 587 throw iaex("Invalid type passed to read: %s", ClassUtils.className(in)); 588 } 589 590 /** 591 * Reads the contents of a path into a string. 592 * 593 * <p> 594 * Assumes default character encoding. 595 * 596 * @param in 597 * The path to read. 598 * <br>Can be <jk>null</jk>. 599 * @return 600 * The contents of the reader as a string, or <jk>null</jk> if path does not exist. 601 * @throws IOException If a problem occurred trying to read from the reader. 602 * @since 9.1.0 603 */ 604 public static String read(Path in) throws IOException { 605 if (in == null || ! Files.exists(in)) { 606 return null; 607 } 608 try (var r = PathReaderBuilder.create(in).build()) { 609 return read(r, Files.size(in)); 610 } 611 } 612 613 /** 614 * Reads the contents of a reader into a string. 615 * 616 * @param in 617 * The input reader. 618 * <br>Can be <jk>null</jk>. 619 * <br>Stream is automatically closed. 620 * @return 621 * The contents of the reader as a string, or <jk>null</jk> if the reader was <jk>null</jk>. 622 * @throws IOException If a problem occurred trying to read from the reader. 623 */ 624 public static String read(Reader in) throws IOException { 625 try (var in2 = in) { 626 return read(in, -1); 627 } 628 } 629 630 /** 631 * Reads the contents of a reader into a string. 632 * 633 * @param in 634 * The input reader. 635 * <br>Can be <jk>null</jk>. 636 * <br>Stream is automatically closed. 637 * @param onException Consumer of any {@link IOException I/O exceptions}. Must not be <jk>null</jk> (invoked on I/O error). 638 * @return 639 * The contents of the reader as a string, or <jk>null</jk> if the reader was <jk>null</jk>. 640 */ 641 public static String read(Reader in, Consumer<IOException> onException) { 642 try (var in2 = in) { 643 return read(in, -1); 644 } catch (IOException e) { 645 onException.accept(e); 646 return null; 647 } 648 } 649 650 /** 651 * Reads the specified input into a {@link String} until the end of the input is reached. 652 * 653 * @param in 654 * The input reader. 655 * <br>Can be <jk>null</jk>. 656 * <br>String is automatically closed. 657 * @param expectedLength 658 * Specify a positive number if the length of the input is known, or <c>-1</c> if unknown. 659 * @return 660 * The contents of the reader as a string, or <jk>null</jk> if the reader was <jk>null</jk>. 661 * @throws IOException If a problem occurred trying to read from the reader. 662 */ 663 public static String read(Reader in, long expectedLength) throws IOException { 664 if (in == null) 665 return null; 666 try (var in2 = in) { 667 var sb = new StringBuilder(buffSize(expectedLength)); // Assume they're ASCII characters. 668 var buf = charBuffer((int)expectedLength); 669 var i = 0; 670 while ((i = in2.read(buf)) != -1) 671 sb.append(buf, 0, i); 672 return sb.toString(); 673 } 674 } 675 676 /** 677 * Read the specified file into a byte array. 678 * 679 * @param in 680 * The file to read into a byte array. 681 * <br>Can be <jk>null</jk> (returns an empty array). 682 * @return The contents of the file as a byte array. Never <jk>null</jk>. 683 * @throws IOException Thrown by underlying stream. 684 */ 685 public static byte[] readBytes(File in) throws IOException { 686 return readBytes(in, -1); 687 } 688 689 /** 690 * Read the specified file into a byte array. 691 * 692 * @param in 693 * The file to read into a byte array. 694 * <br>Can be <jk>null</jk> (returns an empty array). 695 * @param maxBytes 696 * The maximum number of bytes to read, or <jk>-1</jk> to read all bytes. 697 * @return The contents of the file as a byte array. Never <jk>null</jk>. 698 * @throws IOException Thrown by underlying stream. 699 */ 700 public static byte[] readBytes(File in, int maxBytes) throws IOException { 701 if (in == null || ! (in.exists() && in.canRead())) 702 return new byte[0]; 703 try (var is = new FileInputStream(in)) { 704 return readBytes(is, maxBytes); 705 } 706 } 707 708 /** 709 * Reads the specified input stream into the specified byte array. 710 * 711 * @param in 712 * The input stream to read. 713 * <br>Can be <jk>null</jk> (returns an empty array). 714 * <br>Stream is automatically closed. 715 * @return A byte array containing the contents. Never <jk>null</jk>. 716 * @throws IOException Thrown by underlying stream. 717 */ 718 public static byte[] readBytes(InputStream in) throws IOException { 719 try (var in2 = in) { 720 return readBytes(in2, -1); 721 } 722 } 723 724 /** 725 * Reads the specified input stream into the specified byte array. 726 * 727 * @param in 728 * The input stream to read. 729 * <br>Can be <jk>null</jk> (returns an empty array). 730 * <br>Stream is not automatically closed. 731 * @param maxBytes 732 * The maximum number of bytes or <c>-1</c> to read the entire stream. 733 * @return A byte array containing the contents. Never <jk>null</jk>. 734 * @throws IOException Thrown by underlying stream. 735 */ 736 public static byte[] readBytes(InputStream in, int maxBytes) throws IOException { 737 if (in == null) 738 return new byte[0]; 739 var buff = new ByteArrayOutputStream(buffSize(maxBytes)); 740 var nRead = 0; 741 var b = byteBuffer(maxBytes); 742 while ((nRead = in.read(b, 0, b.length)) != -1) 743 buff.write(b, 0, nRead); 744 buff.flush(); 745 return buff.toByteArray(); 746 } 747 748 /** 749 * Reads the specified input stream into the specified byte array. 750 * 751 * @param in 752 * The input stream to read. 753 * <br>Can be <jk>null</jk> (returns an empty array). 754 * <br>Stream is automatically closed. 755 * @return A byte array containing the contents. Never <jk>null</jk>. 756 * @throws IOException Thrown by underlying stream. 757 */ 758 public static byte[] readBytes(Reader in) throws IOException { 759 if (in == null) 760 return new byte[0]; 761 try (var in2 = in) { 762 return read(in2, -1).getBytes(); 763 } 764 } 765 766 /** 767 * Inflates (decompresses) the specified compressed bytes with a hard ceiling on the total output size and an 768 * effective output-to-input ratio guard. 769 * 770 * <p> 771 * Building on the bounded-copy family in this class (such as {@link #readBytes(InputStream,int)} and 772 * {@link #pipe(InputStream,OutputStream,long)}), this method decompresses untrusted input without letting a 773 * small compressed payload expand into an unbounded allocation. Two independent limits are enforced 774 * <i>during</i> inflation, so a violating stream is aborted before its full output is ever materialized: 775 * <ul> 776 * <li><b>Absolute cap.</b> If the running output size would exceed <jv>maxOutputBytes</jv>, an 777 * {@link IOException} is thrown. 778 * <li><b>Ratio guard.</b> If the running output size would exceed <jv>maxRatio</jv> times the compressed 779 * input length, an {@link IOException} is thrown — this catches a highly-compressible payload even 780 * when the absolute cap is generous. 781 * </ul> 782 * 783 * @param compressed 784 * The compressed input bytes. 785 * <br>Can be <jk>null</jk> (returns an empty array). 786 * @param nowrap 787 * Pass <jk>true</jk> for a raw (headerless) DEFLATE stream, or <jk>false</jk> for a zlib-wrapped stream. 788 * <br>Must match the format used to compress the input. 789 * @param maxOutputBytes 790 * The maximum number of decompressed bytes to allow. 791 * <br>Values <c><= 0</c> disable the absolute cap (mirroring the <c>maxLength</c> convention used 792 * elsewhere); the ratio guard, if enabled, still applies. 793 * @param maxRatio 794 * The maximum allowed ratio of output size to compressed input size. 795 * <br>Values <c><= 0</c> disable the ratio guard. 796 * @return The decompressed bytes. Never <jk>null</jk>. 797 * @throws IOException 798 * If the input is not valid for the selected format, or if either the absolute cap or the ratio guard is 799 * exceeded. 800 * @since 10.0.0 801 */ 802 public static byte[] inflate(byte[] compressed, boolean nowrap, long maxOutputBytes, int maxRatio) throws IOException { 803 if (compressed == null || compressed.length == 0) 804 return new byte[0]; 805 var cap = maxOutputBytes <= 0 ? Long.MAX_VALUE : maxOutputBytes; 806 var ratioLimit = maxRatio <= 0 ? Long.MAX_VALUE : (long)compressed.length * maxRatio; 807 var inflater = new Inflater(nowrap); 808 // Inflater isn't AutoCloseable at this module's source/target level, so route end() through 809 // try-with-resources via a Closeable method reference (inflate() already declares throws IOException). 810 try (Closeable end = inflater::end) { 811 inflater.setInput(compressed); 812 return inflateAll(inflater, cap, ratioLimit, maxRatio); 813 } 814 } 815 816 private static byte[] inflateAll(Inflater inflater, long cap, long ratioLimit, int maxRatio) throws IOException { 817 var out = new ByteArrayOutputStream(BUFF_SIZE); 818 var buf = new byte[BUFF_SIZE]; 819 var total = 0L; 820 while (true) { 821 var n = inflateChunk(inflater, buf); 822 if (n > 0) { 823 total += n; 824 checkInflateLimits(total, cap, ratioLimit, maxRatio); 825 out.write(buf, 0, n); 826 } else if (inflater.finished() || inflater.needsDictionary() || inflater.needsInput()) { 827 break; 828 } 829 } 830 return out.toByteArray(); 831 } 832 833 private static int inflateChunk(Inflater inflater, byte[] buf) throws IOException { 834 try { 835 return inflater.inflate(buf); 836 } catch (DataFormatException e) { 837 throw ioex(e, "Invalid compressed data"); 838 } 839 } 840 841 private static void checkInflateLimits(long total, long cap, long ratioLimit, int maxRatio) throws IOException { 842 if (total > cap) 843 throw ioex("Inflated output exceeds maximum allowed %s bytes", cap); 844 if (total > ratioLimit) 845 throw ioex("Inflated output-to-input ratio exceeds maximum allowed %s", maxRatio); 846 } 847 848 /** 849 * Wraps the specified reader in a buffered reader. 850 * 851 * @param r The reader being wrapped. Can be <jk>null</jk> (returns <jk>null</jk>). 852 * @return 853 * The reader wrapped in a {@link BufferedReader}, or the original {@link Reader} if it's already a buffered 854 * reader, or <jk>null</jk> if the argument was <jk>null</jk>. 855 */ 856 public static Reader toBufferedReader(Reader r) { 857 if (r == null || r instanceof BufferedReader || r instanceof StringReader) 858 return r; 859 return new BufferedReader(r); 860 } 861 862 private static int buffSize(long max) { 863 return (max > 0 && max < BUFF_SIZE) ? (int)max : BUFF_SIZE; 864 } 865 866 private static byte[] byteBuffer(int maxBytes) { 867 if (isNotNull(BYTE_BUFFER_CACHE)) { 868 var x = BYTE_BUFFER_CACHE.get(); 869 if (x == null) { 870 x = new byte[BUFF_SIZE]; 871 BYTE_BUFFER_CACHE.set(x); 872 BYTE_BUFFER_CACHE_MISSES.incrementAndGet(); 873 } else { 874 BYTE_BUFFER_CACHE_HITS.incrementAndGet(); 875 } 876 return x; 877 } 878 return new byte[buffSize(maxBytes)]; 879 } 880 881 private static char[] charBuffer(int maxChars) { 882 if (isNotNull(CHAR_BUFFER_CACHE)) { 883 var x = CHAR_BUFFER_CACHE.get(); 884 if (x == null) { 885 x = new char[BUFF_SIZE]; 886 CHAR_BUFFER_CACHE.set(x); 887 CHAR_BUFFER_CACHE_MISSES.incrementAndGet(); 888 } else { 889 CHAR_BUFFER_CACHE_HITS.incrementAndGet(); 890 } 891 return x; 892 } 893 return new char[buffSize(maxChars)]; 894 } 895 896 //----------------------------------------------------------------------------------------------------------------- 897 // Canonical-named methods (pipe overloads delegate to these) 898 //----------------------------------------------------------------------------------------------------------------- 899 900 /** Pipes bytes to output stream. <br>Returns <c>0</c> if either <jv>in</jv> or <jv>out</jv> is <jk>null</jk>. */ 901 public static long pipe(byte[] in, OutputStream out, int maxBytes) throws IOException { 902 if (in == null || out == null) 903 return 0; 904 var length = (maxBytes < 0 || maxBytes > in.length) ? in.length : maxBytes; 905 out.write(in, 0, length); 906 return length; 907 } 908 909 /** Pipes input stream to output stream. <br>Returns <c>0</c> if either <jv>in</jv> or <jv>out</jv> is <jk>null</jk>. */ 910 public static long pipe(InputStream in, OutputStream out) throws IOException { 911 try (var in2 = in) { 912 return pipe(in, out, -1L); 913 } 914 } 915 916 /** Pipes input stream to output stream with exception handler. <br>Returns <c>0</c> if either <jv>in</jv> or <jv>out</jv> is <jk>null</jk>. <br><jv>onException</jv> must not be <jk>null</jk> (invoked on I/O error). */ 917 public static long pipe(InputStream in, OutputStream out, Consumer<IOException> onException) { 918 try { 919 try (var in2 = in) { 920 return pipe(in, out, -1L); 921 } 922 } catch (IOException e) { 923 onException.accept(e); 924 return -1; 925 } 926 } 927 928 /** Pipes input stream to output stream with max bytes. <br>Returns <c>0</c> if either <jv>in</jv> or <jv>out</jv> is <jk>null</jk>. */ 929 public static long pipe(InputStream in, OutputStream out, long maxBytes) throws IOException { 930 if (in == null || out == null) 931 return 0; 932 var buffer = byteBuffer((int)maxBytes); 933 int readLen; 934 var total = 0l; 935 if (maxBytes < 0) { 936 while ((readLen = in.read(buffer)) != -1) { 937 out.write(buffer, 0, readLen); 938 total += readLen; 939 } 940 } else { 941 var remaining = maxBytes; 942 while (remaining > 0) { 943 readLen = in.read(buffer, 0, buffSize(remaining)); 944 if (readLen == -1) 945 break; 946 out.write(buffer, 0, readLen); 947 total += readLen; 948 remaining -= readLen; 949 } 950 } 951 out.flush(); 952 return total; 953 } 954 955 /** Pipes input stream to writer. <br>Returns <c>0</c> if either <jv>in</jv> or <jv>out</jv> is <jk>null</jk>. */ 956 public static long pipe(InputStream in, Writer out) throws IOException { 957 if (in == null || out == null) 958 return 0; 959 return pipe(new InputStreamReader(in, UTF8), out); 960 } 961 962 /** Pipes input stream to writer with exception handler. <br>Returns <c>0</c> if either <jv>in</jv> or <jv>out</jv> is <jk>null</jk>. <br><jv>onException</jv> must not be <jk>null</jk> (invoked on I/O error). */ 963 public static long pipe(InputStream in, Writer out, Consumer<IOException> onException) { 964 try { 965 if (in == null || out == null) 966 return 0; 967 return pipe(new InputStreamReader(in, UTF8), out); 968 } catch (IOException e) { 969 onException.accept(e); 970 return -2; 971 } 972 } 973 974 /** Pipes reader to file. <br>Returns <c>0</c> if either <jv>in</jv> or <jv>out</jv> is <jk>null</jk>. */ 975 public static long pipe(Reader in, File out) throws IOException { 976 if (out == null || in == null) 977 return 0; 978 try (var w = FileWriterBuilder.create(out).buffered().build()) { 979 return pipe(in, w); 980 } 981 } 982 983 /** Pipes reader to output stream. <br>Returns <c>0</c> if either <jv>in</jv> or <jv>out</jv> is <jk>null</jk>. */ 984 public static long pipe(Reader in, OutputStream out) throws IOException { 985 if (in == null || out == null) 986 return 0; 987 var total = 0l; 988 try (var in2 = in) { 989 var osw = new OutputStreamWriter(out, UTF8); 990 var i = 0; 991 var b = charBuffer(-1); 992 while ((i = in.read(b)) > 0) { 993 total += i; 994 osw.write(b, 0, i); 995 } 996 osw.flush(); 997 } 998 return total; 999 } 1000 1001 /** Pipes reader to output stream with exception handler. <br>Returns <c>0</c> if either <jv>in</jv> or <jv>out</jv> is <jk>null</jk>. <br><jv>onException</jv> must not be <jk>null</jk> (invoked on I/O error). */ 1002 public static long pipe(Reader in, OutputStream out, Consumer<IOException> onException) { 1003 try { 1004 return pipe(in, out); 1005 } catch (IOException e) { 1006 onException.accept(e); 1007 return -1; 1008 } 1009 } 1010 1011 /** Pipes reader to writer. <br>Returns <c>0</c> if either <jv>in</jv> or <jv>out</jv> is <jk>null</jk>. */ 1012 public static long pipe(Reader in, Writer out) throws IOException { 1013 if (out == null || in == null) 1014 return 0; 1015 var total = 0l; 1016 try (var in2 = in) { 1017 var buffer = charBuffer(-1); 1018 int readLen; 1019 while ((readLen = in.read(buffer)) != -1) { 1020 out.write(buffer, 0, readLen); 1021 total += readLen; 1022 } 1023 } 1024 out.flush(); 1025 return total; 1026 } 1027 1028 /** Pipes reader to writer with exception handler. <br>Returns <c>0</c> if either <jv>in</jv> or <jv>out</jv> is <jk>null</jk>. <br><jv>onException</jv> must not be <jk>null</jk> (invoked on I/O error). */ 1029 public static long pipe(Reader in, Writer out, Consumer<IOException> onException) { 1030 try { 1031 return pipe(in, out); 1032 } catch (IOException e) { 1033 onException.accept(e); 1034 return -1; 1035 } 1036 } 1037 1038 /** 1039 * Prints all the specified lines to the log with line numbers. 1040 * 1041 * @param lines The lines to print. Must not be <jk>null</jk> (individual <jk>null</jk> entries render as <js>"null"</js>). 1042 */ 1043 public static final void printLines(String[] lines) { 1044 RichLogger log = RichLogger.getLogger(IoUtils.class); 1045 for (var i = 0; i < lines.length; i++) 1046 log.info(f("%4s:%s", i + 1, lines[i])); 1047 } 1048 1049 /** 1050 * Constructor. 1051 */ 1052 protected IoUtils() {} 1053}