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.common.utils; 018 019import java.io.*; 020import java.nio.charset.*; 021import java.nio.file.*; 022import java.util.*; 023import java.util.concurrent.atomic.*; 024import java.util.function.*; 025 026/** 027 * Various I/O related utility methods. 028 */ 029public class IOUtils { 030 031 /** 032 * Constructor. 033 */ 034 protected IOUtils() {} 035 036 /** UTF-8 charset */ 037 public static final Charset UTF8 = StandardCharsets.UTF_8; 038 039 /** Reusable empty input stream. */ 040 public static final InputStream EMPTY_INPUT_STREAM = new InputStream() { 041 @Override 042 public int read() { 043 return -1; // end of stream 044 } 045 }; 046 047 private static final int BUFF_SIZE = 1024; 048 private static final ThreadLocal<byte[]> BYTE_BUFFER_CACHE = (Boolean.getBoolean("juneau.disableIoBufferReuse") ? null : new ThreadLocal<>()); // NOSONAR 049 private static final ThreadLocal<char[]> CHAR_BUFFER_CACHE = (Boolean.getBoolean("juneau.disableIoBufferReuse") ? null : new ThreadLocal<>()); // NOSONAR 050 051 static final AtomicInteger BYTE_BUFFER_CACHE_HITS = new AtomicInteger(); 052 static final AtomicInteger BYTE_BUFFER_CACHE_MISSES = new AtomicInteger(); 053 static final AtomicInteger CHAR_BUFFER_CACHE_HITS = new AtomicInteger(); 054 static final AtomicInteger CHAR_BUFFER_CACHE_MISSES = new AtomicInteger(); 055 056 static { 057 SystemUtils.shutdownMessage(()->"Byte buffer cache: hits="+BYTE_BUFFER_CACHE_HITS.get()+", misses=" + BYTE_BUFFER_CACHE_MISSES); 058 SystemUtils.shutdownMessage(()->"Char buffer cache: hits="+CHAR_BUFFER_CACHE_HITS.get()+", misses=" + CHAR_BUFFER_CACHE_MISSES); 059 } 060 061 /** Reusable empty reader. */ 062 public static final Reader EMPTY_READER = new Reader() { 063 @Override 064 public int read() { 065 return -1; // end of stream 066 } 067 @Override 068 public int read(char[] cbuf, int off, int len) throws IOException { 069 return -1; // end of stream 070 } 071 @Override 072 public void close() throws IOException { /* no-op */ } 073 }; 074 075 //----------------------------------------------------------------------------------------------------------------- 076 // Piping utilities. 077 //----------------------------------------------------------------------------------------------------------------- 078 079 /** 080 * Pipes the contents of the specified <c>Reader</c> to the specified file. 081 * 082 * @param in 083 * The reader to pipe from. 084 * <br>Can be <jk>null</jk>. 085 * <br>Reader is automatically closed. 086 * @param out 087 * The file to write the output to. 088 * <br>Can be <jk>null</jk>. 089 * @return 090 * The number of characters piped. 091 * @throws IOException Thrown by underlying stream. 092 */ 093 public static long pipe(Reader in, File out) throws IOException { 094 if (out == null || in == null) 095 return 0; 096 try (var w = FileWriterBuilder.create(out).buffered().build()) { 097 return pipe(in, w); 098 } 099 } 100 101 /** 102 * Pipes the contents of the specified <c>Reader</c> to the specified <c>Writer</c>. 103 * 104 * @param in 105 * The reader to pipe from. 106 * <br>Can be <jk>null</jk>. 107 * <br>Reader is automatically closed. 108 * @param out 109 * The file to write the output to. 110 * <br>Can be <jk>null</jk>. 111 * <br>Writer is flushed but not automatically closed. 112 * @return 113 * The number of characters piped. 114 * @throws IOException Thrown by underlying stream. 115 */ 116 public static long pipe(Reader in, Writer out) throws IOException { 117 if (out == null || in == null) 118 return 0; 119 var total = 0l; 120 try (var in2 = in) { 121 var buffer = charBuffer(-1); 122 int readLen; 123 while ((readLen = in.read(buffer)) != -1) { 124 out.write(buffer, 0, readLen); 125 total += readLen; 126 } 127 } 128 out.flush(); 129 return total; 130 } 131 132 /** 133 * Pipes the contents of the specified <c>Reader</c> to the specified <c>Writer</c>. 134 * 135 * @param in 136 * The reader to pipe from. 137 * <br>Can be <jk>null</jk>. 138 * <br>Reader is automatically closed. 139 * @param out 140 * The file to write the output to. 141 * <br>Can be <jk>null</jk>. 142 * <br>Writer is flushed but not automatically closed. 143 * @param onException Consumer of any {@link IOException I/O exceptions}. 144 * @return 145 * The number of characters piped. 146 */ 147 public static long pipe(Reader in, Writer out, Consumer<IOException> onException) { 148 try { 149 return pipe(in, out); 150 } catch (IOException e) { 151 onException.accept(e); 152 return -1; 153 } 154 } 155 156 /** 157 * Pipes the contents of the specified <c>Reader</c> to the specified <c>Writer</c> a line at a time. 158 * 159 * <p> 160 * Writer is flushed after every line. Typically useful when writing to consoles. 161 * 162 * @param in 163 * The reader to pipe from. 164 * <br>Can be <jk>null</jk>. 165 * <br>Reader is automatically closed. 166 * @param out 167 * The file to write the output to. 168 * <br>Can be <jk>null</jk>. 169 * <br>Writer is flushed but not automatically closed. 170 * @return 171 * The number of characters piped. 172 * @throws IOException Thrown by underlying stream. 173 */ 174 public static long pipeLines(Reader in, Writer out) throws IOException { 175 if (in == null || out == null) 176 return 0; 177 var total = 0l; 178 try (var in2 = in) { 179 try (var s = new Scanner(in2)) { 180 while (s.hasNextLine()) { 181 var l = s.nextLine(); 182 if (l != null) { 183 out.write(l); 184 out.write("\n"); 185 out.flush(); 186 total += l.length() + 1; 187 } 188 } 189 } 190 } 191 return total; 192 } 193 194 /** 195 * Pipes the contents of the specified input stream to the writer. 196 * 197 * @param in 198 * The stream to pipe from. 199 * <br>Can be <jk>null</jk>. 200 * <br>Streams is automatically closed. 201 * @param out 202 * The writer to pipe to. 203 * <br>Can be <jk>null</jk>. 204 * <br>Stream is not automatically closed. 205 * @return 206 * The number of bytes written. 207 * @throws IOException Thrown by underlying stream. 208 */ 209 public static long pipe(InputStream in, Writer out) throws IOException { 210 if (in == null || out == null) 211 return 0; 212 return pipe(new InputStreamReader(in, UTF8), out); 213 } 214 215 /** 216 * Pipes the contents of the specified input stream to the writer. 217 * 218 * @param in 219 * The stream to pipe from. 220 * <br>Can be <jk>null</jk>. 221 * <br>Streams is automatically closed. 222 * @param out 223 * The writer to pipe to. 224 * <br>Can be <jk>null</jk>. 225 * <br>Stream is not automatically closed. 226 * @param onException Consumer of any {@link IOException I/O exceptions}. 227 * @return 228 * The number of bytes written. 229 */ 230 public static long pipe(InputStream in, Writer out, Consumer<IOException> onException) { 231 try { 232 if (in == null || out == null) 233 return 0; 234 return pipe(new InputStreamReader(in, UTF8), out); 235 } catch (IOException e) { 236 onException.accept(e); 237 return -2; 238 } 239 } 240 241 /** 242 * Pipes the specified input stream to the specified output stream. 243 * 244 * <p> 245 * Either stream is not automatically closed. 246 * 247 * @param in 248 * The input stream. 249 * <br>Can be <jk>null</jk>. 250 * <br>Stream is automatically closed. 251 * @param out 252 * The output stream. 253 * <br>Can be <jk>null</jk>. 254 * <br>Stream is not automatically closed. 255 * @return The number of bytes written. 256 * @throws IOException If thrown from either stream. 257 */ 258 public static long pipe(InputStream in, OutputStream out) throws IOException { 259 try (var in2 = in) { 260 return pipe(in, out, -1); 261 } 262 } 263 264 /** 265 * Pipes the specified input stream to the specified output stream. 266 * 267 * <p> 268 * Either stream is not automatically closed. 269 * 270 * @param in 271 * The input stream. 272 * <br>Can be <jk>null</jk>. 273 * <br>Stream is automatically closed. 274 * @param out 275 * The output stream. 276 * <br>Can be <jk>null</jk>. 277 * <br>Stream is not automatically closed. 278 * @param onException Consumer of any {@link IOException I/O exceptions}. 279 * @return The number of bytes written. 280 */ 281 public static long pipe(InputStream in, OutputStream out, Consumer<IOException> onException) { 282 try { 283 try (var in2 = in) { 284 return pipe(in, out, -1); 285 } 286 } catch (IOException e) { 287 onException.accept(e); 288 return -1; 289 } 290 } 291 292 /** 293 * Pipes the specified input stream to the specified output stream. 294 * 295 * <p> 296 * Either stream is not automatically closed. 297 * 298 * @param in 299 * The input stream. 300 * <br>Can be <jk>null</jk>. 301 * <br>Stream is not automatically closed. 302 * @param out 303 * The output stream. 304 * <br>Can be <jk>null</jk>. 305 * <br>Stream is not automatically closed. 306 * @param maxBytes 307 * The maximum number of bytes or <c>-1</c> to read the entire input stream. 308 * @return The number of bytes written. 309 * @throws IOException If thrown from either stream. 310 */ 311 public static long pipe(InputStream in, OutputStream out, long maxBytes) throws IOException { 312 if (in == null || out == null) 313 return 0; 314 var buffer = byteBuffer((int)maxBytes); 315 int readLen; 316 var total = 0l; 317 if (maxBytes < 0) { 318 while ((readLen = in.read(buffer)) != -1) { 319 out.write(buffer, 0, readLen); 320 total += readLen; 321 } 322 } else { 323 var remaining = maxBytes; 324 while (remaining > 0) { 325 readLen = in.read(buffer, 0, buffSize(remaining)); 326 if (readLen == -1) 327 break; 328 out.write(buffer, 0, readLen); 329 total += readLen; 330 remaining -= readLen; 331 } 332 } 333 out.flush(); 334 return total; 335 } 336 337 /** 338 * Pipes the specified reader to the specified output stream. 339 * 340 * @param in 341 * The input reader. 342 * <br>Can be <jk>null</jk>. 343 * <br>Stream is automatically closed. 344 * @param out 345 * The output stream. 346 * <br>Can be <jk>null</jk>. 347 * <br>Stream is not automatically closed. 348 * @return The number of bytes written. 349 * @throws IOException If thrown from output stream. 350 */ 351 public static long pipe(Reader in, OutputStream out) throws IOException { 352 if (in == null || out == null) 353 return 0; 354 var total = 0l; 355 try (var in2 = in) { 356 var osw = new OutputStreamWriter(out, UTF8); 357 var i = 0; 358 var b = charBuffer(-1); 359 while ((i = in.read(b)) > 0) { 360 total += i; 361 osw.write(b, 0, i); 362 } 363 osw.flush(); 364 } 365 return total; 366 } 367 368 /** 369 * Pipes the specified reader to the specified output stream. 370 * 371 * @param in 372 * The input reader. 373 * <br>Can be <jk>null</jk>. 374 * <br>Stream is automatically closed. 375 * @param out 376 * The output stream. 377 * <br>Can be <jk>null</jk>. 378 * <br>Stream is not automatically closed. 379 * @param onException Consumer of any {@link IOException I/O exceptions}. 380 * @return The number of bytes written. 381 */ 382 public static long pipe(Reader in, OutputStream out, Consumer<IOException> onException) { 383 try { 384 return pipe(in, out); 385 } catch (IOException e) { 386 onException.accept(e); 387 return -1; 388 } 389 } 390 391 /** 392 * Pipes the specified byte array to the specified output stream. 393 * 394 * @param in 395 * The input byte array. 396 * <br>Can be <jk>null</jk>. 397 * @param out 398 * The output stream. 399 * <br>Can be <jk>null</jk>. 400 * <br>Stream is not automatically closed. 401 * @param maxBytes 402 * The maximum number of bytes or <c>-1</c> to read the entire byte array. 403 * @return The number of bytes written. 404 * @throws IOException If thrown from output stream. 405 */ 406 public static long pipe(byte[] in, OutputStream out, int maxBytes) throws IOException { 407 if (in == null || out == null) 408 return 0; 409 var length = (maxBytes < 0 || maxBytes > in.length ) ? in.length : maxBytes; 410 out.write(in, 0, length); 411 return length; 412 } 413 414 //----------------------------------------------------------------------------------------------------------------- 415 // Reading utilities. 416 //----------------------------------------------------------------------------------------------------------------- 417 418 /** 419 * Pipes the specified object to the specified output stream. 420 * 421 * @param in 422 * The input byte array. 423 * <br>Can be <jk>null</jk> or any of the following types: 424 * <ul> 425 * <li>{@link Reader} 426 * <li>{@link InputStream} 427 * <li>{@link File} 428 * <li>byte array. 429 * </ul> 430 * @return The input converted to a string. 431 * @throws IOException If thrown from output stream. 432 */ 433 public static String read(Object in) throws IOException { 434 if (in == null) 435 return null; 436 if (in instanceof Reader in2) 437 return read(in2); 438 if (in instanceof InputStream in2) 439 return read(in2); 440 if (in instanceof File in2) 441 return read(in2); 442 if (in instanceof byte[] in2) 443 return read(in2); 444 throw new IllegalArgumentException("Invalid type passed to read: " + in.getClass().getName()); 445 } 446 447 /** 448 * Reads the specified byte array containing UTF-8 into a string. 449 * 450 * @param in 451 * The input. 452 * <br>Can be <jk>null</jk>. 453 * @return The new string, or <jk>null</jk> if the input was null. 454 */ 455 public static String read(byte[] in) { 456 return read(in, UTF8); 457 } 458 459 /** 460 * Reads the specified byte array into a string. 461 * 462 * @param in 463 * The input. 464 * <br>Can be <jk>null</jk>. 465 * @param charset The character set to use for decoding. 466 * @return The new string, or <jk>null</jk> if the input was null. 467 */ 468 public static String read(byte[] in, Charset charset) { 469 if (in == null) 470 return null; 471 return new String(in, charset); 472 } 473 474 /** 475 * Reads the contents of a file into a string. 476 * 477 * <p> 478 * Assumes default character encoding. 479 * 480 * @param in 481 * The file to read. 482 * <br>Can be <jk>null</jk>. 483 * @return 484 * The contents of the reader as a string, or <jk>null</jk> if file does not exist. 485 * @throws IOException If a problem occurred trying to read from the reader. 486 */ 487 public static String read(File in) throws IOException { 488 if (in == null || ! in.exists()) 489 return null; 490 try (var r = FileReaderBuilder.create(in).build()) { 491 return read(r, in.length()); 492 } 493 } 494 495 /** 496 * Reads the contents of a path into a string. 497 * 498 * <p> 499 * Assumes default character encoding. 500 * 501 * @param in 502 * The path to read. 503 * <br>Can be <jk>null</jk>. 504 * @return 505 * The contents of the reader as a string, or <jk>null</jk> if path does not exist. 506 * @throws IOException If a problem occurred trying to read from the reader. 507 * @since 9.1.0 508 */ 509 public static String read(Path in) throws IOException { 510 if (in == null || !Files.exists(in)) { 511 return null; 512 } 513 try (var r = PathReaderBuilder.create(in).build()) { 514 return read(r, Files.size(in)); 515 } 516 } 517 518 /** 519 * Reads the contents of a reader into a string. 520 * 521 * @param in 522 * The input reader. 523 * <br>Can be <jk>null</jk>. 524 * <br>Stream is automatically closed. 525 * @return 526 * The contents of the reader as a string, or <jk>null</jk> if the reader was <jk>null</jk>. 527 * @throws IOException If a problem occurred trying to read from the reader. 528 */ 529 public static String read(Reader in) throws IOException { 530 try (var in2 = in) { 531 return read(in, -1); 532 } 533 } 534 535 /** 536 * Reads the contents of a reader into a string. 537 * 538 * @param in 539 * The input reader. 540 * <br>Can be <jk>null</jk>. 541 * <br>Stream is automatically closed. 542 * @param onException Consumer of any {@link IOException I/O exceptions}. 543 * @return 544 * The contents of the reader as a string, or <jk>null</jk> if the reader was <jk>null</jk>. 545 */ 546 public static String read(Reader in, Consumer<IOException> onException) { 547 try (var in2 = in) { 548 return read(in, -1); 549 } catch (IOException e) { 550 onException.accept(e); 551 return null; 552 } 553 } 554 555 /** 556 * Reads the specified input into a {@link String} until the end of the input is reached. 557 * 558 * @param in 559 * The input reader. 560 * <br>Can be <jk>null</jk>. 561 * <br>String is automatically closed. 562 * @param expectedLength 563 * Specify a positive number if the length of the input is known, or <c>-1</c> if unknown. 564 * @return 565 * The contents of the reader as a string, or <jk>null</jk> if the reader was <jk>null</jk>. 566 * @throws IOException If a problem occurred trying to read from the reader. 567 */ 568 public static String read(Reader in, long expectedLength) throws IOException { 569 if (in == null) 570 return null; 571 try (var in2 = in) { 572 var sb = new StringBuilder(buffSize(expectedLength)); // Assume they're ASCII characters. 573 var buf = charBuffer((int)expectedLength); 574 var i = 0; 575 while ((i = in2.read(buf)) != -1) 576 sb.append(buf, 0, i); 577 return sb.toString(); 578 } 579 } 580 581 /** 582 * Reads the contents of an input stream into a string. 583 * 584 * <p> 585 * Assumes UTF-8 encoding. 586 * 587 * @param in 588 * The input stream. 589 * <br>Can be <jk>null</jk>. 590 * <br>Stream is automatically closed. 591 * @return 592 * The contents of the reader as a string, or <jk>null</jk> if the input stream was <jk>null</jk>. 593 * @throws IOException If a problem occurred trying to read from the input stream. 594 */ 595 public static String read(InputStream in) throws IOException { 596 return read(in, UTF8); 597 } 598 599 /** 600 * Reads the contents of an input stream into a string. 601 * 602 * <p> 603 * Assumes UTF-8 encoding. 604 * 605 * @param in 606 * The input stream. 607 * <br>Can be <jk>null</jk>. 608 * <br>Stream is automatically closed. 609 * @param onException Consumer of any {@link IOException I/O exceptions}. 610 * @return 611 * The contents of the reader as a string, or <jk>null</jk> if the input stream was <jk>null</jk>. 612 */ 613 public static String read(InputStream in, Consumer<IOException> onException) { 614 return read(in, UTF8, onException); 615 } 616 617 /** 618 * Reads the contents of an input stream into a string using the specified charset. 619 * 620 * @param in 621 * The input stream. 622 * <br>Can be <jk>null</jk>. 623 * <br>Stream is automatically closed. 624 * @param cs 625 * The charset of the contents of the input stream. 626 * @return 627 * The contents of the reader as a string or <jk>null</jk> if input stream was <jk>null</jk>. 628 * @throws IOException If a problem occurred trying to read from the input stream. 629 */ 630 public static String read(InputStream in, Charset cs) throws IOException { 631 if (in == null) 632 return null; 633 try (var isr = new InputStreamReader(in, cs)) { 634 return read(isr); 635 } 636 } 637 638 /** 639 * Reads the contents of an input stream into a string using the specified charset. 640 * 641 * @param in 642 * The input stream. 643 * <br>Can be <jk>null</jk>. 644 * <br>Stream is automatically closed. 645 * @param cs 646 * The charset of the contents of the input stream. 647 * @param onException Consumer of any {@link IOException I/O exceptions}. 648 * @return 649 * The contents of the reader as a string or <jk>null</jk> if input stream was <jk>null</jk>. 650 */ 651 public static String read(InputStream in, Charset cs, Consumer<IOException> onException) { 652 if (in == null) 653 return null; 654 try (var isr = new InputStreamReader(in, cs)) { 655 return read(isr); 656 } catch (IOException e) { 657 onException.accept(e); 658 return null; 659 } 660 } 661 662 /** 663 * Reads the specified input stream into the specified byte array. 664 * 665 * @param in 666 * The input stream to read. 667 * <br>Can be <jk>null</jk>. 668 * <br>Stream is automatically closed. 669 * @return A byte array containing the contents. Never <jk>null</jk>. 670 * @throws IOException Thrown by underlying stream. 671 */ 672 public static byte[] readBytes(InputStream in) throws IOException { 673 try (var in2 = in) { 674 return readBytes(in2, -1); 675 } 676 } 677 678 /** 679 * Reads the specified input stream into the specified byte array. 680 * 681 * @param in 682 * The input stream to read. 683 * <br>Can be <jk>null</jk>. 684 * <br>Stream is not automatically closed. 685 * @param maxBytes 686 * The maximum number of bytes or <c>-1</c> to read the entire stream. 687 * @return A byte array containing the contents. Never <jk>null</jk>. 688 * @throws IOException Thrown by underlying stream. 689 */ 690 public static byte[] readBytes(InputStream in, int maxBytes) throws IOException { 691 if (in == null) 692 return new byte[0]; 693 var buff = new ByteArrayOutputStream(buffSize(maxBytes)); 694 var nRead = 0; 695 var b = byteBuffer(maxBytes); 696 while ((nRead = in.read(b, 0, b.length)) != -1) 697 buff.write(b, 0, nRead); 698 buff.flush(); 699 return buff.toByteArray(); 700 } 701 702 /** 703 * Read the specified file into a byte array. 704 * 705 * @param in 706 * The file to read into a byte array. 707 * @return The contents of the file as a byte array. 708 * @throws IOException Thrown by underlying stream. 709 */ 710 public static byte[] readBytes(File in) throws IOException { 711 return readBytes(in, -1); 712 } 713 714 /** 715 * Read the specified file into a byte array. 716 * 717 * @param in 718 * The file to read into a byte array. 719 * @param maxBytes 720 * The maximum number of bytes to read, or <jk>-1</jk> to read all bytes. 721 * @return The contents of the file as a byte array. 722 * @throws IOException Thrown by underlying stream. 723 */ 724 public static byte[] readBytes(File in, int maxBytes) throws IOException { 725 if (in == null || ! (in.exists() && in.canRead())) 726 return new byte[0]; 727 try (var is = new FileInputStream(in)) { 728 return readBytes(is, maxBytes); 729 } 730 } 731 732 /** 733 * Reads the specified input stream into the specified byte array. 734 * 735 * @param in 736 * The input stream to read. 737 * <br>Can be <jk>null</jk>. 738 * <br>Stream is automatically closed. 739 * @return A byte array containing the contents. Never <jk>null</jk>. 740 * @throws IOException Thrown by underlying stream. 741 */ 742 public static byte[] readBytes(Reader in) throws IOException { 743 if (in == null) 744 return new byte[0]; 745 try (var in2 = in) { 746 return read(in2, -1).getBytes(); 747 } 748 } 749 750 //----------------------------------------------------------------------------------------------------------------- 751 // Other utilities. 752 //----------------------------------------------------------------------------------------------------------------- 753 754 /** 755 * Wraps the specified reader in a buffered reader. 756 * 757 * @param r The reader being wrapped. 758 * @return 759 * The reader wrapped in a {@link BufferedReader}, or the original {@link Reader} if it's already a buffered 760 * reader. 761 */ 762 public static Reader toBufferedReader(Reader r) { 763 if (r == null || r instanceof BufferedReader || r instanceof StringReader) 764 return r; 765 return new BufferedReader(r); 766 } 767 768 /** 769 * Counts the number of bytes in the input stream and then closes the stream. 770 * 771 * @param is The input stream to read from. 772 * @return The number of bytes read. 773 * @throws IOException Thrown by underlying stream. 774 */ 775 public static long count(InputStream is) throws IOException { 776 if (is == null) 777 return 0; 778 var c = 0l; 779 var i = 0l; 780 try { 781 while ((i = is.skip(1024)) != 0) 782 c += i; 783 } finally { 784 is.close(); 785 } 786 return c; 787 } 788 789 /** 790 * Counts the number of characters in the reader and then closes the reader. 791 * 792 * @param r The reader to read from. 793 * @return The number of characters read. 794 * @throws IOException Thrown by underlying stream. 795 */ 796 public static long count(Reader r) throws IOException { 797 if (r == null) 798 return 0; 799 var c = 0l; 800 var i = 0l; 801 try { 802 while ((i = r.skip(1024)) != 0) 803 c += i; 804 } finally { 805 r.close(); 806 } 807 return c; 808 } 809 810 /** 811 * Close input stream and ignore any exceptions. 812 * 813 * <p> 814 * No-op if input stream is <jk>null</jk>. 815 * 816 * @param is The input stream to close. 817 */ 818 public static void closeQuietly(InputStream is) { 819 try { 820 if (is != null) 821 is.close(); 822 } catch (IOException e) { /* ignore */ } 823 } 824 825 /** 826 * Close output stream and ignore any exceptions. 827 * 828 * <p> 829 * No-op if output stream is <jk>null</jk>. 830 * 831 * @param os The output stream to close. 832 */ 833 public static void closeQuietly(OutputStream os) { 834 try { 835 if (os != null) 836 os.close(); 837 } catch (IOException e) { /* ignore */ } 838 } 839 840 /** 841 * Close reader and ignore any exceptions. 842 * 843 * <p> 844 * No-op if reader is <jk>null</jk>. 845 * 846 * @param r The reader to close. 847 */ 848 public static void closeQuietly(Reader r) { 849 try { 850 if (r != null) 851 r.close(); 852 } catch (IOException e) { /* ignore */ } 853 } 854 855 /** 856 * Close writer and ignore any exceptions. 857 * 858 * <p> 859 * No-op if writer is <jk>null</jk>. 860 * 861 * @param w The writer to close. 862 */ 863 public static void closeQuietly(Writer w) { 864 try { 865 if (w != null) 866 w.close(); 867 } catch (IOException e) { /* ignore */ } 868 } 869 870 /** 871 * Quietly close all specified input streams, output streams, readers, and writers. 872 * 873 * @param o The list of all objects to quietly close. 874 */ 875 public static void closeQuietly(Object...o) { 876 for (var o2 : o) { 877 if (o2 instanceof InputStream o3) 878 closeQuietly(o3); 879 if (o2 instanceof OutputStream o3) 880 closeQuietly(o3); 881 if (o2 instanceof Reader o3) 882 closeQuietly(o3); 883 if (o2 instanceof Writer o3) 884 closeQuietly(o3); 885 } 886 } 887 888 /** 889 * Flushes multiple output streams and writers in a single call. 890 * 891 * @param o 892 * The objects to flush. 893 * <jk>null</jk> entries are ignored. 894 * @throws IOException Thrown by underlying stream. 895 */ 896 public static void flush(Object...o) throws IOException { 897 IOException ex = null; 898 for (var o2 : o) { 899 try { 900 if (o2 instanceof OutputStream o3) 901 o3.flush(); 902 if (o2 instanceof Writer o3) 903 o3.flush(); 904 } catch (IOException e) { 905 ex = e; 906 } 907 } 908 if (ex != null) 909 throw ex; 910 } 911 912 /** 913 * Close all specified input streams, output streams, readers, and writers. 914 * 915 * @param o 916 * The list of all objects to close. 917 * <jk>null</jk> entries are ignored. 918 * @throws IOException Thrown by underlying stream. 919 */ 920 public static void close(Object...o) throws IOException { 921 IOException ex = null; 922 for (var o2 : o) { 923 try { 924 if (o2 instanceof InputStream o3) 925 o3.close(); 926 if (o2 instanceof OutputStream o3) 927 o3.close(); 928 if (o2 instanceof Reader o3) 929 o3.close(); 930 if (o2 instanceof Writer o3) 931 o3.close(); 932 } catch (IOException e) { 933 ex = e; 934 } 935 } 936 if (ex != null) 937 throw ex; 938 } 939 940 /** 941 * Loads a text file from either the file system or classpath. 942 * 943 * @param name The file name. 944 * @param paths The paths to search. 945 * @return The file contents, or <jk>null</jk> if not found. 946 * @throws IOException Thrown by underlying stream. 947 */ 948 public static String loadSystemResourceAsString(String name, String...paths) throws IOException { 949 for (var path : paths) { 950 var p = new File(path); 951 if (p.exists()) { 952 var f = new File(p, name); 953 if (f.exists() && f.canRead()) 954 return read(f); 955 } 956 } 957 var cl = Thread.currentThread().getContextClassLoader(); 958 if (cl == null) 959 cl = ClassLoader.getSystemClassLoader(); 960 for (var path : paths) { 961 var n = ".".equals(path) ? name : path + '/' + name; 962 try (var is = cl.getResourceAsStream(n)) { 963 if (is != null) 964 return read(is); 965 } 966 try (var is = ClassLoader.getSystemResourceAsStream(n)) { 967 if (is != null) 968 return read(is); 969 } 970 } 971 return null; 972 } 973 974 private static byte[] byteBuffer(int maxBytes) { 975 if (BYTE_BUFFER_CACHE != null) { 976 var x = BYTE_BUFFER_CACHE.get(); 977 if (x == null) { 978 x = new byte[BUFF_SIZE]; 979 BYTE_BUFFER_CACHE.set(x); 980 BYTE_BUFFER_CACHE_MISSES.incrementAndGet(); 981 } else { 982 BYTE_BUFFER_CACHE_HITS.incrementAndGet(); 983 } 984 return x; 985 } 986 return new byte[buffSize(maxBytes)]; 987 } 988 989 private static char[] charBuffer(int maxChars) { 990 if (CHAR_BUFFER_CACHE != null) { 991 var x = CHAR_BUFFER_CACHE.get(); 992 if (x == null) { 993 x = new char[BUFF_SIZE]; 994 CHAR_BUFFER_CACHE.set(x); 995 CHAR_BUFFER_CACHE_MISSES.incrementAndGet(); 996 } else { 997 CHAR_BUFFER_CACHE_HITS.incrementAndGet(); 998 } 999 return x; 1000 } 1001 return new char[buffSize(maxChars)]; 1002 } 1003 1004 private static int buffSize(long max) { 1005 return (max > 0 && max < BUFF_SIZE) ? (int)max : BUFF_SIZE; 1006 } 1007}