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