/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is NetBeans. The Initial Developer of the Original
* Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
* Microsystems, Inc. All Rights Reserved.
*/
package org.openide.filesystems;
import java.beans.*;
import java.io.*;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.*;
import org.openide.util.actions.SystemAction;
import org.openide.util.SharedClassObject;
import org.openide.util.enums.*;
/** Implementation of <code>FileSystem</code> that simplifies the most
* common tasks. Caches information about the filesystem in
* memory and periodically refreshes its content.
* Many other operations are performed in a safer manner so as to reuse
* known experience; should be substantially simpler to subclass.
*
* @author Jaroslav Tulach
*/
public abstract class AbstractFileSystem extends FileSystem {
/** generated Serialized Version UID */
static final long serialVersionUID = -3345098214331282438L;
/** system actions for this FS if it has refreshTime != 0 */
private static SystemAction[] SYSTEM_ACTIONS;
/** system actions for this FS */
private static final SystemAction[] NO_SYSTEM_ACTIONS = new SystemAction[] {
};
/** root object for the filesystem */
private transient AbstractFileObject root;
/** refresher */
private transient RefreshRequest refresher;
/** cached last value of Enumeration which holds resource name (enumeration like StringTokenizer)*/
static transient private PathElements lastEnum;
/** Provider of hierarchy of files. */
protected List list;
/** Methods for modification of files. */
protected Change change;
/** Methods for moving of files. This field can be left null if the filesystem
* does not require special handling handling of FileObject.move and is satified
* with the default implementation.
*/
protected Transfer transfer;
/** Methods for obtaining information about files. */
protected Info info;
/** Handling of attributes for files. */
protected Attr attr;
/**
* Actually implements contract of FileSystem.refresh().
*/
public void refresh (boolean expected) {
Enumeration en = getAbstractRoot ().existingSubFiles (true);
while (en.hasMoreElements()) {
FileObject fo = (FileObject)en.nextElement();
fo.refresh(expected);
}
}
/* Provides a name for the system that can be presented to the user.
* @return user presentable name of the filesystem
*/
public abstract String getDisplayName ();
/* Getter for root folder in the filesystem.
*
* @return root folder of whole filesystem
*/
public FileObject getRoot () {
return getAbstractRoot ();
}
/* Finds file when its name is provided.
*
* @param aPackage package name where each package is separated by a dot
* @param name name of the file (without dots) or <CODE>null</CODE> if
* one want to obtain name of package and not file in it
* @param ext extension of the file or <CODE>null</CODE> if one needs
* package and not file name
*
* @warning when one of name or ext is <CODE>null</CODE> then name and
* ext should be ignored and scan should look only for a package
*
* @return FileObject that represents file with given name or
* <CODE>null</CODE> if the file does not exist
*/
public FileObject find (String aPackage, String name, String ext) {
// create enumeration of name to look for
StringTokenizer st = new StringTokenizer (aPackage, "."); // NOI18N
if (name == null || ext == null) {
// search for folder, return the object only if it is folder
FileObject fo = getAbstractRoot ().find (st);
return fo != null && fo.isFolder() ? fo : null;
} else {
Enumeration en = new SequenceEnumeration (
st,
new SingletonEnumeration (name + '.' + ext)
);
// tries to find it (can return null)
return getAbstractRoot ().find (en);
}
}
/* Finds file when its resource name is given.
* The name has the usual format for the {@link ClassLoader#getResource(String)}
* method. So it may consist of "package1/package2/filename.ext".
* If there is no package, it may consist only of "filename.ext".
*
* @param name resource name
*
* @return FileObject that represents file with given name or
* <CODE>null</CODE> if the file does not exist
*/
public FileObject findResource (String name) {
if (name.length () == 0) {
return getAbstractRoot ();
}
/**Next piece of code is preformance enhancement; lastEnum = last value cache;
PathElements is StringTokenizer wrapper that caches individual elements*/
PathElements local = lastEnum;
if (local == null || !local.getOriginalName().equals(name)) {
local = new PathElements (name);
lastEnum = local;
}
return getAbstractRoot ().find (local.getEnumeration());
}
/** Creates Reference. In FileSystem, which subclasses AbstractFileSystem, you can overload method
* createReference(FileObject fo) to achieve another type of Reference (weak, strong etc.)
* @param fo is FileObject. It`s reference yourequire to get.
* @return Reference to FileObject
*/
protected Reference createReference(FileObject fo){
return(new WeakReference (fo));
}
/** This method allows to find Reference to resourceName
* @param resourceName is name of resource
* @return Reference to resourceName
*/
protected final Reference findReference(String resourceName){
if (resourceName.length () == 0) {
return null;
} else {
StringTokenizer tok = new StringTokenizer (resourceName, "/"); // NOI18N
return getAbstractRoot ().findRefIfExists (tok);
}
}
/*
* @return true if RefreshAction should be enabled
*/
boolean isEnabledRefreshFolder () {
return (refresher != null);
}
/* Action for this filesystem.
*
* @return refresh action
*/
public SystemAction[] getActions () {
if (!isEnabledRefreshFolder ()) {
return NO_SYSTEM_ACTIONS;
} else {
if (SYSTEM_ACTIONS == null) {
try {
Class c = Class.forName ("org.openide.filesystems.RefreshAction"); // NOI18N
RefreshAction ra = (RefreshAction) SharedClassObject.findObject (c, true);
// initialize the SYSTEM_ACTIONS
SYSTEM_ACTIONS = new SystemAction[] { ra };
} catch (Exception ex) {
// ok, we are probably running in standalone mode and
// classes needed to initialize the RefreshAction are
// not available
SYSTEM_ACTIONS = NO_SYSTEM_ACTIONS;
}
}
return SYSTEM_ACTIONS;
}
}
/** Set the number of milliseconds between automatic
* refreshes of the directory structure.
*
* @param ms number of milliseconds between two refreshes; if <code><= 0</code> then refreshing is disabled
*/
protected synchronized final void setRefreshTime (int ms) {
if (refresher != null) {
refresher.stop ();
}
if (ms <= 0 || System.getProperty ("netbeans.debug.heap") != null) {
refresher = null;
} else {
refresher = new RefreshRequest (this, ms);
}
}
/** Get the number of milliseconds between automatic
* refreshes of the directory structure.
* By default, automatic refreshing is disabled.
* @return the number of milliseconds, or <code>0</code> if refreshing is disabled
*/
protected final int getRefreshTime () {
RefreshRequest r = refresher;
return r == null ? 0 : r.getRefreshTime ();
}
/** Instruct the filesystem
* that the root should change.
* A fresh root is created. Subclasses that support root changes should use this.
*
* @return the new root
*/
final synchronized AbstractFileObject refreshRootImpl () {
root = createFileObject (null, ""); // NOI18N
return root;
}
/** Instruct the filesystem
* that the root should change.
* A fresh root is created. Subclasses that support root changes should use this.
*
* @return the new root
*/
protected final FileObject refreshRoot () {
return refreshRootImpl ();
}
/** @deprecated Just for backward compatibility, renamed during runtime to refreshRoot
* and made available publicly
*/
private AbstractFileObject r3fr3shRoot () {
return refreshRootImpl ();
}
/** Allows subclasses to fire that a change occured in a
* file or folder. The change can be "expected" when it is
* a result of an user action and the user knows that such
* change should occur.
*
* @param name resource name of the file where the change occured
* @param expected true if the user initiated change and expects it
*/
protected final void refreshResource (String name, boolean expected) {
AbstractFileObject fo = (AbstractFileObject)findResourceIfExists (name);
if (fo != null) {
// refresh and behave like the changes is expected
fo.refresh (null, null, true, expected);
}
}
/**
* For the FileObject specified as parameter, returns the recursive enumeration
* of existing children fileobjects (both folders and data). It doesn't create
* any new FileObject instances. Direct children are at the begining of the enumeration.
* @param fo the starting point for the recursive fileobject search
* @return enumeration of currently existing fileobjects.
*/
protected final Enumeration existingFileObjects (FileObject fo) {
AlterEnumeration en = new AlterEnumeration(existingFileObjectsWeak(fo)) {
protected Object alter(Object obj) {
return ((Reference) obj).get();
}
};
FilterEnumeration fen = new FilterEnumeration(en) {
protected boolean accept(Object obj) {
return (obj != null && ((FileObject) obj).isValid ());
}
};
return fen;
}
/**
* Return Enumeration of references to FileObjects.
*/
final Enumeration existingFileObjectsWeak (FileObject fo) {
QueueEnumeration en = new QueueEnumeration () {
public void process (Object o) {
Reference ref = (Reference)o;
AbstractFileObject file = (AbstractFileObject)ref.get ();
if (file != null) {
FileObject[] arr = file.subfiles ();
Reference[] to = new Reference[arr.length];
// make the array weak
for (int i = 0; i < arr.length; i++) {
to[i] = new WeakReference (arr[i]);
}
// put it into the enumeration
put (to);
}
}
};
// weak reference to root
en.put (new WeakReference (fo));
return en;
}
/**
* @return if value of lastModified should be cached
*/
boolean isLastModifiedCacheEnabled () {
return true;
}
/* Finds file when its resource name is given.
* The name has the usual format for the {@link ClassLoader#getResource(String)}
* method. So it may consist of "package1/package2/filename.ext".
* If there is no package, it may consist only of "filename.ext".
*
* @param name resource name
*
* @return FileObject that represents file with given name or
* <CODE>null</CODE> if the file does not exist
*/
private FileObject findResourceIfExists (String name) {
if (name.length () == 0) {
return getAbstractRoot ();
} else {
StringTokenizer tok = new StringTokenizer (name, "/"); // NOI18N
return getAbstractRoot ().findIfExists (tok);
}
}
/** Hooking method to allow MultiFileSystem to be informed when a new
* file object is created. This is the only method that creates AbstractFileObjects.
*
* @param parent parent object
* @param name of the object
*/
AbstractFileObject createFileObject (AbstractFileObject parent, String name) {
return new AbstractFileObject (this, parent, name);
}
/** Creates root object for the fs.
*/
final AbstractFileObject getAbstractRoot () {
if (root == null) {
synchronized (this) {
if (root == null) {
return refreshRootImpl ();
}
}
}
return root;
}
/** Writes the common fields and the state of refresher.
*/
private void writeObject (ObjectOutputStream oos) throws IOException {
ObjectOutputStream.PutField fields = oos.putFields();
fields.put ("change", change); // NOI18N
fields.put ("info", info); // NOI18N
fields.put ("attr", attr); // NOI18N
fields.put ("list", list); // NOI18N
fields.put ("transfer", transfer); // NOI18N
oos.writeFields();
oos.writeInt (getRefreshTime ());
}
/** Reads common fields and state of refresher.
*/
private void readObject (ObjectInputStream ois) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField fields = ois.readFields();
Object o1 = readImpl ("change", fields); // change // NOI18N
Object o2 = readImpl ("info", fields); // info // NOI18N
Object o3 = readImpl ("attr", fields); // attr // NOI18N
Object o4 = readImpl ("list", fields); // list // NOI18N
Object o5 = readImpl ("transfer", fields); // transfer // NOI18N
change = (Change)o1;
info = (Info)o2;
attr = (Attr)o3;
list = (List)o4;
transfer = (Transfer)o5;
setRefreshTime (ois.readInt ());
}
//
// Backward compatibility methods
//
/** Reads object from input stream, if it is
* LocalFileSystem or JarFileSystem then replaces the object
* by its Impl.
*/
static Object readImpl (String name, ObjectInputStream.GetField fields)
throws ClassNotFoundException, IOException {
Object o = fields.get (name, null);
if (o instanceof LocalFileSystem) {
return new LocalFileSystem.Impl ((LocalFileSystem)o);
} else if (o instanceof JarFileSystem) {
return new JarFileSystem.Impl ((JarFileSystem)o);
}
return o;
}
/**
* This method is called from AbstractFileObject.isVirtual. Tests if file
* really exists or is missing. Some operation on it may be restricted if returns true.
* @param name of the file
* @return true indicates that the file is missing.
* @since 1.9
*/
protected boolean checkVirtual(String name) {
return false;
}
/** Tests if this file can be written to.
* @param name resource name
* @return true if this file can be written, false if not.
* @since 3.31
*/
protected boolean canWrite(String name) {
AbstractFileObject afo = (AbstractFileObject)this.findResource(name);
return (afo != null) ? afo.superCanWrite () : false;
}
/** Tests if this file can be read.
* @param name resource name
* @return true if this file can be read, false if not.
* @since 3.31
*/
protected boolean canRead(String name) {
AbstractFileObject afo = (AbstractFileObject)this.findResource(name);
return (afo != null) ? afo.superCanRead () : false;
}
/** Mark the file as being important or unimportant.
* @param name the file to mark
* @param important true indicates that file is important, false conversely
* file is unimportant.
* @since 1.9
*/
protected void markImportant (String name, boolean important) {
if (!important && info != null)
info.markUnimportant(name);
}
/** Provides access to the hierarchy of resources.
*/
public interface List extends java.io.Serializable {
/** @deprecated Only public by accident. */
/* public static final */ long serialVersionUID = -6242105832891012528L;
/** Get a list of children files for a given folder.
*
* @param f the folder, by name; e.g. <code>top/next/afterthat</code>
* @return a list of children of the folder, as <code>file.ext</code> (no path)
* the array can contain <code>null</code> values that will be ignored
*/
public String[] children (String f);
}
/** Controls modification of files.
*/
public interface Change extends java.io.Serializable {
/** @deprecated Only public by accident. */
/* public static final */ long serialVersionUID = -5841597109944924596L;
/** Create new folder.
* @param name full name of new folder, e.g. <code>topfolder/newfolder</code>
* @throws IOException if the operation fails
*/
public void createFolder (String name) throws java.io.IOException;
/** Create new data file.
*
* @param name full name of the file, e.g. <code>path/from/root/filename.ext</code>
*
* @exception IOException if the file cannot be created (e.g. already exists)
*/
public void createData (String name) throws IOException;
/** Rename a file.
*
* @param oldName old name of the file; fully qualified
* @param newName new name of the file; fully qualified
* @throws IOException if it could not be renamed
*/
public void rename(String oldName, String newName) throws IOException;
/** Delete a file.
*
* @param name name of file; fully qualified
* @exception IOException if the file could not be deleted
*/
public void delete (String name) throws IOException;
}
/** Controls on moving of files. This is additional interface to
* allow filesystem that require special handling of move to implement
* it in different way then is the default one.
*/
public interface Transfer extends java.io.Serializable {
/** @deprecated Only public by accident. */
/* public static final */ long serialVersionUID = -8945397853892302838L;
/** Move a file.
*
* @param name of the file on current filesystem
* @param target move implementation
* @param targetName of target file
* @exception IOException if the move fails
* @return false if the method is not able to handle the request and
* default implementation should be used instead
*/
public boolean move (String name, Transfer target, String targetName) throws IOException;
/** Copy a file.
*
* @param name of the file on current filesystem
* @param target target transfer implementation
* @param targetName name of target file
* @exception IOException if the copy fails
* @return false if the method is not able to handle the request and
* default implementation should be used instead
*/
public boolean copy (String name, Transfer target, String targetName) throws IOException;
}
/** Information about files.
*/
public interface Info extends java.io.Serializable {
/** @deprecated Only public by accident. */
/* public static final */ long serialVersionUID = -2438286177948307985L;
/**
* Get last modification time.
* @param name the file to test
* @return the date of last modification
*/
public java.util.Date lastModified(String name);
/** Test if the file is a folder or contains data.
* @param name name of the file
* @return <code>true</code> if the file is folder, <code>false</code> if it is data
*/
public boolean folder (String name);
/** Test whether this file can be written to or not.
* @param name the file to test
* @return <CODE>true</CODE> if the file is read-only
*/
public boolean readOnly (String name);
/** Get the MIME type of the file. If filesystem has no special support
* for MIME types then can simply return null. FileSystem can register
* MIME types for a well-known extensions: FileUtil.setMIMEType(String ext, String mimeType)
* or together with filesystem supply some resolvers subclassed from MIMEResolver.
*
* @param name the file to test
* @return the MIME type textual representation (e.g. <code>"text/plain"</code>)
* or null if no special support for recognizing MIME is implemented.
*/
public String mimeType (String name);
/** Get the size of the file.
*
* @param name the file to test
* @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 long size (String name);
/** Get input stream.
*
* @param name the file to test
* @return an input stream to read the contents of this file
* @exception FileNotFoundException if the file does not exist or is invalid
*/
public InputStream inputStream (String name) throws java.io.FileNotFoundException;
/** Get output stream.
*
* @param name the file to test
* @return output stream to overwrite the contents of this file
* @exception IOException if an error occurs (the file is invalid, etc.)
*/
public OutputStream outputStream (String name) throws java.io.IOException;
/** Lock the file.
* May do nothing if the underlying storage does not support locking.
* This does not affect locking using {@link FileLock} within the IDE, however.
* @param name name of the file
* @throws FileAlreadyLockedException if the file is already locked
*/
public void lock (String name) throws IOException;
/** Unlock the file.
* @param name name of the file
*/
public void unlock (String name);
/** Mark the file as being unimportant.
* If not called, the file is assumed to be important.
*
* @param name the file to mark
*/
public void markUnimportant (String name);
}
/** Handle attributes of files.
*/
public interface Attr extends java.io.Serializable {
/** @deprecated Only public by accident. */
/* public static final */ long serialVersionUID = 5978845941846736946L;
/** Get the file attribute with the specified name.
* @param name the file
* @param attrName name of the attribute
* @return appropriate (serializable) value or <CODE>null</CODE> if the attribute is unset (or could not be properly restored for some reason)
*/
public Object readAttribute(String name, String attrName);
/** Set the file attribute with the specified name.
* @param name the file
* @param attrName name of the attribute
* @param value new value or <code>null</code> 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 NotSerializableException}.
*/
public void writeAttribute(String name, String attrName, Object value) throws IOException;
/** Get all file attribute names for the file.
* @param name the file
* @return enumeration of keys (as strings)
*/
public Enumeration attributes(String name);
/** Called when a file is renamed, to appropriately update its attributes.
* @param oldName old name of the file
* @param newName new name of the file
*/
public void renameAttributes (String oldName, String newName);
/** Called when a file is deleted, to also delete its attributes.
*
* @param name name of the file
*/
public void deleteAttributes (String name);
}
}