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: 925654 $ $Date: 2014-10-13 20:12:40 +0000 (Mon, 13 Oct 2014) $
87   * 
88   */
89  
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 public final class IOUtil
122 {
123     private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
124 
125     /**
126      * Private constructor to prevent instantiation.
127      */
128     private IOUtil()
129     {
130     }
131 
132     ///////////////////////////////////////////////////////////////
133     // Core copy methods
134     ///////////////////////////////////////////////////////////////
135 
136     /**
137      * Copy bytes from an <code>InputStream</code> to an <code>OutputStream</code>.
138      */
139     public static void copy( @Nonnull final InputStream input, @Nonnull final OutputStream output )
140         throws IOException
141     {
142         copy( input, output, DEFAULT_BUFFER_SIZE );
143     }
144 
145     /**
146      * Copy bytes from an <code>InputStream</code> to an <code>OutputStream</code>.
147      *
148      * @param bufferSize Size of internal buffer to use.
149      */
150     public static void copy( @Nonnull final InputStream input, @Nonnull final OutputStream output, final int bufferSize )
151         throws IOException
152     {
153         final byte[] buffer = new byte[bufferSize];
154         int n;
155         while ( -1 != ( n = input.read( buffer ) ) )
156         {
157             output.write( buffer, 0, n );
158         }
159     }
160 
161     /**
162      * Copy chars from a <code>Reader</code> to a <code>Writer</code>.
163      */
164     public static void copy( @Nonnull final Reader input, @Nonnull final Writer output )
165         throws IOException
166     {
167         copy( input, output, DEFAULT_BUFFER_SIZE );
168     }
169 
170     /**
171      * Copy chars from a <code>Reader</code> to a <code>Writer</code>.
172      *
173      * @param bufferSize Size of internal buffer to use.
174      */
175     public static void copy( @Nonnull final Reader input, @Nonnull final Writer output, final int bufferSize )
176         throws IOException
177     {
178         final char[] buffer = new char[bufferSize];
179         int n;
180         while ( -1 != ( n = input.read( buffer ) ) )
181         {
182             output.write( buffer, 0, n );
183         }
184         output.flush();
185     }
186 
187     ///////////////////////////////////////////////////////////////
188     // Derived copy methods
189     // InputStream -> *
190     ///////////////////////////////////////////////////////////////
191 
192     ///////////////////////////////////////////////////////////////
193     // InputStream -> Writer
194 
195     /**
196      * Copy and convert bytes from an <code>InputStream</code> to chars on a
197      * <code>Writer</code>.
198      * The platform's default encoding is used for the byte-to-char conversion.
199      */
200     public static void copy( @Nonnull final InputStream input, @Nonnull final Writer output )
201         throws IOException
202     {
203         copy( input, output, DEFAULT_BUFFER_SIZE );
204     }
205 
206     /**
207      * Copy and convert bytes from an <code>InputStream</code> to chars on a
208      * <code>Writer</code>.
209      * The platform's default encoding is used for the byte-to-char conversion.
210      *
211      * @param bufferSize Size of internal buffer to use.
212      */
213     public static void copy( @Nonnull final InputStream input, @Nonnull final Writer output, final int bufferSize )
214         throws IOException
215     {
216         final InputStreamReader in = new InputStreamReader( input );
217         copy( in, output, bufferSize );
218     }
219 
220     /**
221      * Copy and convert bytes from an <code>InputStream</code> to chars on a
222      * <code>Writer</code>, using the specified encoding.
223      *
224      * @param encoding The name of a supported character encoding. See the
225      *                 <a href="http://www.iana.org/assignments/character-sets">IANA
226      *                 Charset Registry</a> for a list of valid encoding types.
227      */
228     public static void copy( @Nonnull final InputStream input, @Nonnull final Writer output, @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, @Nonnull final String encoding, final int bufferSize )
245         throws IOException
246     {
247         final InputStreamReader in = new InputStreamReader( input, encoding );
248         copy( in, output, bufferSize );
249     }
250 
251     ///////////////////////////////////////////////////////////////
252     // InputStream -> String
253 
254     /**
255      * Get the contents of an <code>InputStream</code> as a String.
256      * The platform's default encoding is used for the byte-to-char conversion.
257      */
258     @Nonnull public static String toString( @Nonnull final InputStream input )
259         throws IOException
260     {
261         return toString( input, DEFAULT_BUFFER_SIZE );
262     }
263 
264     /**
265      * Get the contents of an <code>InputStream</code> as a String.
266      * The platform's default encoding is used for the byte-to-char conversion.
267      *
268      * @param bufferSize Size of internal buffer to use.
269      */
270     @Nonnull public static String toString( @Nonnull final InputStream input, final int bufferSize )
271         throws IOException
272     {
273         final StringWriter sw = new StringWriter();
274         copy( input, sw, bufferSize );
275         return sw.toString();
276     }
277 
278     /**
279      * Get the contents of an <code>InputStream</code> as a String.
280      *
281      * @param encoding The name of a supported character encoding. See the
282      *                 <a href="http://www.iana.org/assignments/character-sets">IANA
283      *                 Charset Registry</a> for a list of valid encoding types.
284      */
285     @Nonnull public static String toString( @Nonnull final InputStream input, @Nonnull final String encoding )
286         throws IOException
287     {
288         return toString( input, encoding, DEFAULT_BUFFER_SIZE );
289     }
290 
291     /**
292      * Get the contents of an <code>InputStream</code> as a String.
293      *
294      * @param encoding   The name of a supported character encoding. See the
295      *                   <a href="http://www.iana.org/assignments/character-sets">IANA
296      *                   Charset Registry</a> for a list of valid encoding types.
297      * @param bufferSize Size of internal buffer to use.
298      */
299     @Nonnull public static String toString( @Nonnull final InputStream input, @Nonnull final String encoding, final int bufferSize )
300         throws IOException
301     {
302         final StringWriter sw = new StringWriter();
303         copy( input, sw, encoding, bufferSize );
304         return sw.toString();
305     }
306 
307     ///////////////////////////////////////////////////////////////
308     // InputStream -> byte[]
309 
310     /**
311      * Get the contents of an <code>InputStream</code> as a <code>byte[]</code>.
312      */
313     @Nonnull public static byte[] toByteArray( @Nonnull final InputStream input )
314         throws IOException
315     {
316         return toByteArray( input, DEFAULT_BUFFER_SIZE );
317     }
318 
319     /**
320      * Get the contents of an <code>InputStream</code> as a <code>byte[]</code>.
321      *
322      * @param bufferSize Size of internal buffer to use.
323      */
324     @Nonnull public static byte[] toByteArray( @Nonnull final InputStream input, final int bufferSize )
325         throws IOException
326     {
327         final ByteArrayOutputStream output = new ByteArrayOutputStream();
328         copy( input, output, bufferSize );
329         return output.toByteArray();
330     }
331 
332     ///////////////////////////////////////////////////////////////
333     // Derived copy methods
334     // Reader -> *
335     ///////////////////////////////////////////////////////////////
336 
337     ///////////////////////////////////////////////////////////////
338     // Reader -> OutputStream
339 
340     /**
341      * Serialize chars from a <code>Reader</code> to bytes on an <code>OutputStream</code>, and
342      * flush the <code>OutputStream</code>.
343      */
344     public static void copy( @Nonnull final Reader input, @Nonnull final OutputStream output )
345         throws IOException
346     {
347         copy( input, output, DEFAULT_BUFFER_SIZE );
348     }
349 
350     /**
351      * Serialize chars from a <code>Reader</code> to bytes on an <code>OutputStream</code>, and
352      * flush the <code>OutputStream</code>.
353      *
354      * @param bufferSize Size of internal buffer to use.
355      */
356     public static void copy( @Nonnull final Reader input, @Nonnull final OutputStream output, final int bufferSize )
357         throws IOException
358     {
359         final OutputStreamWriter out = new OutputStreamWriter( output );
360         copy( input, out, bufferSize );
361         // NOTE: Unless anyone is planning on rewriting OutputStreamWriter, we have to flush
362         // here.
363         out.flush();
364     }
365 
366     ///////////////////////////////////////////////////////////////
367     // Reader -> String
368 
369     /**
370      * Get the contents of a <code>Reader</code> as a String.
371      */
372     @Nonnull public static String toString( @Nonnull final Reader input )
373         throws IOException
374     {
375         return toString( input, DEFAULT_BUFFER_SIZE );
376     }
377 
378     /**
379      * Get the contents of a <code>Reader</code> as a String.
380      *
381      * @param bufferSize Size of internal buffer to use.
382      */
383     @Nonnull public static String toString( @Nonnull final Reader input, final int bufferSize )
384         throws IOException
385     {
386         final StringWriter sw = new StringWriter();
387         copy( input, sw, bufferSize );
388         return sw.toString();
389     }
390 
391     ///////////////////////////////////////////////////////////////
392     // Reader -> byte[]
393 
394     /**
395      * Get the contents of a <code>Reader</code> as a <code>byte[]</code>.
396      */
397     @Nonnull public static byte[] toByteArray( @Nonnull final Reader input )
398         throws IOException
399     {
400         return toByteArray( input, DEFAULT_BUFFER_SIZE );
401     }
402 
403     /**
404      * Get the contents of a <code>Reader</code> as a <code>byte[]</code>.
405      *
406      * @param bufferSize Size of internal buffer to use.
407      */
408     @Nonnull public static byte[] toByteArray( @Nonnull final Reader input, final int bufferSize )
409         throws IOException
410     {
411         ByteArrayOutputStream output = new ByteArrayOutputStream();
412         copy( input, output, bufferSize );
413         return output.toByteArray();
414     }
415 
416     ///////////////////////////////////////////////////////////////
417     // Derived copy methods
418     // String -> *
419     ///////////////////////////////////////////////////////////////
420 
421     ///////////////////////////////////////////////////////////////
422     // String -> OutputStream
423 
424     /**
425      * Serialize chars from a <code>String</code> to bytes on an <code>OutputStream</code>, and
426      * flush the <code>OutputStream</code>.
427      */
428     public static void copy( @Nonnull final String input, @Nonnull final OutputStream output )
429         throws IOException
430     {
431         copy( input, output, DEFAULT_BUFFER_SIZE );
432     }
433 
434     /**
435      * Serialize chars from a <code>String</code> to bytes on an <code>OutputStream</code>, and
436      * flush the <code>OutputStream</code>.
437      *
438      * @param bufferSize Size of internal buffer to use.
439      */
440     public static void copy( @Nonnull final String input, @Nonnull final OutputStream output, final int bufferSize )
441         throws IOException
442     {
443         final StringReader in = new StringReader( input );
444         final OutputStreamWriter out = new OutputStreamWriter( output );
445         copy( in, out, bufferSize );
446         // NOTE: Unless anyone is planning on rewriting OutputStreamWriter, we have to flush
447         // here.
448         out.flush();
449     }
450 
451     ///////////////////////////////////////////////////////////////
452     // String -> Writer
453 
454     /**
455      * Copy chars from a <code>String</code> to a <code>Writer</code>.
456      */
457     public static void copy( @Nonnull final String input, @Nonnull final Writer output )
458         throws IOException
459     {
460         output.write( input );
461     }
462 
463     ///////////////////////////////////////////////////////////////
464     // String -> byte[]
465 
466     /**
467      * Get the contents of a <code>String</code> as a <code>byte[]</code>.
468      */
469     @Nonnull public static byte[] toByteArray( @Nonnull final String input )
470         throws IOException
471     {
472         return toByteArray( input, DEFAULT_BUFFER_SIZE );
473     }
474 
475     /**
476      * Get the contents of a <code>String</code> as a <code>byte[]</code>.
477      *
478      * @param bufferSize Size of internal buffer to use.
479      */
480     @Nonnull public static byte[] toByteArray( @Nonnull final String input, final int bufferSize )
481         throws IOException
482     {
483         ByteArrayOutputStream output = new ByteArrayOutputStream();
484         copy( input, output, bufferSize );
485         return output.toByteArray();
486     }
487 
488     ///////////////////////////////////////////////////////////////
489     // Derived copy methods
490     // byte[] -> *
491     ///////////////////////////////////////////////////////////////
492 
493     ///////////////////////////////////////////////////////////////
494     // byte[] -> Writer
495 
496     /**
497      * Copy and convert bytes from a <code>byte[]</code> to chars on a
498      * <code>Writer</code>.
499      * The platform's default encoding is used for the byte-to-char conversion.
500      */
501     public static void copy( @Nonnull final byte[] input, @Nonnull final Writer output )
502         throws IOException
503     {
504         copy( input, output, DEFAULT_BUFFER_SIZE );
505     }
506 
507     /**
508      * Copy and convert bytes from a <code>byte[]</code> to chars on a
509      * <code>Writer</code>.
510      * The platform's default encoding is used for the byte-to-char conversion.
511      *
512      * @param bufferSize Size of internal buffer to use.
513      */
514     public static void copy( @Nonnull final byte[] input, @Nonnull final Writer output, final int bufferSize )
515         throws IOException
516     {
517         final ByteArrayInputStream in = new ByteArrayInputStream( input );
518         copy( in, output, bufferSize );
519     }
520 
521     /**
522      * Copy and convert bytes from a <code>byte[]</code> to chars on a
523      * <code>Writer</code>, using the specified encoding.
524      *
525      * @param encoding The name of a supported character encoding. See the
526      *                 <a href="http://www.iana.org/assignments/character-sets">IANA
527      *                 Charset Registry</a> for a list of valid encoding types.
528      */
529     public static void copy( @Nonnull final byte[] input, @Nonnull final Writer output, final String encoding )
530         throws IOException
531     {
532         final ByteArrayInputStream in = new ByteArrayInputStream( input );
533         copy( in, output, encoding );
534     }
535 
536     /**
537      * Copy and convert bytes from a <code>byte[]</code> to chars on a
538      * <code>Writer</code>, using the specified encoding.
539      *
540      * @param encoding   The name of a supported character encoding. See the
541      *                   <a href="http://www.iana.org/assignments/character-sets">IANA
542      *                   Charset Registry</a> for a list of valid encoding types.
543      * @param bufferSize Size of internal buffer to use.
544      */
545     public static void copy( @Nonnull final byte[] input, @Nonnull final Writer output, @Nonnull final String encoding, final int bufferSize )
546         throws IOException
547     {
548         final ByteArrayInputStream in = new ByteArrayInputStream( input );
549         copy( in, output, encoding, bufferSize );
550     }
551 
552     ///////////////////////////////////////////////////////////////
553     // byte[] -> String
554 
555     /**
556      * Get the contents of a <code>byte[]</code> as a String.
557      * The platform's default encoding is used for the byte-to-char conversion.
558      */
559     @Nonnull public static String toString( @Nonnull final byte[] input )
560         throws IOException
561     {
562         return toString( input, DEFAULT_BUFFER_SIZE );
563     }
564 
565     /**
566      * Get the contents of a <code>byte[]</code> as a String.
567      * The platform's default encoding is used for the byte-to-char conversion.
568      *
569      * @param bufferSize Size of internal buffer to use.
570      */
571     @Nonnull public static String toString( @Nonnull final byte[] input, final int bufferSize )
572         throws IOException
573     {
574         final StringWriter sw = new StringWriter();
575         copy( input, sw, bufferSize );
576         return sw.toString();
577     }
578 
579     /**
580      * Get the contents of a <code>byte[]</code> as a String.
581      *
582      * @param encoding The name of a supported character encoding. See the
583      *                 <a href="http://www.iana.org/assignments/character-sets">IANA
584      *                 Charset Registry</a> for a list of valid encoding types.
585      */
586     @Nonnull public static String toString( @Nonnull final byte[] input, @Nonnull final String encoding )
587         throws IOException
588     {
589         return toString( input, encoding, DEFAULT_BUFFER_SIZE );
590     }
591 
592     /**
593      * Get the contents of a <code>byte[]</code> as a String.
594      *
595      * @param encoding   The name of a supported character encoding. See the
596      *                   <a href="http://www.iana.org/assignments/character-sets">IANA
597      *                   Charset Registry</a> for a list of valid encoding types.
598      * @param bufferSize Size of internal buffer to use.
599      */
600     @Nonnull public static String toString( @Nonnull final byte[] input, @Nonnull final String encoding, final int bufferSize )
601         throws IOException
602     {
603         final StringWriter sw = new StringWriter();
604         copy( input, sw, encoding, bufferSize );
605         return sw.toString();
606     }
607 
608     ///////////////////////////////////////////////////////////////
609     // byte[] -> OutputStream
610 
611     /**
612      * Copy bytes from a <code>byte[]</code> to an <code>OutputStream</code>.
613      */
614     public static void copy( @Nonnull final byte[] input, @Nonnull final OutputStream output )
615         throws IOException
616     {
617         output.write( input );
618     }
619 
620     /**
621      * Compare the contents of two Streams to determine if they are equal or not.
622      *
623      * @param input1 the first stream
624      * @param input2 the second stream
625      * @return true if the content of the streams are equal or they both don't exist, false otherwise
626      */
627     public static boolean contentEquals( @Nonnull final InputStream input1, @Nonnull final InputStream input2 )
628         throws IOException
629     {
630         final InputStream bufferedInput1 = new BufferedInputStream( input1 );
631         final InputStream bufferedInput2 = new BufferedInputStream( input2 );
632 
633         int ch = bufferedInput1.read();
634         while ( -1 != ch )
635         {
636             final int ch2 = bufferedInput2.read();
637             if ( ch != ch2 )
638             {
639                 return false;
640             }
641             ch = bufferedInput1.read();
642         }
643 
644         final int ch2 = bufferedInput2.read();
645         return -1 == ch2;
646     }
647 
648     // ----------------------------------------------------------------------
649     // closeXXX()
650     // ----------------------------------------------------------------------
651 
652     /**
653      * Closes a channel. Channel can be null and any IOException's will be swallowed.
654      *
655      * @param channel The stream to close.
656      */
657     public static void close( @Nullable Channel channel )
658     {
659         if ( channel == null )
660         {
661             return;
662         }
663 
664         try
665         {
666             channel.close();
667         }
668         catch ( IOException ex )
669         {
670             // ignore
671         }
672     }
673 
674     /**
675      * Closes the input stream. The input stream can be null and any IOException's will be swallowed.
676      *
677      * @param inputStream The stream to close.
678      */
679     public static void close( @Nullable InputStream inputStream )
680     {
681         if ( inputStream == null )
682         {
683             return;
684         }
685 
686         try
687         {
688             inputStream.close();
689         }
690         catch ( IOException ex )
691         {
692             // ignore
693         }
694     }
695 
696     /**
697      * Closes the output stream. The output stream can be null and any IOException's will be swallowed.
698      *
699      * @param outputStream The stream to close.
700      */
701     public static void close( @Nullable OutputStream outputStream )
702     {
703         if ( outputStream == null )
704         {
705             return;
706         }
707 
708         try
709         {
710             outputStream.close();
711         }
712         catch ( IOException ex )
713         {
714             // ignore
715         }
716     }
717 
718     /**
719      * Closes the reader. The reader can be null and any IOException's will be swallowed.
720      *
721      * @param reader The reader to close.
722      */
723     public static void close( @Nullable Reader reader )
724     {
725         if ( reader == null )
726         {
727             return;
728         }
729 
730         try
731         {
732             reader.close();
733         }
734         catch ( IOException ex )
735         {
736             // ignore
737         }
738     }
739 
740     /**
741      * Closes the writer. The writer can be null and any IOException's will be swallowed.
742      *
743      * @param writer The writer to close.
744      */
745     public static void close( @Nullable Writer writer )
746     {
747         if ( writer == null )
748         {
749             return;
750         }
751 
752         try
753         {
754             writer.close();
755         }
756         catch ( IOException ex )
757         {
758             // ignore
759         }
760     }
761 }