001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.vfs2.provider; 018 019import java.io.BufferedInputStream; 020import java.io.IOException; 021import java.io.InputStream; 022import java.io.OutputStream; 023import java.security.cert.Certificate; 024import java.util.Collections; 025import java.util.Map; 026import java.util.Set; 027 028import org.apache.commons.lang3.ArrayUtils; 029import org.apache.commons.vfs2.FileContent; 030import org.apache.commons.vfs2.FileContentInfo; 031import org.apache.commons.vfs2.FileContentInfoFactory; 032import org.apache.commons.vfs2.FileObject; 033import org.apache.commons.vfs2.FileSystemException; 034import org.apache.commons.vfs2.RandomAccessContent; 035import org.apache.commons.vfs2.util.MonitorInputStream; 036import org.apache.commons.vfs2.util.MonitorOutputStream; 037import org.apache.commons.vfs2.util.MonitorRandomAccessContent; 038import org.apache.commons.vfs2.util.RandomAccessMode; 039import org.apache.commons.vfs2.util.RawMonitorInputStream; 040 041/** 042 * The content of a file. 043 */ 044public final class DefaultFileContent implements FileContent { 045 046 /* 047 * static final int STATE_NONE = 0; static final int STATE_READING = 1; static final int STATE_WRITING = 2; static 048 * final int STATE_RANDOM_ACCESS = 3; 049 */ 050 051 /** 052 * An input stream for reading content. Provides buffering, and end-of-stream monitoring. 053 */ 054 private final class FileContentInputStream extends MonitorInputStream { 055 // avoid gc 056 private final FileObject file; 057 058 FileContentInputStream(final FileObject file, final InputStream instr) { 059 super(instr); 060 this.file = file; 061 } 062 063 FileContentInputStream(final FileObject file, final InputStream instr, final int bufferSize) { 064 super(instr, bufferSize); 065 this.file = file; 066 } 067 068 /** 069 * Closes this input stream. 070 */ 071 @Override 072 public void close() throws FileSystemException { 073 try { 074 super.close(); 075 } catch (final IOException e) { 076 throw new FileSystemException("vfs.provider/close-instr.error", file, e); 077 } 078 } 079 080 /** 081 * Called after the stream has been closed. 082 */ 083 @Override 084 protected void onClose() throws IOException { 085 try { 086 super.onClose(); 087 } finally { 088 endInput(this); 089 } 090 } 091 } 092 /** 093 * An output stream for writing content. 094 */ 095 final class FileContentOutputStream extends MonitorOutputStream { 096 // avoid gc 097 private final FileObject file; 098 099 FileContentOutputStream(final FileObject file, final OutputStream outstr) { 100 super(outstr); 101 this.file = file; 102 } 103 104 FileContentOutputStream(final FileObject file, final OutputStream outstr, final int bufferSize) { 105 super(outstr, bufferSize); 106 this.file = file; 107 } 108 109 /** 110 * Closes this output stream. 111 */ 112 @Override 113 public void close() throws FileSystemException { 114 try { 115 super.close(); 116 } catch (final IOException e) { 117 throw new FileSystemException("vfs.provider/close-outstr.error", file, e); 118 } 119 } 120 121 /** 122 * Called after this stream is closed. 123 */ 124 @Override 125 protected void onClose() throws IOException { 126 try { 127 super.onClose(); 128 } finally { 129 try { 130 endOutput(); 131 } catch (final Exception e) { 132 throw new FileSystemException("vfs.provider/close-outstr.error", file, e); 133 } 134 } 135 } 136 } 137 /** 138 * An input/output stream for reading/writing content on random positions 139 */ 140 private final class FileRandomAccessContent extends MonitorRandomAccessContent { 141 // also avoids gc 142 private final FileObject file; 143 144 FileRandomAccessContent(final FileObject file, final RandomAccessContent content) { 145 super(content); 146 this.file = file; 147 } 148 149 @Override 150 public void close() throws FileSystemException { 151 try { 152 super.close(); 153 } catch (final IOException e) { 154 throw new FileSystemException("vfs.provider/close-rac.error", file, e); 155 } 156 } 157 158 /** 159 * Called after the stream has been closed. 160 */ 161 @Override 162 protected void onClose() throws IOException { 163 try { 164 super.onClose(); 165 } finally { 166 endRandomAccess(this); 167 } 168 } 169 } 170 171 /** 172 * An input stream for reading content. Provides buffering, and end-of-stream monitoring. 173 * <p> 174 * This is the same as {@link FileContentInputStream} but without the buffering. 175 * </p> 176 */ 177 private final class RawFileContentInputStream extends RawMonitorInputStream { 178 // avoid gc 179 private final FileObject file; 180 181 RawFileContentInputStream(final FileObject file, final InputStream instr) { 182 super(instr); 183 this.file = file; 184 } 185 186 /** 187 * Closes this input stream. 188 */ 189 @Override 190 public void close() throws FileSystemException { 191 try { 192 super.close(); 193 } catch (final IOException e) { 194 throw new FileSystemException("vfs.provider/close-instr.error", file, e); 195 } 196 } 197 198 /** 199 * Called after the stream has been closed. 200 */ 201 @Override 202 protected void onClose() throws IOException { 203 try { 204 super.onClose(); 205 } finally { 206 endInput(this); 207 } 208 } 209 } 210 211 static final int STATE_CLOSED = 0; 212 static final int STATE_OPENED = 1; 213 214 private static final Certificate[] EMPTY_CERTIFICATE_ARRAY = {}; 215 216 /** 217 * The default buffer size for {@link #write(OutputStream)}. 218 */ 219 private static final int WRITE_BUFFER_SIZE = 4096; 220 private final AbstractFileObject<?> fileObject; 221 222 private Map<String, Object> attrs; 223 private Map<String, Object> roAttrs; 224 225 private FileContentInfo fileContentInfo; 226 227 private final FileContentInfoFactory fileContentInfoFactory; 228 229 private final ThreadLocal<FileContentThreadData> threadLocal = ThreadLocal.withInitial(FileContentThreadData::new); 230 231 private boolean resetAttributes; 232 233 /** 234 * Counts open streams for this file. 235 */ 236 private int openStreams; 237 238 /** 239 * Constructs a new instance. 240 * 241 * @param fileObject The file object. 242 * @param fileContentInfoFactory The info factory. 243 */ 244 public DefaultFileContent(final AbstractFileObject fileObject, final FileContentInfoFactory fileContentInfoFactory) { 245 this.fileObject = fileObject; 246 this.fileContentInfoFactory = fileContentInfoFactory; 247 } 248 249 private InputStream buildInputStream(final int bufferSize) throws FileSystemException { 250 /* 251 * if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS) { throw new 252 * FileSystemException("vfs.provider/read-in-use.error", file); } 253 */ 254 // Get the raw input stream 255 // @formatter:off 256 final InputStream inputStream = bufferSize == 0 257 ? fileObject.getInputStream() 258 : fileObject.getInputStream(bufferSize); 259 // @formatter:on 260 // Double buffering may take place here. 261// final InputStream wrappedInputStream = bufferSize == 0 262// ? new FileContentInputStream(fileObject, inputStream) 263// : new FileContentInputStream(fileObject, inputStream, bufferSize); 264 265 final InputStream wrappedInputStream; 266 if (inputStream instanceof BufferedInputStream) { 267 // Don't double buffer. 268 wrappedInputStream = new RawFileContentInputStream(fileObject, inputStream); 269 } else { 270 // @formatter:off 271 wrappedInputStream = bufferSize == 0 272 ? new FileContentInputStream(fileObject, inputStream) 273 : new FileContentInputStream(fileObject, inputStream, bufferSize); 274 // @formatter:on 275 } 276 getFileContentThreadData().add(wrappedInputStream); 277 streamOpened(); 278 279 return wrappedInputStream; 280 } 281 282 private OutputStream buildOutputStream(final boolean bAppend, final int bufferSize) throws FileSystemException { 283 /* 284 * if (getThreadData().getState() != STATE_NONE) 285 */ 286 final FileContentThreadData threadData = getFileContentThreadData(); 287 288 if (threadData.getOutputStream() != null) { 289 throw new FileSystemException("vfs.provider/write-in-use.error", fileObject); 290 } 291 292 // Get the raw output stream 293 final OutputStream outstr = fileObject.getOutputStream(bAppend); 294 295 // Create and set wrapper 296 final FileContentOutputStream wrapped = bufferSize == 0 ? 297 new FileContentOutputStream(fileObject, outstr) : 298 new FileContentOutputStream(fileObject, outstr, bufferSize); 299 threadData.setOutputStream(wrapped); 300 streamOpened(); 301 302 return wrapped; 303 } 304 305 /** 306 * Closes all resources used by the content, including all streams, readers and writers. 307 * 308 * @throws FileSystemException if an error occurs. 309 */ 310 @Override 311 public void close() throws FileSystemException { 312 FileSystemException caught = null; 313 try { 314 final FileContentThreadData threadData = getFileContentThreadData(); 315 316 // Close the input stream 317 while (threadData.hasInputStream()) { 318 final InputStream inputStream = threadData.removeInputStream(0); 319 try { 320 if (inputStream instanceof FileContentInputStream) { 321 ((FileContentInputStream) inputStream).close(); 322 } else if (inputStream instanceof RawFileContentInputStream) { 323 ((RawFileContentInputStream) inputStream).close(); 324 } else { 325 caught = new FileSystemException("Unsupported InputStream type: " + inputStream); 326 } 327 } catch (final FileSystemException ex) { 328 caught = ex; 329 330 } 331 } 332 333 // Close the randomAccess stream 334 while (threadData.hasRandomAccessContent()) { 335 final FileRandomAccessContent randomAccessContent = (FileRandomAccessContent) threadData 336 .removeRandomAccessContent(0); 337 try { 338 randomAccessContent.close(); 339 } catch (final FileSystemException ex) { 340 caught = ex; 341 } 342 } 343 344 // Close the output stream 345 final FileContentOutputStream outputStream = threadData.getOutputStream(); 346 if (outputStream != null) { 347 threadData.setOutputStream(null); 348 try { 349 outputStream.close(); 350 } catch (final FileSystemException ex) { 351 caught = ex; 352 } 353 } 354 } finally { 355 threadLocal.remove(); 356 } 357 358 // throw last error (out >> rac >> input) after all closes have been tried 359 if (caught != null) { 360 throw caught; 361 } 362 } 363 364 /** 365 * Handles the end of input stream. 366 */ 367 private void endInput(final InputStream instr) { 368 final FileContentThreadData fileContentThreadData = threadLocal.get(); 369 if (fileContentThreadData != null) { 370 fileContentThreadData.remove(instr); 371 } 372 if (fileContentThreadData == null || !fileContentThreadData.hasStreams()) { 373 // remove even when no value is set to remove key 374 threadLocal.remove(); 375 } 376 streamClosed(); 377 } 378 379 /** 380 * Handles the end of output stream. 381 */ 382 private void endOutput() throws Exception { 383 final FileContentThreadData fileContentThreadData = threadLocal.get(); 384 if (fileContentThreadData != null) { 385 fileContentThreadData.setOutputStream(null); 386 } 387 if (fileContentThreadData == null || !fileContentThreadData.hasStreams()) { 388 // remove even when no value is set to remove key 389 threadLocal.remove(); 390 } 391 streamClosed(); 392 fileObject.endOutput(); 393 } 394 395 /** 396 * Handles the end of random access. 397 */ 398 private void endRandomAccess(final RandomAccessContent rac) { 399 final FileContentThreadData fileContentThreadData = threadLocal.get(); 400 if (fileContentThreadData != null) { 401 fileContentThreadData.remove(rac); 402 } 403 if (fileContentThreadData == null || !fileContentThreadData.hasStreams()) { 404 // remove even when no value is set to remove key 405 threadLocal.remove(); 406 } 407 streamClosed(); 408 } 409 410 /** 411 * Gets the value of an attribute. 412 * 413 * @param attrName The attribute name. 414 * @return The value of the attribute or null. 415 * @throws FileSystemException if an error occurs. 416 */ 417 @Override 418 public Object getAttribute(final String attrName) throws FileSystemException { 419 getAttributes(); 420 return attrs.get(attrName); 421 } 422 423 /** 424 * Lists the attributes of this file. 425 * 426 * @return An array of attribute names. 427 * @throws FileSystemException if an error occurs. 428 */ 429 @Override 430 public String[] getAttributeNames() throws FileSystemException { 431 getAttributes(); 432 final Set<String> names = attrs.keySet(); 433 return names.toArray(ArrayUtils.EMPTY_STRING_ARRAY); 434 } 435 436 /** 437 * Returns a read-only map of this file's attributes. 438 * 439 * @return a Map of the file's attributes. 440 * @throws FileSystemException if an error occurs. 441 */ 442 @Override 443 public Map<String, Object> getAttributes() throws FileSystemException { 444 if (!fileObject.getType().hasAttributes()) { 445 throw new FileSystemException("vfs.provider/get-attributes-no-exist.error", fileObject); 446 } 447 if (resetAttributes || roAttrs == null) { 448 try { 449 synchronized (this) { 450 attrs = fileObject.doGetAttributes(); 451 roAttrs = Collections.unmodifiableMap(attrs); 452 resetAttributes = false; 453 } 454 } catch (final Exception e) { 455 throw new FileSystemException("vfs.provider/get-attributes.error", fileObject, e); 456 } 457 } 458 return roAttrs; 459 } 460 461 /** 462 * Returns the certificates used to sign this file. 463 * 464 * @return An array of Certificates. 465 * @throws FileSystemException if an error occurs. 466 */ 467 @Override 468 public Certificate[] getCertificates() throws FileSystemException { 469 if (!fileObject.exists()) { 470 throw new FileSystemException("vfs.provider/get-certificates-no-exist.error", fileObject); 471 } 472 /* 473 * if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS) { throw 474 * new FileSystemException("vfs.provider/get-certificates-writing.error", file); } 475 */ 476 477 try { 478 final Certificate[] certs = fileObject.doGetCertificates(); 479 if (certs != null) { 480 return certs; 481 } 482 return EMPTY_CERTIFICATE_ARRAY; 483 } catch (final Exception e) { 484 throw new FileSystemException("vfs.provider/get-certificates.error", fileObject, e); 485 } 486 } 487 488 /** 489 * Gets the FileContentInfo which describes the content-type, content-encoding. 490 * 491 * @return The FileContentInfo. 492 * @throws FileSystemException if an error occurs. 493 */ 494 @Override 495 public FileContentInfo getContentInfo() throws FileSystemException { 496 if (fileContentInfo == null) { 497 fileContentInfo = fileContentInfoFactory.create(this); 498 } 499 500 return fileContentInfo; 501 } 502 503 /** 504 * Returns the file that this is the content of. 505 * 506 * @return the FileObject. 507 */ 508 @Override 509 public FileObject getFile() { 510 return fileObject; 511 } 512 513 private FileContentThreadData getFileContentThreadData() { 514 return threadLocal.get(); 515 } 516 517 /** 518 * Returns an input stream for reading the content. 519 * 520 * @return The InputStream 521 * @throws FileSystemException if an error occurs. 522 */ 523 @Override 524 public InputStream getInputStream() throws FileSystemException { 525 return buildInputStream(0); 526 } 527 528 /** 529 * Returns an input stream for reading the content. 530 * 531 * @param bufferSize The buffer size to use. 532 * @return The InputStream 533 * @throws FileSystemException if an error occurs. 534 * @since 2.4 535 */ 536 @Override 537 public InputStream getInputStream(final int bufferSize) throws FileSystemException { 538 return buildInputStream(bufferSize); 539 } 540 541 /** 542 * Returns the last-modified timestamp. 543 * 544 * @return The last modified timestamp. 545 * @throws FileSystemException if an error occurs. 546 */ 547 @Override 548 public long getLastModifiedTime() throws FileSystemException { 549 /* 550 * if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS) { throw 551 * new FileSystemException("vfs.provider/get-last-modified-writing.error", file); } 552 */ 553 if (!fileObject.getType().hasAttributes()) { 554 throw new FileSystemException("vfs.provider/get-last-modified-no-exist.error", fileObject); 555 } 556 try { 557 return fileObject.doGetLastModifiedTime(); 558 } catch (final Exception e) { 559 throw new FileSystemException("vfs.provider/get-last-modified.error", fileObject, e); 560 } 561 } 562 563 /** 564 * Returns an output stream for writing the content. 565 * 566 * @return The OutputStream for the file. 567 * @throws FileSystemException if an error occurs. 568 */ 569 @Override 570 public OutputStream getOutputStream() throws FileSystemException { 571 return getOutputStream(false); 572 } 573 574 /** 575 * Returns an output stream for writing the content in append mode. 576 * 577 * @param bAppend true if the data written should be appended. 578 * @return The OutputStream for the file. 579 * @throws FileSystemException if an error occurs. 580 */ 581 @Override 582 public OutputStream getOutputStream(final boolean bAppend) throws FileSystemException { 583 return buildOutputStream(bAppend, 0); 584 } 585 586 /** 587 * Returns an output stream for writing the content in append mode. 588 * 589 * @param bAppend true if the data written should be appended. 590 * @param bufferSize The buffer size to use. 591 * @return The OutputStream for the file. 592 * @throws FileSystemException if an error occurs. 593 * @since 2.4 594 */ 595 @Override 596 public OutputStream getOutputStream(final boolean bAppend, final int bufferSize) throws FileSystemException { 597 return buildOutputStream(bAppend, bufferSize); 598 } 599 600 /** 601 * Returns an output stream for writing the content. 602 * 603 * @param bufferSize The buffer size to use. 604 * @return The OutputStream for the file. 605 * @throws FileSystemException if an error occurs. 606 * @since 2.4 607 */ 608 @Override 609 public OutputStream getOutputStream(final int bufferSize) throws FileSystemException { 610 return buildOutputStream(false, bufferSize); 611 } 612 613 /** 614 * Returns an input/output stream to use to read and write the content of the file in a random manner. 615 * 616 * @param mode The RandomAccessMode. 617 * @return A RandomAccessContent object to access the file. 618 * @throws FileSystemException if an error occurs. 619 */ 620 @Override 621 public RandomAccessContent getRandomAccessContent(final RandomAccessMode mode) throws FileSystemException { 622 /* 623 * if (getThreadData().getState() != STATE_NONE) { throw new 624 * FileSystemException("vfs.provider/read-in-use.error", file); } 625 */ 626 627 // Get the content 628 final RandomAccessContent rastr = fileObject.getRandomAccessContent(mode); 629 630 final FileRandomAccessContent rac = new FileRandomAccessContent(fileObject, rastr); 631 632 getFileContentThreadData().add(rac); 633 streamOpened(); 634 635 return rac; 636 } 637 638 /** 639 * Returns the size of the content (in bytes). 640 * 641 * @return The size of the content (in bytes). 642 * @throws FileSystemException if an error occurs. 643 */ 644 @Override 645 public long getSize() throws FileSystemException { 646 // Do some checking 647 if (!fileObject.getType().hasContent()) { 648 throw new FileSystemException("vfs.provider/get-size-not-file.error", fileObject); 649 } 650 /* 651 * if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS) { throw 652 * new FileSystemException("vfs.provider/get-size-write.error", file); } 653 */ 654 655 try { 656 // Get the size 657 return fileObject.doGetContentSize(); 658 } catch (final Exception exc) { 659 throw new FileSystemException("vfs.provider/get-size.error", exc, fileObject); 660 } 661 } 662 663 /** 664 * Checks if an attribute exists. 665 * 666 * @param attrName The name of the attribute to check. 667 * @return true if the attribute is associated with the file. 668 * @throws FileSystemException if an error occurs. 669 * @since 2.0 670 */ 671 @Override 672 public boolean hasAttribute(final String attrName) throws FileSystemException { 673 if (!fileObject.getType().hasAttributes()) { 674 throw new FileSystemException("vfs.provider/exists-attributes-no-exist.error", fileObject); 675 } 676 getAttributes(); 677 return attrs.containsKey(attrName); 678 } 679 680 /** 681 * Checks if an input and/or output stream is open. 682 * <p> 683 * This checks only the scope of the current thread. 684 * </p> 685 * 686 * @return true if this is the case 687 */ 688 @Override 689 public boolean isOpen() { 690 final FileContentThreadData fileContentThreadData = threadLocal.get(); 691 if (fileContentThreadData != null && fileContentThreadData.hasStreams()) { 692 return true; 693 } 694 // threadData.get() created empty entry 695 threadLocal.remove(); 696 return false; 697 } 698 699 /** 700 * Checks if an input or output stream is open. This checks all threads. 701 * 702 * @return true if this is the case 703 */ 704 public boolean isOpenGlobal() { 705 synchronized (this) { 706 return openStreams > 0; 707 } 708 } 709 710 /** 711 * Removes an attribute. 712 * 713 * @param attrName The name of the attribute to remove. 714 * @throws FileSystemException if an error occurs. 715 * @since 2.0 716 */ 717 @Override 718 public void removeAttribute(final String attrName) throws FileSystemException { 719 if (!fileObject.getType().hasAttributes()) { 720 throw new FileSystemException("vfs.provider/remove-attribute-no-exist.error", fileObject); 721 } 722 723 try { 724 fileObject.doRemoveAttribute(attrName); 725 } catch (final Exception e) { 726 throw new FileSystemException("vfs.provider/remove-attribute.error", e, attrName, fileObject); 727 } 728 729 if (attrs != null) { 730 attrs.remove(attrName); 731 } 732 } 733 734 /** 735 * Used internally to flag situations where the file attributes should be retrieved again. 736 * 737 * @since 2.0 738 */ 739 public void resetAttributes() { 740 resetAttributes = true; 741 } 742 743 /** 744 * Sets the value of an attribute. 745 * 746 * @param attrName The name of the attribute to add. 747 * @param value The value of the attribute. 748 * @throws FileSystemException if an error occurs. 749 */ 750 @Override 751 public void setAttribute(final String attrName, final Object value) throws FileSystemException { 752 if (!fileObject.getType().hasAttributes()) { 753 throw new FileSystemException("vfs.provider/set-attribute-no-exist.error", attrName, fileObject); 754 } 755 try { 756 fileObject.doSetAttribute(attrName, value); 757 } catch (final Exception e) { 758 throw new FileSystemException("vfs.provider/set-attribute.error", e, attrName, fileObject); 759 } 760 761 if (attrs != null) { 762 attrs.put(attrName, value); 763 } 764 } 765 766 /** 767 * Sets the last-modified timestamp. 768 * 769 * @param modTime The last modified timestamp. 770 * @throws FileSystemException if an error occurs. 771 */ 772 @Override 773 public void setLastModifiedTime(final long modTime) throws FileSystemException { 774 /* 775 * if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS) { throw 776 * new FileSystemException("vfs.provider/set-last-modified-writing.error", file); } 777 */ 778 if (!fileObject.getType().hasAttributes()) { 779 throw new FileSystemException("vfs.provider/set-last-modified-no-exist.error", fileObject); 780 } 781 try { 782 if (!fileObject.doSetLastModifiedTime(modTime)) { 783 throw new FileSystemException("vfs.provider/set-last-modified.error", fileObject); 784 } 785 } catch (final Exception e) { 786 throw new FileSystemException("vfs.provider/set-last-modified.error", fileObject, e); 787 } 788 } 789 790 void streamClosed() { 791 synchronized (this) { 792 if (openStreams > 0) { 793 openStreams--; 794 if (openStreams < 1) { 795 fileObject.notifyAllStreamsClosed(); 796 } 797 } 798 } 799 ((AbstractFileSystem) fileObject.getFileSystem()).streamClosed(); 800 } 801 802 void streamOpened() { 803 synchronized (this) { 804 openStreams++; 805 } 806 ((AbstractFileSystem) fileObject.getFileSystem()).streamOpened(); 807 } 808 809 /** 810 * Writes this content to another FileContent. 811 * 812 * @param fileContent The target FileContent. 813 * @return the total number of bytes written 814 * @throws IOException if an error occurs writing the content. 815 * @since 2.1 816 */ 817 @Override 818 public long write(final FileContent fileContent) throws IOException { 819 try (OutputStream output = fileContent.getOutputStream()) { 820 return write(output); 821 } 822 } 823 824 /** 825 * Writes this content to another FileObject. 826 * 827 * @param file The target FileObject. 828 * @return the total number of bytes written 829 * @throws IOException if an error occurs writing the content. 830 * @since 2.1 831 */ 832 @Override 833 public long write(final FileObject file) throws IOException { 834 return write(file.getContent()); 835 } 836 837 /** 838 * Writes this content to an OutputStream. 839 * 840 * @param output The target OutputStream. 841 * @return the total number of bytes written 842 * @throws IOException if an error occurs writing the content. 843 * @since 2.1 844 */ 845 @Override 846 public long write(final OutputStream output) throws IOException { 847 return write(output, WRITE_BUFFER_SIZE); 848 } 849 850 /** 851 * Writes this content to an OutputStream. 852 * 853 * @param output The target OutputStream. 854 * @param bufferSize The buffer size to write data chunks. 855 * @return the total number of bytes written 856 * @throws IOException if an error occurs writing the file. 857 * @since 2.1 858 */ 859 @Override 860 public long write(final OutputStream output, final int bufferSize) throws IOException { 861 final InputStream input = getInputStream(); 862 long count = 0; 863 try { 864 // This read/write code from Apache Commons IO 865 final byte[] buffer = new byte[bufferSize]; 866 int n; 867 while (-1 != (n = input.read(buffer))) { 868 output.write(buffer, 0, n); 869 count += n; 870 } 871 } finally { 872 input.close(); 873 } 874 return count; 875 } 876}