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 -> 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 }