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