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