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