/* * 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.IOException; import java.text.MessageFormat; import org.openide.util.actions.SystemAction; import org.openide.util.NbBundle; /** Interface that provides basic information about a virtual * filesystem in the IDE. Classes that implement it * should follow JavaBean conventions because when a new * instance of a filesystem class is inserted into the system, it should * permit the user to modify it with standard Bean properties. * <P> * Implementing classes should also have associated subclasses of {@link FileObject}. * <p>Although the class is serializable, only the {@link #isHidden hidden state} and {@link #getSystemName system name} * are serialized, and the deserialized object is by default {@link #isValid invalid} (and may be a distinct * object from a valid filesystem in the Repository). If you wish to safely deserialize a file * system, you should after deserialization try to replace it with a filesystem of the * {@link Repository#findFileSystem same name} in the Repository. * @author Jaroslav Tulach */ public abstract class FileSystem implements java.io.Serializable { /** generated Serialized Version UID */ static final long serialVersionUID = -8931487924240189180L; /** Property name indicating validity of filesystem. */ public static final String PROP_VALID = "valid"; // NOI18N /** Property name indicating whether filesystem is hidden. */ public static final String PROP_HIDDEN = "hidden"; // NOI18N /** Property name giving internal system name of filesystem. */ public static final String PROP_SYSTEM_NAME = "systemName"; // NOI18N /** Property name giving display name of filesystem. * @since 2.1 */ public static final String PROP_DISPLAY_NAME = "displayName"; // NOI18N /** Property name giving root folder of filesystem. */ public static final String PROP_ROOT = "root"; // NOI18N /** Property name giving read-only state. */ public static final String PROP_READ_ONLY = "readOnly"; // NOI18N /** Property name giving capabilities state. */ static final String PROP_CAPABILITIES = "capabilities"; // NOI18N /** is this filesystem valid? * It can be invalid if there is another filesystem with the * same name in the filesystem pool. */ transient private boolean valid = false; /** True if the filesystem is assigned to pool. * Is modified from Repository methods. */ transient boolean assigned = false; /**Repository that contains this FileSystem or null*/ private transient Repository repository = null; private transient FCLSupport fclSupport; /** Describes capabilities of the filesystem. */ private FileSystemCapability capability; /** property listener on FileSystemCapability. */ private transient PropertyChangeListener capabilityListener; /** hidden flag */ private boolean hidden = false; /** system name */ private String systemName = "".intern (); // NOI18N /** Utility field used by event firing mechanism. */ private transient ListenerList fileStatusList; private transient ListenerList vetoableChangeList; private transient PropertyChangeSupport changeSupport; /** Used for synchronization purpose*/ private static Object internLock = new Object (); /** Default constructor. */ public FileSystem () { capability = new FileSystemCapability.Bean (); capability.addPropertyChangeListener(getCapabilityChangeListener ()); } /** Should check for external modifications. All existing FileObjects will be * refreshed. 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 default implementation is to do nothing, in contradiction to the rest * of the description. Unless subclasses override it, the method does not work. * * @param expected should the file events be marked as expected change or not? * @see FileEvent#isExpected * @since 2.16 */ public void refresh (boolean expected) { } /** Test whether filesystem is valid. * Generally invalidity would be caused by a name conflict in the filesystem pool. * @return true if the filesystem is valid */ public final boolean isValid () { return valid; } /** Setter for validity. Accessible only from filesystem pool. * @param v the new value */ final void setValid (boolean v) { if (v != valid) { valid = v; firePropertyChange (PROP_VALID, !v ? Boolean.TRUE : Boolean.FALSE, v ? Boolean.TRUE : Boolean.FALSE, Boolean.FALSE); } } /** Set hidden state of the object. * A hidden filesystem is not presented to the user in the Repository list (though it may be present in the Repository Settings list). * * @param hide <code>true</code> if the filesystem should be hidden */ public final void setHidden (boolean hide) { if (hide != hidden) { hidden = hide; firePropertyChange (PROP_HIDDEN, !hide ? Boolean.TRUE : Boolean.FALSE, hide ? Boolean.TRUE : Boolean.FALSE); } } /** Getter for the hidden property. */ public final boolean isHidden () { return hidden; } /** Tests whether filesystem will survive reloading of system pool. * If true then when * {@link Repository} is reloading its content, it preserves this * filesystem in the pool. * <P> * This can be used when the pool contains system level and user level * filesystems. The system ones should be preserved when the user changes * the content (for example when he is loading a new project). * <p>The default implementation returns <code>false</code>. * * @return true if the filesystem should be persistent */ protected boolean isPersistent () { return false; } /** Provides a name for the system that can be presented to the user. * <P> * This call should <STRONG>never</STRONG> be used to attempt to identify the file root * of the filesystem. On some systems it may happen to look the same but this is a * coincidence and may well change in the future. Either check whether * you are working with a {@link LocalFileSystem} or similar implementation and use * {@link LocalFileSystem#getRootDirectory}; or better, try * {@link FileUtil#toFile} which is designed to do this correctly. * * @return user presentable name of the filesystem */ public abstract String getDisplayName (); /** Internal (system) name of the filesystem. * Should uniquely identify the filesystem, as it will * be used during serialization of its files. The preferred way of doing this is to concatenate the * name of the filesystem type (e.g. the class) and the textual form of its parameters. * <P> * A change of the system name should be interpreted as a change of the internal * state of the filesystem. For example, if the root directory is moved to different * location, one should rebuild representations for all files * in the system. * <P> * This call should <STRONG>never</STRONG> be used to attempt to identify the file root * of the filesystem. On Unix systems it may happen to look the same but this is a * coincidence and may well change in the future. Either check whether * you are working with a {@link LocalFileSystem} or similar implementation and use * {@link LocalFileSystem#getRootDirectory}; or better, try * {@link FileUtil#toFile} which is designed to do this correctly. * * @return string with system name */ public final String getSystemName () { return systemName; } /** Changes system name of the filesystem. * This property is bound and constrained: first of all * all vetoable listeners are asked whether they agree with the change. If so, * the change is made and all change listeners are notified of * the change. * * <p><em>Warning:</em> this method is protected so that only subclasses can change * the system name. * * @param name new system name * @exception PropertyVetoException if the change is not allowed by a listener */ protected final void setSystemName (String name) throws PropertyVetoException { synchronized (Repository.class) { if (systemName.equals (name)) { return; } // I must be the only one who works with system pool (that is listening) // on this interface fireVetoableChange (PROP_SYSTEM_NAME, systemName, name); String old = systemName; systemName = name.intern (); firePropertyChange (PROP_SYSTEM_NAME, old, systemName); /** backward compatibility for FileSystems that don`t fire * PROP_DISPLAY_NAME*/ firePropertyChange (PROP_DISPLAY_NAME, null, null); } } /** Returns <code>true</code> if the filesystem is default one of the IDE. * @see Repository#getDefaultFileSystem */ public final boolean isDefault () { return this == ExternalUtil.getRepository ().getDefaultFileSystem (); } /** Test if the filesystem is read-only or not. * @return true if the system is read-only */ public abstract boolean isReadOnly (); /** Getter for root folder in the filesystem. * * @return root folder of whole filesystem */ public abstract FileObject getRoot (); /** Finds file in the filesystem by name. * <P> * The default implementation converts dots in the package name into slashes, * concatenates the strings, adds any extension prefixed by a dot and calls * the {@link #findResource findResource} method. * * <p><em>Note:</em> when both of <code>name</code> and <code>ext</code> are <CODE>null</CODE> then name and * extension should be ignored and scan should look only for a package. * * @param aPackage package name where each package component is separated by a dot * @param name name of the file (without dots) or <CODE>null</CODE> if * one wants to obtain a folder (package) and not a file in it * @param ext extension of the file (without leading dot) or <CODE>null</CODE> if one needs * a package and not a file * * @return a file object that represents a file with the given name or * <CODE>null</CODE> if the file does not exist * @deprecated Please use the <a href="@JAVA/API@/org/netbeans/api/java/classpath/api.html">ClassPath API</a> instead, or use {@link #findResource} if you are not interested in classpaths. */ public FileObject find (String aPackage, String name, String ext) { StringBuffer bf = new StringBuffer (); // append package and name if (!aPackage.equals ("")) { // NOI18N String p = aPackage.replace ('.', '/'); bf.append (p); bf.append ('/'); } // append name if (name != null) { bf.append (name); } // append extension if there is one if (ext != null) { bf.append ('.'); bf.append (ext); } return findResource (bf.toString ()); } /** Finds a file given its full resource path. * @param name the resource path, e.g. "dir/subdir/file.ext" or "dir/subdir" or "dir" * @return a file object with the given path or * <CODE>null</CODE> if no such file exists */ public abstract FileObject findResource (String name); /** Returns an array of actions that can be invoked on any file in * this filesystem. * These actions should preferably * support the {@link org.openide.util.actions.Presenter.Menu Menu}, * {@link org.openide.util.actions.Presenter.Popup Popup}, * and {@link org.openide.util.actions.Presenter.Toolbar Toolbar} presenters. * * @return array of available actions */ public abstract SystemAction[] getActions (); public SystemAction[] getActions (java.util.Set foSet) { return this.getActions(); } /** Reads object from stream and creates listeners. * @param in the input stream to read from * @exception IOException error during read * @exception ClassNotFoundException when class not found */ private void readObject (java.io.ObjectInputStream in) throws java.io.IOException, java.lang.ClassNotFoundException { in.defaultReadObject (); if (capability == null) capability = new FileSystemCapability.Bean (); capability.addPropertyChangeListener(getCapabilityChangeListener ()); } public String toString () { return getSystemName () + "[" + super.toString () + "]"; // NOI18N } /** Allows filesystems to set up the environment for external execution * and compilation. * Each filesystem can add its own values that * influence the environment. The set of operations that can modify * environment is described by the {@link Environment} interface. * <P> * The default implementation throws an exception to signal that it does not * support external compilation or execution. * * @param env the environment to setup * @exception EnvironmentNotSupportedException if external execution * and compilation cannot be supported */ public void prepareEnvironment (Environment env) throws EnvironmentNotSupportedException { throw new EnvironmentNotSupportedException (this); } /** Get a status object that can annotate a set of files by changing the names or icons * associated with them. * <P> * The default implementation returns a status object making no modifications. * * @return the status object for this filesystem */ public Status getStatus () { return STATUS_NONE; } /** The object describing capabilities of this filesystem. * Subclasses can override it. */ public final FileSystemCapability getCapability () { return capability; } /** Allows subclasses to change a set of capabilities of the * filesystem. * @param capability the capability to use */ protected final void setCapability (FileSystemCapability capability) { if (this.capability != null) this.capability.removePropertyChangeListener(getCapabilityChangeListener ()); this.capability = capability; if (this.capability != null) this.capability.addPropertyChangeListener (getCapabilityChangeListener ()); } /** Executes atomic action. The atomic action represents a set of * operations constituting one logical unit. It is guaranteed that during * execution of such an action no events about changes in the filesystem * will be fired. * <P> * <em>Warning:</em> the action should not take a significant amount of time, and should finish as soon as * possible--otherwise all event notifications will be blocked. * <p><strong>Warning:</strong> do not be misled by the name of this method; * it does not require the filesystem to treat the changes as an atomic block of * commits in the database sense! That is, if an exception is thrown in the middle * of the action, partial results will not be undone (in general this would be * impossible to implement for all filesystems anyway). * @param run the action to run * @exception IOException if there is an <code>IOException</code> thrown in the actions' {@link AtomicAction#run run} * method */ public final void runAtomicAction (final AtomicAction run) throws IOException { getEventControl ().runAtomicAction (run); } /** * Begin of block, that should be performed without firing events. * Firing of events is postponed after end of block . * There is strong necessity to use always both methods: beginAtomicAction * and finishAtomicAction. It is recomended use it in try - finally block. * @param run Events fired from this atomic action will be marked as events * that were fired from this run. */ void beginAtomicAction (FileSystem.AtomicAction run) { getEventControl ().beginAtomicAction (run); } void beginAtomicAction () { beginAtomicAction (null); } /** * End of block, that should be performed without firing events. * Firing of events is postponed after end of block . * There is strong necessity to use always both methods: beginAtomicAction * and finishAtomicAction. It is recomended use it in try - finally block. */ void finishAtomicAction () { getEventControl ().finishAtomicAction (); } /** * Inside atomicAction adds an event dispatcher to the queue of FS events * and firing of events is postponed. If not event handlers are called directly. * @param run dispatcher to run */ void dispatchEvent (EventDispatcher run) { getEventControl ().dispatchEvent (run); } /** returns property listener on FileSystemCapability. */ private synchronized PropertyChangeListener getCapabilityChangeListener () { if (capabilityListener == null) { capabilityListener = new PropertyChangeListener() { public void propertyChange(java.beans.PropertyChangeEvent propertyChangeEvent) { firePropertyChange(PROP_CAPABILITIES, propertyChangeEvent.getOldValue() , propertyChangeEvent.getNewValue()); } }; } return capabilityListener; } private transient static ThreadLocal thrLocal = new ThreadLocal (); private final EventControl getEventControl () { EventControl evnCtrl = (EventControl)thrLocal.get(); if (evnCtrl == null) thrLocal.set(evnCtrl = new EventControl ()); return evnCtrl; } /** Registers FileStatusListener to receive events. * The implementation registers the listener only when getStatus () is * overriden to return a special value. * * @param listener The listener to register. */ public final void addFileStatusListener ( org.openide.filesystems.FileStatusListener listener ) { synchronized (internLock) { // JST: Ok? Do not register listeners when the fs cannot change status? if (getStatus () == STATUS_NONE) return; if (fileStatusList == null) fileStatusList = new ListenerList (FileStatusListener.class); fileStatusList.add (listener); } } /** Removes FileStatusListener from the list of listeners. *@param listener The listener to remove. */ public final void removeFileStatusListener ( org.openide.filesystems.FileStatusListener listener ) { if (fileStatusList == null) return; fileStatusList.remove (listener); } /** Notifies all registered listeners about change of status of some files. * * @param event The event to be fired */ protected final void fireFileStatusChanged(FileStatusEvent event) { if (fileStatusList == null) return; Object[] listeners = fileStatusList.getAllListeners (); for (int i = 0; i < listeners.length; i++) { ((org.openide.filesystems.FileStatusListener)listeners[i]).annotationChanged (event); } } /** Adds listener for the veto of property change. * @param listener the listener */ public final void addVetoableChangeListener( java.beans.VetoableChangeListener listener ) { synchronized (internLock) { if (vetoableChangeList == null) vetoableChangeList = new ListenerList (VetoableChangeListener.class); vetoableChangeList.add (listener); } } /** Removes listener for the veto of property change. * @param listener the listener */ public final void removeVetoableChangeListener( java.beans.VetoableChangeListener listener ) { if (vetoableChangeList == null) return; vetoableChangeList.remove (listener); } /** Fires property vetoable event. * @param name name of the property * @param o old value of the property * @param n new value of the property * @exception PropertyVetoException if an listener vetoed the change */ protected final void fireVetoableChange ( java.lang.String name, java.lang.Object o, java.lang.Object n ) throws PropertyVetoException { if (vetoableChangeList == null) return; java.beans.PropertyChangeEvent e = null; Object[] listeners = vetoableChangeList.getAllListeners (); for (int i = 0; i < listeners.length; i++) { if (e == null) e = new java.beans.PropertyChangeEvent (this, name, o, n); ((java.beans.VetoableChangeListener)listeners[i]).vetoableChange (e); } } /** Registers PropertyChangeListener to receive events. *@param listener The listener to register. */ public final void addPropertyChangeListener(PropertyChangeListener listener) { synchronized (internLock) { if (changeSupport == null) changeSupport = new PropertyChangeSupport(this); } changeSupport.addPropertyChangeListener(listener); } /** Removes PropertyChangeListener from the list of listeners. *@param listener The listener to remove. */ public final void removePropertyChangeListener(PropertyChangeListener listener) { if (changeSupport != null) changeSupport.removePropertyChangeListener(listener); } /** Fires property change event. * @param name name of the property * @param o old value of the property * @param n new value of the property */ protected final void firePropertyChange (String name, Object o, Object n) { firePropertyChange (name, o, n, null); } final void firePropertyChange (String name, Object o, Object n, Object propagationId) { if (changeSupport == null) return; if (o != null && n != null && o.equals(n)) return; PropertyChangeEvent e = new PropertyChangeEvent(this, name, o, n); e.setPropagationId(propagationId); changeSupport.firePropertyChange(e); } /** Notifies this filesystem that it has been added to the repository. * Various initialization tasks could go here. The default implementation does nothing. * <p>Note that this method is <em>advisory</em> and serves as an optimization * to avoid retaining resources for too long etc. Filesystems should maintain correct * semantics regardless of whether and when this method is called. */ public void addNotify () { } /** Notifies this filesystem that it has been removed from the repository. * Concrete filesystem implementations could perform clean-up here. * The default implementation does nothing. * <p>Note that this method is <em>advisory</em> and serves as an optimization * to avoid retaining resources for too long etc. Filesystems should maintain correct * semantics regardless of whether and when this method is called. */ public void removeNotify () { } /** An action that it is to be called atomically with respect to filesystem event notification. * During its execution (via {@link FileSystem#runAtomicAction runAtomicAction}) * no events about changes in filesystems are fired. */ public static interface AtomicAction { /** Executed when it is guaranteed that no events about changes * in filesystems will be notified. * * @exception IOException if there is an error during execution */ public void run () throws IOException; } /** Interface that allows filesystems to set up the Java environment * for external execution and compilation. * Currently just used to append entries to the external class path. * @deprecated Please use the <a href="@JAVA/API@/org/netbeans/api/java/classpath/api.html">ClassPath API</a> instead. */ public static abstract class Environment extends Object { /** Adds one element to the class path environment variable. * @param classPathElement string representing the one element * @deprecated Please use the <a href="@JAVA/API@/org/netbeans/api/java/classpath/api.html">ClassPath API</a> instead. */ public void addClassPath (String classPathElement) { } } /** Allows a filesystem to annotate a group of files (typically comprising a data object) with additional markers. * <p>This could be useful, for * example, for a filesystem supporting version control. * It could annotate names and icons of data nodes according to whether the files were current, locked, etc. */ public static interface Status { /** Annotate the name of a file cluster. * @param name the name suggested by default * @param files an immutable set of {@link FileObject}s belonging to this filesystem * @return the annotated name (may be the same as the passed-in name) * @exception ClassCastException if the files in the set are not of valid types */ public String annotateName (String name, java.util.Set files); /** Annotate the icon of a file cluster. * <p>Please do <em>not</em> modify the original; create a derivative icon image, * using a weak-reference cache if necessary. * @param icon the icon suggested by default * @param iconType an icon type from {@link java.beans.BeanInfo} * @param files an immutable set of {@link FileObject}s belonging to this filesystem * @return the annotated icon (may be the same as the passed-in icon) * @exception ClassCastException if the files in the set are not of valid types */ public java.awt.Image annotateIcon (java.awt.Image icon, int iconType, java.util.Set files); } /** Empty status */ private static final Status STATUS_NONE = new Status () { public String annotateName (String name, java.util.Set files) { return name; } public java.awt.Image annotateIcon (java.awt.Image icon, int iconType, java.util.Set files) { return icon; } }; /** Class used to notify events for the filesystem. */ static abstract class EventDispatcher extends Object implements Runnable { public final void run () { dispatch (); } final void dispatch () { dispatch (false); } /** @param onlyPriority if true then invokes only priority listeners * else all listeners are invoked. */ protected abstract void dispatch (boolean onlyPriority); /** @param propID */ protected abstract void setAtomicActionLink (EventControl.AtomicActionLink propID); } /** Getter for the resource string * @param s the resource name * @return the resource */ static String getString(String s) { /*This call to getBundle should ensure that currentClassLoader is not used to load resources from. This should prevent from deadlock, that occured: one waits for FileObject and has resource, second one waits for resource and has FileObject*/ return NbBundle.getBundle("org.openide.filesystems.Bundle", java.util.Locale.getDefault(), FileSystem.class.getClassLoader ()).getString (s); } /** Creates message for given string property with one parameter. * @param s resource name * @param obj the parameter to the message * @return the string for that text */ static String getString (String s, Object obj) { return MessageFormat.format (getString (s), new Object[] { obj }); } /** Creates message for given string property with two parameters. * @param s resource name * @param obj1 the parameter to the message * @param obj2 the parameter to the message * @return the string for that text */ static String getString (String s, Object obj1, Object obj2) { return MessageFormat.format (getString (s), new Object[] { obj1, obj2 }); } /** Creates message for given string property with three parameters. * @param s resource name * @param obj1 the parameter to the message * @param obj2 the parameter to the message * @param obj3 the parameter to the message * @return the string for that text */ static String getString (String s, Object obj1, Object obj2, Object obj3) { return MessageFormat.format (getString (s), new Object[] { obj1, obj2, obj3 }); } /** getter for Repository * @return Repository that contains this FileSystem or null if FileSystem * is not part of any Repository */ final Repository getRepository() { return repository; } void setRepository(Repository rep) { repository = rep; } final FCLSupport getFCLSupport() { synchronized (FCLSupport.class) { if (fclSupport == null) fclSupport = new FCLSupport (); } return fclSupport; } /** Add new listener to this object. * @param fcl the listener * @since 2.8 */ public final void addFileChangeListener(FileChangeListener fcl) { getFCLSupport ().addFileChangeListener(fcl); } /** Remove listener from this object. * @param fcl the listener * @since 2.8 */ public final void removeFileChangeListener(FileChangeListener fcl) { getFCLSupport ().removeFileChangeListener(fcl); } }