/* | |
* Licensed to the Apache Software Foundation (ASF) under one or more | |
* contributor license agreements. See the NOTICE file distributed with | |
* this work for additional information regarding copyright ownership. | |
* The ASF licenses this file to You under the Apache License, Version 2.0 | |
* (the "License"); you may not use this file except in compliance with | |
* the License. You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package org.apache.commons.vfs2.provider; | |
import java.io.BufferedInputStream; | |
import java.io.FileNotFoundException; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.io.OutputStream; | |
import java.net.URL; | |
import java.security.AccessController; | |
import java.security.PrivilegedActionException; | |
import java.security.PrivilegedExceptionAction; | |
import java.security.cert.Certificate; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.Collections; | |
import java.util.Iterator; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Objects; | |
import java.util.stream.Stream; | |
import org.apache.commons.vfs2.Capability; | |
import org.apache.commons.vfs2.FileContent; | |
import org.apache.commons.vfs2.FileContentInfoFactory; | |
import org.apache.commons.vfs2.FileName; | |
import org.apache.commons.vfs2.FileNotFolderException; | |
import org.apache.commons.vfs2.FileObject; | |
import org.apache.commons.vfs2.FileSelector; | |
import org.apache.commons.vfs2.FileSystem; | |
import org.apache.commons.vfs2.FileSystemException; | |
import org.apache.commons.vfs2.FileType; | |
import org.apache.commons.vfs2.NameScope; | |
import org.apache.commons.vfs2.RandomAccessContent; | |
import org.apache.commons.vfs2.Selectors; | |
import org.apache.commons.vfs2.operations.DefaultFileOperations; | |
import org.apache.commons.vfs2.operations.FileOperations; | |
import org.apache.commons.vfs2.util.FileObjectUtils; | |
import org.apache.commons.vfs2.util.RandomAccessMode; | |
/** | |
* A partial file object implementation. | |
* | |
* TODO - Chop this class up - move all the protected methods to several interfaces, so that structure and content can | |
* be separately overridden. | |
* | |
* <p> | |
* TODO - Check caps in methods like getChildren(), etc, and give better error messages (eg 'this file type does not | |
* support listing children', vs 'this is not a folder') | |
* </p> | |
* | |
* @param <AFS> An AbstractFileSystem subclass | |
*/ | |
public abstract class AbstractFileObject<AFS extends AbstractFileSystem> implements FileObject { | |
/** | |
* Same as {@link BufferedInputStream}. | |
*/ | |
public static final int DEFAULT_BUFFER_SIZE = 8192; | |
private static final int INITIAL_LIST_SIZE = 5; | |
private static final String DO_GET_INPUT_STREAM_INT = "doGetInputStream(int)"; | |
private final AbstractFileName fileName; | |
private final AFS fileSystem; | |
private FileContent content; | |
// Cached info | |
private boolean attached; | |
private FileType type; | |
private FileObject parent; | |
// Changed to hold only the name of the children and let the object | |
// go into the global files cache | |
// private FileObject[] children; | |
private FileName[] children; | |
private List<Object> objects; | |
/** | |
* FileServices instance. | |
*/ | |
private FileOperations operations; | |
/** | |
* Constructs a new instance. | |
* | |
* @param fileName the file name. | |
* @param fileSystem the file system. | |
*/ | |
protected AbstractFileObject(final AbstractFileName fileName, final AFS fileSystem) { | |
this.fileName = fileName; | |
this.fileSystem = fileSystem; | |
fileSystem.fileObjectHanded(this); | |
} | |
/** | |
* Traverses a file. | |
*/ | |
private static void traverse(final DefaultFileSelectorInfo fileInfo, final FileSelector selector, | |
final boolean depthwise, final List<FileObject> selected) throws Exception { | |
// Check the file itself | |
final FileObject file = fileInfo.getFile(); | |
final int index = selected.size(); | |
// If the file is a folder, traverse it | |
if (file.getType().hasChildren() && selector.traverseDescendents(fileInfo)) { | |
final int curDepth = fileInfo.getDepth(); | |
fileInfo.setDepth(curDepth + 1); | |
// Traverse the children | |
final FileObject[] children = file.getChildren(); | |
for (final FileObject child : children) { | |
fileInfo.setFile(child); | |
traverse(fileInfo, selector, depthwise, selected); | |
} | |
fileInfo.setFile(file); | |
fileInfo.setDepth(curDepth); | |
} | |
// Add the file if doing depthwise traversal | |
if (selector.includeFile(fileInfo)) { | |
if (depthwise) { | |
// Add this file after its descendants | |
selected.add(file); | |
} else { | |
// Add this file before its descendants | |
selected.add(index, file); | |
} | |
} | |
} | |
/** | |
* Attaches to the file. | |
* | |
* @throws FileSystemException if an error occurs. | |
*/ | |
private void attach() throws FileSystemException { | |
synchronized (fileSystem) { | |
if (attached) { | |
return; | |
} | |
try { | |
// Attach and determine the file type | |
doAttach(); | |
attached = true; | |
// now the type could already be injected by doAttach (e.g. from parent to child) | |
/* | |
* VFS-210: determine the type when really asked fore if (type == null) { setFileType(doGetType()); } if | |
* (type == null) { setFileType(FileType.IMAGINARY); } | |
*/ | |
} catch (final Exception exc) { | |
throw new FileSystemException("vfs.provider/get-type.error", exc, fileName); | |
} | |
// fs.fileAttached(this); | |
} | |
} | |
/** | |
* Queries the object if a simple rename to the file name of {@code newfile} is possible. | |
* | |
* @param newfile the new file name | |
* @return true if rename is possible | |
*/ | |
@Override | |
public boolean canRenameTo(final FileObject newfile) { | |
return fileSystem == newfile.getFileSystem(); | |
} | |
/** | |
* Notifies the file that its children have changed. | |
* | |
* @param childName The name of the child. | |
* @param newType The type of the child. | |
* @throws Exception if an error occurs. | |
*/ | |
protected void childrenChanged(final FileName childName, final FileType newType) throws Exception { | |
// TODO - this may be called when not attached | |
if (children != null && childName != null && newType != null) { | |
// TODO - figure out if children[] can be replaced by list | |
final ArrayList<FileName> list = new ArrayList<>(Arrays.asList(children)); | |
if (newType.equals(FileType.IMAGINARY)) { | |
list.remove(childName); | |
} else { | |
list.add(childName); | |
} | |
children = list.toArray(FileName.EMPTY_ARRAY); | |
} | |
// removeChildrenCache(); | |
onChildrenChanged(childName, newType); | |
} | |
/** | |
* Closes this file, and its content. | |
* | |
* @throws FileSystemException if an error occurs. | |
*/ | |
@Override | |
public void close() throws FileSystemException { | |
FileSystemException exc = null; | |
synchronized (fileSystem) { | |
// Close the content | |
if (content != null) { | |
try { | |
content.close(); | |
content = null; | |
} catch (final FileSystemException e) { | |
exc = e; | |
} | |
} | |
// Detach from the file | |
try { | |
detach(); | |
} catch (final Exception e) { | |
exc = new FileSystemException("vfs.provider/close.error", fileName, e); | |
} | |
if (exc != null) { | |
throw exc; | |
} | |
} | |
} | |
/** | |
* Compares two FileObjects (ignores case). | |
* | |
* @param file the object to compare. | |
* @return a negative integer, zero, or a positive integer when this object is less than, equal to, or greater than | |
* the given object. | |
*/ | |
@Override | |
public int compareTo(final FileObject file) { | |
if (file == null) { | |
return 1; | |
} | |
return this.toString().compareToIgnoreCase(file.toString()); | |
} | |
/** | |
* Copies another file to this file. | |
* | |
* @param file The FileObject to copy. | |
* @param selector The FileSelector. | |
* @throws FileSystemException if an error occurs. | |
*/ | |
@Override | |
public void copyFrom(final FileObject file, final FileSelector selector) throws FileSystemException { | |
if (!FileObjectUtils.exists(file)) { | |
throw new FileSystemException("vfs.provider/copy-missing-file.error", file); | |
} | |
// Locate the files to copy across | |
final ArrayList<FileObject> files = new ArrayList<>(); | |
file.findFiles(selector, false, files); | |
// Copy everything across | |
for (final FileObject srcFile : files) { | |
// Determine the destination file | |
final String relPath = file.getName().getRelativeName(srcFile.getName()); | |
final FileObject destFile = resolveFile(relPath, NameScope.DESCENDENT_OR_SELF); | |
// Clean up the destination file, if necessary | |
if (FileObjectUtils.exists(destFile) && destFile.getType() != srcFile.getType()) { | |
// The destination file exists, and is not of the same type, | |
// so delete it | |
// TODO - add a pluggable policy for deleting and overwriting existing files | |
destFile.deleteAll(); | |
} | |
// Copy across | |
try { | |
if (srcFile.getType().hasContent()) { | |
FileObjectUtils.writeContent(srcFile, destFile); | |
} else if (srcFile.getType().hasChildren()) { | |
destFile.createFolder(); | |
} | |
} catch (final IOException e) { | |
throw new FileSystemException("vfs.provider/copy-file.error", e, srcFile, destFile); | |
} | |
} | |
} | |
/** | |
* Creates this file, if it does not exist. | |
* | |
* @throws FileSystemException if an error occurs. | |
*/ | |
@Override | |
public void createFile() throws FileSystemException { | |
synchronized (fileSystem) { | |
try { | |
// VFS-210: We do not want to trunc any existing file, checking for its existence is | |
// still required | |
if (exists() && !isFile()) { | |
throw new FileSystemException("vfs.provider/create-file.error", fileName); | |
} | |
if (!exists()) { | |
try (FileContent content = getContent()) { | |
if (content != null) { | |
try (OutputStream ignored = content.getOutputStream()) { | |
// Avoids NPE on OutputStream#close() | |
} | |
} | |
} | |
} | |
} catch (final RuntimeException re) { | |
throw re; | |
} catch (final Exception e) { | |
throw new FileSystemException("vfs.provider/create-file.error", fileName, e); | |
} | |
} | |
} | |
/** | |
* Creates this folder, if it does not exist. Also creates any ancestor files which do not exist. | |
* | |
* @throws FileSystemException if an error occurs. | |
*/ | |
@Override | |
public void createFolder() throws FileSystemException { | |
synchronized (fileSystem) { | |
// VFS-210: we create a folder only if it does not already exist. So this check should be safe. | |
if (getType().hasChildren()) { | |
// Already exists as correct type | |
return; | |
} | |
if (getType() != FileType.IMAGINARY) { | |
throw new FileSystemException("vfs.provider/create-folder-mismatched-type.error", fileName); | |
} | |
/* | |
* VFS-210: checking for writable is not always possible as the security constraint might be more complex | |
* if (!isWriteable()) { throw new FileSystemException("vfs.provider/create-folder-read-only.error", name); | |
* } | |
*/ | |
// Traverse up the hierarchy and make sure everything is a folder | |
final FileObject parent = getParent(); | |
if (parent != null) { | |
parent.createFolder(); | |
} | |
try { | |
// Create the folder | |
doCreateFolder(); | |
// Update cached info | |
handleCreate(FileType.FOLDER); | |
} catch (final RuntimeException re) { | |
throw re; | |
} catch (final Exception exc) { | |
throw new FileSystemException("vfs.provider/create-folder.error", fileName, exc); | |
} | |
} | |
} | |
/** | |
* Deletes this file. | |
* <p> | |
* TODO - This will not fail if this is a non-empty folder. | |
* </p> | |
* | |
* @return true if this object has been deleted | |
* @throws FileSystemException if an error occurs. | |
*/ | |
@Override | |
public boolean delete() throws FileSystemException { | |
return delete(Selectors.SELECT_SELF) > 0; | |
} | |
/** | |
* Deletes this file, and all children matching the {@code selector}. | |
* | |
* @param selector The FileSelector. | |
* @return the number of deleted files. | |
* @throws FileSystemException if an error occurs. | |
*/ | |
@Override | |
public int delete(final FileSelector selector) throws FileSystemException { | |
int nuofDeleted = 0; | |
/* | |
* VFS-210 if (getType() == FileType.IMAGINARY) { // File does not exist return nuofDeleted; } | |
*/ | |
// Locate all the files to delete | |
final ArrayList<FileObject> files = new ArrayList<>(); | |
findFiles(selector, true, files); | |
// Delete 'em | |
for (final FileObject fileObject : files) { | |
final AbstractFileObject file = FileObjectUtils.getAbstractFileObject(fileObject); | |
// file.attach(); | |
// VFS-210: It seems impossible to me that findFiles will return a list with hidden files/directories | |
// in it, else it would not be hidden. Checking for the file-type seems ok in this case | |
// If the file is a folder, make sure all its children have been deleted | |
if (file.getType().hasChildren() && file.getChildren().length != 0) { | |
// Skip - as the selector forced us not to delete all files | |
continue; | |
} | |
// Delete the file | |
if (file.deleteSelf()) { | |
nuofDeleted++; | |
} | |
} | |
return nuofDeleted; | |
} | |
/** | |
* Deletes this file and all children. Shorthand for {@code delete(Selectors.SELECT_ALL)} | |
* | |
* @return the number of deleted files. | |
* @throws FileSystemException if an error occurs. | |
* @see #delete(FileSelector) | |
* @see Selectors#SELECT_ALL | |
*/ | |
@Override | |
public int deleteAll() throws FileSystemException { | |
return this.delete(Selectors.SELECT_ALL); | |
} | |
/** | |
* Deletes this file, once all its children have been deleted | |
* | |
* @return true if this file has been deleted | |
* @throws FileSystemException if an error occurs. | |
*/ | |
private boolean deleteSelf() throws FileSystemException { | |
synchronized (fileSystem) { | |
// It's possible to delete a read-only file if you have write-execute access to the directory | |
/* | |
* VFS-210 if (getType() == FileType.IMAGINARY) { // File does not exist return false; } | |
*/ | |
try { | |
// Delete the file | |
doDelete(); | |
// Update cached info | |
handleDelete(); | |
} catch (final RuntimeException re) { | |
throw re; | |
} catch (final Exception exc) { | |
throw new FileSystemException("vfs.provider/delete.error", exc, fileName); | |
} | |
return true; | |
} | |
} | |
/** | |
* Detaches this file, invalidating all cached info. This will force a call to {@link #doAttach} next time this file | |
* is used. | |
* | |
* @throws Exception if an error occurs. | |
*/ | |
private void detach() throws Exception { | |
synchronized (fileSystem) { | |
if (attached) { | |
try { | |
doDetach(); | |
} finally { | |
attached = false; | |
setFileType(null); | |
parent = null; | |
// fs.fileDetached(this); | |
removeChildrenCache(); | |
// children = null; | |
} | |
} | |
} | |
} | |
/** | |
* Attaches this file object to its file resource. | |
* <p> | |
* This method is called before any of the doBlah() or onBlah() methods. Sub-classes can use this method to perform | |
* lazy initialization. | |
* </p> | |
* <p> | |
* This implementation does nothing. | |
* </p> | |
* | |
* @throws Exception if an error occurs. | |
*/ | |
protected void doAttach() throws Exception { | |
// noop | |
} | |
/** | |
* Create a FileContent implementation. | |
* | |
* @return The FileContent. | |
* @throws FileSystemException if an error occurs. | |
* @since 2.0 | |
*/ | |
protected FileContent doCreateFileContent() throws FileSystemException { | |
return new DefaultFileContent(this, getFileContentInfoFactory()); | |
} | |
/** | |
* Creates this file as a folder. Is only called when: | |
* <ul> | |
* <li>{@link #doGetType} returns {@link FileType#IMAGINARY}.</li> | |
* <li>The parent folder exists and is writable, or this file is the root of the file system.</li> | |
* </ul> | |
* This implementation throws an exception. | |
* | |
* @throws Exception if an error occurs. | |
*/ | |
protected void doCreateFolder() throws Exception { | |
throw new FileSystemException("vfs.provider/create-folder-not-supported.error"); | |
} | |
/** | |
* Deletes the file. Is only called when: | |
* <ul> | |
* <li>{@link #doGetType} does not return {@link FileType#IMAGINARY}.</li> | |
* <li>{@link #doIsWriteable} returns true.</li> | |
* <li>This file has no children, if a folder.</li> | |
* </ul> | |
* This implementation throws an exception. | |
* | |
* @throws Exception if an error occurs. | |
*/ | |
protected void doDelete() throws Exception { | |
throw new FileSystemException("vfs.provider/delete-not-supported.error"); | |
} | |
/** | |
* Detaches this file object from its file resource. | |
* <p> | |
* Called when this file is closed. Note that the file object may be reused later, so should be able to be | |
* reattached. | |
* </p> | |
* <p> | |
* This implementation does nothing. | |
* </p> | |
* | |
* @throws Exception if an error occurs. | |
*/ | |
protected void doDetach() throws Exception { | |
// noop | |
} | |
/** | |
* Returns the attributes of this file. Is only called if {@link #doGetType} does not return | |
* {@link FileType#IMAGINARY}. | |
* <p> | |
* This implementation always returns an empty map. | |
* </p> | |
* | |
* @return The attributes of the file. | |
* @throws Exception if an error occurs. | |
*/ | |
protected Map<String, Object> doGetAttributes() throws Exception { | |
return Collections.emptyMap(); | |
} | |
/** | |
* Returns the certificates used to sign this file. Is only called if {@link #doGetType} does not return | |
* {@link FileType#IMAGINARY}. | |
* <p> | |
* This implementation always returns null. | |
* </p> | |
* | |
* @return The certificates used to sign the file. | |
* @throws Exception if an error occurs. | |
*/ | |
protected Certificate[] doGetCertificates() throws Exception { | |
return null; | |
} | |
/** | |
* Returns the size of the file content (in bytes). Is only called if {@link #doGetType} returns | |
* {@link FileType#FILE}. | |
* | |
* @return The size of the file in bytes. | |
* @throws Exception if an error occurs. | |
*/ | |
protected abstract long doGetContentSize() throws Exception; | |
/** | |
* Creates an input stream to read the file content from. Is only called if {@link #doGetType} returns | |
* {@link FileType#FILE}. | |
* <p> | |
* It is guaranteed that there are no open output streams for this file when this method is called. | |
* </p> | |
* <p> | |
* The returned stream does not have to be buffered. | |
* </p> | |
* | |
* @return An InputStream to read the file content. | |
* @throws Exception if an error occurs. | |
*/ | |
protected InputStream doGetInputStream() throws Exception { | |
// Backward compatibility. | |
return doGetInputStream(DEFAULT_BUFFER_SIZE); | |
} | |
/** | |
* Creates an input stream to read the file content from. Is only called if {@link #doGetType} returns | |
* {@link FileType#FILE}. | |
* <p> | |
* It is guaranteed that there are no open output streams for this file when this method is called. | |
* </p> | |
* <p> | |
* The returned stream does not have to be buffered. | |
* </p> | |
* @param bufferSize Buffer size hint. | |
* @return An InputStream to read the file content. | |
* @throws Exception if an error occurs. | |
*/ | |
protected InputStream doGetInputStream(final int bufferSize) throws Exception { | |
throw new UnsupportedOperationException(DO_GET_INPUT_STREAM_INT); | |
} | |
/** | |
* Returns the last modified time of this file. Is only called if {@link #doGetType} does not return | |
* <p> | |
* This implementation throws an exception. | |
* </p> | |
* | |
* @return The last modification time. | |
* @throws Exception if an error occurs. | |
*/ | |
protected long doGetLastModifiedTime() throws Exception { | |
throw new FileSystemException("vfs.provider/get-last-modified-not-supported.error"); | |
} | |
/** | |
* Creates an output stream to write the file content to. Is only called if: | |
* <ul> | |
* <li>{@link #doIsWriteable} returns true. | |
* <li>{@link #doGetType} returns {@link FileType#FILE}, or {@link #doGetType} returns {@link FileType#IMAGINARY}, | |
* and the file's parent exists and is a folder. | |
* </ul> | |
* It is guaranteed that there are no open stream (input or output) for this file when this method is called. | |
* <p> | |
* The returned stream does not have to be buffered. | |
* </p> | |
* <p> | |
* This implementation throws an exception. | |
* </p> | |
* | |
* @param bAppend true if the file should be appended to, false if it should be overwritten. | |
* @return An OutputStream to write to the file. | |
* @throws Exception if an error occurs. | |
*/ | |
protected OutputStream doGetOutputStream(final boolean bAppend) throws Exception { | |
throw new FileSystemException("vfs.provider/write-not-supported.error"); | |
} | |
/** | |
* Creates access to the file for random i/o. Is only called if {@link #doGetType} returns {@link FileType#FILE}. | |
* <p> | |
* It is guaranteed that there are no open output streams for this file when this method is called. | |
* </p> | |
* | |
* @param mode The mode to access the file. | |
* @return The RandomAccessContext. | |
* @throws Exception if an error occurs. | |
*/ | |
protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception { | |
throw new FileSystemException("vfs.provider/random-access-not-supported.error"); | |
} | |
/** | |
* Determines the type of this file. Must not return null. The return value of this method is cached, so the | |
* implementation can be expensive. | |
* | |
* @return the type of the file. | |
* @throws Exception if an error occurs. | |
*/ | |
protected abstract FileType doGetType() throws Exception; | |
/** | |
* Determines if this file is executable. Is only called if {@link #doGetType} does not return | |
* {@link FileType#IMAGINARY}. | |
* <p> | |
* This implementation always returns false. | |
* </p> | |
* | |
* @return true if the file is executable, false otherwise. | |
* @throws Exception if an error occurs. | |
*/ | |
protected boolean doIsExecutable() throws Exception { | |
return false; | |
} | |
/** | |
* Determines if this file is hidden. Is only called if {@link #doGetType} does not return | |
* {@link FileType#IMAGINARY}. | |
* <p> | |
* This implementation always returns false. | |
* </p> | |
* | |
* @return true if the file is hidden, false otherwise. | |
* @throws Exception if an error occurs. | |
*/ | |
protected boolean doIsHidden() throws Exception { | |
return false; | |
} | |
/** | |
* Determines if this file can be read. Is only called if {@link #doGetType} does not return | |
* {@link FileType#IMAGINARY}. | |
* <p> | |
* This implementation always returns true. | |
* </p> | |
* | |
* @return true if the file is readable, false otherwise. | |
* @throws Exception if an error occurs. | |
*/ | |
protected boolean doIsReadable() throws Exception { | |
return true; | |
} | |
/** | |
* Checks if this fileObject is the same file as {@code destFile} just with a different name. E.g. for | |
* case-insensitive file systems like Windows. | |
* | |
* @param destFile The file to compare to. | |
* @return true if the FileObjects are the same. | |
* @throws FileSystemException if an error occurs. | |
*/ | |
protected boolean doIsSameFile(final FileObject destFile) throws FileSystemException { | |
return false; | |
} | |
/** | |
* Determines if this file is a symbolic link. Is only called if {@link #doGetType} does not return | |
* {@link FileType#IMAGINARY}. | |
* <p> | |
* This implementation always returns false. | |
* </p> | |
* | |
* @return true if the file is readable, false otherwise. | |
* @throws Exception if an error occurs. | |
* @since 2.4 | |
*/ | |
protected boolean doIsSymbolicLink() throws Exception { | |
return false; | |
} | |
/** | |
* Determines if this file can be written to. Is only called if {@link #doGetType} does not return | |
* {@link FileType#IMAGINARY}. | |
* <p> | |
* This implementation always returns true. | |
* </p> | |
* | |
* @return true if the file is writable. | |
* @throws Exception if an error occurs. | |
*/ | |
protected boolean doIsWriteable() throws Exception { | |
return true; | |
} | |
/** | |
* Lists the children of this file. Is only called if {@link #doGetType} returns {@link FileType#FOLDER}. The return | |
* value of this method is cached, so the implementation can be expensive. | |
* | |
* @return a possible empty String array if the file is a directory or null or an exception if the file is not a | |
* directory or can't be read. | |
* @throws Exception if an error occurs. | |
*/ | |
protected abstract String[] doListChildren() throws Exception; | |
/** | |
* Lists the children of this file. | |
* <p> | |
* Is only called if {@link #doGetType} returns {@link FileType#FOLDER}. | |
* </p> | |
* <p> | |
* The return value of this method is cached, so the implementation can be expensive. | |
* Other than {@code doListChildren} you could return FileObject's to e.g. reinitialize the type of the file. | |
* </p> | |
* <p> | |
* (Introduced for Webdav: "permission denied on resource" during getType()) | |
* </p> | |
* | |
* @return The children of this FileObject. | |
* @throws Exception if an error occurs. | |
*/ | |
protected FileObject[] doListChildrenResolved() throws Exception { | |
return null; | |
} | |
/** | |
* Removes an attribute of this file. | |
* <p> | |
* Is only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}. | |
* </p> | |
* <p> | |
* This implementation throws an exception. | |
* </p> | |
* | |
* @param attrName The name of the attribute to remove. | |
* @throws Exception if an error occurs. | |
* @since 2.0 | |
*/ | |
protected void doRemoveAttribute(final String attrName) throws Exception { | |
throw new FileSystemException("vfs.provider/remove-attribute-not-supported.error"); | |
} | |
/** | |
* Renames the file. | |
* <p> | |
* Is only called when: | |
* </p> | |
* <ul> | |
* <li>{@link #doIsWriteable} returns true.</li> | |
* </ul> | |
* <p> | |
* This implementation throws an exception. | |
* </p> | |
* | |
* @param newFile A FileObject with the new file name. | |
* @throws Exception if an error occurs. | |
*/ | |
protected void doRename(final FileObject newFile) throws Exception { | |
throw new FileSystemException("vfs.provider/rename-not-supported.error"); | |
} | |
/** | |
* Sets an attribute of this file. | |
* <p> | |
* Is only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}. | |
* </p> | |
* <p> | |
* This implementation throws an exception. | |
* </p> | |
* | |
* @param attrName The attribute name. | |
* @param value The value to be associated with the attribute name. | |
* @throws Exception if an error occurs. | |
*/ | |
protected void doSetAttribute(final String attrName, final Object value) throws Exception { | |
throw new FileSystemException("vfs.provider/set-attribute-not-supported.error"); | |
} | |
/** | |
* Make the file executable. | |
* <p> | |
* Only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}. | |
* </p> | |
* <p> | |
* This implementation returns false. | |
* </p> | |
* | |
* @param executable True to allow access, false to disallow. | |
* @param ownerOnly If {@code true}, the permission applies only to the owner; otherwise, it applies to everybody. | |
* @return true if the operation succeeded. | |
* @throws Exception Any Exception thrown is wrapped in FileSystemException. | |
* @see #setExecutable(boolean, boolean) | |
* @since 2.1 | |
*/ | |
protected boolean doSetExecutable(final boolean executable, final boolean ownerOnly) throws Exception { | |
return false; | |
} | |
/** | |
* Sets the last modified time of this file. | |
* <p> | |
* Is only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}. | |
* </p> | |
* <p> | |
* This implementation throws an exception. | |
* </p> | |
* | |
* @param modtime The last modification time. | |
* @return true if the time was set. | |
* @throws Exception Any Exception thrown is wrapped in FileSystemException. | |
*/ | |
protected boolean doSetLastModifiedTime(final long modtime) throws Exception { | |
throw new FileSystemException("vfs.provider/set-last-modified-not-supported.error"); | |
} | |
/** | |
* Make the file or folder readable. | |
* <p> | |
* Only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}. | |
* </p> | |
* <p> | |
* This implementation returns false. | |
* </p> | |
* | |
* @param readable True to allow access, false to disallow | |
* @param ownerOnly If {@code true}, the permission applies only to the owner; otherwise, it applies to everybody. | |
* @return true if the operation succeeded | |
* @throws Exception Any Exception thrown is wrapped in FileSystemException. | |
* @see #setReadable(boolean, boolean) | |
* @since 2.1 | |
*/ | |
protected boolean doSetReadable(final boolean readable, final boolean ownerOnly) throws Exception { | |
return false; | |
} | |
/** | |
* Make the file or folder writable. | |
* <p> | |
* Only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}. | |
* </p> | |
* | |
* @param writable True to allow access, false to disallow | |
* @param ownerOnly If {@code true}, the permission applies only to the owner; otherwise, it applies to everybody. | |
* @return true if the operation succeeded | |
* @throws Exception Any Exception thrown is wrapped in FileSystemException. | |
* @see #setWritable(boolean, boolean) | |
* @since 2.1 | |
*/ | |
protected boolean doSetWritable(final boolean writable, final boolean ownerOnly) throws Exception { | |
return false; | |
} | |
/** | |
* Called when the output stream for this file is closed. | |
* | |
* @throws Exception if an error occurs. | |
*/ | |
protected void endOutput() throws Exception { | |
if (getType() == FileType.IMAGINARY) { | |
// File was created | |
handleCreate(FileType.FILE); | |
} else { | |
// File has changed | |
onChange(); | |
} | |
} | |
/** | |
* Determines if the file exists. | |
* | |
* @return true if the file exists, false otherwise, | |
* @throws FileSystemException if an error occurs. | |
*/ | |
@Override | |
public boolean exists() throws FileSystemException { | |
return getType() != FileType.IMAGINARY; | |
} | |
private FileName[] extractNames(final FileObject[] objects) { | |
if (objects == null) { | |
return null; | |
} | |
return Stream.of(objects).filter(Objects::nonNull).map(FileObject::getName).toArray(FileName[]::new); | |
} | |
@Override | |
protected void finalize() throws Throwable { | |
fileSystem.fileObjectDestroyed(this); | |
super.finalize(); | |
} | |
/** | |
* Finds the set of matching descendants of this file, in depthwise order. | |
* | |
* @param selector The FileSelector. | |
* @return list of files or null if the base file (this object) do not exist | |
* @throws FileSystemException if an error occurs. | |
*/ | |
@Override | |
public FileObject[] findFiles(final FileSelector selector) throws FileSystemException { | |
final List<FileObject> list = this.listFiles(selector); | |
return list == null ? null : list.toArray(FileObject.EMPTY_ARRAY); | |
} | |
/** | |
* Traverses the descendants of this file, and builds a list of selected files. | |
* | |
* @param selector The FileSelector. | |
* @param depthwise if true files are added after their descendants, before otherwise. | |
* @param selected A List of the located FileObjects. | |
* @throws FileSystemException if an error occurs. | |
*/ | |
@Override | |
public void findFiles(final FileSelector selector, final boolean depthwise, final List<FileObject> selected) | |
throws FileSystemException { | |
try { | |
if (exists()) { | |
// Traverse starting at this file | |
final DefaultFileSelectorInfo info = new DefaultFileSelectorInfo(); | |
info.setBaseFolder(this); | |
info.setDepth(0); | |
info.setFile(this); | |
traverse(info, selector, depthwise, selected); | |
} | |
} catch (final Exception e) { | |
throw new FileSystemException("vfs.provider/find-files.error", fileName, e); | |
} | |
} | |
/** | |
* Returns the file system this file belongs to. | |
* | |
* @return The FileSystem this file is associated with. | |
*/ | |
protected AFS getAbstractFileSystem() { | |
return fileSystem; | |
} | |
/** | |
* Returns a child of this file. | |
* | |
* @param name The name of the child to locate. | |
* @return The FileObject for the file or null if the child does not exist. | |
* @throws FileSystemException if an error occurs. | |
*/ | |
@Override | |
public FileObject getChild(final String name) throws FileSystemException { | |
// TODO - use a hashtable when there are a large number of children | |
final FileObject[] children = getChildren(); | |
for (final FileObject element : children) { | |
final FileName child = element.getName(); | |
final String childBaseName = child.getBaseName(); | |
// TODO - use a comparator to compare names | |
if (childBaseName.equals(name) || UriParser.decode(childBaseName).equals(name)) { | |
return resolveFile(child); | |
} | |
} | |
return null; | |
} | |
/** | |
* Returns the children of the file. | |
* | |
* @return an array of FileObjects, one per child. | |
* @throws FileSystemException if an error occurs. | |
*/ | |
@Override | |
public FileObject[] getChildren() throws FileSystemException { | |
synchronized (fileSystem) { | |
// VFS-210 | |
if (!fileSystem.hasCapability(Capability.LIST_CHILDREN)) { | |
throw new FileNotFolderException(fileName); | |
} | |
/* | |
* VFS-210 if (!getType().hasChildren()) { throw new | |
* FileSystemException("vfs.provider/list-children-not-folder.error", name); } | |
*/ | |
attach(); | |
// Use cached info, if present | |
if (children != null) { | |
return resolveFiles(children); | |
} | |
// allow the filesystem to return resolved children. e.g. prefill type for webdav | |
final FileObject[] childrenObjects; | |
try { | |
childrenObjects = doListChildrenResolved(); | |
children = extractNames(childrenObjects); | |
} catch (final FileSystemException exc) { | |
// VFS-210 | |
throw exc; | |
} catch (final Exception exc) { | |
throw new FileSystemException("vfs.provider/list-children.error", exc, fileName); | |
} | |
if (childrenObjects != null) { | |
return childrenObjects; | |
} | |
// List the children | |
final String[] files; | |
try { | |
files = doListChildren(); | |
} catch (final FileSystemException exc) { | |
// VFS-210 | |
throw exc; | |
} catch (final Exception exc) { | |
throw new FileSystemException("vfs.provider/list-children.error", exc, fileName); | |
} | |
if (files == null) { | |
// VFS-210 | |
// honor the new doListChildren contract | |
// return null; | |
throw new FileNotFolderException(fileName); | |
} | |
if (files.length == 0) { | |
// No children | |
children = FileName.EMPTY_ARRAY; | |
} else { | |
// Create file objects for the children | |
final FileName[] cache = new FileName[files.length]; | |
for (int i = 0; i < files.length; i++) { | |
final String file = "./" + files[i]; // VFS-741: assume scheme prefix is filename only | |
cache[i] = fileSystem.getFileSystemManager().resolveName(fileName, file, NameScope.CHILD); | |
} | |
// VFS-285: only assign the children file names after all of them have been | |
// resolved successfully to prevent an inconsistent internal state | |
children = cache; | |
} | |
return resolveFiles(children); | |
} | |
} | |
/** | |
* Returns the file's content. | |
* | |
* @return the FileContent for this FileObject. | |
* @throws FileSystemException if an error occurs. | |
*/ | |
@Override | |
public FileContent getContent() throws FileSystemException { | |
synchronized (fileSystem) { | |
attach(); | |
if (content == null) { | |
content = doCreateFileContent(); | |
} | |
return content; | |
} | |
} | |
/** | |
* Creates the FileContentInfo factory. | |
* | |
* @return The FileContentInfoFactory. | |
*/ | |
protected FileContentInfoFactory getFileContentInfoFactory() { | |
return fileSystem.getFileSystemManager().getFileContentInfoFactory(); | |
} | |
/** | |
* @return FileOperations interface that provides access to the operations API. | |
* @throws FileSystemException if an error occurs. | |
*/ | |
@Override | |
public FileOperations getFileOperations() throws FileSystemException { | |
if (operations == null) { | |
operations = new DefaultFileOperations(this); | |
} | |
return operations; | |
} | |
/** | |
* Returns the file system this file belongs to. | |
* | |
* @return The FileSystem this file is associated with. | |
*/ | |
@Override | |
public FileSystem getFileSystem() { | |
return fileSystem; | |
} | |
/** | |
* Returns an input stream to use to read the content of the file. | |
* | |
* @return The InputStream to access this file's content. | |
* @throws FileSystemException if an error occurs. | |
*/ | |
public InputStream getInputStream() throws FileSystemException { | |
return getInputStream(DEFAULT_BUFFER_SIZE); | |
} | |
/** | |
* Returns an input stream to use to read the content of the file. | |
* | |
* @param bufferSize buffer size hint. | |
* @return The InputStream to access this file's content. | |
* @throws FileSystemException if an error occurs. | |
*/ | |
public InputStream getInputStream(final int bufferSize) throws FileSystemException { | |
// Get the raw input stream | |
try { | |
return doGetInputStream(bufferSize); | |
} catch (final org.apache.commons.vfs2.FileNotFoundException | FileNotFoundException exc) { | |
throw new org.apache.commons.vfs2.FileNotFoundException(fileName, exc); | |
} catch (final FileSystemException exc) { | |
throw exc; | |
} catch (final UnsupportedOperationException exc) { | |
// Backward compatibility for subclasses before 2.5.0 | |
if (DO_GET_INPUT_STREAM_INT.equals(exc.getMessage())) { | |
try { | |
// Invoke old API. | |
return doGetInputStream(); | |
} catch (final Exception e) { | |
if (e instanceof FileSystemException) { | |
throw (FileSystemException) e; | |
} | |
throw new FileSystemException("vfs.provider/read.error", fileName, exc); | |
} | |
} | |
throw exc; | |
} catch (final Exception exc) { | |
throw new FileSystemException("vfs.provider/read.error", fileName, exc); | |
} | |
} | |
/** | |
* Returns the name of the file. | |
* | |
* @return The FileName, never {@code null}. | |
*/ | |
@Override | |
public FileName getName() { | |
return fileName; | |
} | |
// TODO: remove this method for the next major version as it is unused | |
/** | |
* Prepares this file for writing. Makes sure it is either a file, or its parent folder exists. Returns an output | |
* stream to use to write the content of the file to. | |
* | |
* @return An OutputStream where the new contents of the file can be written. | |
* @throws FileSystemException if an error occurs. | |
*/ | |
public OutputStream getOutputStream() throws FileSystemException { | |
return getOutputStream(false); | |
} | |
// TODO: mark this method as `final` and package-private for the next major version because | |
// it shouldn't be used from anywhere other than `DefaultFileContent` | |
/** | |
* Prepares this file for writing. Makes sure it is either a file, or its parent folder exists. Returns an output | |
* stream to use to write the content of the file to. | |
* | |
* @param bAppend true when append to the file. | |
* Note: If the underlying file system does not support appending, a FileSystemException is thrown. | |
* @return An OutputStream where the new contents of the file can be written. | |
* @throws FileSystemException if an error occurs; for example: | |
* bAppend is true, and the underlying FileSystem does not support it | |
*/ | |
public OutputStream getOutputStream(final boolean bAppend) throws FileSystemException { | |
/* | |
* VFS-210 if (getType() != FileType.IMAGINARY && !getType().hasContent()) { throw new | |
* FileSystemException("vfs.provider/write-not-file.error", name); } if (!isWriteable()) { throw new | |
* FileSystemException("vfs.provider/write-read-only.error", name); } | |
*/ | |
if (bAppend && !fileSystem.hasCapability(Capability.APPEND_CONTENT)) { | |
throw new FileSystemException("vfs.provider/write-append-not-supported.error", fileName); | |
} | |
if (getType() == FileType.IMAGINARY) { | |
// Does not exist - make sure parent does | |
final FileObject parent = getParent(); | |
if (parent != null) { | |
parent.createFolder(); | |
} | |
} | |
// Get the raw output stream | |
try { | |
return doGetOutputStream(bAppend); | |
} catch (final RuntimeException re) { | |
throw re; | |
} catch (final Exception exc) { | |
throw new FileSystemException("vfs.provider/write.error", exc, fileName); | |
} | |
} | |
/** | |
* Returns the parent of the file. | |
* | |
* @return the parent FileObject. | |
* @throws FileSystemException if an error occurs. | |
*/ | |
@Override | |
public FileObject getParent() throws FileSystemException { | |
// equals is not implemented :-/ | |
if (this.compareTo(fileSystem.getRoot()) == 0) { | |
if (fileSystem.getParentLayer() == null) { | |
// Root file has no parent | |
return null; | |
} | |
// Return the parent of the parent layer | |
return fileSystem.getParentLayer().getParent(); | |
} | |
synchronized (fileSystem) { | |
// Locate the parent of this file | |
if (parent == null) { | |
final FileName name = fileName.getParent(); | |
if (name == null) { | |
return null; | |
} | |
parent = fileSystem.resolveFile(name); | |
} | |
return parent; | |
} | |
} | |
/** | |
* Returns the receiver as a URI String for public display, like, without a password. | |
* | |
* @return A URI String without a password, never {@code null}. | |
*/ | |
@Override | |
public String getPublicURIString() { | |
return fileName.getFriendlyURI(); | |
} | |
/** | |
* Returns an input/output stream to use to read and write the content of the file in and random manner. | |
* | |
* @param mode The RandomAccessMode. | |
* @return The RandomAccessContent. | |
* @throws FileSystemException if an error occurs. | |
*/ | |
public RandomAccessContent getRandomAccessContent(final RandomAccessMode mode) throws FileSystemException { | |
// | |
// VFS-210 if (!getType().hasContent()) { throw new FileSystemException("vfs.provider/read-not-file.error", | |
// name); } | |
// | |
if (mode.requestRead()) { | |
if (!fileSystem.hasCapability(Capability.RANDOM_ACCESS_READ)) { | |
throw new FileSystemException("vfs.provider/random-access-read-not-supported.error"); | |
} | |
if (!isReadable()) { | |
throw new FileSystemException("vfs.provider/read-not-readable.error", fileName); | |
} | |
} | |
if (mode.requestWrite()) { | |
if (!fileSystem.hasCapability(Capability.RANDOM_ACCESS_WRITE)) { | |
throw new FileSystemException("vfs.provider/random-access-write-not-supported.error"); | |
} | |
if (!isWriteable()) { | |
throw new FileSystemException("vfs.provider/write-read-only.error", fileName); | |
} | |
} | |
// Get the raw input stream | |
try { | |
return doGetRandomAccessContent(mode); | |
} catch (final Exception exc) { | |
throw new FileSystemException("vfs.provider/random-access.error", fileName, exc); | |
} | |
} | |
/** | |
* Returns the file's type. | |
* | |
* @return The FileType. | |
* @throws FileSystemException if an error occurs. | |
*/ | |
@Override | |
public FileType getType() throws FileSystemException { | |
synchronized (fileSystem) { | |
attach(); | |
// VFS-210: get the type only if requested for | |
try { | |
if (type == null) { | |
setFileType(doGetType()); | |
} | |
if (type == null) { | |
setFileType(FileType.IMAGINARY); | |
} | |
} catch (final Exception e) { | |
throw new FileSystemException("vfs.provider/get-type.error", e, fileName); | |
} | |
return type; | |
} | |
} | |
/** | |
* Returns a URL representation of the file. | |
* | |
* @return The URL representation of the file. | |
* @throws FileSystemException if an error occurs. | |
*/ | |
@Override | |
public URL getURL() throws FileSystemException { | |
try { | |
return AccessController.doPrivileged((PrivilegedExceptionAction<URL>) () -> { | |
final StringBuilder buf = new StringBuilder(); | |
final String scheme = UriParser.extractScheme(fileSystem.getContext().getFileSystemManager().getSchemes(), fileName.getURI(), buf); | |
return new URL(scheme, "", -1, buf.toString(), | |
new DefaultURLStreamHandler(fileSystem.getContext(), fileSystem.getFileSystemOptions())); | |
}); | |
} catch (final PrivilegedActionException e) { | |
throw new FileSystemException("vfs.provider/get-url.error", fileName, e.getException()); | |
} | |
} | |
/** | |
* Called when this file is changed. | |
* <p> | |
* This will only happen if you monitor the file using {@link org.apache.commons.vfs2.FileMonitor}. | |
* </p> | |
* | |
* @throws Exception if an error occurs. | |
*/ | |
protected void handleChanged() throws Exception { | |
// Notify the file system | |
fileSystem.fireFileChanged(this); | |
} | |
/** | |
* Called when this file is created. Updates cached info and notifies the parent and file system. | |
* | |
* @param newType The type of the file. | |
* @throws Exception if an error occurs. | |
*/ | |
protected void handleCreate(final FileType newType) throws Exception { | |
synchronized (fileSystem) { | |
if (attached) { | |
// Fix up state | |
injectType(newType); | |
removeChildrenCache(); | |
// Notify subclass | |
onChange(); | |
} | |
// Notify parent that its child list may no longer be valid | |
notifyParent(this.getName(), newType); | |
// Notify the file system | |
fileSystem.fireFileCreated(this); | |
} | |
} | |
/** | |
* Called when this file is deleted. Updates cached info and notifies subclasses, parent and file system. | |
* | |
* @throws Exception if an error occurs. | |
*/ | |
protected void handleDelete() throws Exception { | |
synchronized (fileSystem) { | |
if (attached) { | |
// Fix up state | |
injectType(FileType.IMAGINARY); | |
removeChildrenCache(); | |
// Notify subclass | |
onChange(); | |
} | |
// Notify parent that its child list may no longer be valid | |
notifyParent(this.getName(), FileType.IMAGINARY); | |
// Notify the file system | |
fileSystem.fireFileDeleted(this); | |
} | |
} | |
/** | |
* This method is meant to add an object where this object holds a strong reference then. E.g. an archive-file system | |
* creates a list of all children and they shouldn't get garbage collected until the container is garbage collected | |
* | |
* @param strongRef The Object to add. | |
*/ | |
// TODO should this be a FileObject? | |
public void holdObject(final Object strongRef) { | |
if (objects == null) { | |
objects = new ArrayList<>(INITIAL_LIST_SIZE); | |
} | |
objects.add(strongRef); | |
} | |
protected void injectType(final FileType fileType) { | |
setFileType(fileType); | |
} | |
/** | |
* Check if the internal state is "attached". | |
* | |
* @return true if this is the case | |
*/ | |
@Override | |
public boolean isAttached() { | |
return attached; | |
} | |
/** | |
* Check if the content stream is open. | |
* | |
* @return true if this is the case | |
*/ | |
@Override | |
public boolean isContentOpen() { | |
if (content == null) { | |
return false; | |
} | |
return content.isOpen(); | |
} | |
/** | |
* Determines if this file is executable. | |
* | |
* @return {@code true} if this file is executable, {@code false} if not. | |
* @throws FileSystemException On error determining if this file exists. | |
*/ | |
@Override | |
public boolean isExecutable() throws FileSystemException { | |
try { | |
return exists() && doIsExecutable(); | |
} catch (final Exception exc) { | |
throw new FileSystemException("vfs.provider/check-is-executable.error", fileName, exc); | |
} | |
} | |
/** | |
* Checks if this file is a regular file by using its file type. | |
* | |
* @return true if this file is a regular file. | |
* @throws FileSystemException if an error occurs. | |
* @see #getType() | |
* @see FileType#FILE | |
*/ | |
@Override | |
public boolean isFile() throws FileSystemException { | |
// Use equals instead of == to avoid any class loader worries. | |
return FileType.FILE.equals(this.getType()); | |
} | |
/** | |
* Checks if this file is a folder by using its file type. | |
* | |
* @return true if this file is a regular file. | |
* @throws FileSystemException if an error occurs. | |
* @see #getType() | |
* @see FileType#FOLDER | |
*/ | |
@Override | |
public boolean isFolder() throws FileSystemException { | |
// Use equals instead of == to avoid any class loader worries. | |
return FileType.FOLDER.equals(this.getType()); | |
} | |
/** | |
* Determines if this file can be read. | |
* | |
* @return true if the file is a hidden file, false otherwise. | |
* @throws FileSystemException if an error occurs. | |
*/ | |
@Override | |
public boolean isHidden() throws FileSystemException { | |
try { | |
return exists() && doIsHidden(); | |
} catch (final Exception exc) { | |
throw new FileSystemException("vfs.provider/check-is-hidden.error", fileName, exc); | |
} | |
} | |
/** | |
* Determines if this file can be read. | |
* | |
* @return true if the file can be read, false otherwise. | |
* @throws FileSystemException if an error occurs. | |
*/ | |
@Override | |
public boolean isReadable() throws FileSystemException { | |
try { | |
return exists() && doIsReadable(); | |
} catch (final Exception exc) { | |
throw new FileSystemException("vfs.provider/check-is-readable.error", fileName, exc); | |
} | |
} | |
/** | |
* Checks if this fileObject is the same file as {@code destFile} just with a different name. E.g. for | |
* case-insensitive file systems like windows. | |
* | |
* @param destFile The file to compare to. | |
* @return true if the FileObjects are the same. | |
* @throws FileSystemException if an error occurs. | |
*/ | |
protected boolean isSameFile(final FileObject destFile) throws FileSystemException { | |
attach(); | |
return doIsSameFile(destFile); | |
} | |
/** | |
* Determines if this file can be read. | |
* | |
* @return true if the file can be read, false otherwise. | |
* @throws FileSystemException if an error occurs. | |
* @since 2.4 | |
*/ | |
@Override | |
public boolean isSymbolicLink() throws FileSystemException { | |
try { | |
return exists() && doIsSymbolicLink(); | |
} catch (final Exception exc) { | |
throw new FileSystemException("vfs.provider/check-is-symbolic-link.error", fileName, exc); | |
} | |
} | |
/** | |
* Determines if this file can be written to. | |
* | |
* @return true if the file can be written to, false otherwise. | |
* @throws FileSystemException if an error occurs. | |
*/ | |
@Override | |
public boolean isWriteable() throws FileSystemException { | |
try { | |
if (exists()) { | |
return doIsWriteable(); | |
} | |
final FileObject parent = getParent(); | |
if (parent != null) { | |
return parent.isWriteable(); | |
} | |
return true; | |
} catch (final Exception exc) { | |
throw new FileSystemException("vfs.provider/check-is-writable.error", fileName, exc); | |
} | |
} | |
/** | |
* Returns an iterator over a set of all FileObject in this file object. | |
* | |
* @return an Iterator. | |
*/ | |
@Override | |
public Iterator<FileObject> iterator() { | |
try { | |
return listFiles(Selectors.SELECT_ALL).iterator(); | |
} catch (final FileSystemException e) { | |
throw new IllegalStateException(e); | |
} | |
} | |
/** | |
* Lists the set of matching descendants of this file, in depthwise order. | |
* | |
* @param selector The FileSelector. | |
* @return list of files or null if the base file (this object) do not exist or the {@code selector} is null | |
* @throws FileSystemException if an error occurs. | |
*/ | |
public List<FileObject> listFiles(final FileSelector selector) throws FileSystemException { | |
if (!exists() || selector == null) { | |
return null; | |
} | |
final ArrayList<FileObject> list = new ArrayList<>(); | |
this.findFiles(selector, true, list); | |
return list; | |
} | |
/** | |
* Moves (rename) the file to another one. | |
* | |
* @param destFile The target FileObject. | |
* @throws FileSystemException if an error occurs. | |
*/ | |
@Override | |
public void moveTo(final FileObject destFile) throws FileSystemException { | |
if (canRenameTo(destFile)) { | |
if (!getParent().isWriteable()) { | |
throw new FileSystemException("vfs.provider/rename-parent-read-only.error", getName(), | |
getParent().getName()); | |
} | |
} else if (!isWriteable()) { | |
throw new FileSystemException("vfs.provider/rename-read-only.error", getName()); | |
} | |
if (destFile.exists() && !isSameFile(destFile)) { | |
destFile.deleteAll(); | |
// throw new FileSystemException("vfs.provider/rename-dest-exists.error", destFile.getName()); | |
} | |
if (canRenameTo(destFile)) { | |
// issue rename on same filesystem | |
try { | |
attach(); | |
// remember type to avoid attach | |
final FileType srcType = getType(); | |
doRename(destFile); | |
FileObjectUtils.getAbstractFileObject(destFile).handleCreate(srcType); | |
destFile.close(); // now the destFile is no longer imaginary. force reattach. | |
handleDelete(); // fire delete-events. This file-object (src) is like deleted. | |
} catch (final RuntimeException re) { | |
throw re; | |
} catch (final Exception exc) { | |
throw new FileSystemException("vfs.provider/rename.error", exc, getName(), destFile.getName()); | |
} | |
} else { | |
// different fs - do the copy/delete stuff | |
destFile.copyFrom(this, Selectors.SELECT_SELF); | |
if ((destFile.getType().hasContent() | |
&& destFile.getFileSystem().hasCapability(Capability.SET_LAST_MODIFIED_FILE) | |
|| destFile.getType().hasChildren() | |
&& destFile.getFileSystem().hasCapability(Capability.SET_LAST_MODIFIED_FOLDER)) | |
&& fileSystem.hasCapability(Capability.GET_LAST_MODIFIED)) { | |
destFile.getContent().setLastModifiedTime(this.getContent().getLastModifiedTime()); | |
} | |
deleteSelf(); | |
} | |
} | |
/** | |
* Called after this file-object closed all its streams. | |
*/ | |
protected void notifyAllStreamsClosed() { | |
// noop | |
} | |
/** | |
* Notify the parent of a change to its children, when a child is created or deleted. | |
* | |
* @param childName The name of the child. | |
* @param newType The type of the child. | |
* @throws Exception if an error occurs. | |
*/ | |
private void notifyParent(final FileName childName, final FileType newType) throws Exception { | |
if (parent == null) { | |
final FileName parentName = fileName.getParent(); | |
if (parentName != null) { | |
// Locate the parent, if it is cached | |
parent = fileSystem.getFileFromCache(parentName); | |
} | |
} | |
if (parent != null) { | |
FileObjectUtils.getAbstractFileObject(parent).childrenChanged(childName, newType); | |
} | |
} | |
/** | |
* Called when the type or content of this file changes. | |
* <p> | |
* This implementation does nothing. | |
* </p> | |
* | |
* @throws Exception if an error occurs. | |
*/ | |
protected void onChange() throws Exception { | |
// noop | |
} | |
/** | |
* Called when the children of this file change. Allows subclasses to refresh any cached information about the | |
* children of this file. | |
* <p> | |
* This implementation does nothing. | |
* </p> | |
* | |
* @param child The name of the child that changed. | |
* @param newType The type of the file. | |
* @throws Exception if an error occurs. | |
*/ | |
protected void onChildrenChanged(final FileName child, final FileType newType) throws Exception { | |
// noop | |
} | |
/** | |
* This will prepare the fileObject to get resynchronized with the underlying file system if required. | |
* | |
* @throws FileSystemException if an error occurs. | |
*/ | |
@Override | |
public void refresh() throws FileSystemException { | |
// Detach from the file | |
try { | |
detach(); | |
} catch (final Exception e) { | |
throw new FileSystemException("vfs.provider/resync.error", fileName, e); | |
} | |
} | |
private void removeChildrenCache() { | |
children = null; | |
} | |
private FileObject resolveFile(final FileName child) throws FileSystemException { | |
return fileSystem.resolveFile(child); | |
} | |
/** | |
* Finds a file, relative to this file. | |
* | |
* @param path The path of the file to locate. Can either be a relative path, which is resolved relative to this | |
* file, or an absolute path, which is resolved relative to the file system that contains this file. | |
* @return The FileObject. | |
* @throws FileSystemException if an error occurs. | |
*/ | |
@Override | |
public FileObject resolveFile(final String path) throws FileSystemException { | |
final FileName otherName = fileSystem.getFileSystemManager().resolveName(fileName, path); | |
return fileSystem.resolveFile(otherName); | |
} | |
/** | |
* Returns a child by name. | |
* | |
* @param name The name of the child to locate. | |
* @param scope the NameScope. | |
* @return The FileObject for the file or null if the child does not exist. | |
* @throws FileSystemException if an error occurs. | |
*/ | |
@Override | |
public FileObject resolveFile(final String name, final NameScope scope) throws FileSystemException { | |
// return fs.resolveFile(this.name.resolveName(name, scope)); | |
return fileSystem.resolveFile(fileSystem.getFileSystemManager().resolveName(this.fileName, name, scope)); | |
} | |
private FileObject[] resolveFiles(final FileName[] children) throws FileSystemException { | |
if (children == null) { | |
return null; | |
} | |
final FileObject[] objects = new FileObject[children.length]; | |
for (int iterChildren = 0; iterChildren < children.length; iterChildren++) { | |
objects[iterChildren] = resolveFile(children[iterChildren]); | |
} | |
return objects; | |
} | |
@Override | |
public boolean setExecutable(final boolean readable, final boolean ownerOnly) throws FileSystemException { | |
try { | |
return exists() && doSetExecutable(readable, ownerOnly); | |
} catch (final Exception exc) { | |
throw new FileSystemException("vfs.provider/set-executable.error", fileName, exc); | |
} | |
} | |
private void setFileType(final FileType type) { | |
if (type != null && type != FileType.IMAGINARY) { | |
try { | |
fileName.setType(type); | |
} catch (final FileSystemException e) { | |
throw new RuntimeException(e.getMessage()); | |
} | |
} | |
this.type = type; | |
} | |
@Override | |
public boolean setReadable(final boolean readable, final boolean ownerOnly) throws FileSystemException { | |
try { | |
return exists() && doSetReadable(readable, ownerOnly); | |
} catch (final Exception exc) { | |
throw new FileSystemException("vfs.provider/set-readable.error", fileName, exc); | |
} | |
} | |
@Override | |
public boolean setWritable(final boolean readable, final boolean ownerOnly) throws FileSystemException { | |
try { | |
return exists() && doSetWritable(readable, ownerOnly); | |
} catch (final Exception exc) { | |
throw new FileSystemException("vfs.provider/set-writable.error", fileName, exc); | |
} | |
} | |
/** | |
* Returns the URI as a String. | |
* | |
* @return Returns the URI as a String. | |
*/ | |
@Override | |
public String toString() { | |
return fileName.getURI(); | |
} | |
} |