/* * 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.nodes; import java.awt.Image; import java.awt.datatransfer.Transferable; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.IOException; import java.util.*; import java.lang.ref.WeakReference; import org.openide.ErrorManager; import org.openide.util.datatransfer.NewType; import org.openide.util.datatransfer.PasteType; import org.openide.util.HelpCtx; import org.openide.util.Lookup; import org.openide.util.LookupEvent; import org.openide.util.LookupListener; import org.openide.util.actions.SystemAction; /** A proxy for another node. * Unless otherwise mentioned, all methods of the original node are delegated to. * If desired, you can disable delegation of certain methods which are concrete in <code>Node</code> * by calling {@link #disableDelegation}. * * <p><strong>Note:</strong> it is fine to subclass this class and use * it to filter things. But please do not ever try to cast a node to * <code>FilterNode</code>: it probably means you are doing something * wrong. Instead, ask whatever <code>Node</code> you have for a proper * kind of cookie (e.g. {@link org.openide.loaders.DataObject}). * * @author Jaroslav Tulach */ public class FilterNode extends Node { /** node to delegate to */ private Node original; /** listener to property changes, * accessible thru getPropertyChangeListener */ private PropertyChangeListener propL; /** listener to node changes * Accessible thru get node listener */ private NodeListener nodeL; // Note: int (not long) to avoid need to ever synchronize when accessing it // (Java VM spec does not guarantee that long's will be stored atomically) /** @see #delegating */ private int delegateMask; /** Whether to delegate <code>setName</code>. */ protected static final int DELEGATE_SET_NAME = 1 << 0; /** Whether to delegate <code>getName</code>. */ protected static final int DELEGATE_GET_NAME = 1 << 1; /** Whether to delegate <code>setDisplayName</code>. */ protected static final int DELEGATE_SET_DISPLAY_NAME = 1 << 2; /** Whether to delegate <code>getDisplayName</code>. */ protected static final int DELEGATE_GET_DISPLAY_NAME = 1 << 3; /** Whether to delegate <code>setShortDescription</code>. */ protected static final int DELEGATE_SET_SHORT_DESCRIPTION = 1 << 4; /** Whether to delegate <code>getShortDescription</code>. */ protected static final int DELEGATE_GET_SHORT_DESCRIPTION = 1 << 5; /** Whether to delegate <code>destroy</code>. */ protected static final int DELEGATE_DESTROY = 1 << 6; /** Whether to delegate <code>getActions</code>. */ protected static final int DELEGATE_GET_ACTIONS = 1 << 7; /** Whether to delegate <code>getContextActions</code>. */ protected static final int DELEGATE_GET_CONTEXT_ACTIONS = 1 << 8; /** Mask indicating delegation of all possible methods. */ private static final int DELEGATE_ALL = DELEGATE_SET_NAME | DELEGATE_GET_NAME | DELEGATE_SET_DISPLAY_NAME | DELEGATE_GET_DISPLAY_NAME | DELEGATE_SET_SHORT_DESCRIPTION | DELEGATE_GET_SHORT_DESCRIPTION | DELEGATE_DESTROY | DELEGATE_GET_ACTIONS | DELEGATE_GET_CONTEXT_ACTIONS; /** Is PropertyChangeListener attached to original node */ private boolean pchlAttached = false; /** Create proxy. * @param original the node to delegate to */ public FilterNode(Node original) { this ( original, original.isLeaf () ? org.openide.nodes.Children.LEAF : new Children (original) ); } /** Create proxy with a different set of children. * * @param original the node to delegate to * @param children a set of children for this node */ public FilterNode( Node original, org.openide.nodes.Children children ) { super (children, new FilterLookup ()); this.original = original; init (); Lookup lkp = internalLookup (false); if (lkp instanceof FilterLookup) { ((FilterLookup)lkp).ownNode (this); } } private static final WeakHashMap replaceProvidedLookupCache = new WeakHashMap (27); /** Overrides package private method of a node that allows us to say * that the lookup provided in the constructor should be replaced by * something else * * @param lookup * @return lookup or null */ final Lookup replaceProvidedLookup (Lookup lookup) { synchronized (replaceProvidedLookupCache) { Boolean b = (Boolean)replaceProvidedLookupCache.get (getClass ()); if (b == null) { b = overridesAMethod ("getCookie", new Class[] { Class.class }) ? Boolean.FALSE : Boolean.TRUE; // NOI18N replaceProvidedLookupCache.put (getClass (), b); } return b.booleanValue() ? lookup : null; } } /** Checks whether subclass overrides a method */ private boolean overridesAMethod (String name, Class[] arguments) { if (getClass () == FilterNode.class) { return false; } // we are subclass of FilterNode try { java.lang.reflect.Method m = getClass ().getMethod (name, arguments); if (m.getDeclaringClass () != FilterNode.class) { // ok somebody overriden getCookie method return true; } } catch (NoSuchMethodException ex) { ErrorManager.getDefault().notify(ex); } return false; } /** Initializes the node. */ private void init () { delegateMask = DELEGATE_ALL; } void notifyPropertyChangeListenerAdded( PropertyChangeListener l ) { if ( !pchlAttached ) { original.addPropertyChangeListener (getPropertyChangeListener ()); pchlAttached = true; } } void notifyPropertyChangeListenerRemoved( PropertyChangeListener l ) { if ( getPropertyChangeListenersCount() == 0 ) { original.removePropertyChangeListener (getPropertyChangeListener ()); pchlAttached = false; } } /** Removes all listeners (property and node) on * the original node. Called from {@link NodeListener#nodeDestroyed}, * but can be called by any subclass to stop reflecting changes * in the original node. */ protected void finalize () { original.removePropertyChangeListener (getPropertyChangeListener ()); original.removeNodeListener (getNodeListener ()); } /** Enable delegation of a set of methods. * These will be delegated to the original node. * Since all available methods are delegated by default, normally you will not need to call this. * @param mask bitwise disjunction of <code>DELEGATE_XXX</code> constants * @throws IllegalArgumentException if the mask is invalid */ protected final void enableDelegation (int mask) { if ((mask & ~DELEGATE_ALL) != 0) throw new IllegalArgumentException ("Bad delegation mask: " + mask); // NOI18N delegateMask |= mask; } /** Disable delegation of a set of methods. * The methods will retain their behavior from {@link Node}. * <p>For example, if you wish to subclass <code>FilterNode</code>, giving your * node a distinctive display name and tooltip, and performing some special * action upon deletion, you may do so without risk of affecting the original * node as follows: * <br><code><pre> * public MyNode extends FilterNode { * public MyNode (Node orig) { * super (orig, new MyChildren (orig)); * disableDelegation (DELEGATE_GET_DISPLAY_NAME | DELEGATE_SET_DISPLAY_NAME | * DELEGATE_GET_SHORT_DESCRIPTION | DELEGATE_SET_SHORT_DESCRIPTION | * DELEGATE_DESTROY); * // these will affect only the filter node: * setDisplayName ("Linking -> " + orig.getDisplayName ()); * setShortDescription ("Something different."); * } * public boolean canRename () { return false; } * public void destroy () throws IOException { * doMyCleanup (); * super.destroy (); // calls Node.destroy(), not orig.destroy() * } * } * </pre></code> * <br>You may still manually delegate where desired using {@link #getOriginal}. * Other methods abstract in <code>Node</code> may simply be overridden without * any special handling. * @param mask bitwise disjunction of <code>DELEGATE_XXX</code> constants * @throws IllegalArgumentException if the mask is invalid */ protected final void disableDelegation (int mask) { if ((mask & ~DELEGATE_ALL) != 0) throw new IllegalArgumentException ("Bad delegation mask: " + mask); // NOI18N delegateMask &= ~mask; } /** Test whether we are currently delegating to some method. */ private final boolean delegating (int what) { return (delegateMask & what) != 0; } /** Create new filter node for the original. * Subclasses do not have to override this, but if they do not, * the default implementation will filter the subclass filter, which is not * very efficient. * @return copy of this node */ public Node cloneNode () { if (isDefault ()) { // this is realy filter node without changed behaviour // with the normal children => use normal constructor for the // original node return new FilterNode (original); } else { // create filter node for this node to reflect changed // behaviour return new FilterNode (this); } } /** Changes the original node for this node. *@param original The new original node. *@param changeChildren If set to <CODE>true</CODE> changes children * of this node according to the new original node. If you pass * children which are not instance of class * <CODE>FilterNode.Children</CODE> into the constructor set this * parameter to <CODE>false</CODE>. *@throws java.lang.IllegalStateException if children which are not * instance of <CODE>FilterNode.Children</CODE> were passed * into the constructor and the method was called with the parameter * <CODE>changeChildren</CODE> set to <CODE>true</CODE>. *@since 1.39 */ protected final void changeOriginal( Node original, boolean changeChildren ) { if ( changeChildren && !(getChildren() instanceof FilterNode.Children) && !(getChildren() == Children.LEAF /* && original.isLeaf () */)) { throw new IllegalStateException( "Can't change implicitly defined Children on FilterNode" ); // NOI18N } try { Children.PR.enterWriteAccess(); // First remove the listeners from current original node this.original.removeNodeListener( getNodeListener() ); if ( pchlAttached ) { this.original.removePropertyChangeListener( getPropertyChangeListener() ); } // Set the new original node this.original = original; // attach listeners to new original node this.original.addNodeListener( getNodeListener() ); if ( pchlAttached ) { this.original.addPropertyChangeListener( getPropertyChangeListener() ); } // Reset children's original node. if ( changeChildren /* && !original.isLeaf () */) { if ( original.isLeaf() && getChildren() != Children.LEAF ) { setChildren( Children.LEAF ); } else if ( !original.isLeaf() && getChildren() == Children.LEAF ) { setChildren( new Children( original ) ); } else if ( !original.isLeaf() && getChildren() != Children.LEAF) { ((FilterNode.Children)getChildren()).changeOriginal( original ); } } } finally { Children.PR.exitWriteAccess(); } // Fire all sorts of events (everything gets changed after we // reset the original node.) Lookup lkp = internalLookup (false); if (lkp instanceof FilterLookup) { ((FilterLookup)lkp).checkNode(); } fireCookieChange(); fireNameChange(null, null); fireDisplayNameChange(null, null); fireShortDescriptionChange(null, null); fireIconChange(); fireOpenedIconChange(); firePropertySetsChange( null, null ); } // ------------- START OF DELEGATED METHODS ------------ /* Setter for system name. Fires info about property change. * @param s the string */ public void setName (String s) { if (delegating (DELEGATE_SET_NAME)) { original.setName (s); } else { super.setName (s); } } /* @return the name of the original node */ public String getName () { if (delegating (DELEGATE_GET_NAME)) { return original.getName (); } else { return super.getName (); } } /* Setter for display name. Fires info about property change. * @param s the string */ public void setDisplayName (String s) { if (delegating (DELEGATE_SET_DISPLAY_NAME)) { original.setDisplayName (s); } else { super.setDisplayName (s); } } /* @return the display name of the original node */ public String getDisplayName () { if (delegating (DELEGATE_GET_DISPLAY_NAME)) { return original.getDisplayName (); } else { return super.getDisplayName (); } } /* Setter for short description. Fires info about property change. * @param s the string */ public void setShortDescription (String s) { if (delegating (DELEGATE_SET_SHORT_DESCRIPTION)) { original.setShortDescription (s); } else { super.setShortDescription (s); } } /* @return the description of the original node */ public String getShortDescription () { if (delegating (DELEGATE_GET_SHORT_DESCRIPTION)) { return original.getShortDescription (); } else { return super.getShortDescription (); } } /* Finds an icon for this node. Delegates to the original. * * @see java.bean.BeanInfo * @param type constants from <CODE>java.bean.BeanInfo</CODE> * @return icon to use to represent the bean */ public Image getIcon (int type) { return original.getIcon (type); } /* Finds an icon for this node. This icon should represent the node * when it is opened (if it can have children). Delegates to original. * * @see java.bean.BeanInfo * @param type constants from <CODE>java.bean.BeanInfo</CODE> * @return icon to use to represent the bean when opened */ public Image getOpenedIcon (int type) { return original.getOpenedIcon (type); } public HelpCtx getHelpCtx () { return original.getHelpCtx (); } /* Can the original node be renamed? * * @return true if the node can be renamed */ public boolean canRename () { return original.canRename (); } /* Can the original node be deleted? * @return <CODE>true</CODE> if can, <CODE>false</CODE> otherwise */ public boolean canDestroy () { return original.canDestroy (); } /* Degelates the delete operation to original. */ public void destroy () throws java.io.IOException { if (delegating (DELEGATE_DESTROY)) original.destroy (); else super.destroy (); } /** Used to access the destroy method when original nodes * has been deleted */ private final void originalDestroyed () { try { super.destroy (); } catch (IOException ex) { ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, ex); } } /* Getter for the list of property sets. Delegates to original. * * @return the array of property sets. */ public PropertySet[] getPropertySets () { return original.getPropertySets (); } /* Called when an object is to be copied to clipboard. * @return the transferable object dedicated to represent the * content of clipboard * @exception IOException is thrown when the * operation cannot be performed */ public Transferable clipboardCopy () throws IOException { return original.clipboardCopy (); } /* Called when an object is to be cut to clipboard. * @return the transferable object dedicated to represent the * content of clipboard * @exception IOException is thrown when the * operation cannot be performed */ public Transferable clipboardCut () throws IOException { return original.clipboardCut (); } /* Returns true if this object allows copying. * @returns true if this object allows copying. */ public boolean canCopy () { return original.canCopy (); } /* Returns true if this object allows cutting. * @returns true if this object allows cutting. */ public boolean canCut () { return original.canCut (); } public Transferable drag () throws IOException { return original.drag (); } /* Default implementation that tries to delegate the implementation * to the createPasteTypes method. Simply calls the method and * tries to take the first provided argument. Ignores the action * argument and index. * * @param t the transferable * @param action the drag'n'drop action to do DnDConstants.ACTION_MOVE, ACTION_COPY, ACTION_LINK * @param index index between children the drop occured at or -1 if not specified * @return null if the transferable cannot be accepted or the paste type * to execute when the drop occures */ public PasteType getDropType (Transferable t, int action, int index) { return original.getDropType (t, action, index); } /* Which paste operations are allowed when transferable t is in clipboard? * @param t the transferable in clipboard * @return array of operations that are allowed */ public PasteType[] getPasteTypes (Transferable t) { return original.getPasteTypes (t); } /* Support for new types that can be created in this node. * @return array of new type operations that are allowed */ public NewType[] getNewTypes () { return original.getNewTypes (); } /* Delegates to original. * * @return array of system actions that should be in popup menu */ public SystemAction[] getActions () { if (delegating (DELEGATE_GET_ACTIONS)) return original.getActions (); else return super.getActions (); } /* Delegates to original */ public SystemAction[] getContextActions () { if (delegating (DELEGATE_GET_CONTEXT_ACTIONS)) return original.getContextActions (); else return super.getContextActions (); } /* * @return default action of the original node or null */ public SystemAction getDefaultAction () { return original.getDefaultAction (); } public javax.swing.Action[] getActions(boolean context) { if (context) { if (!delegating (DELEGATE_GET_ACTIONS) || overridesAMethod ("getContextActions", new Class[0])) // NOI18N return super.getActions (context); } else { if (!delegating (DELEGATE_GET_CONTEXT_ACTIONS) || overridesAMethod ("getActions", new Class[0])) // NOI18N return super.getActions (context); } javax.swing.Action[] retValue; retValue = original.getActions(context); return retValue; } public javax.swing.Action getPreferredAction() { javax.swing.Action retValue; if (overridesAMethod ("getDefaultAction", new Class[0])) { // NOI18N retValue = super.getPreferredAction(); } else { retValue = original.getPreferredAction(); } return retValue; } /* * @return <CODE>true</CODE> if the original has a customizer. */ public boolean hasCustomizer () { return original.hasCustomizer (); } /* Returns the customizer component. * @return the component or <CODE>null</CODE> if there is no customizer */ public java.awt.Component getCustomizer () { return original.getCustomizer (); } /* Delegates to original. * * @param type the class to look for * @return instance of that class or null if this class of cookie * is not supported */ public Node.Cookie getCookie (Class type) { return original.getCookie (type); } /** If this is FilterNode without any changes (subclassed, changed children) * and the original provides handle, stores them and * returns a new handle for the proxy. * <p>Subclasses <strong>must</strong> override this if they wish for their nodes to be * properly serializable. * * @return the handle, or <code>null</code> if this node is subclassed or * uses changed children */ public Node.Handle getHandle () { if (!isDefault ()) { // subclasses has to implement the method by its own return null; } Node.Handle original = this.original.getHandle (); if (original == null) { // no original handle => no handle here return null; } return new FilterHandle (original); } /** Test equality of original nodes. * Note that for subclasses of <code>FilterNode</code>, or filter nodes with non-default children, * the test reverts to object identity. * <strong>Note:</strong> if you wish that the {@link Index} cookie works correctly on * filtered nodes and their subnodes, and you are subclassing <code>FilterNode</code> or * using non-default children, you will probably want to override this method to test * equality of the specified node with this filter node's original node; otherwise Move Up * and Move Down actions may be disabled. * <p>Note though that it is often better to provide your own index cookie from a filter * node. Only then it is possible to change the number of children relative to the original. * And in many cases this is easier anyway, as for example with * {@link org.openide.loaders.DataFolder.Index data folders}. * @param o something to compare to, presumably a node or <code>FilterNode</code> of one * @return true if this node's original node is the same as the parameter (or original node of parameter) */ public boolean equals (Object o) { // VERY DANGEROUS! Completely messes up visualizers and often original node is displayed rather than filter. // Jst: I know that it is dangerous, but some code probably depends on it // if (o == null) { return false; } if (/*super.equals (o)*/ this == o) { return true; } if (!isDefault ()) { return false; } return original.equals (o) || o.equals (original); } /** Hash by original nodes. * Note that for subclasses of <code>FilterNode</code>, or filter nodes with non-default children, * the hash reverts to the identity hash code. * @return the delegated hash code */ public int hashCode () { // JST: Pretty, pretty please with suggar on the top, do not comment this // method out or I spend next two hours looking for why filternodes are // not removed from hashtable even they are equal! return isDefault () ? original.hashCode () : super.hashCode (); } // public String toString () { // return super.toString () + " original has children: " + original.getChildren ().getNodesCount (); // NOI18N // } // ----------- END OF DELEGATED METHODS ------------ /** Get the original node. * <p><strong>Yes</strong> this is supposed to be protected! If you * are not subclassing <code>FilterNode</code> yourself, you should * not be calling it (nor casting to <code>FilterNode</code>). Use * cookies instead. * @return the node proxied to */ protected Node getOriginal () { return original; } /** Create a property change listener that allows listening on the * original node properties (contained in property sets) and propagating * them to the proxy. * <P> * This method is called during initialization and allows subclasses * to modify the default behaviour. * * @return a {@link PropertyChangeAdapter} in the default implementation */ protected PropertyChangeListener createPropertyChangeListener () { return new PropertyChangeAdapter (this); } /** Creates a node listener that allows listening on the * original node and propagating events to the proxy. * <p>Intended for overriding by subclasses, as with {@link #createPropertyChangeListener}. * * @return a {@link FilterNode.NodeAdapter} in the default implementation */ protected NodeListener createNodeListener () { return new NodeAdapter (this); } /** Getter for property change listener. */ synchronized PropertyChangeListener getPropertyChangeListener () { if (propL == null) { propL = createPropertyChangeListener (); } return propL; } /** Getter for node listener. */ synchronized NodeListener getNodeListener () { if (nodeL == null) { nodeL = createNodeListener (); getOriginal().addNodeListener(nodeL); } return nodeL; } /** Notified from Node that a listener has been added. * Thus we force initialization of listeners. */ final void listenerAdded () { getNodeListener (); } /** Check method whether the node has default behaviour or * if it is either subclass of uses different children. * @return true if it is default */ private boolean isDefault () { //System.err.print ("FilterNode.isDefault: "); if (getClass () != FilterNode.class) { //System.err.println("false\n\tsubclass of FilterNode"); return false; } org.openide.nodes.Children ch = getChildren (); if ((original.isLeaf () && ch == Children.LEAF) || (! original.isLeaf () && ch.getClass () == /* FilterNode. */ Children.class && ((Children) ch).original == original)) { //System.err.println("true"); return true; } else { //System.err.println("false"); //System.err.println("\toriginal.isLeaf: " + original.isLeaf ()); //System.err.println("\tch == Children.LEAF: " + (ch == Children.LEAF)); //System.err.println("\tch.class: " + ch.getClass ()); //if (ch instanceof Children) // System.err.println("ch.original == original: " + (((Children) ch).original == original)); return false; } } /** Adapter that listens on changes in an original node * and refires them in a proxy. * This adapter is created during * initialization in {@link FilterNode#createPropertyChangeListener}. The method * can be overriden and this class used as the super class for the * new implementation. * <P> * A reference to the proxy is stored by weak reference, so it does not * prevent the node from being finalized. */ protected static class PropertyChangeAdapter extends Object implements PropertyChangeListener { /** weak reference to filter node */ private WeakReference fn; /** Create a new adapter. * @param fn the proxy */ public PropertyChangeAdapter (FilterNode fn) { this.fn = new WeakReference (fn); } /* Find the node we are attached to. If it is not null call property * change method with two arguments. */ public final void propertyChange (PropertyChangeEvent ev) { FilterNode fn = (FilterNode)this.fn.get (); if (fn == null) { return; } propertyChange (fn, ev); } /** Actually propagate the event. * Intended for overriding. * @param fn the proxy * @param ev the event */ protected void propertyChange (FilterNode fn, PropertyChangeEvent ev) { fn.firePropertyChange ( ev.getPropertyName (), ev.getOldValue (), ev.getNewValue () ); } } /** Adapter that listens on changes in an original node and refires them * in a proxy. Created in {@link FilterNode#createNodeListener}. * @see FilterNode.PropertyChangeAdapter */ protected static class NodeAdapter extends Object implements NodeListener { /** weak reference to filter node */ private WeakReference fn; /** Create an adapter. * @param fn the proxy */ public NodeAdapter (FilterNode fn) { this.fn = new WeakReference (fn); } /* Tests if the reference to the node provided in costructor is * still valid (it has not been finalized) and if so, calls propertyChange (Node, ev). */ public final void propertyChange (PropertyChangeEvent ev) { FilterNode fn = (FilterNode)this.fn.get (); if (fn == null) { return; } propertyChange (fn, ev); } /** Actually refire the change event in a subclass. * The default implementation ignores changes of the <code>parentNode</code> property but refires * everything else. * * @param fn the filter node * @param ev the event to fire */ protected void propertyChange (FilterNode fn, PropertyChangeEvent ev) { String n = ev.getPropertyName (); if (n.equals (Node.PROP_PARENT_NODE)) { // does nothing return; } if (n.equals (Node.PROP_DISPLAY_NAME)) { fn.fireOwnPropertyChange ( PROP_DISPLAY_NAME, (String)ev.getOldValue (), (String)ev.getNewValue () ); return; } if (n.equals (Node.PROP_NAME)) { fn.fireOwnPropertyChange ( PROP_NAME, (String)ev.getOldValue (), (String)ev.getNewValue () ); return; } if (n.equals (Node.PROP_SHORT_DESCRIPTION)) { fn.fireOwnPropertyChange ( PROP_SHORT_DESCRIPTION, (String)ev.getOldValue (), (String)ev.getNewValue () ); return; } if (n.equals (Node.PROP_ICON)) { fn.fireIconChange (); return; } if (n.equals (Node.PROP_OPENED_ICON)) { fn.fireOpenedIconChange (); return; } if (n.equals (Node.PROP_PROPERTY_SETS)) { fn.firePropertySetsChange ((PropertySet[])ev.getOldValue (), (PropertySet[])ev.getNewValue ()); return; } if (n.equals (Node.PROP_COOKIE)) { fn.fireCookieChange (); return; } if (n.equals(Node.PROP_LEAF)) { fn.fireOwnPropertyChange( Node.PROP_LEAF, ev.getOldValue(), ev.getNewValue() ); } } /** Does nothing. * @param ev event describing the action */ public void childrenAdded (NodeMemberEvent ev) { } /** Does nothing. * @param ev event describing the action */ public void childrenRemoved (NodeMemberEvent ev) { } /** Does nothing. * @param ev event describing the action */ public void childrenReordered (NodeReorderEvent ev) { } /* Does nothing. * @param ev event describing the node */ public final void nodeDestroyed (NodeEvent ev) { FilterNode fn = (FilterNode)this.fn.get (); if (fn == null) { return; } fn.originalDestroyed (); } } /** Children for a filter node. Listens on changes in subnodes of * the original node and asks this filter node to creates representants for * these subnodes. * <P> * This class is used as the default for subnodes of filter node, but * subclasses may modify it or provide a totally different implementation. * <p><code>FilterNode.Children</code> is not well suited to cases where you need to insert * additional nodes at the beginning or end of the list, or where you may need * to merge together multiple original children lists, or reorder them, etc. * That is because the keys are of type <code>Node</code>, one for each original * child, and the keys are reset during {@link #addNotify}, {@link #filterChildrenAdded}, * {@link #filterChildrenRemoved}, and {@link #filterChildrenReordered}, so it is * not trivial to use different keys: you would need to override <code>addNotify</code> * (calling super first!) and the other three update methods. For such complex cases * you will do better by creating your own <code>Children.Keys</code> subclass, setting * keys that are useful to you, and keeping a <code>NodeListener</code> on the original * node to handle changes. */ public static class Children extends org.openide.nodes.Children.Keys implements Cloneable { /** Original node. Should not be modified. */ protected Node original; /** node listener on original */ private ChildrenAdapter nodeL; /** Create children. * @param or original node to take children from */ public Children (Node or) { original = or; } /** Sets the original children for this children * @param original The new original node. * @since 1.39 */ protected final void changeOriginal( Node original ) { try { PR.enterWriteAccess(); boolean wasAttached = nodeL != null; // uregister from the original node if ( wasAttached ) { this.original.removeNodeListener( nodeL ); nodeL = null; } // reset the original node this.original = original; if ( wasAttached ) { addNotifyImpl(); } } finally { PR.exitWriteAccess(); } } /** Closes the listener, if any, on the original node. */ protected void finalize () { if (nodeL != null) original.removeNodeListener (nodeL); nodeL = null; } /* Clones the children object. */ public Object clone () { return new Children (original); } /** Initializes listening to changes in original node. */ protected void addNotify () { addNotifyImpl(); } private void addNotifyImpl () { // add itself to reflect to changes children of original node nodeL = new ChildrenAdapter (this); original.addNodeListener (nodeL); updateKeys (); } /** Clears current keys, because all mirrored nodes disappeared. */ protected void removeNotify () { setKeys (Collections.EMPTY_SET); if (nodeL != null) { original.removeNodeListener (nodeL); nodeL = null; } } /** Allows subclasses to override * creation of node representants for nodes in the mirrored children * list. The default implementation simply uses {@link Node#cloneNode}. * <p>Note that this method is only suitable for a 1-to-1 mirroring. * * @param node node to create copy of * @return copy of the original node */ protected Node copyNode (Node node) { return node.cloneNode (); } /* Implements find of child by finding the original child and then [PENDING] * @param name of node to find * @return the node or null */ public Node findChild (String name) { original.getChildren ().findChild (name); return super.findChild (name); } /** Create nodes representing copies of the original node's children. * The default implementation returns exactly one representative for each original node, * as returned by {@link #copyNode}. * Subclasses may override this to avoid displaying a copy of an original child at all, * or even to display multiple nodes representing the original. * @param key the original child node * @return zero or more nodes representing the original child node */ protected Node[] createNodes (Object key) { Node n = (Node)key; // is run under read access lock so nobody can change children return new Node[] { copyNode (n) }; } /* Delegates to children of the original node. * * @param arr nodes to add * @return true/false */ public boolean add (Node[] arr) { return original.getChildren ().add (arr); } /* Delegates to filter node. * @param arr nodes to remove * @return true/false */ public boolean remove (Node[] arr) { return original.getChildren ().remove (arr); } /** Called when the filter node adds a new child. * The default implementation makes a corresponding change. * @param ev info about the change */ protected void filterChildrenAdded (NodeMemberEvent ev) { updateKeys (); } /** Called when the filter node removes a child. * The default implementation makes a corresponding change. * @param ev info about the change */ protected void filterChildrenRemoved (NodeMemberEvent ev) { updateKeys (); } /** Called when the filter node reorders its children. * The default implementation makes a corresponding change. * @param ev info about the change */ protected void filterChildrenReordered (NodeReorderEvent ev) { updateKeys (); } /** variable to notify that there is a cyclic update. * Used only in updateKeys method */ // private transient boolean cyclic; /** Update keys from original nodes */ private void updateKeys () { ChildrenAdapter runnable = nodeL; if (runnable != null) { runnable.run (); } } /** * Implementation that ensures the original node is fully initialized * if optimal result is requested. * * @param optimalResult if <code>true</code>, the method will block * until the original node is fully initialized. */ public Node[] getNodes(boolean optimalResult) { if (optimalResult) { setKeys (original.getChildren ().getNodes (true)); } return getNodes(); } } /** Adapter that listens on changes in the original node and fires them * in this node. * Used as the default listener in {@link FilterNode.Children}, * and is intended for refinement by its subclasses. */ private static class ChildrenAdapter extends Object implements NodeListener, Runnable { /** children object to notify about addition of children. * Can be null. Set from Children's initNodes method. */ private WeakReference children; /** Create a new adapter. * @param ch the children list */ public ChildrenAdapter (Children ch) { this.children = new WeakReference (ch); } /** Called to update the content of children. */ public void run () { Children ch = (Children)children.get (); if (ch != null) { Node[] arr = ch.original.getChildren ().getNodes (); ch.setKeys (arr); } } /** Does nothing. * @param ev the event */ public void propertyChange (PropertyChangeEvent ev) { } /* Informs that a set of new children has been added. * @param ev event describing the action */ public void childrenAdded (NodeMemberEvent ev) { Children children = (Children)this.children.get (); if (children == null) return; children.filterChildrenAdded (ev); } /* Informs that a set of children has been removed. * @param ev event describing the action */ public void childrenRemoved (NodeMemberEvent ev) { Children children = (Children)this.children.get (); if (children == null) return; children.filterChildrenRemoved (ev); } /* Informs that a set of children has been reordered. * @param ev event describing the action */ public void childrenReordered (NodeReorderEvent ev) { Children children = (Children)this.children.get (); if (children == null) return; children.filterChildrenReordered (ev); } /** Does nothing. * @param ev the event */ public void nodeDestroyed (NodeEvent ev) { } } /** Filter node handle. */ private static final class FilterHandle implements Node.Handle { private Node.Handle original; static final long serialVersionUID =7928908039428333839L; public FilterHandle (Node.Handle original) { this.original = original; } public Node getNode () throws IOException { return new FilterNode (original.getNode ()); } public String toString () { return "FilterHandle[" + original + "]"; // NOI18N } } /** Special ProxyLookup */ private static final class FilterLookup extends org.openide.util.Lookup { /** node we belong to */ private FilterNode node; /** lookup we delegate too */ private Lookup delegate; /** set of all results associated to this lookup */ private org.openide.util.WeakSet results; FilterLookup () { } /** Registers own node. */ public void ownNode (FilterNode n) { this.node = n; } /** A method that replaces instance of original node * with a new one */ private Object replaceNodes (Object orig, Class clazz ) { if (isNodeQuery( clazz ) && orig == node.getOriginal() && clazz.isInstance( node )) { return node; } else { return orig; } } /** Changes the node we delegate to if necessary. * @param n the node to delegate to */ public Lookup checkNode () { Lookup l = node.getOriginal ().getLookup(); if (delegate == l) return l; Iterator toCheck = null; synchronized (this) { if (l != delegate) { this.delegate = l; if (results != null) { toCheck = Arrays.asList (results.toArray ()).iterator(); } } } if (toCheck != null) { // update Iterator it = toCheck; while (it.hasNext()) { ProxyResult p = (ProxyResult)it.next (); if (p.updateLookup (l)) { p.resultChanged (null); } } } return delegate; } public Result lookup(Template template) { ProxyResult p = new ProxyResult (template); synchronized (this) { if (results == null) { results = new org.openide.util.WeakSet (); } results.add (p); } return p; } public Object lookup(Class clazz) { return replaceNodes (checkNode ().lookup (clazz), clazz ); } /** Finds out whether a query for a class can be influenced * by a state of the "nodes" lookup and whether we should * initialize listening */ private static boolean isNodeQuery (Class c) { return Node.class.isAssignableFrom (c) || c.isAssignableFrom (Node.class); } public Item lookupItem(Template template) { Item i = checkNode ().lookupItem (template); return isNodeQuery (template.getType ()) ? new FilterItem (i, template.getType()) : i; } /** * Result used in SimpleLookup. It holds a reference to the collection * passed in constructor. As the contents of this lookup result never * changes the addLookupListener and removeLookupListener are empty. */ private final class ProxyResult extends Result implements LookupListener { /** Template used for this result. It is never null.*/ private Template template; /** result to delegate to */ private Lookup.Result delegate; /** listeners set */ private javax.swing.event.EventListenerList listeners; /** Just remembers the supplied argument in variable template.*/ ProxyResult (Template template) { this.template = template; } /** Checks state of the result */ private Result checkResult () { updateLookup (checkNode ()); return this.delegate; } /** Updates the state of the lookup. * @return true if the lookup really changed */ public boolean updateLookup (Lookup l) { Collection oldPairs = delegate != null ? delegate.allItems () : null; synchronized (this) { if (delegate != null) { delegate.removeLookupListener (this); } delegate = l.lookup (template); delegate.addLookupListener (this); } if (oldPairs == null) { // nobody knows about a change return false; } Collection newPairs = delegate.allItems (); return !oldPairs.equals (newPairs); } public synchronized void addLookupListener(LookupListener l) { if (listeners == null) { listeners = new javax.swing.event.EventListenerList (); } listeners.add (LookupListener.class, l); } public synchronized void removeLookupListener(LookupListener l) { if (listeners != null) { listeners.remove (LookupListener.class, l); } } public java.util.Collection allInstances() { java.util.Collection c = checkResult ().allInstances (); if (isNodeQuery (template.getType ())) { ArrayList ll = new ArrayList (c.size ()); Iterator it = c.iterator(); while (it.hasNext()) { ll.add (replaceNodes (it.next (), template.getType())); } return ll; } else { return c; } } public Set allClasses () { return checkResult ().allClasses (); } public Collection allItems () { return checkResult ().allItems (); } /** A change in lookup occured. * @param ev event describing the change * */ public void resultChanged(LookupEvent anEvent) { javax.swing.event.EventListenerList l = this.listeners; if (l == null) return; Object[] listeners = l.getListenerList(); if (listeners.length == 0) return; LookupEvent ev = new LookupEvent (this); for (int i = listeners.length - 1; i >= 0; i -= 2) { LookupListener ll = (LookupListener)listeners[i]; ll.resultChanged(ev); } } } // end of ProxyResult /** Item that exchanges the original node for the FilterNode */ private final class FilterItem extends Lookup.Item { private Item delegate; private Class clazz; FilterItem (Item d, Class clazz) { this.delegate = d; this.clazz = clazz; } public String getDisplayName() { return delegate.getDisplayName (); } public String getId() { return delegate.getId (); } public Object getInstance() { return replaceNodes (delegate.getInstance (), clazz); } public Class getType() { return delegate.getType (); } } } // end of FilterLookup }