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