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}