org.openide.filesystems.FileObject Maven / Gradle / Ivy
/*
* 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.openide.filesystems;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import org.openide.util.Enumerations;
import org.openide.util.NbBundle;
import org.openide.util.Lookup;
import org.openide.util.Lookup.Result;
/** This is the base for all implementations of file objects on a filesystem.
* Provides basic information about the object (its name, parent,
* whether it exists, etc.) and operations on it (move, delete, etc.).
*
* @author Jaroslav Tulach, Petr Hamernik, Ian Formanek
*/
public abstract class FileObject extends Object implements Serializable, Lookup.Provider {
/**
* Name of default line separator attribute.
* File object can provide default line separator if it differs from
* System.getProperty("line.separator")
. Call
* fo.getAttribute(DEFAULT_LINE_SEPARATOR_PROP)
returns string with
* default line separator. Default line separator will be used by the text
* editor if saving new content to an initially empty file. Any other code
* which creates file content programmatically must manually read this
* property if it cares.
* @since 7.56
*/
public static final String DEFAULT_LINE_SEPARATOR_ATTR = "default-line-separator"; //NOI18N
/**
* Name of default path name separator attribute.
* File object can provide default separator if it differs from
* File.separator
. Call
* fo.getAttribute(DEFAULT_PATHNAME_SEPARATOR_ATTR)
returns string with
* default separator.
* @since 9.6
*/
public static final String DEFAULT_PATHNAME_SEPARATOR_ATTR = "default-pathname-separator"; //NOI18N
/** generated Serialized Version UID */
static final long serialVersionUID = 85305031923497718L;
/** implementation of lookup associated with this file object */
private FileObjectLkp lkp;
/** Get the name without extension of this file or folder.
* Period at first position is not considered as extension-separator
* For the root folder of a filesystem, this will be the empty
* string (the extension will also be the empty string, and the
* fully qualified name with any delimiters will also be the
* empty string).
* @return name of the file or folder(in its enclosing folder)
*/
public abstract String getName();
/** Get the extension of this file or folder.
* Period at first position is not considered as extension-separator
* This is the string after the last dot of the full name, if any.
*
* @return extension of the file or folder (if any) or empty string if there is none
*/
public abstract String getExt();
/** Renames this file (or folder).
* Both the new basename and new extension should be specified.
*
* Note that using this call, it is currently only possible to rename within
* a parent folder, and not to do moves across folders.
* Conversely, implementing filesystems need only implement "simple" renames.
* If you wish to move a file across folders, you should call {@link FileUtil#moveFile}.
* @param lock File must be locked before renaming.
* @param name new basename of file
* @param ext new extension of file (ignored for folders)
*/
public abstract void rename(FileLock lock, String name, String ext)
throws IOException;
/** Copies this file. This allows the filesystem to perform any additional
* operation associated with the copy. But the default implementation is simple
* copy of the file and its attributes
*
* @param target target folder to move this file to
* @param name new basename of file
* @param ext new extension of file (ignored for folders)
* @return the newly created file object representing the moved file
*/
public FileObject copy(FileObject target, String name, String ext)
throws IOException {
if (isFolder()) {
if (FileUtil.isParentOf(this, target)) {
throw new FSException(NbBundle.getMessage(FileObject.class, "EXC_OperateChild", this, target)); // NOI18N
}
FileObject peer = target.createFolder(name);
FileUtil.copyAttributes(this, peer);
for (FileObject fo : getChildren()) {
fo.copy(peer, fo.getName(), fo.getExt());
}
return peer;
}
FileObject dest = FileUtil.copyFileImpl(this, target, name, ext);
return dest;
}
/** Moves this file. This allows the filesystem to perform any additional
* operation associated with the move. But the default implementation is encapsulated
* as copy and delete.
*
* @param lock File must be locked before renaming.
* @param target target folder to move this file to
* @param name new basename of file
* @param ext new extension of file (ignored for folders)
* @return the newly created file object representing the moved file
*/
public FileObject move(FileLock lock, FileObject target, String name, String ext)
throws IOException {
if (getParent().equals(target)) {
// it is possible to do only rename
rename(lock, name, ext);
return this;
} else {
// have to do copy
FileObject dest = copy(target, name, ext);
delete(lock);
FileObjectLkp.reassign(this, dest);
return dest;
}
}
/**
* Gets a textual represtentation of this FileObject
.
* The precise format is not defined. In particular it is probably
* not a resource path.
* For that purpose use {@link #getPath} directly.
*
Typically it is useful for debugging purposes. Example of correct usage:
*
* FileObject fo = getSomeFileObject();
* ErrorManager.getDefault().log("Got a change from " + fo);
*
* @return some representation of this file object
*/
@Override
public String toString() {
String cname = getClass().getName();
String cnameShort = cname.substring(cname.lastIndexOf('.') + 1);
try {
return cnameShort + '@' + Integer.toHexString(System.identityHashCode(this)) + '[' + (isRoot() ? "root of " + getFileSystem() : getPath()) + ']'; // NOI18N
} catch (FileStateInvalidException x) {
return cnameShort + '@' + Integer.toHexString(System.identityHashCode(this)) + "[???]"; // NOI18N
}
}
/** Get the full resource path of this file object starting from the filesystem root.
* Folders are separated with forward slashes. File extensions, if present,
* are included. The root folder's path is the empty string. The path of a folder
* never ends with a slash.
* Subclasses are strongly encouraged to override this method.
*
Never use this to get a display name for the file! Use {@link FileUtil#getFileDisplayName}.
*
Do not use this method to find a file path on disk! Use {@link FileUtil#toFile}.
* @return the path, for example path/from/root.ext
* @see FileSystem#findResource
* @since 3.7
*/
public String getPath() {
StringBuilder[] buf = { null };
constructName(buf, '/', 0);
return buf[0].toString();
}
/** Get fully-qualified filename. Does so by walking through all folders
* to the root of the filesystem. Separates files with provided separatorChar
.
* The extension, if present, is separated from the basename with extSepChar
.
*
Note: fo.getPath() will have the
* same effect as using this method with / and . (the standard
* path and extension delimiters).
* @param separatorChar char to separate folders and files
* @param extSepChar char to separate extension
* @return the fully-qualified filename
* @deprecated Please use the ClassPath API instead.
*/
@Deprecated
public String getPackageNameExt(char separatorChar, char extSepChar) {
assert false : "Deprecated.";
if (isRoot() || getParent().isRoot()) {
return getNameExt();
}
StringBuilder[] arr = new StringBuilder[1];
getParent().constructName(arr, separatorChar, 50);
String ext = getExt();
if ((ext == null) || ext.equals("")) { // NOI18N
arr[0].append(separatorChar).append(getNameExt());
} else {
arr[0].append(separatorChar).append(getName()).append(extSepChar).append(getExt());
}
return arr[0].toString();
}
/** Get fully-qualified filename, but without extension.
* Like {@link #getPackageNameExt} but omits the extension.
* @param separatorChar char to separate folders and files
* @return the fully-qualified filename
* @deprecated Please use the ClassPath API instead.
*/
@Deprecated
public String getPackageName(char separatorChar) {
assert false : "Deprecated.";
if (isRoot() || getParent().isRoot()) {
return (isFolder()) ? getNameExt() : getName();
}
StringBuilder[] arr = new StringBuilder[1];
String name = getName();
getParent().constructName(arr, separatorChar, name.length());
arr[0].append(separatorChar).append(name);
return arr[0].toString();
}
/** Getter for name and extension of a file object. Dot is used
* as separator between name and ext.
* @return string name of the file in the folder (with extension)
*/
public String getNameExt() {
String n = getName();
String e = getExt();
return ((e == null) || (e.length() == 0)) ? n : (n + '.' + e);
}
/** Constructs path of file.
* @param arr to place the string buffer
* @param sepChar separator character
*/
private void constructName(StringBuilder[] arr, char sepChar, int lengthSoFar) {
String myName = getNameExt();
int myLen = lengthSoFar + myName.length();
FileObject parent = getParent();
if (parent == this) {
Object fs;
try {
fs = getFileSystem();
} catch (IOException ex) {
fs = "unknown"; // NOI18N
}
throw new IllegalStateException("Dangerous self-reproductive parentship: " + this + " type: " + getClass() + " fs: " + fs); // NOI18N
}
if ((parent != null) && !parent.isRoot()) {
parent.constructName(arr, sepChar, myLen + 1);
arr[0].append(sepChar);
} else {
assert arr[0] == null;
arr[0] = new StringBuilder(myLen);
}
arr[0].append(getNameExt());
}
/** Get the filesystem containing this file.
*
* Note that it may be possible for a stale file object to exist which refers to a now-defunct filesystem.
* If this is the case, this method will throw an exception.
* @return the filesystem
* @exception FileStateInvalidException if the reference to the file
* system has been lost (e.g., if the filesystem was deleted)
*/
public abstract FileSystem getFileSystem() throws FileStateInvalidException;
/** Get parent folder.
* The returned object will satisfy {@link #isFolder}.
*
* @return the parent folder or null
if this object {@link #isRoot}.
*/
public abstract FileObject getParent();
/** Test whether this object is a folder.
* @return true if the file object is a folder (i.e., can have children)
*/
public abstract boolean isFolder();
/**
* Get last modification time.
* @return the date
*/
public abstract java.util.Date lastModified();
/** Test whether this object is the root folder.
* The root should always be a folder.
* @return true if the object is the root of a filesystem
*/
public abstract boolean isRoot();
/** Test whether this object is a data object.
* This is exclusive with {@link #isFolder}.
* @return true if the file object represents data (i.e., can be read and written)
*/
public abstract boolean isData();
/** Test whether the file is valid. The file can be invalid if it has been deserialized
* and the file no longer exists on disk; or if the file has been deleted.
*
* @return true if the file object is valid
*/
public abstract boolean isValid();
/** Test whether there is a file with the same basename and only a changed extension in the same folder.
* The default implementation asks this file's parent using {@link #getFileObject(String name, String ext)}.
*
* @param ext the alternate extension
* @return true if there is such a file
*/
public boolean existsExt(String ext) {
FileObject parent = getParent();
return (parent != null) && (parent.getFileObject(getName(), ext) != null);
}
/** Delete this file. If the file is a folder and it is not empty then
* all of its contents are also recursively deleted.
*
* @param lock the lock obtained by a call to {@link #lock}
* @exception IOException if the file could not be deleted
*/
public abstract void delete(FileLock lock) throws IOException;
/** Delete this file. If the file is a folder and it is not empty then
* all of its contents are also recursively deleted. FileObject is locked
* before delete and finally is this lock released.
*
* @exception IOException if the file could not be deleted or
* FileAlreadyLockedException if the file is already locked {@link #lock}
* @since 1.15
*/
public final void delete() throws IOException {
FileLock lock = lock();
try {
delete(lock);
} finally {
lock.releaseLock();
}
}
/** A lookup containing various logical views of the underlying represented file.
* The lookup is supposed to contain this
{@link FileObject}
* (however not necessarily only one, possibly more). The identity of the
* lookup should survive
* {@link #move(org.openide.filesystems.FileLock, org.openide.filesystems.FileObject, java.lang.String, java.lang.String) move operation}
* - the resulting {@link FileObject} after successful move
* will share the same {@link Lookup} as the original {@link FileObject}.
* That is why one can put fileObject.getLookup()
into
* {@link java.util.IdentityHashMap}{@code } and cache
* Anything
regardless the actual location of (moved) file.
* Or one can obtain a {@link Result} from the {@link Lookup}, keep
* its reference, attach a listener to it and be assured that it
* will fire events even if the file gets renamed.
*
*
* Inside of NetBeans Platform application the content of this lookup is usually
* identical to the one provided by the
* DataObject.find(this).getLookup()
.
* This functionality is provided by the org.netbeans.modules.settings
* module.
* DataObject.move
* operation preserves the object's identity, and to mimic the same behavior
* without reference to
* DataObject
* the behavior of {@link FileObject#getLookup() FileObject.getLookup()} has
* been modelled.
*
*
* @return lookup providing logical interfaces additionally describing the
* content of the underlying file
*
* @since 8.0
*/
@Override
public Lookup getLookup() {
return FileObjectLkp.create(this, true);
}
final FileObjectLkp lookup() {
assert Thread.holdsLock(FileObjectLkp.class);
return lkp;
}
final void assignLookup(FileObjectLkp lkp) {
assert Thread.holdsLock(FileObjectLkp.class);
if (this.lkp == lkp) {
return;
}
assert this.lkp == null : "Should be null, but was " + this.lkp;
this.lkp = lkp;
}
/** Get the file attribute with the specified name.
* @param attrName name of the attribute
* @return appropriate (serializable) value or null
if the attribute is unset (or could not be properly restored for some reason)
*/
abstract public Object getAttribute(String attrName);
/** Set the file attribute with the specified name. The actual meaning of
* this method is implementation dependent. It is generally expected that
* the attribute will later be available from {@link #getAttribute(java.lang.String)}
* method.
*
*
* Many FileSystem
* implementations (since version 7.43)
* support special form of arguments for their
* setAttribute
method. One can use
* prefix methodvalue:
or newvalue:
* to store references to
* Method or
* Class respectively.
* The meaning is then similar to {@link XMLFileSystem} attributes
* methodvalue
and newvalue
.
*
* @param attrName name of the attribute
* @param value new value or null
to clear the attribute. Must be serializable, although particular filesystems may or may not use serialization to store attribute values.
* @exception IOException if the attribute cannot be set. If serialization is used to store it, this may in fact be a subclass such as {@link java.io.NotSerializableException}.
*/
abstract public void setAttribute(String attrName, Object value)
throws IOException;
/** Get all file attribute names for this file.
* @return enumeration of keys (as strings)
*/
abstract public Enumeration getAttributes();
/** Test whether this file has the specified extension.
* @param ext the extension the file should have
* @return true if the text after the last period (.
) is equal to the given extension
*/
public final boolean hasExt(String ext) {
if (isHasExtOverride()) {
return hasExtOverride(ext);
}
return getExt().equals(ext);
}
/** Overriden in AbstractFolder */
boolean isHasExtOverride() {
return false;
}
/** Overridden in AbstractFolder */
boolean hasExtOverride(String ext) {
return false;
}
/** Add new listener to this object.
* @param fcl the listener
*/
public abstract void addFileChangeListener(FileChangeListener fcl);
/** Remove listener from this object.
* @param fcl the listener
*/
public abstract void removeFileChangeListener(FileChangeListener fcl);
/** Adds a listener to this {@link FileObject} and all its children and
* children or its children.
* It is guaranteed that whenever a change
* is made via the FileSystem API itself under this {@link FileObject}
* that it is notified to the fcl
listener. Whether external
* changes (if they make sense) are detected and
* notified depends on actual implementation. As some implementations may
* need to perform non-trivial amount of work during initialization of
* listeners, this methods can take long time. Usage of this method may
* consume a lot of system resources and as such it shall be used with care.
* Traditional {@link #addFileChangeListener(org.openide.filesystems.FileChangeListener)}
* is definitely preferred variant.
*
* If you are running with the MasterFS module enabled, it guarantees
* that for files backed with real {@link File}, the system initializes
* itself to detect external changes on the whole subtree.
* This requires non-trivial amount of work and especially on slow
* disks (aka networks ones) may take a long time to add the listener
* and also refresh the system when {@link FileObject#refresh()}
* and especially {@link FileUtil#refreshAll()} is requested.
*
*
* @param fcl the listener to register
* @since 7.28
*/
public void addRecursiveListener(FileChangeListener fcl) {
if (!isFolder()) {
addFileChangeListener(fcl);
return;
}
try {
boolean allowsExternalChanges = getFileSystem() instanceof LocalFileSystem;
getFileSystem().addFileChangeListener(new RecursiveListener(this, fcl, allowsExternalChanges));
} catch (FileStateInvalidException ex) {
ExternalUtil.LOG.log(Level.FINE, "Cannot remove listener from " + this, ex);
}
}
/** Removes listener previously added by {@link #addRecursiveListener(org.openide.filesystems.FileChangeListener)}
*
* @param fcl the listener to remove
* @since 7.28
*/
public void removeRecursiveListener(FileChangeListener fcl) {
if (!isFolder()) {
removeFileChangeListener(fcl);
return;
}
try {
getFileSystem().removeFileChangeListener(new RecursiveListener(this, fcl, false));
} catch (FileStateInvalidException ex) {
ExternalUtil.LOG.log(Level.FINE, "Cannot remove listener from " + this, ex);
}
}
/** Fire data creation event.
* @param en listeners that should receive the event
* @param fe the event to fire in this object
*/
protected void fireFileDataCreatedEvent(Enumeration en, FileEvent fe) {
dispatchEvent(FCLSupport.Op.DATA_CREATED, en, fe);
}
/** Fire folder creation event.
* @param en listeners that should receive the event
* @param fe the event to fire in this object
*/
protected void fireFileFolderCreatedEvent(Enumeration en, FileEvent fe) {
dispatchEvent(FCLSupport.Op.FOLDER_CREATED, en, fe);
}
/** Fire file change event.
* @param en listeners that should receive the event
* @param fe the event to fire in this object
*/
protected void fireFileChangedEvent(Enumeration en, FileEvent fe) {
dispatchEvent(FCLSupport.Op.FILE_CHANGED, en, fe);
}
/** Fire file deletion event.
* @param en listeners that should receive the event
* @param fe the event to fire in this object
*/
protected void fireFileDeletedEvent(Enumeration en, FileEvent fe) {
dispatchEvent(FCLSupport.Op.FILE_DELETED, en, fe);
}
/** Fire file attribute change event.
* @param en listeners that should receive the event
* @param fe the event to fire in this object
*/
protected void fireFileAttributeChangedEvent(Enumeration en, FileAttributeEvent fe) {
dispatchEvent(FCLSupport.Op.ATTR_CHANGED, en, fe);
}
/** Fire file rename event.
* @param en listeners that should receive the event
* @param fe the event to fire in this object
*/
protected void fireFileRenamedEvent(Enumeration en, FileRenameEvent fe) {
dispatchEvent(FCLSupport.Op.FILE_RENAMED, en, fe);
}
/** Puts the dispatch event into the filesystem.
*/
private void dispatchEvent(FCLSupport.Op op, Enumeration en, FileEvent fe) {
try {
FileSystem fs = getFileSystem();
fs.dispatchEvent(new ED(op, en, fe));
} catch (FileStateInvalidException ex) {
// no filesystem, no notification
}
}
final void dispatchEvent(Enumeration en, FileEvent fe) {
try {
getFileSystem().dispatchEvent(new ED(en, fe));
} catch (FileStateInvalidException ex) {
// no filesystem, no notification
}
}
/** Get the MIME type of this file.
* The MIME type identifies the type of the file's contents and should be used in the same way as in the Java
* Activation Framework or in the {@link java.awt.datatransfer} package.
*
* The default implementation calls {@link FileUtil#getMIMEType}.
* (As a fallback return value, content/unknown
is used.)
* @return the MIME type textual representation, e.g. "text/plain"
; never null
*/
public String getMIMEType() {
String mimeType = FileUtil.getMIMEType(this);
return mimeType == null ? "content/unknown" : mimeType; //NOI18N
}
/** Resolves MIME type from the list of acceptable ones. By default
* calls {@link FileUtil#getMIMEType(org.openide.filesystems.FileObject, java.lang.String[])},
* but subclasses may override this method to be more effective.
*
* @param withinMIMETypes
* A hint to the underlaying infrastructure to
* limit the search to given array of MIME types.
* @return the MIME type for the FileObject, or null
if
* the FileObject is unrecognized. It may return {@code content/unknown} instead of {@code null}.
* It is possible for the resulting MIME type to not be a member of given withinMIMETypes
* list.
* @since 7.50
*/
public String getMIMEType(String... withinMIMETypes) {
return FileUtil.getMIMEType(this, withinMIMETypes);
}
/** Get the size of the file.
* @return the size of the file in bytes or zero if the file does not contain data (does not
* exist or is a folder).
*/
public abstract long getSize();
/** Get input stream.
* @return an input stream to read the contents of this file
* @exception FileNotFoundException if the file does not exists, is a folder
* rather than a regular file or is invalid
*/
public abstract InputStream getInputStream() throws FileNotFoundException;
/** Reads the full content of the file object and returns it as array of
* bytes.
* @return array of bytes
* @exception IOException in case the content cannot be fully read
* @since 7.21
*/
public byte[] asBytes() throws IOException {
long len = getSize();
if (len > Integer.MAX_VALUE) {
throw new IOException("Too big file " + getPath()); // NOI18N
}
InputStream is = getInputStream();
try {
byte[] arr = new byte[(int)len];
int pos = 0;
while (pos < arr.length) {
int read = is.read(arr, pos, arr.length - pos);
if (read == -1) {
break;
}
pos += read;
}
if (pos != arr.length) {
throw new IOException("Just " + pos + " bytes read from " + getPath()); // NOI18N
}
return arr;
} finally {
is.close();
}
}
/** Reads the full content of the file object and returns it as string.
* @param encoding the encoding to use
* @return string representing the content of the file
* @exception IOException in case the content cannot be fully read
* @since 7.21
*/
public String asText(String encoding) throws IOException {
return new String(asBytes(), encoding);
}
/** Reads the full content of the file object and returns it as string.
* This is similar to calling {@link #asText(java.lang.String)} with
* default system encoding.
*
* @return string representing the content of the file
* @exception IOException in case the content cannot be fully read
* @since 7.21
*/
public String asText() throws IOException {
return asText(Charset.defaultCharset().name());
}
/** Reads the full content of the file line by line with default
* system encoding. Typical usage is
* in for
loops:
*
* for (String line : fo.asLines()) {
* // do something
* }
*
*
* The list is optimized for iterating line by line, other operations,
* like accessing all the lines or counting the number of its lines may
* be suboptimal.
*
* @return list of strings representing the content of the file
* @exception IOException in case the content cannot be fully read
* @since 7.21
*/
public List asLines() throws IOException {
return asLines(Charset.defaultCharset().name());
}
/** Reads the full content of the file line by line. Typical usage is
* in for
loops:
*
* for (String line : fo.asLines("UTF-8")) {
* // do something
* }
*
*
* The list is optimized for iterating line by line, other operations,
* like accessing all the lines or counting the number of its lines may
* be suboptimal.
*
* @param encoding the encoding to use
* @return list of strings representing the content of the file
* @exception IOException in case the content cannot be fully read
* @since 7.21
*/
public List asLines(final String encoding) throws IOException {
return new FileObjectLines(encoding, this);
}
/** Get output stream.
* @param lock the lock that belongs to this file (obtained by a call to
* {@link #lock})
* @return output stream to overwrite the contents of this file
* @exception IOException if an error occures (the file is invalid, etc.)
*/
public abstract OutputStream getOutputStream(FileLock lock)
throws IOException;
/** Get output stream.
* @return output stream to overwrite the contents of this file
* @throws IOException if an error occurs (the file is invalid, etc.)
* @throws FileAlreadyLockedException if the file is already locked
* @since 6.6
*/
public final OutputStream getOutputStream() throws FileAlreadyLockedException, IOException {
final FileLock lock = lock();
final OutputStream os;
try {
os = getOutputStream(lock);
return new FilterOutputStream(os) {
@Override
public void write(byte b[], int off, int len) throws IOException {
// Delegate to real stream because it is more efficient if it is FileOutputStream.
// Otherwise it is copied byte by byte.
os.write(b, off, len);
}
@Override
public void close() throws IOException {
try {
super.flush();
lock.releaseLock();
super.close();
} catch(IOException iex) {
if (lock.isValid()) {
lock.releaseLock();
}
throw iex;
}
}
};
} catch(IOException iex) {
if (lock.isValid()) {
lock.releaseLock();
}
throw iex;
}
}
/** Lock this file.
* @return lock that can be used to perform various modifications on the file
* @throws FileAlreadyLockedException if the file is already locked
* @throws IOException (UserQuestionException) in case when the lock cannot be obtained now,
* but the underlaying implementation is able to do it after some
* complex/dangerous/long-lasting operation and request confirmation
* from the user
*
*/
public abstract FileLock lock() throws IOException;
/**
* Test if file is locked
* @return true if file is locked
* @since 7.3
*/
public boolean isLocked() {
FileLock fLock = null;
try {
fLock = lock();
} catch (FileAlreadyLockedException fax) {
return true;
} catch (IOException ex) {
return false;
} finally {
if (fLock != null) {
fLock.releaseLock();
}
}
return fLock == null;
}
/** Indicate whether this file is important from a user perspective.
* This method allows a filesystem to distingush between important and
* unimportant files when this distinction is possible.
*
* For example: Java sources have important .java
files and
* unimportant .class
files. If the filesystem provides
* an "archive" feature it should archive only .java
files.
* @param b true if the file should be considered important
* @deprecated No longer used. Instead use
* SharabilityQuery
.
*/
@Deprecated
public abstract void setImportant(boolean b);
/** Get all children of this folder (files and subfolders). If the file does not have children
* (does not exist or is not a folder) then an empty array should be returned. No particular order is assumed.
*
* @return array of direct children
* @see #getChildren(boolean)
* @see #getFolders
* @see #getData
*/
public abstract FileObject[] getChildren();
/** Enumerate all children of this folder. If the children should be enumerated
* recursively, first all direct children are listed; then children of direct subfolders; and so on.
*
* @param rec whether to enumerate recursively
* @return enumeration of type FileObject
*/
public Enumeration extends FileObject> getChildren(final boolean rec) {
class WithChildren implements Enumerations.Processor {
@Override
public FileObject process(FileObject fo, Collection toAdd) {
if (rec && fo.isFolder()) {
for (FileObject child : fo.getChildren()) {
try {
if (!FileUtil.isRecursiveSymbolicLink(child)) { // #218795
toAdd.add(child);
}
} catch (IOException ex) {
ExternalUtil.LOG.log(Level.INFO, null, ex);
}
}
}
return fo;
}
}
return Enumerations.queue(Enumerations.array(getChildren()), new WithChildren());
}
/** Enumerate the subfolders of this folder.
* @param rec whether to recursively list subfolders
* @return enumeration of type FileObject
(satisfying {@link #isFolder})
*/
public Enumeration extends FileObject> getFolders(boolean rec) {
return Enumerations.filter(getChildren(rec), new OnlyFolders(true));
}
/** Enumerate all data files in this folder.
* @param rec whether to recursively search subfolders
* @return enumeration of type FileObject
(satisfying {@link #isData})
*/
public Enumeration extends FileObject> getData(boolean rec) {
return Enumerations.filter(getChildren(rec), new OnlyFolders(false));
}
/** Retrieve file or folder contained in this folder by name.
* Note that neither file nor folder is created on disk.
* @param name basename of the file or folder (in this folder)
* @param ext extension of the file; null
or ""
* if the file should have no extension or if folder is requested
* @return the object representing this file or null
if the file
* or folder does not exist
* @exception IllegalArgumentException if this
is not a folder
*/
public abstract FileObject getFileObject(String name, String ext);
/** Retrieve file or folder relative to a current folder, with a given relative path.
* Note that neither file nor folder is created on disk. This method isn't final since revision 1.93.
* Since 7.45 common implementations of this method
* accept also ".." which is interpreted as a reference to parent.
*
* @param relativePath is just basename of the file or (since 4.16) the relative path delimited by '/'
* @return the object representing this file or null
if the file
* or folder does not exist
* @exception IllegalArgumentException if this
is not a folder
*/
public FileObject getFileObject(String relativePath) {
if (relativePath.startsWith("/") && !relativePath.startsWith("//")) {
relativePath = relativePath.substring(1);
}
FileObject myObj = this;
StringTokenizer st = new StringTokenizer(relativePath, "/");
if(relativePath.startsWith("//")) {
// if it is UNC absolute path, start with //ComputerName/sharedFolder
myObj = myObj.getFileObject("//"+st.nextToken()+"/"+st.nextToken(), null);
}
while ((myObj != null) && st.hasMoreTokens()) {
String nameExt = st.nextToken();
if (nameExt.equals("..")) { // NOI18N
myObj = myObj.getParent();
} else {
if (!nameExt.equals(".")) {
myObj = myObj.getFileObject(nameExt, null);
}
}
}
return myObj;
}
/**
* Create a new folder below this one with the specified name.
* Fires {@link FileChangeListener#fileFolderCreated}.
*
* @param name the name of folder to create. Periods in name are allowed (but no slashes).
* @return the new folder
* @exception IOException if the folder cannot be created (e.g. already exists), or if this
is not a folder
* @see FileUtil#createFolder
*/
public abstract FileObject createFolder(String name)
throws IOException;
/**
* Create new data file in this folder with the specified name.
* Fires {@link FileChangeListener#fileDataCreated}.
*
* @param name the name of data object to create (can contain a period, but no slashes)
* @param ext the extension of the file (or null
or ""
)
* @return the new data file object
* @exception IOException if the file cannot be created (e.g. already exists), or if this
is not a folder
* @see FileUtil#createData
*/
public abstract FileObject createData(String name, String ext)
throws IOException;
/**
* Create new data file in this folder with the specified name.
* Fires {@link FileChangeListener#fileDataCreated}.
*
* @param name the name of data object to create (can contain a period, but no slashes)
* @return the new data file object
* @exception IOException if the file cannot be created (e.g. already exists), or if this
is not a folder
* @since 1.17
* @see FileUtil#createData
*/
public FileObject createData(String name) throws IOException {
return createData(name, ""); // NOI18N
}
/**
* Creates new file in this folder and immediately opens it for writing.
* This method prevents possible race condition which can happen
* when using the {@link #createData(java.lang.String)} method.
* Using {@link #createData(java.lang.String) that method} makes it
* possible for someone to read the content of the newly created
* file before its content is written. This method does its best to eliminate
* such race condition.
*
*
* This method usually delivers both, {@link FileChangeListener#fileDataCreated(org.openide.filesystems.FileEvent) data created}
* and {@link FileChangeListener#fileChanged(org.openide.filesystems.FileEvent) changed}
* events. Preferably it delivers them asynchronously. The assumption
* is that the file will be (at least partially) written before
* the listeners start to process the first event. The safety is additionally
* ensured by mutual exclusion
between output and input streams
* for the same file (any call to {@link #getInputStream()} will be blocked
* for at least two seconds if there is existing open output stream).
* If you finish writing the content of your file in those two seconds,
* you can be sure, nobody will have read its content yet.
*
*
* @param name name of file to create with its extension
* @return output stream to use to write content of the file
* @throws IOException if the file cannot be created (e.g. already exists), or if this
is not a folder
* @since 7.41
*/
public OutputStream createAndOpen(final String name) throws IOException {
class R implements FileSystem.AsyncAtomicAction {
OutputStream os;
@Override
public void run() throws IOException {
FileObject fo = createData(name);
os = fo.getOutputStream();
}
@Override
public boolean isAsynchronous() {
return true;
}
}
R r = new R();
getFileSystem().runAtomicAction(r);
return r.os;
}
/** Test whether this file can be written to or not.
*
* The value returned from this method should indicate the capabilities of the
* file from the point of view of users of the FileObject's API, the actual
* state of the file on a disk does not matter if the implementation of the
* filesystem can change it when requested.
*
* The result returned from this method should be tight together with
* the expected behaviour of getOutputStream
. If it is
* likely that the method successfully returns a stream that can be
* written to, let the isReadOnly
return false
.
*
* Also other fileobject methods like delete
* are suggested to be connected to result of this method. If not
* read only, then it can be deleted, etc.
*
* It is a good idea to call this method before attempting to perform any
* operation on the FileObject that might throw an IOException simply
* because it is read-only. If isReadOnly returns true, the operation may
* be skipped, or the user notified that it cannot be done.
* However it is often desirable for the user to be able to
* continue the operation in case the filesystem supports making a file
* writable. In this case calling code should:
*
* - Call {@link #lock} and catch any exception thrown.
*
- Then:
*
* - If no exception is thrown, proceed with the operation.
*
- If a UserQuestionException is thrown,
* call {@link UserQuestionException#confirmed} on it
* (asynchronously - do not block any important threads). If
true
,
* proceed with the operation. If false
, exit.
* If an IOException
is thrown, notify it and exit.
* - If another
IOException
is thrown, call {@link #isReadOnly}.
* If true
, ignore the exception (it is expected).
* If false
, notify it.
*
* In either case, exit.
*
*
*
* @return true
if file is read-only
* @deprecated Please use the {@link #canWrite}.
*/
@Deprecated
public abstract boolean isReadOnly();
/**
* Tests if this file can be written to.
*
* The default implementation simply uses java.io.File.canWrite
* if there exists conversion to java.io.File
(see {@link FileUtil#toFile}).
* If conversion is not possible, then deprecated method {@link #isReadOnly} is used.
* @return true if this file can be written, false if not.
* @since 3.31
*/
public boolean canWrite() {
File f = FileUtil.toFile(this);
if (f != null) {
return f.canWrite();
}
return !isReadOnly();
}
/**
* Tests if this file can be read.
*
* The default implementation simply uses java.io.File.canRead
* if there exists conversion to java.io.File
(see {@link FileUtil#toFile}).
* If conversion is not possible, then true
is returned.
* @return true if this file can be read, false if not.
* @since 3.31
*/
public boolean canRead() {
File f = FileUtil.toFile(this);
if (f != null) {
return f.canRead();
}
return true;
}
static final String REMOVE_WRITABLES_ATTR = "removeWritables";
/**
* Checks whether this file can be reverted to a pristine state.
* @return whether {@link #revert} might do something
* @since 7.55
*/
public final boolean canRevert() {
return getAttribute(REMOVE_WRITABLES_ATTR) instanceof Callable>;
}
/**
* Revert this file to a pristine state.
* Generally only meaningful for files on the system filesystem (layers + user directory):
* if the file is defined in a layer but modified in the user directory, it is reset;
* if it is defined only in the user directory, it is deleted.
* If {@link #canRevert} is false, does nothing.
* Note that while content can be reset, it may not be possible to reset attributes.
*
Implementors: for historical reasons this method checks {@link #getAttribute}
* for a special attribute named {@code removeWritables} which must be of type
* {@link Callable Callable}. If present, the file is considered modified.
* @since 7.55
*/
public final void revert() throws IOException {
Object v = getAttribute(REMOVE_WRITABLES_ATTR);
if (v instanceof Callable>) {
try {
((Callable>) v).call();
} catch (IOException x) {
throw x;
} catch (Exception x) {
throw new IOException(x);
}
}
}
/** Should check for external modifications. For folders it should reread
* the content of disk, for data file it should check for the last
* time the file has been modified.
*
* @param expected should the file events be marked as expected change or not?
* @see FileEvent#isExpected
*/
public void refresh(boolean expected) {
}
/** Should check for external modifications. For folders it should reread
* the content of disk, for data file it should check for the last
* time the file has been modified.
*
* The file events are marked as unexpected.
*/
public void refresh() {
refresh(false);
}
/**
* @throws FileStateInvalidException never
* @deprecated Use {@link #toURL} instead.
*/
@Deprecated
public final URL getURL() throws FileStateInvalidException {
return toURL();
}
/** Get URL that can be used to access this file.
* If the file object does not correspond to a disk file or JAR entry,
* the URL will only be usable within NetBeans as it uses a special protocol handler.
* Otherwise an attempt is made to produce an external URL.
* @return URL of this file object
* @see URLMapper#findURL
* @see URLMapper#INTERNAL
* @since 7.57
*/
public final URL toURL() {
return URLMapper.findURL(this, URLMapper.INTERNAL);
}
/**
* Gets a URI for this file.
* Similar to {@link #toURL}.
* @return an absolute URI representing this file location
* @since 7.57
*/
public final URI toURI() {
try {
URI uri = toURL().toURI();
assert uri.isAbsolute() : uri;
assert uri.equals(uri.normalize()) : uri + " == " + uri.normalize() + " from " + this;
return uri;
} catch (URISyntaxException x) {
throw new IllegalStateException(x);
}
}
/**
* Tests if file really exists or is missing. Some operation on it may be restricted.
* @return true indicates that the file is missing.
* @since 1.9
*/
public boolean isVirtual() {
return false;
}
/**
* Check whether this FileObject is a symbolic link.
*
* The default implementation returns false, but on filesystems that support
* symbolic links this method should be overriden.
*
* @return True if this FileObject represents a symbolic link, false
* otherwise.
* @throws java.io.IOException If some I/O problem occurs.
* @since openide.filesystem/9.4
*/
public boolean isSymbolicLink() throws IOException {
return false;
}
/**
* Read symbolic link.
*
* If this FileObject represents a symbolic link, return a FileObject it
* refers to, or null if the referred file or directory does not exist. If
* this FileObject doesn't represent a symbolic link, the return value is
* undefined.
*
* The default implementation returns null, but on filesystems that support
* symbolic links this method should be overriden.
*
* @return The referred FileObject, or null if it is not available.
* @throws java.io.IOException If some I/O problem occurs.
* @since openide.filesystem/9.4
*/
public FileObject readSymbolicLink() throws IOException {
return null;
}
/**
* Read symbolic link path.
*
* If this FileObject represents a symbolic link, return the path it refers
* to, which can be absolute or relative. If this FileObject doesn't
* represent a symbolic link, the return value is undefined.
*
* The default implementation returns this FileObject's path, but on
* filesystems that support symbolic links this method should be overriden.
*
* @return The referred FileObject path.
* @throws java.io.IOException If some I/O problem occurs.
* @since openide.filesystem/9.4
*/
public String readSymbolicLinkPath() throws IOException {
return this.getPath();
}
/**
* Return a FileObject with path where all symbolic links are resolved.
*
* The default implementation returns this object, but on filesystems that
* support symbolic links this method should be overriden.
*
* @return The FileObject with path where all symlinks are resolved, or null
* if it doesn't exist.
* @throws java.io.IOException If some I/O problem occurs.
* @since openide.filesystem/9.4
*/
public FileObject getCanonicalFileObject() throws IOException {
return this;
}
/** Listeners registered from MultiFileObject are considered as priority
* listeners.
*/
static boolean isPriorityListener(FileChangeListener fcl) {
if (fcl instanceof PriorityFileChangeListener) {
return true;
} else {
return false;
}
}
interface PriorityFileChangeListener extends FileChangeListener {}
private static class ED extends FileSystem.EventDispatcher {
private FCLSupport.Op op;
private Enumeration en;
final private List fsList;
final private List repList;
private FileEvent fe;
public ED(FCLSupport.Op op, Enumeration en, FileEvent fe) {
this.op = op;
this.en = en;
this.fe = fe;
FileSystem fs = null;
try {
fs = this.fe.getFile().getFileSystem();
} catch (FileStateInvalidException ex) {
ExternalUtil.exception(ex);
}
ListenerList fsll = (fs != null) ? fs.getFCLSupport().listeners : null;
ListenerList repll = (fs != null && fs.getRepository() != null) ? fs.getRepository().getFCLSupport().listeners : null;
fsList = ListenerList.allListeners(fsll);
repList = ListenerList.allListeners(repll);
}
public ED(Enumeration en, FileEvent fe) {
this(null, en, fe);
}
/** @param onlyPriority if true then invokes only priority listeners
* else all listeners are invoked.
*/
@Override
protected void dispatch(boolean onlyPriority, Collection postNotify) {
if (this.op == null) {
this.op = fe.getFile().isFolder() ? FCLSupport.Op.FOLDER_CREATED : FCLSupport.Op.DATA_CREATED;
}
LinkedList newEnum = new LinkedList(); // later lazy
while (en.hasMoreElements()) {
FileChangeListener fcl = en.nextElement();
if (onlyPriority && !isPriorityListener(fcl)) {
newEnum.add(fcl);
continue;
}
FCLSupport.dispatchEvent(fcl, fe, op, postNotify);
}
if (onlyPriority) {
this.en = Collections.enumeration(newEnum);
}
/** FileEvents are forked in may cases. But FileEvents fired from
* FileSystem and from Repository mustn`t be forked.
*/
FileObject fo = fe.getFile();
boolean transmit = false;
if (fo != null) {
switch (op) {
case FILE_CHANGED:
transmit = fo.equals(fe.getSource());
break;
default:
transmit = !fo.equals(fe.getSource());
if (!transmit && fe instanceof Enumeration && !((Enumeration) fe).hasMoreElements()) {
transmit = true;
}
}
}
if (!en.hasMoreElements() && transmit && !onlyPriority) {
FileSystem fs = null;
Repository rep = null;
try {
fs = fe.getFile().getFileSystem();
rep = fs.getRepository();
} catch (FileStateInvalidException fsix) {
return;
}
if (fs != null && fsList != null) {
FCLSupport.dispatchEvent(fsList, fe, op, postNotify);
}
if (rep != null && repList != null) {
FCLSupport.dispatchEvent(repList, fe, op, postNotify);
}
}
}
@Override
protected void setAtomicActionLink(EventControl.AtomicActionLink propID) {
fe.setAtomicActionLink(propID);
}
}
/** Filters folders or data files.
*/
private static final class OnlyFolders implements Enumerations.Processor {
private boolean folders;
public OnlyFolders(boolean folders) {
this.folders = folders;
}
@Override
public FileObject process(FileObject obj, Collection coll) {
FileObject fo = obj;
if (folders) {
return fo.isFolder() ? fo : null;
} else {
return fo.isData() ? fo : null;
}
}
}
// end of OnlyFolders
}