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