alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*
 *                 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 java.lang.reflect.Method;
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 Node
* by calling {@link #disableDelegation}.
*
* 

Note: it is fine to subclass this class and use * it to filter things. But please do not ever try to cast a node to * FilterNode: it probably means you are doing something * wrong. Instead, ask whatever Node you have for a proper * kind of cookie (e.g. 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 setName. */ protected static final int DELEGATE_SET_NAME = 1 << 0; /** Whether to delegate getName. */ protected static final int DELEGATE_GET_NAME = 1 << 1; /** Whether to delegate setDisplayName. */ protected static final int DELEGATE_SET_DISPLAY_NAME = 1 << 2; /** Whether to delegate getDisplayName. */ protected static final int DELEGATE_GET_DISPLAY_NAME = 1 << 3; /** Whether to delegate setShortDescription. */ protected static final int DELEGATE_SET_SHORT_DESCRIPTION = 1 << 4; /** Whether to delegate getShortDescription. */ protected static final int DELEGATE_GET_SHORT_DESCRIPTION = 1 << 5; /** Whether to delegate destroy. */ protected static final int DELEGATE_DESTROY = 1 << 6; /** Whether to delegate getActions. */ protected static final int DELEGATE_GET_ACTIONS = 1 << 7; /** Whether to delegate getContextActions. */ protected static final int DELEGATE_GET_CONTEXT_ACTIONS = 1 << 8; /** Whether to delegate setValue. * @since 4.25 */ protected static final int DELEGATE_SET_VALUE = 1 << 9; /** Whether to delegate getValue. * @since 4.25 */ protected static final int DELEGATE_GET_VALUE = 1 << 10; /** 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 | DELEGATE_SET_VALUE | DELEGATE_GET_VALUE; /** Is PropertyChangeListener attached to original node */ private boolean pchlAttached = false; /** children provided or created the default ones? */ private boolean childrenProvided; /** Create proxy. * @param original the node to delegate to */ public FilterNode(Node original) { this (original, null); } /** 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 ) { this (original, children, new FilterLookup ()); } /** Constracts new filter node. * @param original the node we delegate to * @param children the children to use for the filter node or null if * default children should be provided * @param lookup lookup to use * * @since 4.4 */ public FilterNode (Node original, org.openide.nodes.Children children, Lookup lookup) { super ( children == null ? (original.isLeaf () ? org.openide.nodes.Children.LEAF : new Children (original)) : children, lookup ); this.childrenProvided = children != null; this.original = original; init (); Lookup lkp = internalLookup (false); if (lkp instanceof FilterLookup) { ((FilterLookup)lkp).ownNode (this); } else { if (lkp == null) { // rely on default NodeLookup around getCookie. getNodeListener(); } } } private static final WeakHashMap overridesGetDisplayNameCache = new WeakHashMap (27); 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 DELEGATE_XXX 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}. *

For example, if you wish to subclass FilterNode, 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: *

    * 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()
    *   }
    * }
    * 
*
You may still manually delegate where desired using {@link #getOriginal}. * Other methods abstract in Node may simply be overridden without * any special handling. * @param mask bitwise disjunction of DELEGATE_XXX 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 true changes children * of this node according to the new original node. If you pass * children which are not instance of class * FilterNode.Children into the constructor set this * parameter to false. *@throws java.lang.IllegalStateException if children which are not * instance of FilterNode.Children were passed * into the constructor and the method was called with the parameter * changeChildren set to true. *@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 } if (original == this) { throw new IllegalArgumentException ("Cannot be own original: " + original); // 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 ------------ public void setValue(String attributeName, Object value) { if (delegating (DELEGATE_SET_VALUE)) { original.setValue (attributeName, value); } else { super.setValue (attributeName, value); } } public Object getValue(String attributeName) { if (delegating (DELEGATE_GET_VALUE)) { return original.getValue (attributeName); } else { return super.getValue (attributeName); } } /* 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 java.bean.BeanInfo * @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 java.bean.BeanInfo * @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 true if can, false 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; } /** Get a display name containing HTML markup. Note: If you subclass * FilterNode and override getDisplayName(), this method will * always return null unless you override it as well (assuming that if you're * changing the display name, you don't want an HTML display name constructed * from the original node's display name to be what shows up in views of * this node). If getDisplayName() is not overridden, * this method will return whatever the original node returns from this * method. *

* Note that if you do override getDisplayName, you should also override * this method to return null. * * * * @see org.openide.nodes.Node#getHtmlDisplayName * @return An HTML display name, if available, or null if no display name * is available */ public String getHtmlDisplayName() { if (overridesGetDisplayName()) { return null; } else { return delegating (DELEGATE_GET_DISPLAY_NAME) ? original.getHtmlDisplayName() : super.getHtmlDisplayName(); } } private boolean overridesGetDisplayName() { synchronized (overridesGetDisplayNameCache) { Boolean b = (Boolean)overridesGetDisplayNameCache.get (getClass ()); if (b == null) { b = overridesAMethod ("getDisplayName", null) ? Boolean.TRUE : Boolean.FALSE; // NOI18N overridesGetDisplayNameCache.put (getClass (), b); } return b.booleanValue() ? true : false; } } /* * @return true if the original has a customizer. */ public boolean hasCustomizer () { return original.hasCustomizer (); } /* Returns the customizer component. * @return the component or null if there is no customizer */ public java.awt.Component getCustomizer () { return original.getCustomizer (); } /* Delegates to original, if not special lookup provided in constructor, * Otherwise it delegates to the lookup. * * @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) { Lookup l = internalLookup (true); if (l != null) { Object o = l.lookup (type); if (o instanceof Node.Cookie) { return (Node.Cookie)o; } } 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. *

Subclasses must override this if they wish for their nodes to be * properly serializable. * * @return the handle, or null 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 FilterNode, or filter nodes with non-default children, * the test reverts to object identity. * Note: if you wish that the {@link Index} cookie works correctly on * filtered nodes and their subnodes, and you are subclassing FilterNode 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. *

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 * DataFolder.Index for data folders. * @param o something to compare to, presumably a node or FilterNode 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 instanceof Node)) return false; // something else or null if (this == o) return true; // shortcut // get the "most original" ones.... Node left = getRepresentation(this); Node right = getRepresentation((Node)o); // cover nondefault FilterNodes (possibly) deep in the stack if ((left instanceof FilterNode) || (right instanceof FilterNode)) { return left == right; } return left.equals(right); } private static Node getRepresentation(Node n) { while (n instanceof FilterNode) { FilterNode fn = (FilterNode)n; if ( ! fn.isDefault() ) return n; n = fn.original; } return n; // either node or nondefault FilterNode } /** Hash by original nodes. * Note that for subclasses of FilterNode, or filter nodes with non-default children, * the hash reverts to the identity hash code. * @return the delegated hash code */ public int hashCode () { try { assert hashCodeLogging (true) : ""; // NOI18N int result = isDefault () ? original.hashCode () : super.hashCode (); assert hashCodeLogging (false) : ""; // NOI18N return result; } catch (StackError err) { err.add (this); throw err; } } /** Depth of stack trace. */ private static volatile int hashCodeDepth; /** Method for tracing the issue 46993. Counts the depth of execution * and if larger than 1000 throws debugging exception. */ private static boolean hashCodeLogging (boolean enter) { if (hashCodeDepth > 1000) { hashCodeDepth = 0; throw new StackError (); } if (enter) { hashCodeDepth++; } else { hashCodeDepth--; } return true; } /** An exception to be thrown from hashCode() to debug issue 46993. */ private static class StackError extends StackOverflowError { private java.util.IdentityHashMap nodes; public void add (FilterNode n) { if (nodes == null) { nodes = new java.util.IdentityHashMap (); } if (nodes.get (n) == null) { nodes.put (n, n); } } public String getMessage () { StringBuffer sb = new StringBuffer (); sb.append ("StackOver in FilterNodes:\n"); // NOI18N Iterator it = nodes.keySet ().iterator (); while (it.hasNext ()) { FilterNode f = (FilterNode)it.next (); sb.append (" class: "); // NOI18N sb.append (f.getClass ().getName()); sb.append (" id: "); // NOI18N sb.append (Integer.toString (System.identityHashCode (f), 16)); sb.append ("\n"); // NOI18N } return sb.toString (); } } // public String toString () { // return super.toString () + " original has children: " + original.getChildren ().getNodesCount (); // NOI18N // } // ----------- END OF DELEGATED METHODS ------------ /** Get the original node. *

Yes this is supposed to be protected! If you * are not subclassing FilterNode yourself, you should * not be calling it (nor casting to FilterNode). 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. *

* 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. *

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; } return !childrenProvided; } /** * This method is used to change the Children from Children.LEAF to Children * typically used to when there is a setChildren() on the original node * setChildren will fire the appropriate events */ final void updateChildren () { if (isDefault ()) { org.openide.nodes.Children newChildren = null; try { Children.PR.enterReadAccess(); if ( original.hierarchy == Children.LEAF && hierarchy != Children.LEAF ) { newChildren = Children.LEAF; } else if ( original.hierarchy != Children.LEAF && hierarchy == Children.LEAF ) { newChildren = new Children (original); } } finally { Children.PR.exitReadAccess(); } if (newChildren != null) { final org.openide.nodes.Children set = newChildren; Children.MUTEX.postWriteRequest(new Runnable () { public void run () { setChildren (set); } }); } } } /** 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. *

* 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 parentNode 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.updateChildren (); /* 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. *

* This class is used as the default for subnodes of filter node, but * subclasses may modify it or provide a totally different implementation. *

FilterNode.Children 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 Node, 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 addNotify * (calling super first!) and the other three update methods. For such complex cases * you will do better by creating your own Children.Keys subclass, setting * keys that are useful to you, and keeping a NodeListener 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}. *

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 true, the method will block * until the original node is fully initialized. * @since 3.9 */ 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 }

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.