All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openide.loaders.DataObject Maven / Gradle / Ivy

Go to download

The NetBeans Platform is a generic base for desktop applications. It provides the services common to almost all large desktop applications: window management, menus, settings and storage, an update manager, and file access. Get a head start by reusing these standard components, allowing you to concentrate fully on your application's business logic.

The newest version!
/*
 *                 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-2004 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.openide.loaders;

import java.awt.datatransfer.*;
import java.awt.*;
import java.beans.*;
import java.io.*;
import java.util.Set;
import java.util.HashSet;
import java.util.Collections;
import java.util.Iterator;

import javax.swing.*;
import javax.swing.event.ChangeListener;
import javax.swing.event.ChangeEvent;

import org.openide.*;
import org.openide.util.datatransfer.*;
import org.openide.filesystems.*;
import org.openide.filesystems.FileSystem; // override java.io.FileSystem
import org.openide.util.*;
import org.openide.nodes.*;

/** Object that represents one or more file objects, with added behavior.
*
* @author Jaroslav Tulach, Petr Hamernik, Jan Jancura, Ian Formanek
*/
public abstract class DataObject extends Object implements Node.Cookie, Serializable, HelpCtx.Provider {
    /** generated Serialized Version UID */
    private static final long serialVersionUID = 3328227388376142699L;

    /** Name of the template property. */
    public static final String PROP_TEMPLATE = "template"; // NOI18N

    /** Name of the name property. */
    public static final String PROP_NAME = "name"; // NOI18N

    /** Name of the help context property. */
    public static final String PROP_HELP = "helpCtx"; // NOI18N

    /** Name of the modified property. */
    public static final String PROP_MODIFIED = "modified"; // NOI18N

    /** Name of the property used during notification of changes in the set of cookies attached to this object. */
    public static final String PROP_COOKIE = Node.PROP_COOKIE;

    /** Name of valid property. Allows listening to deletion or disposal of the data object. */
    public static final String PROP_VALID = "valid"; // NOI18N

    /** Name of primary file property. Primary file is changed when the object is moved */
    public static final String PROP_PRIMARY_FILE = "primaryFile"; // NOI18N
    /** Name of files property. Allows listening to set of files handled by this object. */
    public static final String PROP_FILES = "files"; // NOI18N

    /** Extended attribute for holding the class of the loader that should
    * be used to recognize a file object before the normal processing takes
    * place.
    */
    static final String EA_ASSIGNED_LOADER = "NetBeansAttrAssignedLoader"; // NOI18N
    /** Extended attribute which may be used in addition to EA_ASSIGNED_LOADER
     * which indicates the code name base of the module that installed that preferred
     * loader. If the indicated module is not installed, ignore the loader request.
     * See #13816.
     */
    static final String EA_ASSIGNED_LOADER_MODULE = "NetBeansAttrAssignedLoaderModule"; // NOI18N

    /** all modified data objects contains DataObjects.
    * ! Use syncModified for modifications instead !*/
    private static ModifiedRegistry modified = new ModifiedRegistry();
    /** sync modified data (for modification operations) */
    private static Set syncModified = Collections.synchronizedSet(modified);

    /** Modified flag */
    private boolean modif = false;

    /** the node delegate for this data object */
    private transient Node nodeDelegate;

    /** item with info about this data object */
    DataObjectPool.Item item;

    /** the loader for this data object */
    private DataLoader loader;

    /** property change listener support */
    private PropertyChangeSupport changeSupport;
    private VetoableChangeSupport vetoableChangeSupport;

    /** The synchronization lock used only for methods creating listeners 
     * objects. It is static and shared among all DataObjects.
     */
    private static final Object listenersMethodLock = new Object();
    
    /** Lock used for ensuring there will be just one node delegate */
    private Object nodeCreationLock = new Object();

