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