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