View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.vfs2.provider;
18  
19  import java.io.BufferedInputStream;
20  import java.io.FileNotFoundException;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.OutputStream;
24  import java.net.URL;
25  import java.security.AccessController;
26  import java.security.PrivilegedActionException;
27  import java.security.PrivilegedExceptionAction;
28  import java.security.cert.Certificate;
29  import java.util.ArrayList;
30  import java.util.Arrays;
31  import java.util.Collections;
32  import java.util.Iterator;
33  import java.util.List;
34  import java.util.Map;
35  import java.util.Objects;
36  import java.util.stream.Stream;
37  
38  import org.apache.commons.io.function.Uncheck;
39  import org.apache.commons.vfs2.Capability;
40  import org.apache.commons.vfs2.FileContent;
41  import org.apache.commons.vfs2.FileContentInfoFactory;
42  import org.apache.commons.vfs2.FileName;
43  import org.apache.commons.vfs2.FileNotFolderException;
44  import org.apache.commons.vfs2.FileObject;
45  import org.apache.commons.vfs2.FileSelector;
46  import org.apache.commons.vfs2.FileSystem;
47  import org.apache.commons.vfs2.FileSystemException;
48  import org.apache.commons.vfs2.FileType;
49  import org.apache.commons.vfs2.NameScope;
50  import org.apache.commons.vfs2.RandomAccessContent;
51  import org.apache.commons.vfs2.Selectors;
52  import org.apache.commons.vfs2.operations.DefaultFileOperations;
53  import org.apache.commons.vfs2.operations.FileOperations;
54  import org.apache.commons.vfs2.util.FileObjectUtils;
55  import org.apache.commons.vfs2.util.RandomAccessMode;
56  
57  /**
58   * A partial file object implementation.
59   *
60   * TODO - Chop this class up - move all the protected methods to several interfaces, so that structure and content can
61   * be separately overridden.
62   *
63   * <p>
64   * TODO - Check caps in methods like getChildren(), etc, and give better error messages (eg 'this file type does not
65   * support listing children', vs 'this is not a folder')
66   * </p>
67   *
68   * @param <AFS> An AbstractFileSystem subclass
69   */
70  public abstract class AbstractFileObject<AFS extends AbstractFileSystem> implements FileObject {
71  
72      /**
73       * Same as {@link BufferedInputStream}.
74       */
75      public static final int DEFAULT_BUFFER_SIZE = 8192;
76  
77      private static final int INITIAL_LIST_SIZE = 5;
78  
79      private static final String DO_GET_INPUT_STREAM_INT = "doGetInputStream(int)";
80  
81      /**
82       * Traverses a file.
83       */
84      private static void traverse(final DefaultFileSelectorInfo fileInfo, final FileSelector selector,
85              final boolean depthwise, final List<FileObject> selected) throws Exception {
86          // Check the file itself
87          final FileObject file = fileInfo.getFile();
88          final int index = selected.size();
89  
90          // If the file is a folder, traverse it
91          if (file.getType().hasChildren() && selector.traverseDescendants(fileInfo)) {
92              final int curDepth = fileInfo.getDepth();
93              fileInfo.setDepth(curDepth + 1);
94  
95              // Traverse the children
96              final FileObject[] children = file.getChildren();
97              for (final FileObject child : children) {
98                  fileInfo.setFile(child);
99                  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 }