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