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