1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.vfs2.provider;
18
19 import java.io.BufferedInputStream;
20 import java.io.FileNotFoundException;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.OutputStream;
24 import java.net.URL;
25 import java.security.AccessController;
26 import java.security.PrivilegedActionException;
27 import java.security.PrivilegedExceptionAction;
28 import java.security.cert.Certificate;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.Collections;
32 import java.util.Iterator;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Objects;
36 import java.util.concurrent.atomic.AtomicReference;
37 import java.util.stream.Stream;
38
39 import org.apache.commons.io.IOUtils;
40 import org.apache.commons.io.function.Uncheck;
41 import org.apache.commons.vfs2.Capability;
42 import org.apache.commons.vfs2.FileContent;
43 import org.apache.commons.vfs2.FileContentInfoFactory;
44 import org.apache.commons.vfs2.FileName;
45 import org.apache.commons.vfs2.FileNotFolderException;
46 import org.apache.commons.vfs2.FileObject;
47 import org.apache.commons.vfs2.FileSelector;
48 import org.apache.commons.vfs2.FileSystem;
49 import org.apache.commons.vfs2.FileSystemException;
50 import org.apache.commons.vfs2.FileType;
51 import org.apache.commons.vfs2.NameScope;
52 import org.apache.commons.vfs2.RandomAccessContent;
53 import org.apache.commons.vfs2.Selectors;
54 import org.apache.commons.vfs2.operations.DefaultFileOperations;
55 import org.apache.commons.vfs2.operations.FileOperations;
56 import org.apache.commons.vfs2.util.FileObjectUtils;
57 import org.apache.commons.vfs2.util.RandomAccessMode;
58
59 /**
60 * A partial file object implementation.
61 *
62 * TODO - Chop this class up - move all the protected methods to several interfaces, so that structure and content can
63 * be separately overridden.
64 *
65 * <p>
66 * TODO - Check caps in methods like getChildren(), etc, and give better error messages (eg 'this file type does not
67 * support listing children', vs 'this is not a folder')
68 * </p>
69 *
70 * @param <AFS> An AbstractFileSystem subclass
71 */
72 public abstract class AbstractFileObject<AFS extends AbstractFileSystem> implements FileObject {
73
74 /**
75 * Same as {@link BufferedInputStream}.
76 */
77 public static final int DEFAULT_BUFFER_SIZE = 8192;
78
79 private static final int INITIAL_LIST_SIZE = 5;
80
81 private static final String DO_GET_INPUT_STREAM_INT = "doGetInputStream(int)";
82
83 /**
84 * Traverses a file.
85 */
86 private static void traverse(final DefaultFileSelectorInfo fileInfo, final FileSelector selector,
87 final boolean depthwise, final List<FileObject> selected) throws Exception {
88 // Check the file itself
89 final FileObject file = fileInfo.getFile();
90 final int index = selected.size();
91
92 // If the file is a folder, traverse it
93 if (file.getType().hasChildren() && selector.traverseDescendants(fileInfo)) {
94 final int curDepth = fileInfo.getDepth();
95 fileInfo.setDepth(curDepth + 1);
96
97 // Traverse the children
98 final FileObject[] children = file.getChildren();
99 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 }