View Javadoc
1   package org.apache.maven.shared.utils.io;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import javax.annotation.Nonnull;
23  import javax.annotation.Nullable;
24  
25  import java.io.BufferedInputStream;
26  import java.io.ByteArrayInputStream;
27  import java.io.ByteArrayOutputStream;
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.io.InputStreamReader;
31  import java.io.OutputStream;
32  import java.io.OutputStreamWriter;
33  import java.io.Reader;
34  import java.io.StringReader;
35  import java.io.StringWriter;
36  import java.io.Writer;
37  import java.nio.channels.Channel;
38  
39  /**
40   * General IO Stream manipulation.
41   * <p>
42   * This class provides static utility methods for input/output operations, particularly buffered
43   * copying between sources (<code>InputStream</code>, <code>Reader</code>, <code>String</code> and
44   * <code>byte[]</code>) and destinations (<code>OutputStream</code>, <code>Writer</code>,
45   * <code>String</code> and <code>byte[]</code>).
46   * </p>
47   * <p/>
48   * <p>Unless otherwise noted, these <code>copy</code> methods do <em>not</em> flush or close the
49   * streams. Often, doing so would require making non-portable assumptions about the streams' origin
50   * and further use. This means that both streams' <code>close()</code> methods must be called after
51   * copying. if one omits this step, then the stream resources (sockets, file descriptors) are
52   * released when the associated Stream is garbage-collected. It is not a good idea to rely on this
53   * mechanism. For a good overview of the distinction between "memory management" and "resource
54   * management", see <a href="http://www.unixreview.com/articles/1998/9804/9804ja/ja.htm">this
55   * UnixReview article</a></p>
56   * <p/>
57   * <p>For each <code>copy</code> method, a variant is provided that allows the caller to specify the
58   * buffer size (the default is 4k). As the buffer size can have a fairly large impact on speed, this
59   * may be worth tweaking. Often "large buffer -&gt; faster" does not hold, even for large data
60   * transfers.</p>
61   * <p/>
62   * <p>For byte-to-char methods, a <code>copy</code> variant allows the encoding to be selected
63   * (otherwise the platform default is used).</p>
64   * <p/>
65   * <p>The <code>copy</code> methods use an internal buffer when copying. It is therefore advisable
66   * <em>not</em> to deliberately wrap the stream arguments to the <code>copy</code> methods in
67   * <code>Buffered*</code> streams. For example, don't do the
68   * following:</p>
69   * <p/>
70   * <code>copy( new BufferedInputStream( in ), new BufferedOutputStream( out ) );</code>
71   * <p/>
72   * <p>The rationale is as follows:</p>
73   * <p/>
74   * <p>Imagine that an InputStream's read() is a very expensive operation, which would usually suggest
75   * wrapping in a BufferedInputStream. The BufferedInputStream works by issuing infrequent
76   * {@link java.io.InputStream#read(byte[] b, int off, int len)} requests on the underlying InputStream, to
77   * fill an internal buffer, from which further <code>read</code> requests can inexpensively get
78   * their data (until the buffer runs out).</p>
79   * <p>However, the <code>copy</code> methods do the same thing, keeping an internal buffer,
80   * populated by {@link InputStream#read(byte[] b, int off, int len)} requests. Having two buffers
81   * (or three if the destination stream is also buffered) is pointless, and the unnecessary buffer
82   * management hurts performance slightly (about 3%, according to some simple experiments).</p>
83   *
84   * @author <a href="mailto:peter@apache.org">Peter Donald</a>
85   * @author <a href="mailto:jefft@apache.org">Jeff Turner</a>
86   * @version CVS $Revision: 1670518 $ $Date: 2015-04-01 01:38:42 +0200 (Wed, 01 Apr 2015) $
87   * 
88   */
89  public final class IOUtil
90  /*
91   * Behold, intrepid explorers; a map of this class:
92   *
93   *       Method      Input               Output          Dependency
94   *       ------      -----               ------          -------
95   * 1     copy        InputStream         OutputStream    (primitive)
96   * 2     copy        Reader              Writer          (primitive)
97   *
98   * 3     copy        InputStream         Writer          2
99   * 4     toString    InputStream         String          3
100  * 5     toByteArray InputStream         byte[]          1
101  *
102  * 6     copy        Reader              OutputStream    2
103  * 7     toString    Reader              String          2
104  * 8     toByteArray Reader              byte[]          6
105  *
106  * 9     copy        String              OutputStream    2
107  * 10    copy        String              Writer          (trivial)
108  * 11    toByteArray String              byte[]          9
109  *
110  * 12    copy        byte[]              Writer          3
111  * 13    toString    byte[]              String          12
112  * 14    copy        byte[]              OutputStream    (trivial)
113  *
114  *
115  * Note that only the first two methods shuffle bytes; the rest use these two, or (if possible) copy
116  * using native Java copy methods. As there are method variants to specify buffer size and encoding,
117  * each row may correspond to up to 4 methods.
118  *
119  */
120 {
121     private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
122 
123     /**
124      * Private constructor to prevent instantiation.
125      */
126     private IOUtil()
127     {
128     }
129 
130     ///////////////////////////////////////////////////////////////
131     // Core copy methods
132     ///////////////////////////////////////////////////////////////
133 
134     /**
135      * Copy bytes from an <code>InputStream</code> to an <code>OutputStream</code>.
136      */
137     public static void copy( @Nonnull final InputStream input, @Nonnull final OutputStream output )
138         throws IOException
139     {
140         copy( input, output, DEFAULT_BUFFER_SIZE );
141     }
142 
143     /**
144      * Copy bytes from an <code>InputStream</code> to an <code>OutputStream</code>.
145      *
146      * @param bufferSize Size of internal buffer to use.
147      */
148     public static void copy( @Nonnull final InputStream input, @Nonnull final OutputStream output,
149                              final int bufferSize )
150         throws IOException
151     {
152         final byte[] buffer = new byte[bufferSize];
153         int n;
154         while ( -1 != ( n = input.read( buffer ) ) )
155         {
156             output.write( buffer, 0, n );
157         }
158     }
159 
160     /**
161      * Copy chars from a <code>Reader</code> to a <code>Writer</code>.
162      */
163     public static void copy( @Nonnull final Reader input, @Nonnull final Writer output )
164         throws IOException
165     {
166         copy( input, output, DEFAULT_BUFFER_SIZE );
167     }
168 
169     /**
170      * Copy chars from a <code>Reader</code> to a <code>Writer</code>.
171      *
172      * @param bufferSize Size of internal buffer to use.
173      */
174     public static void copy( @Nonnull final Reader input, @Nonnull final Writer output, final int bufferSize )
175         throws IOException
176     {
177         final char[] buffer = new char[bufferSize];
178         int n;
179         while ( -1 != ( n = input.read( buffer ) ) )
180         {
181             output.write( buffer, 0, n );
182         }
183         output.flush();
184     }
185 
186     ///////////////////////////////////////////////////////////////
187     // Derived copy methods
188     // InputStream -> *
189     ///////////////////////////////////////////////////////////////
190 
191     ///////////////////////////////////////////////////////////////
192     // InputStream -> Writer
193 
194     /**
195      * Copy and convert bytes from an <code>InputStream</code> to chars on a
196      * <code>Writer</code>.
197      * The platform's default encoding is used for the byte-to-char conversion.
198      */
199     public static void copy( @Nonnull final InputStream input, @Nonnull final Writer output )
200         throws IOException
201     {
202         copy( input, output, DEFAULT_BUFFER_SIZE );
203     }
204 
205     /**
206      * Copy and convert bytes from an <code>InputStream</code> to chars on a
207      * <code>Writer</code>.
208      * The platform's default encoding is used for the byte-to-char conversion.
209      *
210      * @param bufferSize Size of internal buffer to use.
211      */
212     public static void copy( @Nonnull final InputStream input, @Nonnull final Writer output, final int bufferSize )
213         throws IOException
214     {
215         final InputStreamReader in = new InputStreamReader( input );
216         copy( in, output, bufferSize );
217     }
218 
219     /**
220      * Copy and convert bytes from an <code>InputStream</code> to chars on a
221      * <code>Writer</code>, using the specified encoding.
222      *
223      * @param encoding The name of a supported character encoding. See the
224      *                 <a href="http://www.iana.org/assignments/character-sets">IANA
225      *                 Charset Registry</a> for a list of valid encoding types.
226      */
227     public static void copy( @Nonnull final InputStream input, @Nonnull final Writer output,
228                              @Nonnull final String encoding )
229         throws IOException
230     {
231         final InputStreamReader in = new InputStreamReader( input, encoding );
232         copy( in, output );
233     }
234 
235     /**
236      * Copy and convert bytes from an <code>InputStream</code> to chars on a
237      * <code>Writer</code>, using the specified encoding.
238      *
239      * @param encoding   The name of a supported character encoding. See the
240      *                   <a href="http://www.iana.org/assignments/character-sets">IANA
241      *                   Charset Registry</a> for a list of valid encoding types.
242      * @param bufferSize Size of internal buffer to use.
243      */
244     public static void copy( @Nonnull final InputStream input, @Nonnull final Writer output,
245                              @Nonnull final String encoding, final int bufferSize )
246         throws IOException
247     {
248         final InputStreamReader in = new InputStreamReader( input, encoding );
249         copy( in, output, bufferSize );
250     }
251 
252     ///////////////////////////////////////////////////////////////
253     // InputStream -> String
254 
255     /**
256      * Get the contents of an <code>InputStream</code> as a String.
257      * The platform's default encoding is used for the byte-to-char conversion.
258      */
259     @Nonnull public static String toString( @Nonnull final InputStream input )
260         throws IOException
261     {
262         return toString( input, DEFAULT_BUFFER_SIZE );
263     }
264 
265     /**
266      * Get the contents of an <code>InputStream</code> as a String.
267      * The platform's default encoding is used for the byte-to-char conversion.
268      *
269      * @param bufferSize Size of internal buffer to use.
270      */
271     @Nonnull public static String toString( @Nonnull final InputStream input, final int bufferSize )
272         throws IOException
273     {
274         final StringWriter sw = new StringWriter();
275         copy( input, sw, bufferSize );
276         return sw.toString();
277     }
278 
279     /**
280      * Get the contents of an <code>InputStream</code> as a String.
281      *
282      * @param encoding The name of a supported character encoding. See the
283      *                 <a href="http://www.iana.org/assignments/character-sets">IANA
284      *                 Charset Registry</a> for a list of valid encoding types.
285      */
286     @Nonnull public static String toString( @Nonnull final InputStream input, @Nonnull final String encoding )
287         throws IOException
288     {
289         return toString( input, encoding, DEFAULT_BUFFER_SIZE );
290     }
291 
292     /**
293      * Get the contents of an <code>InputStream</code> as a String.
294      *
295      * @param encoding   The name of a supported character encoding. See the
296      *                   <a href="http://www.iana.org/assignments/character-sets">IANA
297      *                   Charset Registry</a> for a list of valid encoding types.
298      * @param bufferSize Size of internal buffer to use.
299      */
300     @Nonnull public static String toString( @Nonnull final InputStream input, @Nonnull final String encoding,
301                                             final int bufferSize )
302         throws IOException
303     {
304         final StringWriter sw = new StringWriter();
305         copy( input, sw, encoding, bufferSize );
306         return sw.toString();
307     }
308 
309     ///////////////////////////////////////////////////////////////
310     // InputStream -> byte[]
311 
312     /**
313      * Get the contents of an <code>InputStream</code> as a <code>byte[]</code>.
314      */
315     @Nonnull public static byte[] toByteArray( @Nonnull final InputStream input )
316         throws IOException
317     {
318         return toByteArray( input, DEFAULT_BUFFER_SIZE );
319     }
320 
321     /**
322      * Get the contents of an <code>InputStream</code> as a <code>byte[]</code>.
323      *
324      * @param bufferSize Size of internal buffer to use.
325      */
326     @Nonnull public static byte[] toByteArray( @Nonnull final InputStream input, final int bufferSize )
327         throws IOException
328     {
329         final ByteArrayOutputStream output = new ByteArrayOutputStream();
330         copy( input, output, bufferSize );
331         return output.toByteArray();
332     }
333 
334     ///////////////////////////////////////////////////////////////
335     // Derived copy methods
336     // Reader -> *
337     ///////////////////////////////////////////////////////////////
338 
339     ///////////////////////////////////////////////////////////////
340     // Reader -> OutputStream
341 
342     /**
343      * Serialize chars from a <code>Reader</code> to bytes on an <code>OutputStream</code>, and
344      * flush the <code>OutputStream</code>.
345      */
346     public static void copy( @Nonnull final Reader input, @Nonnull final OutputStream output )
347         throws IOException
348     {
349         copy( input, output, DEFAULT_BUFFER_SIZE );
350     }
351 
352     /**
353      * Serialize chars from a <code>Reader</code> to bytes on an <code>OutputStream</code>, and
354      * flush the <code>OutputStream</code>.
355      *
356      * @param bufferSize Size of internal buffer to use.
357      */
358     public static void copy( @Nonnull final Reader input, @Nonnull final OutputStream output, final int bufferSize )
359         throws IOException
360     {
361         final OutputStreamWriter out = new OutputStreamWriter( output );
362         copy( input, out, bufferSize );
363         // NOTE: Unless anyone is planning on rewriting OutputStreamWriter, we have to flush
364         // here.
365         out.flush();
366     }
367 
368     ///////////////////////////////////////////////////////////////
369     // Reader -> String
370 
371     /**
372      * Get the contents of a <code>Reader</code> as a String.
373      */
374     @Nonnull public static String toString( @Nonnull final Reader input )
375         throws IOException
376     {
377         return toString( input, DEFAULT_BUFFER_SIZE );
378     }
379 
380     /**
381      * Get the contents of a <code>Reader</code> as a String.
382      *
383      * @param bufferSize Size of internal buffer to use.
384      */
385     @Nonnull public static String toString( @Nonnull final Reader input, final int bufferSize )
386         throws IOException
387     {
388         final StringWriter sw = new StringWriter();
389         copy( input, sw, bufferSize );
390         return sw.toString();
391     }
392 
393     ///////////////////////////////////////////////////////////////
394     // Reader -> byte[]
395 
396     /**
397      * Get the contents of a <code>Reader</code> as a <code>byte[]</code>.
398      */
399     @Nonnull public static byte[] toByteArray( @Nonnull final Reader input )
400         throws IOException
401     {
402         return toByteArray( input, DEFAULT_BUFFER_SIZE );
403     }
404 
405     /**
406      * Get the contents of a <code>Reader</code> as a <code>byte[]</code>.
407      *
408      * @param bufferSize Size of internal buffer to use.
409      */
410     @Nonnull public static byte[] toByteArray( @Nonnull final Reader input, final int bufferSize )
411         throws IOException
412     {
413         ByteArrayOutputStream output = new ByteArrayOutputStream();
414         copy( input, output, bufferSize );
415         return output.toByteArray();
416     }
417 
418     ///////////////////////////////////////////////////////////////
419     // Derived copy methods
420     // String -> *
421     ///////////////////////////////////////////////////////////////
422 
423     ///////////////////////////////////////////////////////////////
424     // String -> OutputStream
425 
426     /**
427      * Serialize chars from a <code>String</code> to bytes on an <code>OutputStream</code>, and
428      * flush the <code>OutputStream</code>.
429      */
430     public static void copy( @Nonnull final String input, @Nonnull final OutputStream output )
431         throws IOException
432     {
433         copy( input, output, DEFAULT_BUFFER_SIZE );
434     }
435 
436     /**
437      * Serialize chars from a <code>String</code> to bytes on an <code>OutputStream</code>, and
438      * flush the <code>OutputStream</code>.
439      *
440      * @param bufferSize Size of internal buffer to use.
441      */
442     public static void copy( @Nonnull final String input, @Nonnull final OutputStream output, final int bufferSize )
443         throws IOException
444     {
445         final StringReader in = new StringReader( input );
446         final OutputStreamWriter out = new OutputStreamWriter( output );
447         copy( in, out, bufferSize );
448         // NOTE: Unless anyone is planning on rewriting OutputStreamWriter, we have to flush
449         // here.
450         out.flush();
451     }
452 
453     ///////////////////////////////////////////////////////////////
454     // String -> Writer
455 
456     /**
457      * Copy chars from a <code>String</code> to a <code>Writer</code>.
458      */
459     public static void copy( @Nonnull final String input, @Nonnull final Writer output )
460         throws IOException
461     {
462         output.write( input );
463     }
464 
465     ///////////////////////////////////////////////////////////////
466     // String -> byte[]
467 
468     /**
469      * Get the contents of a <code>String</code> as a <code>byte[]</code>.
470      */
471     @Nonnull public static byte[] toByteArray( @Nonnull final String input )
472         throws IOException
473     {
474         return toByteArray( input, DEFAULT_BUFFER_SIZE );
475     }
476 
477     /**
478      * Get the contents of a <code>String</code> as a <code>byte[]</code>.
479      *
480      * @param bufferSize Size of internal buffer to use.
481      */
482     @Nonnull public static byte[] toByteArray( @Nonnull final String input, final int bufferSize )
483         throws IOException
484     {
485         ByteArrayOutputStream output = new ByteArrayOutputStream();
486         copy( input, output, bufferSize );
487         return output.toByteArray();
488     }
489 
490     ///////////////////////////////////////////////////////////////
491     // Derived copy methods
492     // byte[] -> *
493     ///////////////////////////////////////////////////////////////
494 
495     ///////////////////////////////////////////////////////////////
496     // byte[] -> Writer
497 
498     /**
499      * Copy and convert bytes from a <code>byte[]</code> to chars on a
500      * <code>Writer</code>.
501      * The platform's default encoding is used for the byte-to-char conversion.
502      */
503     public static void copy( @Nonnull final byte[] input, @Nonnull final Writer output )
504         throws IOException
505     {
506         copy( input, output, DEFAULT_BUFFER_SIZE );
507     }
508 
509     /**
510      * Copy and convert bytes from a <code>byte[]</code> to chars on a
511      * <code>Writer</code>.
512      * The platform's default encoding is used for the byte-to-char conversion.
513      *
514      * @param bufferSize Size of internal buffer to use.
515      */
516     public static void copy( @Nonnull final byte[] input, @Nonnull final Writer output, final int bufferSize )
517         throws IOException
518     {
519         final ByteArrayInputStream in = new ByteArrayInputStream( input );
520         copy( in, output, bufferSize );
521     }
522 
523     /**
524      * Copy and convert bytes from a <code>byte[]</code> to chars on a
525      * <code>Writer</code>, using the specified encoding.
526      *
527      * @param encoding The name of a supported character encoding. See the
528      *                 <a href="http://www.iana.org/assignments/character-sets">IANA
529      *                 Charset Registry</a> for a list of valid encoding types.
530      */
531     public static void copy( @Nonnull final byte[] input, @Nonnull final Writer output, final String encoding )
532         throws IOException
533     {
534         final ByteArrayInputStream in = new ByteArrayInputStream( input );
535         copy( in, output, encoding );
536     }
537 
538     /**
539      * Copy and convert bytes from a <code>byte[]</code> to chars on a
540      * <code>Writer</code>, using the specified encoding.
541      *
542      * @param encoding   The name of a supported character encoding. See the
543      *                   <a href="http://www.iana.org/assignments/character-sets">IANA
544      *                   Charset Registry</a> for a list of valid encoding types.
545      * @param bufferSize Size of internal buffer to use.
546      */
547     public static void copy( @Nonnull final byte[] input, @Nonnull final Writer output, @Nonnull final String encoding,
548                              final int bufferSize )
549         throws IOException
550     {
551         final ByteArrayInputStream in = new ByteArrayInputStream( input );
552         copy( in, output, encoding, bufferSize );
553     }
554 
555     ///////////////////////////////////////////////////////////////
556     // byte[] -> String
557 
558     /**
559      * Get the contents of a <code>byte[]</code> as a String.
560      * The platform's default encoding is used for the byte-to-char conversion.
561      */
562     @Nonnull public static String toString( @Nonnull final byte[] input )
563         throws IOException
564     {
565         return toString( input, DEFAULT_BUFFER_SIZE );
566     }
567 
568     /**
569      * Get the contents of a <code>byte[]</code> as a String.
570      * The platform's default encoding is used for the byte-to-char conversion.
571      *
572      * @param bufferSize Size of internal buffer to use.
573      */
574     @Nonnull public static String toString( @Nonnull final byte[] input, final int bufferSize )
575         throws IOException
576     {
577         final StringWriter sw = new StringWriter();
578         copy( input, sw, bufferSize );
579         return sw.toString();
580     }
581 
582     /**
583      * Get the contents of a <code>byte[]</code> as a String.
584      *
585      * @param encoding The name of a supported character encoding. See the
586      *                 <a href="http://www.iana.org/assignments/character-sets">IANA
587      *                 Charset Registry</a> for a list of valid encoding types.
588      */
589     @Nonnull public static String toString( @Nonnull final byte[] input, @Nonnull final String encoding )
590         throws IOException
591     {
592         return toString( input, encoding, DEFAULT_BUFFER_SIZE );
593     }
594 
595     /**
596      * Get the contents of a <code>byte[]</code> as a String.
597      *
598      * @param encoding   The name of a supported character encoding. See the
599      *                   <a href="http://www.iana.org/assignments/character-sets">IANA
600      *                   Charset Registry</a> for a list of valid encoding types.
601      * @param bufferSize Size of internal buffer to use.
602      */
603     @Nonnull public static String toString( @Nonnull final byte[] input, @Nonnull final String encoding,
604                                             final int bufferSize )
605         throws IOException
606     {
607         final StringWriter sw = new StringWriter();
608         copy( input, sw, encoding, bufferSize );
609         return sw.toString();
610     }
611 
612     ///////////////////////////////////////////////////////////////
613     // byte[] -> OutputStream
614 
615     /**
616      * Copy bytes from a <code>byte[]</code> to an <code>OutputStream</code>.
617      */
618     public static void copy( @Nonnull final byte[] input, @Nonnull final OutputStream output )
619         throws IOException
620     {
621         output.write( input );
622     }
623 
624     /**
625      * Compare the contents of two Streams to determine if they are equal or not.
626      *
627      * @param input1 the first stream
628      * @param input2 the second stream
629      * @return true if the content of the streams are equal or they both don't exist, false otherwise
630      */
631     public static boolean contentEquals( @Nonnull final InputStream input1, @Nonnull final InputStream input2 )
632         throws IOException
633     {
634         final InputStream bufferedInput1 = new BufferedInputStream( input1 );
635         final InputStream bufferedInput2 = new BufferedInputStream( input2 );
636 
637         int ch = bufferedInput1.read();
638         while ( -1 != ch )
639         {
640             final int ch2 = bufferedInput2.read();
641             if ( ch != ch2 )
642             {
643                 return false;
644             }
645             ch = bufferedInput1.read();
646         }
647 
648         final int ch2 = bufferedInput2.read();
649         return -1 == ch2;
650     }
651 
652     // ----------------------------------------------------------------------
653     // closeXXX()
654     // ----------------------------------------------------------------------
655 
656     /**
657      * Closes a channel. Channel can be null and any IOException's will be swallowed.
658      *
659      * @param channel The stream to close.
660      */
661     public static void close( @Nullable Channel channel )
662     {
663         if ( channel == null )
664         {
665             return;
666         }
667 
668         try
669         {
670             channel.close();
671         }
672         catch ( IOException ex )
673         {
674             // ignore
675         }
676     }
677 
678     /**
679      * Closes the input stream. The input stream can be null and any IOException's will be swallowed.
680      *
681      * @param inputStream The stream to close.
682      */
683     public static void close( @Nullable InputStream inputStream )
684     {
685         if ( inputStream == null )
686         {
687             return;
688         }
689 
690         try
691         {
692             inputStream.close();
693         }
694         catch ( IOException ex )
695         {
696             // ignore
697         }
698     }
699 
700     /**
701      * Closes the output stream. The output stream can be null and any IOException's will be swallowed.
702      *
703      * @param outputStream The stream to close.
704      */
705     public static void close( @Nullable OutputStream outputStream )
706     {
707         if ( outputStream == null )
708         {
709             return;
710         }
711 
712         try
713         {
714             outputStream.close();
715         }
716         catch ( IOException ex )
717         {
718             // ignore
719         }
720     }
721 
722     /**
723      * Closes the reader. The reader can be null and any IOException's will be swallowed.
724      *
725      * @param reader The reader to close.
726      */
727     public static void close( @Nullable Reader reader )
728     {
729         if ( reader == null )
730         {
731             return;
732         }
733 
734         try
735         {
736             reader.close();
737         }
738         catch ( IOException ex )
739         {
740             // ignore
741         }
742     }
743 
744     /**
745      * Closes the writer. The writer can be null and any IOException's will be swallowed.
746      *
747      * @param writer The writer to close.
748      */
749     public static void close( @Nullable Writer writer )
750     {
751         if ( writer == null )
752         {
753             return;
754         }
755 
756         try
757         {
758             writer.close();
759         }
760         catch ( IOException ex )
761         {
762             // ignore
763         }
764     }
765 }