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