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.FileNotFoundException; 021import java.io.IOException; 022import java.io.InputStream; 023import java.io.OutputStream; 024import java.net.URL; 025import java.security.AccessController; 026import java.security.PrivilegedActionException; 027import java.security.PrivilegedExceptionAction; 028import java.security.cert.Certificate; 029import java.util.ArrayList; 030import java.util.Arrays; 031import java.util.Collections; 032import java.util.Iterator; 033import java.util.List; 034import java.util.Map; 035import java.util.Objects; 036import java.util.stream.Stream; 037 038import org.apache.commons.io.function.Uncheck; 039import org.apache.commons.vfs2.Capability; 040import org.apache.commons.vfs2.FileContent; 041import org.apache.commons.vfs2.FileContentInfoFactory; 042import org.apache.commons.vfs2.FileName; 043import org.apache.commons.vfs2.FileNotFolderException; 044import org.apache.commons.vfs2.FileObject; 045import org.apache.commons.vfs2.FileSelector; 046import org.apache.commons.vfs2.FileSystem; 047import org.apache.commons.vfs2.FileSystemException; 048import org.apache.commons.vfs2.FileType; 049import org.apache.commons.vfs2.NameScope; 050import org.apache.commons.vfs2.RandomAccessContent; 051import org.apache.commons.vfs2.Selectors; 052import org.apache.commons.vfs2.operations.DefaultFileOperations; 053import org.apache.commons.vfs2.operations.FileOperations; 054import org.apache.commons.vfs2.util.FileObjectUtils; 055import org.apache.commons.vfs2.util.RandomAccessMode; 056 057/** 058 * A partial file object implementation. 059 * 060 * TODO - Chop this class up - move all the protected methods to several interfaces, so that structure and content can 061 * be separately overridden. 062 * 063 * <p> 064 * TODO - Check caps in methods like getChildren(), etc, and give better error messages (eg 'this file type does not 065 * support listing children', vs 'this is not a folder') 066 * </p> 067 * 068 * @param <AFS> An AbstractFileSystem subclass 069 */ 070public abstract class AbstractFileObject<AFS extends AbstractFileSystem> implements FileObject { 071 072 /** 073 * Same as {@link BufferedInputStream}. 074 */ 075 public static final int DEFAULT_BUFFER_SIZE = 8192; 076 077 private static final int INITIAL_LIST_SIZE = 5; 078 079 private static final String DO_GET_INPUT_STREAM_INT = "doGetInputStream(int)"; 080 081 /** 082 * Traverses a file. 083 */ 084 private static void traverse(final DefaultFileSelectorInfo fileInfo, final FileSelector selector, 085 final boolean depthwise, final List<FileObject> selected) throws Exception { 086 // Check the file itself 087 final FileObject file = fileInfo.getFile(); 088 final int index = selected.size(); 089 090 // If the file is a folder, traverse it 091 if (file.getType().hasChildren() && selector.traverseDescendants(fileInfo)) { 092 final int curDepth = fileInfo.getDepth(); 093 fileInfo.setDepth(curDepth + 1); 094 095 // Traverse the children 096 final FileObject[] children = file.getChildren(); 097 for (final FileObject child : children) { 098 fileInfo.setFile(child); 099 traverse(fileInfo, selector, depthwise, selected); 100 } 101 102 fileInfo.setFile(file); 103 fileInfo.setDepth(curDepth); 104 } 105 106 // Add the file if doing depthwise traversal 107 if (selector.includeFile(fileInfo)) { 108 if (depthwise) { 109 // Add this file after its descendants 110 selected.add(file); 111 } else { 112 // Add this file before its descendants 113 selected.add(index, file); 114 } 115 } 116 } 117 private final AbstractFileName fileName; 118 119 private final AFS fileSystem; 120 private FileContent content; 121 // Cached info 122 private boolean attached; 123 124 private FileType type; 125 private FileObject parent; 126 127 // Changed to hold only the name of the children and let the object 128 // go into the global files cache 129 // private FileObject[] children; 130 private FileName[] children; 131 132 private List<Object> objects; 133 134 /** 135 * FileServices instance. 136 */ 137 private FileOperations operations; 138 139 /** 140 * Constructs a new instance for subclasses. 141 * 142 * @param fileName the file name. 143 * @param fileSystem the file system. 144 */ 145 protected AbstractFileObject(final AbstractFileName fileName, final AFS fileSystem) { 146 this.fileName = fileName; 147 this.fileSystem = fileSystem; 148 fileSystem.fileObjectHanded(this); 149 } 150 151 /** 152 * Attaches to the file. 153 * 154 * @throws FileSystemException if an error occurs. 155 */ 156 private void attach() throws FileSystemException { 157 synchronized (fileSystem) { 158 if (attached) { 159 return; 160 } 161 162 try { 163 // Attach and determine the file type 164 doAttach(); 165 attached = true; 166 // now the type could already be injected by doAttach (e.g. from parent to child) 167 168 /* 169 * VFS-210: determine the type when really asked fore if (type == null) { setFileType(doGetType()); } if 170 * (type == null) { setFileType(FileType.IMAGINARY); } 171 */ 172 } catch (final Exception exc) { 173 throw new FileSystemException("vfs.provider/get-type.error", exc, fileName); 174 } 175 176 // fs.fileAttached(this); 177 } 178 } 179 180 /** 181 * Queries the object if a simple rename to the file name of {@code newfile} is possible. 182 * 183 * @param newfile the new file name 184 * @return true if rename is possible 185 */ 186 @Override 187 public boolean canRenameTo(final FileObject newfile) { 188 return fileSystem == newfile.getFileSystem(); 189 } 190 191 /** 192 * Notifies the file that its children have changed. 193 * 194 * @param childName The name of the child. 195 * @param newType The type of the child. 196 * @throws Exception if an error occurs. 197 */ 198 protected void childrenChanged(final FileName childName, final FileType newType) throws Exception { 199 // TODO - this may be called when not attached 200 201 if (children != null && childName != null && newType != null) { 202 // TODO - figure out if children[] can be replaced by list 203 final ArrayList<FileName> list = new ArrayList<>(Arrays.asList(children)); 204 if (newType.equals(FileType.IMAGINARY)) { 205 list.remove(childName); 206 } else { 207 list.add(childName); 208 } 209 children = list.toArray(FileName.EMPTY_ARRAY); 210 } 211 212 // removeChildrenCache(); 213 onChildrenChanged(childName, newType); 214 } 215 216 /** 217 * Closes this file, and its content. 218 * 219 * @throws FileSystemException if an error occurs. 220 */ 221 @Override 222 public void close() throws FileSystemException { 223 FileSystemException exc = null; 224 225 synchronized (fileSystem) { 226 // Close the content 227 if (content != null) { 228 try { 229 content.close(); 230 content = null; 231 } catch (final FileSystemException e) { 232 exc = e; 233 } 234 } 235 236 // Detach from the file 237 try { 238 detach(); 239 } catch (final Exception e) { 240 exc = new FileSystemException("vfs.provider/close.error", fileName, e); 241 } 242 243 if (exc != null) { 244 throw exc; 245 } 246 } 247 } 248 249 /** 250 * Compares two FileObjects (ignores case). 251 * 252 * @param file the object to compare. 253 * @return a negative integer, zero, or a positive integer when this object is less than, equal to, or greater than 254 * the given object. 255 */ 256 @Override 257 public int compareTo(final FileObject file) { 258 if (file == null) { 259 return 1; 260 } 261 return this.toString().compareToIgnoreCase(file.toString()); 262 } 263 264 /** 265 * Copies another file to this file. 266 * 267 * @param file The FileObject to copy. 268 * @param selector The FileSelector. 269 * @throws FileSystemException if an error occurs. 270 */ 271 @Override 272 public void copyFrom(final FileObject file, final FileSelector selector) throws FileSystemException { 273 if (!FileObjectUtils.exists(file)) { 274 throw new FileSystemException("vfs.provider/copy-missing-file.error", file); 275 } 276 277 // Locate the files to copy across 278 final ArrayList<FileObject> files = new ArrayList<>(); 279 file.findFiles(selector, false, files); 280 281 // Copy everything across 282 for (final FileObject srcFile : files) { 283 // Determine the destination file 284 final String relPath = file.getName().getRelativeName(srcFile.getName()); 285 final FileObject destFile = resolveFile(relPath, NameScope.DESCENDENT_OR_SELF); 286 287 // Clean up the destination file, if necessary 288 if (FileObjectUtils.exists(destFile) && destFile.getType() != srcFile.getType()) { 289 // The destination file exists, and is not of the same type, 290 // so delete it 291 // TODO - add a pluggable policy for deleting and overwriting existing files 292 destFile.deleteAll(); 293 } 294 295 // Copy across 296 try { 297 if (srcFile.getType().hasContent()) { 298 FileObjectUtils.writeContent(srcFile, destFile); 299 } else if (srcFile.getType().hasChildren()) { 300 destFile.createFolder(); 301 } 302 } catch (final IOException e) { 303 throw new FileSystemException("vfs.provider/copy-file.error", e, srcFile, destFile); 304 } 305 } 306 } 307 308 /** 309 * Creates this file, if it does not exist. 310 * 311 * @throws FileSystemException if an error occurs. 312 */ 313 @Override 314 public void createFile() throws FileSystemException { 315 synchronized (fileSystem) { 316 try { 317 // VFS-210: We do not want to trunc any existing file, checking for its existence is 318 // still required 319 if (exists() && !isFile()) { 320 throw new FileSystemException("vfs.provider/create-file.error", fileName); 321 } 322 323 if (!exists()) { 324 try (FileContent content = getContent()) { 325 if (content != null) { 326 try (OutputStream ignored = content.getOutputStream()) { 327 // Avoids NPE on OutputStream#close() 328 } 329 } 330 } 331 } 332 } catch (final RuntimeException re) { 333 throw re; 334 } catch (final Exception e) { 335 throw new FileSystemException("vfs.provider/create-file.error", fileName, e); 336 } 337 } 338 } 339 340 /** 341 * Creates this folder, if it does not exist. Also creates any ancestor files which do not exist. 342 * 343 * @throws FileSystemException if an error occurs. 344 */ 345 @Override 346 public void createFolder() throws FileSystemException { 347 synchronized (fileSystem) { 348 // VFS-210: we create a folder only if it does not already exist. So this check should be safe. 349 if (getType().hasChildren()) { 350 // Already exists as correct type 351 return; 352 } 353 if (getType() != FileType.IMAGINARY) { 354 throw new FileSystemException("vfs.provider/create-folder-mismatched-type.error", fileName); 355 } 356 /* 357 * VFS-210: checking for writable is not always possible as the security constraint might be more complex 358 * if (!isWriteable()) { throw new FileSystemException("vfs.provider/create-folder-read-only.error", name); 359 * } 360 */ 361 362 // Traverse up the hierarchy and make sure everything is a folder 363 final FileObject parent = getParent(); 364 if (parent != null) { 365 parent.createFolder(); 366 } 367 368 try { 369 // Create the folder 370 doCreateFolder(); 371 372 // Update cached info 373 handleCreate(FileType.FOLDER); 374 } catch (final RuntimeException re) { 375 throw re; 376 } catch (final Exception exc) { 377 throw new FileSystemException("vfs.provider/create-folder.error", fileName, exc); 378 } 379 } 380 } 381 382 /** 383 * Deletes this file. 384 * <p> 385 * TODO - This will not fail if this is a non-empty folder. 386 * </p> 387 * 388 * @return true if this object has been deleted 389 * @throws FileSystemException if an error occurs. 390 */ 391 @Override 392 public boolean delete() throws FileSystemException { 393 return delete(Selectors.SELECT_SELF) > 0; 394 } 395 396 /** 397 * Deletes this file, and all children matching the {@code selector}. 398 * 399 * @param selector The FileSelector. 400 * @return the number of deleted files. 401 * @throws FileSystemException if an error occurs. 402 */ 403 @Override 404 public int delete(final FileSelector selector) throws FileSystemException { 405 int nuofDeleted = 0; 406 407 /* 408 * VFS-210 if (getType() == FileType.IMAGINARY) { // File does not exist return nuofDeleted; } 409 */ 410 411 // Locate all the files to delete 412 final ArrayList<FileObject> files = new ArrayList<>(); 413 findFiles(selector, true, files); 414 415 // Delete 'em 416 for (final FileObject fileObject : files) { 417 final AbstractFileObject file = FileObjectUtils.getAbstractFileObject(fileObject); 418 // file.attach(); 419 // VFS-210: It seems impossible to me that findFiles will return a list with hidden files/directories 420 // in it, else it would not be hidden. Checking for the file-type seems ok in this case 421 // If the file is a folder, make sure all its children have been deleted 422 if (file.getType().hasChildren() && file.getChildren().length != 0) { 423 // Skip - as the selector forced us not to delete all files 424 continue; 425 } 426 // Delete the file 427 if (file.deleteSelf()) { 428 nuofDeleted++; 429 } 430 } 431 return nuofDeleted; 432 } 433 434 /** 435 * Deletes this file and all children. Shorthand for {@code delete(Selectors.SELECT_ALL)} 436 * 437 * @return the number of deleted files. 438 * @throws FileSystemException if an error occurs. 439 * @see #delete(FileSelector) 440 * @see Selectors#SELECT_ALL 441 */ 442 @Override 443 public int deleteAll() throws FileSystemException { 444 return this.delete(Selectors.SELECT_ALL); 445 } 446 447 /** 448 * Deletes this file, once all its children have been deleted 449 * 450 * @return true if this file has been deleted 451 * @throws FileSystemException if an error occurs. 452 */ 453 private boolean deleteSelf() throws FileSystemException { 454 synchronized (fileSystem) { 455 // It's possible to delete a read-only file if you have write-execute access to the directory 456 457 /* 458 * VFS-210 if (getType() == FileType.IMAGINARY) { // File does not exist return false; } 459 */ 460 461 try { 462 // Delete the file 463 doDelete(); 464 465 // Update cached info 466 handleDelete(); 467 } catch (final RuntimeException re) { 468 throw re; 469 } catch (final Exception exc) { 470 throw new FileSystemException("vfs.provider/delete.error", exc, fileName); 471 } 472 473 return true; 474 } 475 } 476 477 /** 478 * Detaches this file, invalidating all cached info. This will force a call to {@link #doAttach} next time this file 479 * is used. 480 * 481 * @throws Exception if an error occurs. 482 */ 483 private void detach() throws Exception { 484 synchronized (fileSystem) { 485 if (attached) { 486 try { 487 doDetach(); 488 } finally { 489 attached = false; 490 setFileType(null); 491 parent = null; 492 493 // fs.fileDetached(this); 494 495 removeChildrenCache(); 496 // children = null; 497 } 498 } 499 } 500 } 501 502 /** 503 * Attaches this file object to its file resource. 504 * <p> 505 * This method is called before any of the doBlah() or onBlah() methods. Sub-classes can use this method to perform 506 * lazy initialization. 507 * </p> 508 * <p> 509 * This implementation does nothing. 510 * </p> 511 * 512 * @throws Exception if an error occurs. 513 */ 514 protected void doAttach() throws Exception { 515 // noop 516 } 517 518 /** 519 * Create a FileContent implementation. 520 * 521 * @return The FileContent. 522 * @throws FileSystemException if an error occurs. 523 * @since 2.0 524 */ 525 protected FileContent doCreateFileContent() throws FileSystemException { 526 return new DefaultFileContent(this, getFileContentInfoFactory()); 527 } 528 529 /** 530 * Creates this file as a folder. Is only called when: 531 * <ul> 532 * <li>{@link #doGetType} returns {@link FileType#IMAGINARY}.</li> 533 * <li>The parent folder exists and is writable, or this file is the root of the file system.</li> 534 * </ul> 535 * This implementation throws an exception. 536 * 537 * @throws Exception if an error occurs. 538 */ 539 protected void doCreateFolder() throws Exception { 540 throw new FileSystemException("vfs.provider/create-folder-not-supported.error"); 541 } 542 543 /** 544 * Deletes the file. Is only called when: 545 * <ul> 546 * <li>{@link #doGetType} does not return {@link FileType#IMAGINARY}.</li> 547 * <li>{@link #doIsWriteable} returns true.</li> 548 * <li>This file has no children, if a folder.</li> 549 * </ul> 550 * This implementation throws an exception. 551 * 552 * @throws Exception if an error occurs. 553 */ 554 protected void doDelete() throws Exception { 555 throw new FileSystemException("vfs.provider/delete-not-supported.error"); 556 } 557 558 /** 559 * Detaches this file object from its file resource. 560 * <p> 561 * Called when this file is closed. Note that the file object may be reused later, so should be able to be 562 * reattached. 563 * </p> 564 * <p> 565 * This implementation does nothing. 566 * </p> 567 * 568 * @throws Exception if an error occurs. 569 */ 570 protected void doDetach() throws Exception { 571 // noop 572 } 573 574 /** 575 * Returns the attributes of this file. Is only called if {@link #doGetType} does not return 576 * {@link FileType#IMAGINARY}. 577 * <p> 578 * This implementation always returns an empty map. 579 * </p> 580 * 581 * @return The attributes of the file. 582 * @throws Exception if an error occurs. 583 */ 584 protected Map<String, Object> doGetAttributes() throws Exception { 585 return Collections.emptyMap(); 586 } 587 588 /** 589 * Returns the certificates used to sign this file. Is only called if {@link #doGetType} does not return 590 * {@link FileType#IMAGINARY}. 591 * <p> 592 * This implementation always returns null. 593 * </p> 594 * 595 * @return The certificates used to sign the file. 596 * @throws Exception if an error occurs. 597 */ 598 protected Certificate[] doGetCertificates() throws Exception { 599 return null; 600 } 601 602 /** 603 * Returns the size of the file content (in bytes). Is only called if {@link #doGetType} returns 604 * {@link FileType#FILE}. 605 * 606 * @return The size of the file in bytes. 607 * @throws Exception if an error occurs. 608 */ 609 protected abstract long doGetContentSize() throws Exception; 610 611 /** 612 * Creates an input stream to read the file content from. Is only called if {@link #doGetType} returns 613 * {@link FileType#FILE}. 614 * <p> 615 * It is guaranteed that there are no open output streams for this file when this method is called. 616 * </p> 617 * <p> 618 * The returned stream does not have to be buffered. 619 * </p> 620 * 621 * @return An InputStream to read the file content. 622 * @throws Exception if an error occurs. 623 */ 624 protected InputStream doGetInputStream() throws Exception { 625 // Backward compatibility. 626 return doGetInputStream(DEFAULT_BUFFER_SIZE); 627 } 628 629 /** 630 * Creates an input stream to read the file content from. Is only called if {@link #doGetType} returns 631 * {@link FileType#FILE}. 632 * <p> 633 * It is guaranteed that there are no open output streams for this file when this method is called. 634 * </p> 635 * <p> 636 * The returned stream does not have to be buffered. 637 * </p> 638 * @param bufferSize Buffer size hint. 639 * @return An InputStream to read the file content. 640 * @throws Exception if an error occurs. 641 */ 642 protected InputStream doGetInputStream(final int bufferSize) throws Exception { 643 throw new UnsupportedOperationException(DO_GET_INPUT_STREAM_INT); 644 } 645 646 /** 647 * Returns the last modified time of this file. Is only called if {@link #doGetType} does not return 648 * <p> 649 * This implementation throws an exception. 650 * </p> 651 * 652 * @return The last modification time. 653 * @throws Exception if an error occurs. 654 */ 655 protected long doGetLastModifiedTime() throws Exception { 656 throw new FileSystemException("vfs.provider/get-last-modified-not-supported.error"); 657 } 658 659 /** 660 * Creates an output stream to write the file content to. Is only called if: 661 * <ul> 662 * <li>{@link #doIsWriteable} returns true. 663 * <li>{@link #doGetType} returns {@link FileType#FILE}, or {@link #doGetType} returns {@link FileType#IMAGINARY}, 664 * and the file's parent exists and is a folder. 665 * </ul> 666 * It is guaranteed that there are no open stream (input or output) for this file when this method is called. 667 * <p> 668 * The returned stream does not have to be buffered. 669 * </p> 670 * <p> 671 * This implementation throws an exception. 672 * </p> 673 * 674 * @param bAppend true if the file should be appended to, false if it should be overwritten. 675 * @return An OutputStream to write to the file. 676 * @throws Exception if an error occurs. 677 */ 678 protected OutputStream doGetOutputStream(final boolean bAppend) throws Exception { 679 throw new FileSystemException("vfs.provider/write-not-supported.error"); 680 } 681 682 /** 683 * Creates access to the file for random i/o. Is only called if {@link #doGetType} returns {@link FileType#FILE}. 684 * <p> 685 * It is guaranteed that there are no open output streams for this file when this method is called. 686 * </p> 687 * 688 * @param mode The mode to access the file. 689 * @return The RandomAccessContext. 690 * @throws Exception if an error occurs. 691 */ 692 protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception { 693 throw new FileSystemException("vfs.provider/random-access-not-supported.error"); 694 } 695 696 /** 697 * Determines the type of this file. Must not return null. The return value of this method is cached, so the 698 * implementation can be expensive. 699 * 700 * @return the type of the file. 701 * @throws Exception if an error occurs. 702 */ 703 protected abstract FileType doGetType() throws Exception; 704 705 /** 706 * Determines if this file is executable. Is only called if {@link #doGetType} does not return 707 * {@link FileType#IMAGINARY}. 708 * <p> 709 * This implementation always returns false. 710 * </p> 711 * 712 * @return true if the file is executable, false otherwise. 713 * @throws Exception if an error occurs. 714 */ 715 protected boolean doIsExecutable() throws Exception { 716 return false; 717 } 718 719 /** 720 * Determines if this file is hidden. Is only called if {@link #doGetType} does not return 721 * {@link FileType#IMAGINARY}. 722 * <p> 723 * This implementation always returns false. 724 * </p> 725 * 726 * @return true if the file is hidden, false otherwise. 727 * @throws Exception if an error occurs. 728 */ 729 protected boolean doIsHidden() throws Exception { 730 return false; 731 } 732 733 /** 734 * Determines if this file can be read. Is only called if {@link #doGetType} does not return 735 * {@link FileType#IMAGINARY}. 736 * <p> 737 * This implementation always returns true. 738 * </p> 739 * 740 * @return true if the file is readable, false otherwise. 741 * @throws Exception if an error occurs. 742 */ 743 protected boolean doIsReadable() throws Exception { 744 return true; 745 } 746 747 /** 748 * Checks if this fileObject is the same file as {@code destFile} just with a different name. E.g. for 749 * case-insensitive file systems like Windows. 750 * 751 * @param destFile The file to compare to. 752 * @return true if the FileObjects are the same. 753 * @throws FileSystemException if an error occurs. 754 */ 755 protected boolean doIsSameFile(final FileObject destFile) throws FileSystemException { 756 return false; 757 } 758 759 /** 760 * Determines if this file is a symbolic link. Is only called if {@link #doGetType} does not return 761 * {@link FileType#IMAGINARY}. 762 * <p> 763 * This implementation always returns false. 764 * </p> 765 * 766 * @return true if the file is readable, false otherwise. 767 * @throws Exception if an error occurs. 768 * @since 2.4 769 */ 770 protected boolean doIsSymbolicLink() throws Exception { 771 return false; 772 } 773 774 /** 775 * Determines if this file can be written to. Is only called if {@link #doGetType} does not return 776 * {@link FileType#IMAGINARY}. 777 * <p> 778 * This implementation always returns true. 779 * </p> 780 * 781 * @return true if the file is writable. 782 * @throws Exception if an error occurs. 783 */ 784 protected boolean doIsWriteable() throws Exception { 785 return true; 786 } 787 788 /** 789 * Lists the children of this file. Is only called if {@link #doGetType} returns {@link FileType#FOLDER}. The return 790 * value of this method is cached, so the implementation can be expensive. 791 * 792 * @return a possible empty String array if the file is a directory or null or an exception if the file is not a 793 * directory or can't be read. 794 * @throws Exception if an error occurs. 795 */ 796 protected abstract String[] doListChildren() throws Exception; 797 798 /** 799 * Lists the children of this file. 800 * <p> 801 * Is only called if {@link #doGetType} returns {@link FileType#FOLDER}. 802 * </p> 803 * <p> 804 * The return value of this method is cached, so the implementation can be expensive. 805 * Other than {@code doListChildren} you could return FileObject's to e.g. reinitialize the type of the file. 806 * </p> 807 * <p> 808 * (Introduced for WebDAV: "permission denied on resource" during getType()) 809 * </p> 810 * 811 * @return The children of this FileObject. 812 * @throws Exception if an error occurs. 813 */ 814 protected FileObject[] doListChildrenResolved() throws Exception { 815 return null; 816 } 817 818 /** 819 * Removes an attribute of this file. 820 * <p> 821 * Is only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}. 822 * </p> 823 * <p> 824 * This implementation throws an exception. 825 * </p> 826 * 827 * @param attrName The name of the attribute to remove. 828 * @throws Exception if an error occurs. 829 * @since 2.0 830 */ 831 protected void doRemoveAttribute(final String attrName) throws Exception { 832 throw new FileSystemException("vfs.provider/remove-attribute-not-supported.error"); 833 } 834 835 /** 836 * Renames the file. 837 * <p> 838 * Is only called when: 839 * </p> 840 * <ul> 841 * <li>{@link #doIsWriteable} returns true.</li> 842 * </ul> 843 * <p> 844 * This implementation throws an exception. 845 * </p> 846 * 847 * @param newFile A FileObject with the new file name. 848 * @throws Exception if an error occurs. 849 */ 850 protected void doRename(final FileObject newFile) throws Exception { 851 throw new FileSystemException("vfs.provider/rename-not-supported.error"); 852 } 853 854 /** 855 * Sets an attribute of this file. 856 * <p> 857 * Is only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}. 858 * </p> 859 * <p> 860 * This implementation throws an exception. 861 * </p> 862 * 863 * @param attrName The attribute name. 864 * @param value The value to be associated with the attribute name. 865 * @throws Exception if an error occurs. 866 */ 867 protected void doSetAttribute(final String attrName, final Object value) throws Exception { 868 throw new FileSystemException("vfs.provider/set-attribute-not-supported.error"); 869 } 870 871 /** 872 * Make the file executable. 873 * <p> 874 * Only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}. 875 * </p> 876 * <p> 877 * This implementation returns false. 878 * </p> 879 * 880 * @param executable True to allow access, false to disallow. 881 * @param ownerOnly If {@code true}, the permission applies only to the owner; otherwise, it applies to everybody. 882 * @return true if the operation succeeded. 883 * @throws Exception Any Exception thrown is wrapped in FileSystemException. 884 * @see #setExecutable(boolean, boolean) 885 * @since 2.1 886 */ 887 protected boolean doSetExecutable(final boolean executable, final boolean ownerOnly) throws Exception { 888 return false; 889 } 890 891 /** 892 * Sets the last modified time of this file. 893 * <p> 894 * Is only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}. 895 * </p> 896 * <p> 897 * This implementation throws an exception. 898 * </p> 899 * 900 * @param modtime The last modification time. 901 * @return true if the time was set. 902 * @throws Exception Any Exception thrown is wrapped in FileSystemException. 903 */ 904 protected boolean doSetLastModifiedTime(final long modtime) throws Exception { 905 throw new FileSystemException("vfs.provider/set-last-modified-not-supported.error"); 906 } 907 908 /** 909 * Make the file or folder readable. 910 * <p> 911 * Only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}. 912 * </p> 913 * <p> 914 * This implementation returns false. 915 * </p> 916 * 917 * @param readable True to allow access, false to disallow 918 * @param ownerOnly If {@code true}, the permission applies only to the owner; otherwise, it applies to everybody. 919 * @return true if the operation succeeded 920 * @throws Exception Any Exception thrown is wrapped in FileSystemException. 921 * @see #setReadable(boolean, boolean) 922 * @since 2.1 923 */ 924 protected boolean doSetReadable(final boolean readable, final boolean ownerOnly) throws Exception { 925 return false; 926 } 927 928 /** 929 * Make the file or folder writable. 930 * <p> 931 * Only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}. 932 * </p> 933 * 934 * @param writable True to allow access, false to disallow 935 * @param ownerOnly If {@code true}, the permission applies only to the owner; otherwise, it applies to everybody. 936 * @return true if the operation succeeded 937 * @throws Exception Any Exception thrown is wrapped in FileSystemException. 938 * @see #setWritable(boolean, boolean) 939 * @since 2.1 940 */ 941 protected boolean doSetWritable(final boolean writable, final boolean ownerOnly) throws Exception { 942 return false; 943 } 944 945 /** 946 * Called when the output stream for this file is closed. 947 * 948 * @throws Exception if an error occurs. 949 */ 950 protected void endOutput() throws Exception { 951 if (getType() == FileType.IMAGINARY) { 952 // File was created 953 handleCreate(FileType.FILE); 954 } else { 955 // File has changed 956 onChange(); 957 } 958 } 959 960 /** 961 * Determines if the file exists. 962 * 963 * @return true if the file exists, false otherwise, 964 * @throws FileSystemException if an error occurs. 965 */ 966 @Override 967 public boolean exists() throws FileSystemException { 968 return getType() != FileType.IMAGINARY; 969 } 970 971 private FileName[] extractNames(final FileObject[] objects) { 972 if (objects == null) { 973 return null; 974 } 975 return Stream.of(objects).filter(Objects::nonNull).map(FileObject::getName).toArray(FileName[]::new); 976 } 977 978 @Override 979 protected void finalize() throws Throwable { 980 fileSystem.fileObjectDestroyed(this); 981 982 super.finalize(); 983 } 984 985 /** 986 * Finds the set of matching descendants of this file, in depthwise order. 987 * 988 * @param selector The FileSelector. 989 * @return list of files or null if the base file (this object) do not exist 990 * @throws FileSystemException if an error occurs. 991 */ 992 @Override 993 public FileObject[] findFiles(final FileSelector selector) throws FileSystemException { 994 final List<FileObject> list = this.listFiles(selector); 995 return list == null ? null : list.toArray(EMPTY_ARRAY); 996 } 997 998 /** 999 * Traverses the descendants of this file, and builds a list of selected files. 1000 * 1001 * @param selector The FileSelector. 1002 * @param depthwise if true files are added after their descendants, before otherwise. 1003 * @param selected A List of the located FileObjects. 1004 * @throws FileSystemException if an error occurs. 1005 */ 1006 @Override 1007 public void findFiles(final FileSelector selector, final boolean depthwise, final List<FileObject> selected) 1008 throws FileSystemException { 1009 try { 1010 if (exists()) { 1011 // Traverse starting at this file 1012 final DefaultFileSelectorInfo info = new DefaultFileSelectorInfo(); 1013 info.setBaseFolder(this); 1014 info.setDepth(0); 1015 info.setFile(this); 1016 traverse(info, selector, depthwise, selected); 1017 } 1018 } catch (final Exception e) { 1019 throw new FileSystemException("vfs.provider/find-files.error", fileName, e); 1020 } 1021 } 1022 1023 /** 1024 * Returns the file system this file belongs to. 1025 * 1026 * @return The FileSystem this file is associated with. 1027 */ 1028 protected AFS getAbstractFileSystem() { 1029 return fileSystem; 1030 } 1031 1032 /** 1033 * Returns a child of this file. 1034 * 1035 * @param name The name of the child to locate. 1036 * @return The FileObject for the file or null if the child does not exist. 1037 * @throws FileSystemException if an error occurs. 1038 */ 1039 @Override 1040 public FileObject getChild(final String name) throws FileSystemException { 1041 // TODO - use a hashtable when there are a large number of children 1042 final FileObject[] children = getChildren(); 1043 for (final FileObject element : children) { 1044 final FileName child = element.getName(); 1045 final String childBaseName = child.getBaseName(); 1046 // TODO - use a comparator to compare names 1047 if (childBaseName.equals(name) || UriParser.decode(childBaseName).equals(name)) { 1048 return resolveFile(child); 1049 } 1050 } 1051 return null; 1052 } 1053 1054 /** 1055 * Returns the children of the file. 1056 * 1057 * @return an array of FileObjects, one per child. 1058 * @throws FileSystemException if an error occurs. 1059 */ 1060 @Override 1061 public FileObject[] getChildren() throws FileSystemException { 1062 synchronized (fileSystem) { 1063 // VFS-210 1064 if (!fileSystem.hasCapability(Capability.LIST_CHILDREN)) { 1065 throw new FileNotFolderException(fileName); 1066 } 1067 1068 /* 1069 * VFS-210 if (!getType().hasChildren()) { throw new 1070 * FileSystemException("vfs.provider/list-children-not-folder.error", name); } 1071 */ 1072 attach(); 1073 1074 // Use cached info, if present 1075 if (children != null) { 1076 return resolveFiles(children); 1077 } 1078 1079 // allow the filesystem to return resolved children. e.g. prefill type for webdav 1080 final FileObject[] childrenObjects; 1081 try { 1082 childrenObjects = doListChildrenResolved(); 1083 children = extractNames(childrenObjects); 1084 } catch (final FileSystemException exc) { 1085 // VFS-210 1086 throw exc; 1087 } catch (final Exception exc) { 1088 throw new FileSystemException("vfs.provider/list-children.error", exc, fileName); 1089 } 1090 1091 if (childrenObjects != null) { 1092 return childrenObjects; 1093 } 1094 1095 // List the children 1096 final String[] files; 1097 try { 1098 files = doListChildren(); 1099 } catch (final FileSystemException exc) { 1100 // VFS-210 1101 throw exc; 1102 } catch (final Exception exc) { 1103 throw new FileSystemException("vfs.provider/list-children.error", exc, fileName); 1104 } 1105 1106 if (files == null) { 1107 // VFS-210 1108 // honor the new doListChildren contract 1109 // return null; 1110 throw new FileNotFolderException(fileName); 1111 } 1112 if (files.length == 0) { 1113 // No children 1114 children = FileName.EMPTY_ARRAY; 1115 } else { 1116 // Create file objects for the children 1117 final FileName[] cache = new FileName[files.length]; 1118 for (int i = 0; i < files.length; i++) { 1119 final String file = "./" + files[i]; // VFS-741: assume scheme prefix is file name only 1120 cache[i] = fileSystem.getFileSystemManager().resolveName(fileName, file, NameScope.CHILD); 1121 } 1122 // VFS-285: only assign the children file names after all of them have been 1123 // resolved successfully to prevent an inconsistent internal state 1124 children = cache; 1125 } 1126 1127 return resolveFiles(children); 1128 } 1129 } 1130 1131 /** 1132 * Returns the file's content. 1133 * 1134 * @return the FileContent for this FileObject. 1135 * @throws FileSystemException if an error occurs. 1136 */ 1137 @Override 1138 public FileContent getContent() throws FileSystemException { 1139 synchronized (fileSystem) { 1140 attach(); 1141 if (content == null) { 1142 content = doCreateFileContent(); 1143 } 1144 return content; 1145 } 1146 } 1147 1148 /** 1149 * Creates the FileContentInfo factory. 1150 * 1151 * @return The FileContentInfoFactory. 1152 */ 1153 protected FileContentInfoFactory getFileContentInfoFactory() { 1154 return fileSystem.getFileSystemManager().getFileContentInfoFactory(); 1155 } 1156 1157 /** 1158 * @return FileOperations interface that provides access to the operations API. 1159 * @throws FileSystemException if an error occurs. 1160 */ 1161 @Override 1162 public FileOperations getFileOperations() throws FileSystemException { 1163 if (operations == null) { 1164 operations = new DefaultFileOperations(this); 1165 } 1166 1167 return operations; 1168 } 1169 1170 /** 1171 * Returns the file system this file belongs to. 1172 * 1173 * @return The FileSystem this file is associated with. 1174 */ 1175 @Override 1176 public FileSystem getFileSystem() { 1177 return fileSystem; 1178 } 1179 1180 /** 1181 * Returns an input stream to use to read the content of the file. 1182 * 1183 * @return The InputStream to access this file's content. 1184 * @throws FileSystemException if an error occurs. 1185 */ 1186 public InputStream getInputStream() throws FileSystemException { 1187 return getInputStream(DEFAULT_BUFFER_SIZE); 1188 } 1189 1190 /** 1191 * Returns an input stream to use to read the content of the file. 1192 * 1193 * @param bufferSize buffer size hint. 1194 * @return The InputStream to access this file's content. 1195 * @throws FileSystemException if an error occurs. 1196 */ 1197 public InputStream getInputStream(final int bufferSize) throws FileSystemException { 1198 // Get the raw input stream 1199 try { 1200 return doGetInputStream(bufferSize); 1201 } catch (final org.apache.commons.vfs2.FileNotFoundException | FileNotFoundException exc) { 1202 throw new org.apache.commons.vfs2.FileNotFoundException(fileName, exc); 1203 } catch (final FileSystemException exc) { 1204 throw exc; 1205 } catch (final UnsupportedOperationException uoe) { 1206 // TODO Remove for 3.0 1207 // Backward compatibility for subclasses before 2.5.0 1208 if (DO_GET_INPUT_STREAM_INT.equals(uoe.getMessage())) { 1209 try { 1210 // Invoke old API. 1211 return doGetInputStream(); 1212 } catch (final Exception e) { 1213 if (e instanceof FileSystemException) { 1214 throw (FileSystemException) e; 1215 } 1216 throw new FileSystemException("vfs.provider/read.error", fileName, e); 1217 } 1218 } 1219 throw uoe; 1220 } catch (final Exception exc) { 1221 throw new FileSystemException("vfs.provider/read.error", fileName, exc); 1222 } 1223 } 1224 1225 /** 1226 * Returns the name of the file. 1227 * 1228 * @return The FileName, never {@code null}. 1229 */ 1230 @Override 1231 public FileName getName() { 1232 return fileName; 1233 } 1234 1235 // TODO: remove this method for the next major version as it is unused 1236 /** 1237 * Prepares this file for writing. Makes sure it is either a file, or its parent folder exists. Returns an output 1238 * stream to use to write the content of the file to. 1239 * 1240 * @return An OutputStream where the new contents of the file can be written. 1241 * @throws FileSystemException if an error occurs. 1242 */ 1243 public OutputStream getOutputStream() throws FileSystemException { 1244 return getOutputStream(false); 1245 } 1246 1247 // TODO: mark this method as `final` and package-private for the next major version because 1248 // it shouldn't be used from anywhere other than `DefaultFileContent` 1249 /** 1250 * Prepares this file for writing. Makes sure it is either a file, or its parent folder exists. Returns an output 1251 * stream to use to write the content of the file to. 1252 * 1253 * @param bAppend true when append to the file. 1254 * Note: If the underlying file system does not support appending, a FileSystemException is thrown. 1255 * @return An OutputStream where the new contents of the file can be written. 1256 * @throws FileSystemException if an error occurs; for example: 1257 * bAppend is true, and the underlying FileSystem does not support it 1258 */ 1259 public OutputStream getOutputStream(final boolean bAppend) throws FileSystemException { 1260 /* 1261 * VFS-210 if (getType() != FileType.IMAGINARY && !getType().hasContent()) { throw new 1262 * FileSystemException("vfs.provider/write-not-file.error", name); } if (!isWriteable()) { throw new 1263 * FileSystemException("vfs.provider/write-read-only.error", name); } 1264 */ 1265 1266 if (bAppend && !fileSystem.hasCapability(Capability.APPEND_CONTENT)) { 1267 throw new FileSystemException("vfs.provider/write-append-not-supported.error", fileName); 1268 } 1269 1270 if (getType() == FileType.IMAGINARY) { 1271 // Does not exist - make sure parent does 1272 final FileObject parent = getParent(); 1273 if (parent != null) { 1274 parent.createFolder(); 1275 } 1276 } 1277 1278 // Get the raw output stream 1279 try { 1280 return doGetOutputStream(bAppend); 1281 } catch (final RuntimeException re) { 1282 throw re; 1283 } catch (final Exception exc) { 1284 throw new FileSystemException("vfs.provider/write.error", exc, fileName); 1285 } 1286 } 1287 1288 /** 1289 * Returns the parent of the file. 1290 * 1291 * @return the parent FileObject. 1292 * @throws FileSystemException if an error occurs. 1293 */ 1294 @Override 1295 public FileObject getParent() throws FileSystemException { 1296 // equals is not implemented :-/ 1297 if (this.compareTo(fileSystem.getRoot()) == 0) { 1298 if (fileSystem.getParentLayer() == null) { 1299 // Root file has no parent 1300 return null; 1301 } 1302 // Return the parent of the parent layer 1303 return fileSystem.getParentLayer().getParent(); 1304 } 1305 1306 synchronized (fileSystem) { 1307 // Locate the parent of this file 1308 if (parent == null) { 1309 final FileName name = fileName.getParent(); 1310 if (name == null) { 1311 return null; 1312 } 1313 parent = fileSystem.resolveFile(name); 1314 } 1315 return parent; 1316 } 1317 } 1318 1319 /** 1320 * Returns the receiver as a URI String for public display, like, without a password. 1321 * 1322 * @return A URI String without a password, never {@code null}. 1323 */ 1324 @Override 1325 public String getPublicURIString() { 1326 return fileName.getFriendlyURI(); 1327 } 1328 1329 /** 1330 * Returns an input/output stream to use to read and write the content of the file in and random manner. 1331 * 1332 * @param mode The RandomAccessMode. 1333 * @return The RandomAccessContent. 1334 * @throws FileSystemException if an error occurs. 1335 */ 1336 public RandomAccessContent getRandomAccessContent(final RandomAccessMode mode) throws FileSystemException { 1337 // 1338 // VFS-210 if (!getType().hasContent()) { throw new FileSystemException("vfs.provider/read-not-file.error", 1339 // name); } 1340 // 1341 if (mode.requestRead()) { 1342 if (!fileSystem.hasCapability(Capability.RANDOM_ACCESS_READ)) { 1343 throw new FileSystemException("vfs.provider/random-access-read-not-supported.error"); 1344 } 1345 if (!isReadable()) { 1346 throw new FileSystemException("vfs.provider/read-not-readable.error", fileName); 1347 } 1348 } 1349 1350 if (mode.requestWrite()) { 1351 if (!fileSystem.hasCapability(Capability.RANDOM_ACCESS_WRITE)) { 1352 throw new FileSystemException("vfs.provider/random-access-write-not-supported.error"); 1353 } 1354 if (!isWriteable()) { 1355 throw new FileSystemException("vfs.provider/write-read-only.error", fileName); 1356 } 1357 } 1358 1359 // Get the raw input stream 1360 try { 1361 return doGetRandomAccessContent(mode); 1362 } catch (final Exception exc) { 1363 throw new FileSystemException("vfs.provider/random-access.error", fileName, exc); 1364 } 1365 } 1366 1367 /** 1368 * Returns the file's type. 1369 * 1370 * @return The FileType. 1371 * @throws FileSystemException if an error occurs. 1372 */ 1373 @Override 1374 public FileType getType() throws FileSystemException { 1375 synchronized (fileSystem) { 1376 attach(); 1377 1378 // VFS-210: get the type only if requested for 1379 try { 1380 if (type == null) { 1381 setFileType(doGetType()); 1382 } 1383 if (type == null) { 1384 setFileType(FileType.IMAGINARY); 1385 } 1386 } catch (final Exception e) { 1387 throw new FileSystemException("vfs.provider/get-type.error", e, fileName); 1388 } 1389 1390 return type; 1391 } 1392 } 1393 1394 /** 1395 * Returns a URL representation of the file. 1396 * 1397 * @return The URL representation of the file. 1398 * @throws FileSystemException if an error occurs. 1399 */ 1400 @Override 1401 public URL getURL() throws FileSystemException { 1402 try { 1403 return AccessController.doPrivileged((PrivilegedExceptionAction<URL>) () -> { 1404 final StringBuilder buf = new StringBuilder(); 1405 final String scheme = UriParser.extractScheme(fileSystem.getContext().getFileSystemManager().getSchemes(), fileName.getURI(), buf); 1406 return new URL(scheme, "", -1, buf.toString(), 1407 new DefaultURLStreamHandler(fileSystem.getContext(), fileSystem.getFileSystemOptions())); 1408 }); 1409 } catch (final PrivilegedActionException e) { 1410 throw new FileSystemException("vfs.provider/get-url.error", fileName, e.getException()); 1411 } 1412 } 1413 1414 /** 1415 * Called when this file is changed. 1416 * <p> 1417 * This will only happen if you monitor the file using {@link org.apache.commons.vfs2.FileMonitor}. 1418 * </p> 1419 * 1420 * @throws Exception if an error occurs. 1421 */ 1422 protected void handleChanged() throws Exception { 1423 // Notify the file system 1424 fileSystem.fireFileChanged(this); 1425 } 1426 1427 /** 1428 * Called when this file is created. Updates cached info and notifies the parent and file system. 1429 * 1430 * @param newType The type of the file. 1431 * @throws Exception if an error occurs. 1432 */ 1433 protected void handleCreate(final FileType newType) throws Exception { 1434 synchronized (fileSystem) { 1435 if (attached) { 1436 // Fix up state 1437 injectType(newType); 1438 1439 removeChildrenCache(); 1440 1441 // Notify subclass 1442 onChange(); 1443 } 1444 1445 // Notify parent that its child list may no longer be valid 1446 notifyParent(this.getName(), newType); 1447 1448 // Notify the file system 1449 fileSystem.fireFileCreated(this); 1450 } 1451 } 1452 1453 /** 1454 * Called when this file is deleted. Updates cached info and notifies subclasses, parent and file system. 1455 * 1456 * @throws Exception if an error occurs. 1457 */ 1458 protected void handleDelete() throws Exception { 1459 synchronized (fileSystem) { 1460 if (attached) { 1461 // Fix up state 1462 injectType(FileType.IMAGINARY); 1463 removeChildrenCache(); 1464 1465 // Notify subclass 1466 onChange(); 1467 } 1468 1469 // Notify parent that its child list may no longer be valid 1470 notifyParent(this.getName(), FileType.IMAGINARY); 1471 1472 // Notify the file system 1473 fileSystem.fireFileDeleted(this); 1474 } 1475 } 1476 1477 /** 1478 * This method is meant to add an object where this object holds a strong reference then. E.g. an archive-file system 1479 * creates a list of all children and they shouldn't get garbage collected until the container is garbage collected 1480 * 1481 * @param strongRef The Object to add. 1482 */ 1483 // TODO should this be a FileObject? 1484 public void holdObject(final Object strongRef) { 1485 if (objects == null) { 1486 objects = new ArrayList<>(INITIAL_LIST_SIZE); 1487 } 1488 objects.add(strongRef); 1489 } 1490 1491 /** 1492 * Sets the file type. 1493 * 1494 * @param fileType the file type. 1495 */ 1496 protected void injectType(final FileType fileType) { 1497 setFileType(fileType); 1498 } 1499 1500 /** 1501 * Check if the internal state is "attached". 1502 * 1503 * @return true if this is the case 1504 */ 1505 @Override 1506 public boolean isAttached() { 1507 return attached; 1508 } 1509 1510 /** 1511 * Check if the content stream is open. 1512 * 1513 * @return true if this is the case 1514 */ 1515 @Override 1516 public boolean isContentOpen() { 1517 if (content == null) { 1518 return false; 1519 } 1520 1521 return content.isOpen(); 1522 } 1523 1524 /** 1525 * Determines if this file is executable. 1526 * 1527 * @return {@code true} if this file is executable, {@code false} if not. 1528 * @throws FileSystemException On error determining if this file exists. 1529 */ 1530 @Override 1531 public boolean isExecutable() throws FileSystemException { 1532 try { 1533 return exists() && doIsExecutable(); 1534 } catch (final Exception exc) { 1535 throw new FileSystemException("vfs.provider/check-is-executable.error", fileName, exc); 1536 } 1537 } 1538 1539 /** 1540 * Checks if this file is a regular file by using its file type. 1541 * 1542 * @return true if this file is a regular file. 1543 * @throws FileSystemException if an error occurs. 1544 * @see #getType() 1545 * @see FileType#FILE 1546 */ 1547 @Override 1548 public boolean isFile() throws FileSystemException { 1549 // Use equals instead of == to avoid any class loader worries. 1550 return FileType.FILE.equals(this.getType()); 1551 } 1552 1553 /** 1554 * Checks if this file is a folder by using its file type. 1555 * 1556 * @return true if this file is a regular file. 1557 * @throws FileSystemException if an error occurs. 1558 * @see #getType() 1559 * @see FileType#FOLDER 1560 */ 1561 @Override 1562 public boolean isFolder() throws FileSystemException { 1563 // Use equals instead of == to avoid any class loader worries. 1564 return FileType.FOLDER.equals(this.getType()); 1565 } 1566 1567 /** 1568 * Determines if this file can be read. 1569 * 1570 * @return true if the file is a hidden file, false otherwise. 1571 * @throws FileSystemException if an error occurs. 1572 */ 1573 @Override 1574 public boolean isHidden() throws FileSystemException { 1575 try { 1576 return exists() && doIsHidden(); 1577 } catch (final Exception exc) { 1578 throw new FileSystemException("vfs.provider/check-is-hidden.error", fileName, exc); 1579 } 1580 } 1581 1582 /** 1583 * Determines if this file can be read. 1584 * 1585 * @return true if the file can be read, false otherwise. 1586 * @throws FileSystemException if an error occurs. 1587 */ 1588 @Override 1589 public boolean isReadable() throws FileSystemException { 1590 try { 1591 return exists() && doIsReadable(); 1592 } catch (final Exception exc) { 1593 throw new FileSystemException("vfs.provider/check-is-readable.error", fileName, exc); 1594 } 1595 } 1596 1597 /** 1598 * Checks if this fileObject is the same file as {@code destFile} just with a different name. E.g. for 1599 * case-insensitive file systems like windows. 1600 * 1601 * @param destFile The file to compare to. 1602 * @return true if the FileObjects are the same. 1603 * @throws FileSystemException if an error occurs. 1604 */ 1605 protected boolean isSameFile(final FileObject destFile) throws FileSystemException { 1606 attach(); 1607 return doIsSameFile(destFile); 1608 } 1609 1610 /** 1611 * Determines if this file can be read. 1612 * 1613 * @return true if the file can be read, false otherwise. 1614 * @throws FileSystemException if an error occurs. 1615 * @since 2.4 1616 */ 1617 @Override 1618 public boolean isSymbolicLink() throws FileSystemException { 1619 try { 1620 return exists() && doIsSymbolicLink(); 1621 } catch (final Exception exc) { 1622 throw new FileSystemException("vfs.provider/check-is-symbolic-link.error", fileName, exc); 1623 } 1624 } 1625 1626 /** 1627 * Determines if this file can be written to. 1628 * 1629 * @return true if the file can be written to, false otherwise. 1630 * @throws FileSystemException if an error occurs. 1631 */ 1632 @Override 1633 public boolean isWriteable() throws FileSystemException { 1634 try { 1635 if (exists()) { 1636 return doIsWriteable(); 1637 } 1638 final FileObject parent = getParent(); 1639 if (parent != null) { 1640 return parent.isWriteable(); 1641 } 1642 return true; 1643 } catch (final Exception exc) { 1644 throw new FileSystemException("vfs.provider/check-is-writable.error", fileName, exc); 1645 } 1646 } 1647 1648 /** 1649 * Returns an iterator over a set of all FileObject in this file object. 1650 * 1651 * @return an Iterator. 1652 */ 1653 @Override 1654 public Iterator<FileObject> iterator() { 1655 try { 1656 return listFiles(Selectors.SELECT_ALL).iterator(); 1657 } catch (final FileSystemException e) { 1658 throw new IllegalStateException(e); 1659 } 1660 } 1661 1662 /** 1663 * Lists the set of matching descendants of this file, in depthwise order. 1664 * 1665 * @param selector The FileSelector. 1666 * @return list of files or null if the base file (this object) do not exist or the {@code selector} is null 1667 * @throws FileSystemException if an error occurs. 1668 */ 1669 public List<FileObject> listFiles(final FileSelector selector) throws FileSystemException { 1670 if (!exists() || selector == null) { 1671 return null; 1672 } 1673 1674 final ArrayList<FileObject> list = new ArrayList<>(); 1675 this.findFiles(selector, true, list); 1676 return list; 1677 } 1678 1679 /** 1680 * Moves (rename) the file to another one. 1681 * 1682 * @param destFile The target FileObject. 1683 * @throws FileSystemException if an error occurs. 1684 */ 1685 @Override 1686 public void moveTo(final FileObject destFile) throws FileSystemException { 1687 if (canRenameTo(destFile)) { 1688 if (!getParent().isWriteable()) { 1689 throw new FileSystemException("vfs.provider/rename-parent-read-only.error", getName(), 1690 getParent().getName()); 1691 } 1692 } else if (!isWriteable()) { 1693 throw new FileSystemException("vfs.provider/rename-read-only.error", getName()); 1694 } 1695 1696 if (destFile.exists() && !isSameFile(destFile)) { 1697 destFile.deleteAll(); 1698 // throw new FileSystemException("vfs.provider/rename-dest-exists.error", destFile.getName()); 1699 } 1700 1701 if (canRenameTo(destFile)) { 1702 // issue rename on same filesystem 1703 try { 1704 attach(); 1705 // remember type to avoid attach 1706 final FileType srcType = getType(); 1707 1708 doRename(destFile); 1709 1710 FileObjectUtils.getAbstractFileObject(destFile).handleCreate(srcType); 1711 destFile.close(); // now the destFile is no longer imaginary. force reattach. 1712 1713 handleDelete(); // fire delete-events. This file-object (src) is like deleted. 1714 } catch (final RuntimeException re) { 1715 throw re; 1716 } catch (final Exception exc) { 1717 throw new FileSystemException("vfs.provider/rename.error", exc, getName(), destFile.getName()); 1718 } 1719 } else { 1720 // different fs - do the copy/delete stuff 1721 1722 destFile.copyFrom(this, Selectors.SELECT_SELF); 1723 1724 if ((destFile.getType().hasContent() 1725 && destFile.getFileSystem().hasCapability(Capability.SET_LAST_MODIFIED_FILE) 1726 || destFile.getType().hasChildren() 1727 && destFile.getFileSystem().hasCapability(Capability.SET_LAST_MODIFIED_FOLDER)) 1728 && fileSystem.hasCapability(Capability.GET_LAST_MODIFIED)) { 1729 destFile.getContent().setLastModifiedTime(this.getContent().getLastModifiedTime()); 1730 } 1731 1732 deleteSelf(); 1733 } 1734 1735 } 1736 1737 /** 1738 * Called after this file-object closed all its streams. 1739 */ 1740 protected void notifyAllStreamsClosed() { 1741 // noop 1742 } 1743 1744 /** 1745 * Notify the parent of a change to its children, when a child is created or deleted. 1746 * 1747 * @param childName The name of the child. 1748 * @param newType The type of the child. 1749 * @throws Exception if an error occurs. 1750 */ 1751 private void notifyParent(final FileName childName, final FileType newType) throws Exception { 1752 if (parent == null) { 1753 final FileName parentName = fileName.getParent(); 1754 if (parentName != null) { 1755 // Locate the parent, if it is cached 1756 parent = fileSystem.getFileFromCache(parentName); 1757 } 1758 } 1759 1760 if (parent != null) { 1761 FileObjectUtils.getAbstractFileObject(parent).childrenChanged(childName, newType); 1762 } 1763 } 1764 1765 /** 1766 * Called when the type or content of this file changes. 1767 * <p> 1768 * This implementation does nothing. 1769 * </p> 1770 * 1771 * @throws Exception if an error occurs. 1772 */ 1773 protected void onChange() throws Exception { 1774 // noop 1775 } 1776 1777 /** 1778 * Called when the children of this file change. Allows subclasses to refresh any cached information about the 1779 * children of this file. 1780 * <p> 1781 * This implementation does nothing. 1782 * </p> 1783 * 1784 * @param child The name of the child that changed. 1785 * @param newType The type of the file. 1786 * @throws Exception if an error occurs. 1787 */ 1788 protected void onChildrenChanged(final FileName child, final FileType newType) throws Exception { 1789 // noop 1790 } 1791 1792 /** 1793 * This will prepare the fileObject to get resynchronized with the underlying file system if required. 1794 * 1795 * @throws FileSystemException if an error occurs. 1796 */ 1797 @Override 1798 public void refresh() throws FileSystemException { 1799 // Detach from the file 1800 try { 1801 detach(); 1802 } catch (final Exception e) { 1803 throw new FileSystemException("vfs.provider/resync.error", fileName, e); 1804 } 1805 } 1806 1807 private void removeChildrenCache() { 1808 children = null; 1809 } 1810 1811 private FileObject resolveFile(final FileName child) throws FileSystemException { 1812 return fileSystem.resolveFile(child); 1813 } 1814 1815 /** 1816 * Finds a file, relative to this file. 1817 * 1818 * @param path The path of the file to locate. Can either be a relative path, which is resolved relative to this 1819 * file, or an absolute path, which is resolved relative to the file system that contains this file. 1820 * @return The FileObject. 1821 * @throws FileSystemException if an error occurs. 1822 */ 1823 @Override 1824 public FileObject resolveFile(final String path) throws FileSystemException { 1825 final FileName otherName = fileSystem.getFileSystemManager().resolveName(fileName, path); 1826 return fileSystem.resolveFile(otherName); 1827 } 1828 1829 /** 1830 * Returns a child by name. 1831 * 1832 * @param name The name of the child to locate. 1833 * @param scope the NameScope. 1834 * @return The FileObject for the file or null if the child does not exist. 1835 * @throws FileSystemException if an error occurs. 1836 */ 1837 @Override 1838 public FileObject resolveFile(final String name, final NameScope scope) throws FileSystemException { 1839 // return fs.resolveFile(this.name.resolveName(name, scope)); 1840 return fileSystem.resolveFile(fileSystem.getFileSystemManager().resolveName(fileName, name, scope)); 1841 } 1842 1843 private FileObject[] resolveFiles(final FileName[] children) throws FileSystemException { 1844 if (children == null) { 1845 return null; 1846 } 1847 1848 final FileObject[] objects = new FileObject[children.length]; 1849 for (int iterChildren = 0; iterChildren < children.length; iterChildren++) { 1850 objects[iterChildren] = resolveFile(children[iterChildren]); 1851 } 1852 1853 return objects; 1854 } 1855 1856 @Override 1857 public boolean setExecutable(final boolean readable, final boolean ownerOnly) throws FileSystemException { 1858 try { 1859 return exists() && doSetExecutable(readable, ownerOnly); 1860 } catch (final Exception exc) { 1861 throw new FileSystemException("vfs.provider/set-executable.error", fileName, exc); 1862 } 1863 } 1864 1865 private void setFileType(final FileType type) { 1866 if (type != null && type != FileType.IMAGINARY) { 1867 Uncheck.run(() -> fileName.setType(type)); 1868 } 1869 this.type = type; 1870 } 1871 1872 @Override 1873 public boolean setReadable(final boolean readable, final boolean ownerOnly) throws FileSystemException { 1874 try { 1875 return exists() && doSetReadable(readable, ownerOnly); 1876 } catch (final Exception exc) { 1877 throw new FileSystemException("vfs.provider/set-readable.error", fileName, exc); 1878 } 1879 } 1880 1881 @Override 1882 public boolean setWritable(final boolean readable, final boolean ownerOnly) throws FileSystemException { 1883 try { 1884 return exists() && doSetWritable(readable, ownerOnly); 1885 } catch (final Exception exc) { 1886 throw new FileSystemException("vfs.provider/set-writable.error", fileName, exc); 1887 } 1888 } 1889 1890 /** 1891 * Returns the URI as a String. 1892 * 1893 * @return the URI as a String. 1894 */ 1895 @Override 1896 public String toString() { 1897 return fileName.getURI(); 1898 } 1899}