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