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.internal;
014
015import static org.apache.juneau.internal.ThrowableUtils.*;
016import static org.apache.juneau.internal.StringUtils.*;
017
018import java.io.*;
019import java.nio.charset.*;
020
021import org.apache.juneau.*;
022import org.apache.juneau.utils.*;
023
024/**
025 * Various I/O related utility methods.
026 */
027public final class IOUtils {
028
029   /** UTF-8 charset */
030   public static final Charset UTF8 = Charset.forName("UTF-8");
031
032   /**
033    * Reads the contents of a file into a string.
034    *
035    * @param path The path of the file to read using default character encoding.
036    * @return The contents of the reader as a string, or <jk>null</jk> if file does not exist.
037    * @throws IOException If a problem occurred trying to read from the reader.
038    */
039   public static String readFile(String path) throws IOException {
040      return read(new File(path));
041   }
042
043   /**
044    * Reads the specified object to a <code>String</code>.
045    *
046    * <p>
047    * Can be any of the following object types:
048    * <ul>
049    *    <li>{@link CharSequence}
050    *    <li>{@link File}
051    *    <li>{@link Reader}
052    *    <li>{@link InputStream}
053    *    <li><code><jk>byte</jk>[]</code>
054    * </ul>
055    *
056    * @param in The object to read.
057    * @return The object serialized to a string, or <jk>null</jk> if it wasn't a supported type.
058    * @throws IOException
059    */
060   public static String read(Object in) throws IOException {
061      if (in == null)
062         return null;
063      if (in instanceof CharSequence)
064         return in.toString();
065      if (in instanceof File)
066         return read((File)in);
067      if (in instanceof Reader)
068         return read((Reader)in);
069      if (in instanceof InputStream)
070         return read((InputStream)in);
071      if (in instanceof byte[])
072         return read(new ByteArrayInputStream((byte[])in));
073      throw new IOException("Cannot convert object of type '"+in.getClass().getName()+"' to a String.");
074   }
075
076   /**
077    * Same as {@link #read(Object)} but appends all the input into a single String.
078    *
079    * @param in The objects to read.
080    * @return The objects serialized to a string, never <jk>null</jk>.
081    * @throws IOException
082    */
083   public static String read(Object...in) throws IOException {
084      if (in.length == 1)
085         return read(in[0]);
086      StringWriter sw = new StringWriter();
087      for (Object o : in)
088         sw.write(emptyIfNull(read(o)));
089      return sw.toString();
090   }
091
092   /**
093    * Reads the contents of a file into a string.
094    *
095    * @param in The file to read using default character encoding.
096    * @return The contents of the reader as a string, or <jk>null</jk> if file does not exist.
097    * @throws IOException If a problem occurred trying to read from the reader.
098    */
099   public static String read(File in) throws IOException {
100      if (in == null || ! in.exists())
101         return null;
102      try (Reader r = FileReaderBuilder.create(in).build()) {
103         return read(r, 0, 1024);
104      }
105   }
106
107   /**
108    * Reads the contents of a reader into a string.
109    *
110    * @param in The input reader.
111    * @return The contents of the reader as a string.
112    * @throws IOException If a problem occurred trying to read from the reader.
113    */
114   public static String read(Reader in) throws IOException {
115      return read(in, 0, 1024);
116   }
117
118   /**
119    * Reads the contents of an input stream into a string using the specified charset.
120    *
121    * @param in The input stream.
122    * @param cs The charset of the contents of the input stream.
123    * @return The contents of the reader as a string.  <jk>null</jk> if input stream was null.
124    * @throws IOException If a problem occurred trying to read from the input stream.
125    */
126   public static String read(InputStream in, Charset cs) throws IOException {
127      if (in == null)
128         return null;
129      return read(new InputStreamReader(in, cs));
130   }
131
132   /**
133    * Reads the contents of an input stream into a string using the system default charset.
134    *
135    * @param in The input stream.
136    * @return The contents of the reader as a string, or <jk>null</jk> if the input stream is null.
137    * @throws IOException If a problem occurred trying to read from the input stream.
138    */
139   public static String read(InputStream in) throws IOException {
140      if (in == null)
141         return null;
142      return read(new InputStreamReader(in, Charset.defaultCharset()));
143   }
144
145   /**
146    * Reads the specified input into a {@link String} until the end of the input is reached.
147    *
148    * <p>
149    * The {@code Reader} is automatically closed.
150    *
151    * <p>
152    * If the {@code Reader} is not an instance of a {@code BufferedReader}, then it gets wrapped in a
153    * {@code BufferedReader}.
154    *
155    * @param in The input reader.
156    * @param length Specify a positive number if the length of the input is known.
157    * @param bufferSize Specify the buffer size to use.
158    * @return The contents of the reader as a string.  <jk>null</jk> if reader was null.
159    * @throws IOException If a problem occurred trying to read from the reader.
160    */
161   public static String read(Reader in, int length, int bufferSize) throws IOException {
162      if (in == null)
163         return null;
164      if (bufferSize == 0)
165         bufferSize = 1024;
166      length = (length <= 0 ? bufferSize : length);
167      StringBuilder sb = new StringBuilder(length); // Assume they're ASCII characters.
168      try {
169         char[] buf = new char[Math.min(bufferSize, length)];
170         int i = 0;
171         while ((i = in.read(buf)) != -1)
172            sb.append(buf, 0, i);
173         return sb.toString();
174      } finally {
175         in.close();
176      }
177   }
178
179   /**
180    * Read the specified object into a byte array.
181    *
182    * @param in
183    *    The object to read into a byte array.
184    *    <br>Can be any of the following types:
185    *    <ul>
186    *       <li><code><jk>byte</jk>[]</code>
187    *       <li>{@link InputStream}
188    *       <li>{@link Reader}
189    *       <li>{@link CharSequence}
190    *       <li>{@link File}
191    *    </ul>
192    * @param buffSize
193    *    The buffer size to use.
194    * @return The contents of the stream as a byte array.
195    * @throws IOException Thrown by underlying stream or if object is not a supported type.
196    */
197   public static byte[] readBytes(Object in, int buffSize) throws IOException {
198      if (in == null)
199         return new byte[0];
200      if (in instanceof byte[])
201         return (byte[])in;
202      if (in instanceof CharSequence)
203         return in.toString().getBytes(UTF8);
204      if (in instanceof InputStream)
205         return readBytes((InputStream)in, buffSize);
206      if (in instanceof Reader)
207         return read((Reader)in, 0, buffSize).getBytes(UTF8);
208      if (in instanceof File)
209         return readBytes((File)in, buffSize);
210      throw new IOException("Cannot convert object of type '"+in.getClass().getName()+"' to a byte array.");
211   }
212
213   /**
214    * Read the specified input stream into a byte array.
215    *
216    * @param in
217    *    The stream to read into a byte array.
218    * @return The contents of the stream as a byte array.
219    * @throws IOException Thrown by underlying stream.
220    */
221   public static byte[] readBytes(InputStream in) throws IOException {
222      return readBytes(in, 1024);
223   }
224
225   /**
226    * Read the specified input stream into a byte array.
227    *
228    * @param in
229    *    The stream to read into a byte array.
230    * @param buffSize
231    *    The buffer size to use.
232    * @return The contents of the stream as a byte array.
233    * @throws IOException Thrown by underlying stream.
234    */
235   public static byte[] readBytes(InputStream in, int buffSize) throws IOException {
236      if (buffSize == 0)
237         buffSize = 1024;
238        try (final ByteArrayOutputStream buff = new ByteArrayOutputStream(buffSize)) {
239         int nRead;
240         byte[] b = new byte[buffSize];
241         while ((nRead = in.read(b, 0, b.length)) != -1)
242            buff.write(b, 0, nRead);
243         buff.flush();
244         return buff.toByteArray();
245        }
246   }
247
248   /**
249    * Read the specified file into a byte array.
250    *
251    * @param in
252    *    The file to read into a byte array.
253    * @return The contents of the file as a byte array.
254    * @throws IOException Thrown by underlying stream.
255    */
256   public static byte[] readBytes(File in) throws IOException {
257      return readBytes(in, 1024);
258   }
259
260   /**
261    * Read the specified file into a byte array.
262    *
263    * @param in
264    *    The file to read into a byte array.
265    * @param buffSize
266    *    The buffer size to use.
267    * @return The contents of the file as a byte array.
268    * @throws IOException Thrown by underlying stream.
269    */
270   public static byte[] readBytes(File in, int buffSize) throws IOException {
271      if (buffSize == 0)
272         buffSize = 1024;
273      if (! (in.exists() && in.canRead()))
274         return new byte[0];
275      buffSize = Math.min((int)in.length(), buffSize);
276      try (FileInputStream fis = new FileInputStream(in)) {
277         return readBytes(fis, buffSize);
278      }
279   }
280
281   /**
282    * Shortcut for calling <code>readBytes(in, 1024);</code>
283    *
284    * @param in
285    *    The object to read into a byte array.
286    *    <br>Can be any of the following types:
287    *    <ul>
288    *       <li><code><jk>byte</jk>[]</code>
289    *       <li>{@link InputStream}
290    *       <li>{@link Reader}
291    *       <li>{@link CharSequence}
292    *       <li>{@link File}
293    *    </ul>
294    * @return The contents of the stream as a byte array.
295    * @throws IOException Thrown by underlying stream or if object is not a supported type.
296    */
297   public static byte[] readBytes(Object in) throws IOException {
298      return readBytes(in, 1024);
299   }
300
301   /**
302    * Same as {@link #readBytes(Object)} but appends all the input into a single byte array.
303    *
304    * @param in The objects to read.
305    * @return The objects serialized to a byte array, never <jk>null</jk>.
306    * @throws IOException
307    */
308   public static byte[] readBytes(Object...in) throws IOException {
309      if (in.length == 1)
310         return readBytes(in[0]);
311        try (final ByteArrayOutputStream buff = new ByteArrayOutputStream(1024)) {
312         for (Object o : in) {
313            byte[] bo = readBytes(o);
314            if (bo != null)
315               buff.write(bo);
316         }
317         buff.flush();
318         return buff.toByteArray();
319        }
320   }
321
322   /**
323    * Writes the contents of the specified <code>Reader</code> to the specified file.
324    *
325    * @param out The file to write the output to.
326    * @param in The reader to pipe from.
327    * @return The number of characters written to the file.
328    * @throws IOException
329    */
330   public static int write(File out, Reader in) throws IOException {
331      assertFieldNotNull(out, "out");
332      assertFieldNotNull(in, "in");
333      try (Writer w = FileWriterBuilder.create(out).build()) {
334         return IOPipe.create(in, w).run();
335      }
336   }
337
338   /**
339    * Writes the contents of the specified <code>InputStream</code> to the specified file.
340    *
341    * @param out The file to write the output to.
342    * @param in The input stream to pipe from.
343    * @return The number of characters written to the file.
344    * @throws IOException
345    */
346   public static int write(File out, InputStream in) throws IOException {
347      assertFieldNotNull(out, "out");
348      assertFieldNotNull(in, "in");
349      try (OutputStream os = new FileOutputStream(out)) {
350         return IOPipe.create(in, os).run();
351      }
352   }
353
354   /**
355    * Pipes the contents of the specified object into the writer.
356    *
357    * <p>
358    * The reader is closed, the writer is not.
359    *
360    * @param in
361    *    The input to pipe from.
362    *    Can be any of the types defined by {@link #toReader(Object)}.
363    * @param out
364    *    The writer to pipe to.
365    * @throws IOException
366    */
367   public static void pipe(Object in, Writer out) throws IOException {
368      IOPipe.create(in, out).run();
369   }
370
371   /**
372    * Pipes the contents of the specified object into the output stream.
373    *
374    * <p>
375    * The input stream is closed, the output stream is not.
376    *
377    * @param in
378    *    The input to pipe from.
379    *    Can be any of the types defined by {@link #toInputStream(Object)}.
380    * @param out
381    *    The writer to pipe to.
382    * @throws IOException
383    */
384   public static void pipe(Object in, OutputStream out) throws IOException {
385      IOPipe.create(in, out).run();
386   }
387
388   /**
389    * Wraps the specified reader in a buffered reader.
390    *
391    * @param r The reader being wrapped.
392    * @return
393    *    The reader wrapped in a {@link BufferedReader}, or the original {@link Reader} if it's already a buffered
394    *    reader.
395    */
396   public static Reader getBufferedReader(Reader r) {
397      if (r == null || r instanceof BufferedReader || r instanceof StringReader)
398         return r;
399      return new BufferedReader(r);
400   }
401
402   /**
403    * Counts the number of bytes in the input stream and then closes the stream.
404    *
405    * @param is The input stream to read from.
406    * @return The number of bytes read.
407    * @throws IOException
408    */
409   public static long count(InputStream is) throws IOException {
410      assertFieldNotNull(is, "is");
411      long c = 0;
412      long i;
413      try {
414         while ((i = is.skip(1024)) != 0)
415            c += i;
416      } finally {
417         is.close();
418      }
419      return c;
420   }
421
422   /**
423    * Counts the number of characters in the reader and then closes the reader.
424    *
425    * @param r The reader to read from.
426    * @return The number of characters read.
427    * @throws IOException
428    */
429   public static long count(Reader r) throws IOException {
430      assertFieldNotNull(r, "r");
431      long c = 0;
432      long i;
433      try {
434         while ((i = r.skip(1024)) != 0)
435            c += i;
436      } finally {
437         r.close();
438      }
439      return c;
440   }
441
442   /**
443    * Given the specified <js>"Content-Length"</js> header value, return an appropriate buffer size.
444    *
445    * <p>
446    * The maximum buffer size is 1MB.
447    *
448    * @param contentLength The value of the <js>"Content-Length"</js> header.
449    * @return The appropriate buffer size.
450    */
451   public static int getBufferSize(String contentLength) {
452      try {
453         if (isNotEmpty(contentLength)) {
454            long l = Long.decode(contentLength);
455            if (l > 1048576)
456               return 1048576;
457            if (l <= 0)
458               return 8192;
459            return (int)l;
460         }
461      } catch (Exception e) {
462         return 8192;
463      }
464      return 8192;
465   }
466
467   /**
468    * Close input stream and ignore any exceptions.
469    *
470    * <p>
471    * No-op if input stream is <jk>null</jk>.
472    *
473    * @param is The input stream to close.
474    */
475   public static void closeQuietly(InputStream is) {
476      try {
477         if (is != null)
478            is.close();
479      } catch (IOException e) {}
480   }
481
482   /**
483    * Close output stream and ignore any exceptions.
484    *
485    * <p>
486    * No-op if output stream is <jk>null</jk>.
487    *
488    * @param os The output stream to close.
489    */
490   public static void closeQuietly(OutputStream os) {
491      try {
492         if (os != null)
493            os.close();
494      } catch (IOException e) {}
495   }
496
497   /**
498    * Close reader and ignore any exceptions.
499    *
500    * <p>
501    * No-op if reader is <jk>null</jk>.
502    *
503    * @param r The reader to close.
504    */
505   public static void closeQuietly(Reader r) {
506      try {
507         if (r != null)
508            r.close();
509      } catch (IOException e) {}
510   }
511
512   /**
513    * Close writer and ignore any exceptions.
514    *
515    * <p>
516    * No-op if writer is <jk>null</jk>.
517    *
518    * @param w The writer to close.
519    */
520   public static void closeQuietly(Writer w) {
521      try {
522         if (w != null)
523            w.close();
524      } catch (IOException e) {}
525   }
526
527   /**
528    * Quietly close all specified input streams, output streams, readers, and writers.
529    *
530    * @param o The list of all objects to quietly close.
531    */
532   public static void closeQuietly(Object...o) {
533      for (Object o2 : o) {
534         if (o2 instanceof InputStream)
535            closeQuietly((InputStream)o2);
536         if (o2 instanceof OutputStream)
537            closeQuietly((OutputStream)o2);
538         if (o2 instanceof Reader)
539            closeQuietly((Reader)o2);
540         if (o2 instanceof Writer)
541            closeQuietly((Writer)o2);
542      }
543   }
544
545   /**
546    * Flushes multiple output streams and writers in a single call.
547    *
548    * @param o
549    *    The objects to flush.
550    *    <jk>null</jk> entries are ignored.
551    * @throws IOException
552    */
553   public static void flush(Object...o) throws IOException {
554      IOException ex = null;
555      for (Object o2 : o) {
556         try {
557            if (o2 instanceof OutputStream)
558               ((OutputStream)o2).flush();
559            if (o2 instanceof Writer)
560               ((Writer)o2).flush();
561         } catch (IOException e) {
562            ex = e;
563         }
564      }
565      if (ex != null)
566         throw ex;
567   }
568
569   /**
570    * Close all specified input streams, output streams, readers, and writers.
571    *
572    * @param o
573    *    The list of all objects to close.
574    *    <jk>null</jk> entries are ignored.
575    * @throws IOException
576    */
577   public static void close(Object...o) throws IOException {
578      IOException ex = null;
579      for (Object o2 : o) {
580         try {
581            if (o2 instanceof InputStream)
582               ((InputStream)o2).close();
583            if (o2 instanceof OutputStream)
584               ((OutputStream)o2).close();
585            if (o2 instanceof Reader)
586               ((Reader)o2).close();
587            if (o2 instanceof Writer)
588               ((Writer)o2).close();
589         } catch (IOException e) {
590            ex = e;
591         }
592      }
593      if (ex != null)
594         throw ex;
595   }
596
597   /**
598    * Converts an object to a <code>Reader</code>.
599    *
600    * @param o
601    *    The object to convert to a reader.
602    *    Can be any of the following:
603    *    <ul>
604    *       <li>{@link InputStream}
605    *       <li>{@link Reader}
606    *       <li>{@link File}
607    *       <li>{@link CharSequence}
608    *       <li><code><jk>byte</jk>[]</code>
609    *       <li><code><jk>null</jk></code> - Returns <jk>null</jk>.
610    *    </ul>
611    * @return The object converted to a reader.
612    * @throws IOException If file could not be read.
613    * @throws IllegalArgumentException If invalid object passed in.
614    */
615   public static Reader toReader(Object o) throws IOException {
616      if (o == null)
617         return null;
618      if (o instanceof CharSequence)
619         return new StringReader(o.toString());
620      if (o instanceof File)
621         return new FileReader((File)o);
622      if (o instanceof Reader)
623         return (Reader)o;
624      if (o instanceof InputStream)
625         return new InputStreamReader((InputStream)o, "UTF-8");
626      if (o instanceof byte[])
627         return new InputStreamReader(new ByteArrayInputStream((byte[])o), "UTF-8");
628      throw new FormattedIllegalArgumentException("Invalid object of type {0} passed to IOUtils.toReader(Object)", o.getClass());
629   }
630
631   /**
632    * Converts an object to an <code>InputStream</code>.
633    *
634    * @param o
635    *    The object to convert to an input stream.
636    *    Can be any of the following:
637    *    <ul>
638    *       <li>{@link InputStream}
639    *       <li>{@link Reader}
640    *       <li>{@link File}
641    *       <li>{@link CharSequence} - Converted to UTF-8 stream.
642    *       <li><code><jk>byte</jk>[]</code>
643    *       <li><code><jk>null</jk></code> - Returns <jk>null</jk>.
644    *    </ul>
645    * @return The object converted to an input stream.
646    * @throws IOException If file could not be read.
647    * @throws IllegalArgumentException If invalid object passed in.
648    */
649   public static InputStream toInputStream(Object o) throws IOException {
650      if (o == null)
651         return null;
652      if (o instanceof InputStream)
653         return (InputStream)o;
654      if (o instanceof File)
655         return new FileInputStream((File)o);
656      if (o instanceof byte[])
657         return new ByteArrayInputStream((byte[])o);
658      if (o instanceof CharSequence)
659         return new ByteArrayInputStream(((CharSequence)o).toString().getBytes(UTF8));
660      if (o instanceof Reader)
661         return new ByteArrayInputStream(IOUtils.read((Reader)o).getBytes(UTF8));
662      throw new FormattedIllegalArgumentException("Invalid object of type {0} passed to IOUtils.toInputStream(Object)", o.getClass());
663   }
664
665   /**
666    * Writes the specified string to the specified file.
667    *
668    * @param path The file path.
669    * @param contents The new file contents.
670    * @throws IOException
671    */
672   public static void write(String path, String contents) throws IOException {
673      write(new File(path), new StringReader(contents));
674   }
675}