    /** Create new data object.
     * 

* Important notice: * The constructor registers this data object in DataObjectPool. The registration * is currently delayed by 500 ms. After this time any other thread can obtain * this data object using DataObject.find(fileObject) method. *

It is recommended to eliminate time-consuming functionality * in constructors of DataObject's subclasses. * * @param pf primary file object for this data object * @param loader loader that created the data object * @exception DataObjectExistsException if there is already a data object * for this primary file */ public DataObject (FileObject pf, DataLoader loader) throws DataObjectExistsException { // By registering we'll also get notifications about file changes. this (pf, DataObjectPool.getPOOL().register (pf, loader), loader); } /** Private constructor. At this time the constructor receives * the primary file and pool item where it should register itself. * * @param pf primary file * @param item the item to register into * @param loader loader that created the data object */ private DataObject (FileObject pf, DataObjectPool.Item item, DataLoader loader) { this.item = item; this.loader = loader; item.setDataObject (this); } // This method first unregisters the object, then calls method unreferenced. // After that it asks the parent folder to regenerate its list of children, // so different object is usually created for primary file of this object. /** Allows subclasses to discard the object. When an object is discarded, * it is released from the list of objects registered in the system. * Then the contents of the parent folder (if it still exists) are rescanned, which * may result in the creation of a new data object for the primary file. *

* The normal use of this method is to change the type of a data object. * Because this would usually only be invoked from * the original data object, it is protected. */ protected void dispose () { DataObjectPool.Item item = this.item; if (item != null) { item.deregister (true); item.setDataObject(null); firePropertyChange (PROP_VALID, Boolean.TRUE, Boolean.FALSE); } } /** Setter that allows to destroy this data object. Because such * operation can be dangerous and not always possible (if the data object * is opened in editor) it can be vetoed. Either by this data object * or by any vetoable listener attached to this object (like editor support) * * @param valid should be false * @exception PropertyVetoException if the invalidation has been vetoed */ public void setValid (boolean valid) throws PropertyVetoException { if (!valid && isValid ()) { markInvalid0 (); } } /** Tries to mark the object invalid. Called from setValid or from * MultiDataObject.notifyDeleted */ final void markInvalid0 () throws PropertyVetoException { fireVetoableChange (PROP_VALID, Boolean.TRUE, Boolean.FALSE); dispose (); setModified(false); } /** Test whether the data object is still valid and usable. *

* The object can become invalid when it is deleted, its files are deleted, or * {@link #dispose} is called. *

* When the validity of the object changes a property change event is fired, so * anyone can listen and be notified when the object is deleted/disposed. */ public final boolean isValid () { return item.isValid (); } /** Get the loader that created this data object. * @return the data loader */ public final DataLoader getLoader () { return loader; } /** Mark all contained files as belonging to this loader. * If the files are rescanned (e.g. after a disposal), the current data loader will be given preference. */ protected final void markFiles () throws IOException { Iterator en = files ().iterator (); while (en.hasNext ()) { FileObject fo = (FileObject)en.next (); loader.markFile (fo); } } /** Get all contained files. * These file objects should ideally have had the {@link FileObject#setImportant important flag} set appropriately. *

* The default implementation returns a set consisting only of the primary file. * * @return set of {@link FileObject}s */ public Set files () { return java.util.Collections.singleton (getPrimaryFile ()); } /** Get the node delegate. Either {@link #createNodeDelegate creates it} (if it does not * already exist) or * returns a previously created instance of it. * @return the node delegate (without parent) for this data object * @see Datasystems API - Node Delegates */ public final Node getNodeDelegate () { if (! isValid()) { Exception e = new IllegalStateException("The data object " + getPrimaryFile() + " is invalid; you may not call getNodeDelegate on it any more; see #17020 and please fix your code"); // NOI18N ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); } if (nodeDelegate == null) { // synchronize on something private, so only one delegate can be created // do not synchronize on this, because we could deadlock with // subclasses could synchronize too. Children.MUTEX.readAccess (new Runnable() { public void run() { synchronized(nodeCreationLock) { if (nodeDelegate == null) { nodeDelegate = createNodeDelegate(); } } } }); // JST: debuging code if (nodeDelegate == null) { throw new IllegalStateException("DataObject " + this + " has null node delegate"); // NOI18N } } return nodeDelegate; } /** This method allows DataFolder to filter its nodes. * * @param filter filter for subdata objects * @return the node delegate (without parent) the node is new instance * of node and can be inserted to any place in the hierarchy */ Node getClonedNodeDelegate (DataFilter filter) { return getNodeDelegate ().cloneNode (); } /** Access method for node delagate. * @return node delegate or null */ Node getNodeDelegateOrNull () { return nodeDelegate; } /** Provides node that should represent this data object. *

The default implementation creates an instance of {@link DataNode}. * Most subclasses will override this method to provide a DataNode * (usually subclassed). *

* This method is called only once per data object. *

It is strongly recommended that the resulting node will, when asked for * the cookie DataObject.class, return this same data object. *

It is also recommended that the node: *

    *
  1. Base its name on {@link #getName}. *
  2. Base its display name additionally on {@link DataNode#getShowFileExtensions}. *
  3. Tune its display name and icon according to {@link org.openide.filesystems.FileSystem.Status}. *
* @return the node delegate (without parent) for this data object * @see Datasystems API - Creating a node delegate */ protected Node createNodeDelegate () { return new DataNode (this, Children.LEAF); } /** Obtains lock for primary file. * * @return the lock * @exception IOException if taking the lock fails */ protected FileLock takePrimaryFileLock () throws IOException { return getPrimaryFile ().lock (); } /** Package private method to assign template attribute to a file. * Used also from FileEntry. * * @param fo the file * @param newTempl is template or not * @return true if the value change/false otherwise */ static boolean setTemplate (FileObject fo, boolean newTempl) throws IOException { boolean oldTempl = false; Object o = fo.getAttribute(DataObject.PROP_TEMPLATE); if ((o instanceof Boolean) && ((Boolean)o).booleanValue()) oldTempl = true; if (oldTempl == newTempl) return false; fo.setAttribute(DataObject.PROP_TEMPLATE, (newTempl ? Boolean.TRUE : null)); return true; } /** Set the template status of this data object. * @param newTempl true if the object should be a template * @exception IOException if setting the template state fails */ public final void setTemplate (boolean newTempl) throws IOException { if (!setTemplate (getPrimaryFile(), newTempl)) { // no change in state return; } firePropertyChange(DataObject.PROP_TEMPLATE, !newTempl ? Boolean.TRUE : Boolean.FALSE, newTempl ? Boolean.TRUE : Boolean.FALSE); } /** Get the template status of this data object. * @return true if it is a template */ public final boolean isTemplate () { Object o = getPrimaryFile().getAttribute(PROP_TEMPLATE); boolean ret = false; if (o instanceof Boolean) ret = ((Boolean) o).booleanValue(); return ret; } /** Test whether the object may be deleted. * @return true if it may */ public abstract boolean isDeleteAllowed (); /** Test whether the object may be copied. * @return true if it may */ public abstract boolean isCopyAllowed (); /** Test whether the object may be moved. * @return true if it may */ public abstract boolean isMoveAllowed (); /** Test whether the object may create shadows. *

The default implementation returns true. * @return true if it may */ public boolean isShadowAllowed () { return true; } /** Test whether the object may be renamed. * @return true if it may */ public abstract boolean isRenameAllowed (); /** Test whether the object is modified. * @return true if it is modified */ public boolean isModified() { return modif; } /** Set whether the object is considered modified. * Also fires a change event. * If the new value is true, the data object is added into a {@link #getRegistry registry} of opened data objects. * If the new value is false, * the data object is removed from the registry. */ public void setModified(boolean modif) { if (this.modif != modif) { this.modif = modif; if (modif) { syncModified.add (this); } else { syncModified.remove (this); } firePropertyChange(DataObject.PROP_MODIFIED, !modif ? Boolean.TRUE : Boolean.FALSE, modif ? Boolean.TRUE : Boolean.FALSE); } } /** Get help context for this object. * @return the help context */ public abstract HelpCtx getHelpCtx (); /** Get the primary file for this data object. * For example, * Java source uses *.java and *.class files but the primary one is * always *.java. Please note that two data objects are {@link #equals equivalent} if * they use the same primary file. *

Warning: do not call {@link Node#getHandle} or {@link DefaultHandle#createHandle} in this method. * * @return the primary file */ public final FileObject getPrimaryFile () { return item.primaryFile; } /** Finds the data object for a specified file object. * @param fo file object * @return the data object for that file * @exception DataObjectNotFoundException if the file does not have a * data object */ public static DataObject find (FileObject fo) throws DataObjectNotFoundException { if (fo == null) throw new IllegalArgumentException("Called DataObject.find on null"); // NOI18N try { if (!fo.isValid()) throw new FileStateInvalidException(fo.toString()); // try to scan directly the pool (holds only primary files) DataObject obj = DataObjectPool.getPOOL().find (fo); if (obj != null) { return obj; } // try to use the loaders machinery DataLoaderPool p = DataLoaderPool.getDefault(); assert p != null : "No DataLoaderPool found in " + Lookup.getDefault(); obj = p.findDataObject (fo); if (obj != null) { return obj; } throw new DataObjectNotFoundException (fo); } catch (DataObjectExistsException ex) { return ex.getDataObject (); } catch (IOException ex) { DataObjectNotFoundException donfe = new DataObjectNotFoundException (fo); ErrorManager.getDefault ().annotate (donfe, ex); throw donfe; } } /** the only instance */ private static Registry REGISTRY_INSTANCE = new Registry(); /** Get the registry containing all modified objects. * * @return the registry */ public static Registry getRegistry () { return REGISTRY_INSTANCE; } /** Get the name of the data object. *

The default implementation uses the name of the primary file. * @return the name */ public String getName () { return getPrimaryFile ().getName (); } public String toString () { return super.toString () + '[' + getPrimaryFile () + ']'; } /** Get the folder this data object is stored in. * @return the folder; null if the primary file * is the {@link FileObject#isRoot root} of its filesystem */ public final DataFolder getFolder () { FileObject fo = getPrimaryFile ().getParent (); // could throw IllegalArgumentException but only if fo is not folder // => then there is a bug in filesystem implementation return fo == null ? null : DataFolder.findFolder (fo); } /** Copy this object to a folder. The copy of the object is required to * be deletable and movable. *

An event is fired, and atomicity is implemented. * @param f the folder to copy the object to * @exception IOException if something went wrong * @return the new object */ public final DataObject copy (final DataFolder f) throws IOException { final DataObject[] result = new DataObject[1]; invokeAtomicAction (f.getPrimaryFile (), new FileSystem.AtomicAction () { public void run () throws IOException { result[0] = handleCopy (f); } }); fireOperationEvent ( new OperationEvent.Copy (result[0], this), OperationEvent.COPY ); return result[0]; } /** Copy this object to a folder (implemented by subclasses). * @param f target folder * @return the new data object * @exception IOException if an error occures */ protected abstract DataObject handleCopy (DataFolder f) throws IOException; /** Delete this object. *

Events are fired and atomicity is implemented. * @exception IOException if an error occures */ public final void delete () throws IOException { synchronized ( synchObject() ) { // the object is ready to be closed invokeAtomicAction (getPrimaryFile (), new FileSystem.AtomicAction () { public void run () throws IOException { handleDelete (); item.deregister(false); item.setDataObject(null); } }); } firePropertyChange (PROP_VALID, Boolean.TRUE, Boolean.FALSE); fireOperationEvent (new OperationEvent (this), OperationEvent.DELETE); } /** Delete this object (implemented by subclasses). * @exception IOException if an error occures */ protected abstract void handleDelete () throws IOException; /** Rename this object. *

Events are fired and atomicity is implemented. * * @param name the new name * * @exception IOException if an error occurs */ public final void rename (final String name) throws IOException { if (name != null && name.trim ().length ()==0) { IllegalArgumentException iae = new IllegalArgumentException (this.getName ()); String msg = NbBundle.getMessage (DataObject.class, "MSG_NotValidName", getName ()); // NOI18N ErrorManager.getDefault ().annotate (iae, ErrorManager.INFORMATIONAL, null, msg, null, null); throw iae; } String oldName; String newName; final FileObject[] files = new FileObject[2]; // [old, new] synchronized ( synchObject() ) { oldName = getName (); if (oldName.equals (name)) return; // the new name is the same as the old one files[0] = getPrimaryFile (); // executes atomic action with renaming invokeAtomicAction (files[0].getParent(), new FileSystem.AtomicAction () { public void run () throws IOException { files[1] = handleRename (name); if (files[0] != files[1]) item.changePrimaryFile (files[1]); } }); newName = getName (); } if (files[0] != files[1]) firePropertyChange (PROP_PRIMARY_FILE, files[0], getPrimaryFile ()); firePropertyChange (PROP_NAME, oldName, newName); fireOperationEvent (new OperationEvent.Rename (this, oldName), OperationEvent.RENAME); } /** Rename this object (implemented in subclasses). * * @param name name to rename the object to * @return new primary file of the object * @exception IOException if an error occures */ protected abstract FileObject handleRename (String name) throws IOException; /** Move this object to another folder. *

An event is fired and atomicity is implemented. * @param df folder to move object to * @exception IOException if an error occurs */ public final void move (final DataFolder df) throws IOException { FileObject old; synchronized ( synchObject() ) { if ((getFolder () == null)) return; // cannot move filesystem root if (df.equals (getFolder ())) return; // if the destination folder is the same as the current one ==>> do nothing // executes atomic action for moving old = getPrimaryFile (); invokeAtomicAction (df.getPrimaryFile(), new FileSystem.AtomicAction () { public void run () throws IOException { FileObject mf = handleMove (df); item.changePrimaryFile (mf); } }); } firePropertyChange (PROP_PRIMARY_FILE, old, getPrimaryFile ()); fireOperationEvent ( new OperationEvent.Move (this, old), OperationEvent.MOVE ); } /** Move this object to another folder (implemented in subclasses). * * @param df target data folder * @return new primary file of the object * @exception IOException if an error occures */ protected abstract FileObject handleMove (DataFolder df) throws IOException; /** Creates shadow for this object in specified folder (overridable in subclasses). *

The default * implementation creates a reference data shadow and pastes it into * the specified folder. * * @param f the folder to create a shortcut in * @return the shadow */ protected DataShadow handleCreateShadow (DataFolder f) throws IOException { return DataShadow.create (f, this); } /** Creates shadow for this object in specified folder. *

An event is fired and atomicity is implemented. * * @param f the folder to create shortcut in * @return the shadow */ public final DataShadow createShadow (final DataFolder f) throws IOException { final DataShadow[] result = new DataShadow[1]; invokeAtomicAction (f.getPrimaryFile (), new FileSystem.AtomicAction () { public void run () throws IOException { result[0] = handleCreateShadow (f); } }); fireOperationEvent ( new OperationEvent.Copy (result[0], this), OperationEvent.SHADOW ); return result[0]; } /** Create a new object from template (with a name depending on the template). * * @param f folder to create object in * @return new data object based on this one * @exception IOException if an error occured * @see #createFromTemplate(DataFolder,String) */ public final DataObject createFromTemplate (DataFolder f) throws IOException { return createFromTemplate (f, null); } /** Create a new object from template. * Asks {@link #handleCreateFromTemplate}. * * @param f folder to create object in * @param name name of object that should be created, or null if the * name should be same as that of the template (or otherwise mechanically generated) * @return the new data object * @exception IOException if an error occured */ public final DataObject createFromTemplate ( final DataFolder f, final String name ) throws IOException { final DataObject[] result = new DataObject[1]; invokeAtomicAction (f.getPrimaryFile (), new FileSystem.AtomicAction () { public void run () throws IOException { result[0] = handleCreateFromTemplate (f, name); } }); fireOperationEvent ( new OperationEvent.Copy (result[0], this), OperationEvent.TEMPL ); return result[0]; } /** Create a new data object from template (implemented in subclasses). * This method should * copy the content of the template to the destination folder and assign a new name * to the new object. * * @param df data folder to create object in * @param name name to give to the new object (or null * if the name should be chosen according to the template) * @return the new data object * @exception IOException if an error occured */ protected abstract DataObject handleCreateFromTemplate ( DataFolder df, String name ) throws IOException; /** Fires operation event to data loader pool. * @param ev the event * @param type OperationEvent.XXXX constant */ private static void fireOperationEvent (OperationEvent ev, int type) { DataLoaderPool.getDefault().fireOperationEvent (ev, type); } /** Provide object used for synchronization. * @return this in DataObject implementation. Other DataObjects * (MultiDataObject) can rewrite this method and return own synch object. */ Object synchObject() { return nodeCreationLock; } /** Invokes atomic action. */ private void invokeAtomicAction (FileObject target, FileSystem.AtomicAction action) throws IOException { if (Boolean.getBoolean ("netbeans.dataobject.insecure.operation")) { DataObjectPool.getPOOL ().runAtomicActionSimple (target, action); return; } if (this instanceof DataFolder) { // action is slow DataObjectPool.getPOOL ().runAtomicActionSimple (target, action); } else { // it is quick, make it block DataObject recognition DataObjectPool.getPOOL ().runAtomicAction (target, action); } } // // Property change support // /** Add a property change listener. * @param l the listener to add */ public void addPropertyChangeListener (PropertyChangeListener l) { synchronized (listenersMethodLock) { if (changeSupport == null) changeSupport = new PropertyChangeSupport(this); } changeSupport.addPropertyChangeListener(l); } /** Remove a property change listener. * @param l the listener to remove */ public void removePropertyChangeListener (PropertyChangeListener l) { if (changeSupport != null) changeSupport.removePropertyChangeListener(l); } /** Fires property change notification to all listeners registered via * {@link #addPropertyChangeListener}. * * @param name of property * @param oldValue old value * @param newValue new value */ protected final void firePropertyChange (String name, Object oldValue, Object newValue) { if (changeSupport != null) changeSupport.firePropertyChange(name, oldValue, newValue); } // // Property change support // /** Add a listener to vetoable changes. * @param l the listener to add * @see #PROP_VALID */ public void addVetoableChangeListener (VetoableChangeListener l) { synchronized (listenersMethodLock) { if (vetoableChangeSupport == null) vetoableChangeSupport = new VetoableChangeSupport(this); } vetoableChangeSupport.addVetoableChangeListener(l); } /** Add a listener to vetoable changes. * @param l the listener to remove * @see #PROP_VALID */ public void removeVetoableChangeListener (VetoableChangeListener l) { if (vetoableChangeSupport != null) vetoableChangeSupport.removeVetoableChangeListener(l); } /** Fires vetoable change notification. * * @param name of property * @param oldValue old value * @param newValue new value * @exception PropertyVetoException if the change has been vetoed */ protected final void fireVetoableChange (String name, Object oldValue, Object newValue) throws PropertyVetoException { if (vetoableChangeSupport != null) vetoableChangeSupport.fireVetoableChange(name, oldValue, newValue); } // // Cookie // /** Obtain a cookie from the data object. * May be overridden by subclasses to extend the behaviour of * data objects. *

* The default implementation tests if this object is of the requested class and * if so, returns it. * * @param c class of requested cookie * @return a cookie or null if such cookies are not supported */ public Node.Cookie getCookie (Class c) { if (c.isInstance (this)) { return this; } return null; } /** When a request for a cookie is done on a DataShadow of this DataObject * this methods gets called (by default) so the DataObject knows which * DataShadow is asking and extract some information from the shadow itself. *

* Subclasses can override this method with better logic, but the default * implementation just delegates to getCookie (Class). * * @param clazz class to search for * @param shadow the shadow for which is asking * @return the cookie or null * * @since 1.16 */ protected Node.Cookie getCookie (DataShadow shadow, Class clazz) { return getCookie (clazz); } // ======================= // Serialization methods // /** The Serialization replacement for this object stores the primary file instead. * @return a replacement */ public Object writeReplace () { return new Replace (this); } /** The default replace for the data object */ private static final class Replace extends Object implements Serializable { /** the primary file */ private FileObject fo; /** the object to return */ private transient DataObject obj; private static final long serialVersionUID =-627843044348243058L; /** Constructor. * @param obj the object to use */ public Replace (DataObject obj) { this.obj = obj; this.fo = obj.getPrimaryFile (); } public Object readResolve () { return obj; } /** Read method */ private void readObject (ObjectInputStream ois) throws IOException, ClassNotFoundException { ois.defaultReadObject (); if (fo == null) { throw new java.io.FileNotFoundException (); } // DataObjectNotFoundException extends IOException: obj = DataObject.find(fo); } } /** Getter for a text from resource bundle. */ static String getString (String name) { return NbBundle.getMessage (DataObject.class, name); } /** Interface for objects that can contain other data objects. * For example DataFolder and DataShadow implement this interface * to allow others to access the contained objects in uniform maner */ public static interface Container extends Node.Cookie { /** Name of property that holds children of this container. */ public static final String PROP_CHILDREN = "children"; // NOI18N /** @return the array of contained objects */ public DataObject[] getChildren (); /** Adds a listener. * @param l the listener */ public void addPropertyChangeListener (PropertyChangeListener l); /** Removes property change listener. * @param l the listener */ public void removePropertyChangeListener (PropertyChangeListener l); } /** Registry of modified data objects. * The registry permits attaching of a change listener * to be informed when the count of modified objects changes. */ public static final class Registry extends Object { /** Private constructor */ private Registry () { } /** Add new listener to changes in the set of modified objects. * @param chl listener to add */ public void addChangeListener (final ChangeListener chl) { modified.addChangeListener(chl); } /** Remove a listener to changes in the set of modified objects. * @param chl listener to remove */ public void removeChangeListener (final ChangeListener chl) { modified.removeChangeListener(chl); } /** Get a set of modified data objects. * @return an unmodifiable set of {@link DataObject}s */ public Set getModifiedSet () { return Collections.unmodifiableSet(syncModified); } /** Get modified objects. * @return array of objects */ public DataObject[] getModified () { return (DataObject[])modified.toArray (new DataObject[modified.size()]); } } private static final class ModifiedRegistry extends HashSet { static final long serialVersionUID =-2861723614638919680L; /** Set of listeners listening to changes to the set of modified objs */ private HashSet listeners; ModifiedRegistry() {} /** Adds new listener. * @param chl new listener */ public final synchronized void addChangeListener (final ChangeListener chl) { if (listeners == null) listeners = new HashSet(5); listeners.add(chl); } /** Removes listener from the listener list. * @param chl listener to remove */ public final synchronized void removeChangeListener (final ChangeListener chl) { if (listeners == null) return; listeners.remove(chl); } /***** overriding of methods which change content in order to notify * listeners about the content change */ public boolean add (Object o) { boolean result = super.add(o); if (result) fireChangeEvent(new ChangeEvent(this)); return result; } public boolean remove (Object o) { boolean result = super.remove(o); if (result) fireChangeEvent(new ChangeEvent(this)); return result; } /** Fires change event to all listeners. * @param che change event */ protected final void fireChangeEvent (ChangeEvent che) { if (listeners == null) return; HashSet cloned; // clone listener list synchronized (this) { cloned = (HashSet)listeners.clone(); } // fire on cloned list to prevent from modifications when firing for (Iterator iter = cloned.iterator(); iter.hasNext(); ) { ((ChangeListener)iter.next()).stateChanged(che); } } } // end of ModifiedRegistry inner class /** A.N. - profiling shows that MultiLoader.checkFiles() is called too often * This method is part of the fix - empty for DataObject. */ void recognizedByFolder() { } // This methods are called by DataObjectPool whenever the primary file // gets changed. The Pool listens on the whole FS thus reducing // the number of individual listeners created/registered. void notifyFileRenamed(FileRenameEvent fe) { if (fe.getFile ().equals (getPrimaryFile ())) { firePropertyChange(PROP_NAME, fe.getName(), getName()); } } void notifyFileDeleted(FileEvent fe) { } void notifyFileChanged(FileEvent fe) { } void notifyFileDataCreated(FileEvent fe) { } void notifyAttributeChanged(FileAttributeEvent fae) { if (! EA_ASSIGNED_LOADER.equals(fae.getName())) { // We are interested only in assigned loader return; } FileObject f = fae.getFile(); if (f != null) { String attrFromFO = (String)f.getAttribute(EA_ASSIGNED_LOADER); if (attrFromFO == null || (! attrFromFO.equals(getLoader().getClass().getName()))) { java.util.HashSet single = new java.util.HashSet(); single.add(f); if (!DataObjectPool.getPOOL().revalidate(single).isEmpty()) { ErrorManager.getDefault().log(ErrorManager.INFORMATIONAL, "It was not possible to invalidate data object: " + this); // NOI18N } else { // we need to refresh parent folder if it is there // this should be covered by DataLoaderPoolTest.testChangeIsAlsoReflectedInNodes FolderList.changedDataSystem (f.getParent()); } } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy