? now.err ? now.diff ? X.diff ? X.txt Index: api/doc/changes/apichanges.xml =================================================================== RCS file: /cvs/openide/api/doc/changes/apichanges.xml,v retrieving revision 1.96 diff -c -r1.96 apichanges.xml *** api/doc/changes/apichanges.xml 2 Oct 2002 12:38:27 -0000 1.96 --- api/doc/changes/apichanges.xml 3 Oct 2002 08:11:28 -0000 *************** *** 107,112 **** --- 107,129 ---- + + + Node implements Lookup.Provider + + + + + + Node has been extended to provide method getLookup that allows better + querying possibilities than the old Node.getCookie method. New constructors + have been provided to allow to pass a Lookup instance to newly created + node. In such case Node.getCookie delegates to the provided instance, + otherwise Node.getLookup delegates to Node.getCookie content. + + + + *************** *** 148,154 **** ! New method Lookups.proxy --- 165,171 ---- ! New method Lookups.proxy Index: src/org/openide/nodes/AbstractNode.java =================================================================== RCS file: /cvs/openide/src/org/openide/nodes/AbstractNode.java,v retrieving revision 1.52 diff -c -r1.52 AbstractNode.java *** src/org/openide/nodes/AbstractNode.java 14 Aug 2002 14:16:25 -0000 1.52 --- src/org/openide/nodes/AbstractNode.java 3 Oct 2002 08:11:30 -0000 *************** *** 97,103 **** * it is stored as array */ private Object[] iconBase = { DEFAULT_ICON_BASE }; /** array of cookies for this node */ ! private CookieSet cookieSet; /** set of properties to use */ private Sheet sheet; /** Actions for the node. They are used only for the pop-up menus --- 97,103 ---- * it is stored as array */ private Object[] iconBase = { DEFAULT_ICON_BASE }; /** array of cookies for this node */ ! private Object lookup; /** set of properties to use */ private Sheet sheet; /** Actions for the node. They are used only for the pop-up menus *************** *** 124,130 **** * @param children the children to use for this node */ public AbstractNode(Children children) { ! super (children); // Setting the name to non-null value for the node // to return "reasonable" name and displayName // not using this.setName since the descendants --- 124,141 ---- * @param children the children to use for this node */ public AbstractNode(Children children) { ! this (children, null); ! } ! ! ! /** Create a new abstract node with a given child set and associated ! * lookup. ! * ! * @param children the children to use for this node ! * @since 3.11 ! */ ! public AbstractNode (Children children, Lookup lookup) { ! super (children, lookup); // Setting the name to non-null value for the node // to return "reasonable" name and displayName // not using this.setName since the descendants *************** *** 492,526 **** * * @param s the cookie set to use * @deprecated just use getCookieSet().add(...) instead */ protected final synchronized void setCookieSet (CookieSet s) { if (sheetCookieL == null) { sheetCookieL = new SheetAndCookieListener (); } if (cookieSet != null) { cookieSet.removeChangeListener (sheetCookieL); } s.addChangeListener (sheetCookieL); ! cookieSet = s; fireCookieChange (); } ! /** Get the cookie set. * * @return the cookie set created by {@link #setCookieSet}, or an empty set (never null) */ protected final CookieSet getCookieSet () { ! CookieSet s = cookieSet; if (s != null) return s; synchronized (this) { ! if (cookieSet != null) return cookieSet; // sets empty sheet and adds a listener to it setCookieSet (new CookieSet ()); ! return cookieSet; } } --- 503,550 ---- * * @param s the cookie set to use * @deprecated just use getCookieSet().add(...) instead + * @exception If you pass a Lookup instance into the constructor, this + * method cannot be called and throws a runtime exception. */ protected final synchronized void setCookieSet (CookieSet s) { + if (internalLookup () != null) { + throw new IllegalStateException ("CookieSet cannot be used when lookup is associated with the node"); // NOI18N + } + if (sheetCookieL == null) { sheetCookieL = new SheetAndCookieListener (); } + CookieSet cookieSet = (CookieSet)lookup; if (cookieSet != null) { cookieSet.removeChangeListener (sheetCookieL); } s.addChangeListener (sheetCookieL); ! lookup = s; fireCookieChange (); } ! /** Get the cookie set. * * @return the cookie set created by {@link #setCookieSet}, or an empty set (never null) + * @exception If you pass a Lookup instance into the constructor, this + * method cannot be called and throws a runtime exception. */ protected final CookieSet getCookieSet () { ! if (internalLookup () != null) { ! throw new IllegalStateException ("CookieSet cannot be used when lookup is associated with the node"); // NOI18N ! } ! ! CookieSet s = (CookieSet)lookup; if (s != null) return s; synchronized (this) { ! if (lookup != null) return (CookieSet)lookup; // sets empty sheet and adds a listener to it setCookieSet (new CookieSet ()); ! return (CookieSet)lookup; } } *************** *** 531,541 **** * @return the cookie or null */ public Node.Cookie getCookie (Class type) { ! CookieSet c = cookieSet; ! if (c == null) return null; ! ! return c.getCookie (type); } /** Get a serializable handle for this node. * @return a {@link DefaultHandle} in the default implementation --- 555,568 ---- * @return the cookie or null */ public Node.Cookie getCookie (Class type) { ! if (lookup instanceof CookieSet) { ! CookieSet c = (CookieSet)lookup; ! return c.getCookie (type); ! } else { ! return super.getCookie (type); ! } } + /** Get a serializable handle for this node. * @return a {@link DefaultHandle} in the default implementation Index: src/org/openide/nodes/FilterNode.java =================================================================== RCS file: /cvs/openide/src/org/openide/nodes/FilterNode.java,v retrieving revision 1.59 diff -c -r1.59 FilterNode.java *** src/org/openide/nodes/FilterNode.java 5 Sep 2002 14:16:44 -0000 1.59 --- src/org/openide/nodes/FilterNode.java 3 Oct 2002 08:11:36 -0000 *************** *** 32,37 **** --- 32,38 ---- 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.actions.SystemAction; /** A proxy for another node. *************** *** 116,125 **** Node original, org.openide.nodes.Children children ) { ! super (children); this.original = original; init (); } /** Initializes the node. */ --- 117,155 ---- Node original, org.openide.nodes.Children children ) { ! super (children, new FilterLookup ()); this.original = original; init (); + + if (internalLookup () instanceof FilterLookup) { + ((FilterLookup)internalLookup ()).changeNode(original); + } } + + /** 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) { + if (getClass () != FilterNode.class) { + // we are subclass of FilterNode + try { + java.lang.reflect.Method m = getClass ().getMethod ("getCookie", new Class[] { Class.class }); // NOI18N + + if (m.getDeclaringClass () != FilterNode.class) { + // ok somebody overriden getCookie method + return null; + } + } catch (NoSuchMethodException ex) { + } + } + + // the lookup can stay the same + return lookup; + } /** Initializes the node. */ *************** *** 291,296 **** --- 321,329 ---- // Fire all sorts of events (everything gets changed after we // reset the original node.) + if (internalLookup () instanceof FilterLookup) { + ((FilterLookup)internalLookup ()).changeNode(original); + } fireCookieChange(); fireNameChange( oldName, original.getName() ); fireDisplayNameChange( oldDisplayName, original.getDisplayName() ); *************** *** 1140,1145 **** --- 1173,1190 ---- public String toString () { return "FilterHandle[" + original + "]"; // NOI18N + } + } + + /** Special ProxyLookup + */ + private static final class FilterLookup extends org.openide.util.lookup.ProxyLookup { + public FilterLookup () { + super (new Lookup[0]); + } + + public void changeNode (Node n) { + setLookups (new Lookup[] { n. getLookup () }); } } } Index: src/org/openide/nodes/Node.java =================================================================== RCS file: /cvs/openide/src/org/openide/nodes/Node.java,v retrieving revision 1.50 diff -c -r1.50 Node.java *** src/org/openide/nodes/Node.java 15 Sep 2002 20:23:40 -0000 1.50 --- src/org/openide/nodes/Node.java 3 Oct 2002 08:11:36 -0000 *************** *** 21,30 **** --- 21,33 ---- import java.beans.PropertyEditor; import java.beans.FeatureDescriptor; import java.io.IOException; + import java.lang.ref.Reference; + import java.lang.ref.WeakReference; import java.lang.reflect.InvocationTargetException; import java.util.Enumeration; import java.util.Hashtable; + import java.util.WeakHashMap; import javax.swing.JPopupMenu; import javax.swing.event.EventListenerList; *************** *** 33,38 **** --- 36,42 ---- import org.openide.util.datatransfer.PasteType; import org.openide.util.HelpCtx; import org.openide.util.Lookup; + import org.openide.util.LookupListener; import org.openide.util.Mutex; import org.openide.util.NbBundle; import org.openide.util.actions.SystemAction; *************** *** 64,74 **** * The node is cloneable. When a node is cloned, it is initialized * with an empty set of listeners and no parent. The display name and short description * are copied to the new node. The set of properties is shared. * * @author Jaroslav Tulach, - * @version 1.00, Sep 9, 1998 */ ! public abstract class Node extends FeatureDescriptor { /** An empty leaf node. */ public static final Node EMPTY = new AbstractNode (Children.LEAF); --- 68,80 ---- * The node is cloneable. When a node is cloned, it is initialized * with an empty set of listeners and no parent. The display name and short description * are copied to the new node. The set of properties is shared. + *

+ * Implements Lookup.Provider since 3.11. * * @author Jaroslav Tulach, */ ! public abstract class Node extends FeatureDescriptor ! implements Lookup.Provider { /** An empty leaf node. */ public static final Node EMPTY = new AbstractNode (Children.LEAF); *************** *** 102,112 **** /** Error manager for used for logging */ transient static ErrorManager err; /** children representing parent node, for synchronization reasons must be changed only * under the Children.MUTEX lock */ private Children parent; ! /** children list, for synch. reasons change only under Children.MUTEX * lock */ --- 108,121 ---- /** Error manager for used for logging */ transient static ErrorManager err; + /** cache of all created lookups */ + private static WeakHashMap lookups = new WeakHashMap (37); + /** children representing parent node, for synchronization reasons must be changed only * under the Children.MUTEX lock */ private Children parent; ! /** children list, for synch. reasons change only under Children.MUTEX * lock */ *************** *** 114,127 **** /** listeners for changes in hierarchy. */ ! private transient EventListenerList listeners = new EventListenerList (); /** Creates a new node with a given hierarchy of children. * @exception IllegalStateException if the hierarchy is already in use by * a different node */ protected Node(Children h) throws IllegalStateException { ! hierarchy = h; // attaches to this node h.attachTo (this); --- 123,155 ---- /** listeners for changes in hierarchy. */ ! private transient EventListenerList listeners; /** Creates a new node with a given hierarchy of children. * @exception IllegalStateException if the hierarchy is already in use by * a different node */ protected Node(Children h) throws IllegalStateException { ! this (h, null); ! } ! ! /** Creates a new node with a given hierarchy of children. ! * @since 3.11 ! * @exception IllegalStateException if the hierarchy is already in use by ! * a different node ! */ ! protected Node(Children h, Lookup lookup) throws IllegalStateException { ! this.hierarchy = h; ! ! // allow subclasses (FilterNode) to update the lookup ! lookup = replaceProvidedLookup (lookup); ! ! if (lookup != null) { ! this.listeners = new LookupEventList (lookup); ! } else { ! this.listeners = new EventListenerList (); ! } ! // attaches to this node h.attachTo (this); *************** *** 129,134 **** --- 157,180 ---- err = ErrorManager.getDefault().getInstance("org.openide.nodes.Node"); //NOI18N } } + + /** Method for subclasses to modify provided lookup before its use. + * This implementation does nothing. + */ + Lookup replaceProvidedLookup (Lookup l) { + return l; + } + + /** Method that gives access to internal lookup. + * @return lookup or null + */ + final Lookup internalLookup () { + if (listeners instanceof LookupEventList) { + return ((LookupEventList)listeners).lookup; + } else { + return null; + } + } /** Implements {@link Object#clone} to behave correctly if cloning is desired. * But {@link Cloneable} is not declared by default. *************** *** 157,163 **** // no parent n.parent = null; // empty set of listeners ! n.listeners = new EventListenerList (); return n; } --- 203,214 ---- // no parent n.parent = null; // empty set of listeners ! ! if (listeners instanceof LookupEventList) { ! n.listeners = new LookupEventList (internalLookup ()); ! } else { ! n.listeners = new EventListenerList (); ! } return n; } *************** *** 485,495 **** *

* The set of cookies can change. If a node changes its set of * cookies, it fires a property change event with {@link #PROP_COOKIE}. * * @param type the representation class of the cookie * @return a cookie assignable to that class, or null if this node has no such cookie */ ! public abstract Node.Cookie getCookie (Class type); /** Obtain handle for this node (for serialization). * The handle can be serialized and {@link Handle#getNode} used after --- 536,604 ---- *

* The set of cookies can change. If a node changes its set of * cookies, it fires a property change event with {@link #PROP_COOKIE}. + *

+ * If the Node was constructed with a Lookup in constructor + * than this method delegates to the provided lookup object. * * @param type the representation class of the cookie * @return a cookie assignable to that class, or null if this node has no such cookie + * @see Lookup */ ! public Node.Cookie getCookie (Class type) { ! Lookup l = internalLookup (); ! if (l != null) { ! Object o = l.lookup (type); ! if (o instanceof Node.Cookie) { ! return (Node.Cookie)o; ! } ! } ! return null; ! } ! ! /** Obtains a Lookup represeting additional content of this Node. ! * If the lookup was provided in a constructor, it is returned here, ! * if not, a lookup based on the content of getCookie ! * method is provided. ! * ! * @return lookup for this node ! * @since 3.11 ! */ ! public final Lookup getLookup () { ! synchronized (listeners) { ! Lookup l = internalLookup (); ! if (l != null) { ! return l; ! } ! ! l = findDelegatingLookup (); ! if (l != null) { ! return l; ! } ! ! // create new lookup and use it ! NodeLookup nl = new NodeLookup (this); ! registerDelegatingLookup (nl); ! return nl; ! } ! } ! ! /** Register delegating lookup so it can always be found. ! */ ! final void registerDelegatingLookup (NodeLookup l) { ! // to have just one thread accessing the static lookups variable ! synchronized (lookups) { ! lookups.put (listeners, new WeakReference (l)); ! } ! } ! ! /** Finds delegating lookup that was previously registered ! * @return the lookup or null if nothing was registed or the ! * lookup was GCed. ! */ ! final Lookup findDelegatingLookup () { ! WeakReference ref = (WeakReference)lookups.get (listeners); ! return ref == null ? null : (Lookup)ref.get (); ! } /** Obtain handle for this node (for serialization). * The handle can be serialized and {@link Handle#getNode} used after *************** *** 506,511 **** --- 615,623 ---- * @param l the listener to add */ public final void addNodeListener (NodeListener l) { + if (listeners instanceof LookupEventList) { + ((LookupEventList)listeners).init (); + } listeners.add (NodeListener.class, l); } *************** *** 1074,1078 **** --- 1186,1220 ---- public String toString () { return super.toString () + "[Name="+getName ()+", displayName="+getDisplayName ()+"]"; // NOI18N + } + + + /** template for changes in cookies */ + private static final Lookup.Template TEMPL_COOKIE = new Lookup.Template (Node.Cookie.class); + + /** Special subclass of EventListenerList that can also listen on changes in + * a lookup. + */ + private final class LookupEventList extends javax.swing.event.EventListenerList + implements LookupListener { + + public final Lookup lookup; + private Lookup.Result result; + + public LookupEventList (Lookup l) { + this.lookup = l; + } + + public synchronized void init () { + if (result == null) { + result = lookup.lookup (TEMPL_COOKIE); + result.addLookupListener (this); + result.allItems(); + } + } + + public void resultChanged(org.openide.util.LookupEvent ev) { + fireCookieChange(); + } } } Index: src/org/openide/nodes/NodeLookup.java =================================================================== RCS file: src/org/openide/nodes/NodeLookup.java diff -N src/org/openide/nodes/NodeLookup.java *** /dev/null 1 Jan 1970 00:00:00 -0000 --- src/org/openide/nodes/NodeLookup.java 3 Oct 2002 08:11:36 -0000 *************** *** 0 **** --- 1,199 ---- + /* + * 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-2000 Sun + * Microsystems, Inc. All Rights Reserved. + */ + + package org.openide.nodes; + + import java.lang.ref.Reference; + import java.beans.PropertyChangeListener; + import java.beans.PropertyChangeEvent; + import java.util.Iterator; + import java.util.Collection; + import java.lang.ref.WeakReference; + import javax.swing.ActionMap; + import javax.swing.Action; + import java.util.ArrayList; + + import org.openide.util.WeakSet; + import org.openide.util.lookup.InstanceContent; + import org.openide.windows.TopComponent; + import org.openide.util.lookup.AbstractLookup; + import org.openide.util.WeakListener; + import org.openide.nodes.Node; + import org.openide.util.Lookup; + + import org.openide.nodes.*; + + /** A lookup that flattens its content extracting it from top component. + * + * @author Jaroslav Tulach + */ + final class NodeLookup extends AbstractLookup + implements NodeListener, Collection { + /** instance content to add new objects into */ + private InstanceContent ic; + /** Set of Classes that we have already queried Class */ + private WeakSet queriedCookieClasses = new WeakSet (37); + + /** node we are associated with + */ + private Node node; + + /** New flat lookup. + */ + public NodeLookup (Node n) { + this (new InstanceContent (), n); + } + + private NodeLookup (InstanceContent ic, Node n) { + super (ic); + + this.ic = ic; + this.node = n; + this.ic.add (n); + + n.addNodeListener (WeakListener.node (this, n)); + } + + /** Notifies subclasses that a query is about to be processed. + * @param template the template + */ + protected final void beforeLookup (Template template) { + Class type = template.getType (); + + if (type == Object.class) { + type = Node.Cookie.class; + } + + if (Node.Cookie.class.isAssignableFrom (type)) { + if (!queriedCookieClasses.contains (type)) { + synchronized (this) { + queriedCookieClasses.add (type); + } + + Object res = node.getCookie (type); + if (res != null) { + ic.add (res); + } + } + } + } + + + public void propertyChange(PropertyChangeEvent ev) { + // a change happened in a node + if (ev.getPropertyName () != Node.PROP_COOKIE) { + return; + } + + ArrayList instances = new ArrayList (); + + // if it is cookie change, do the rescan + synchronized (this) { + Iterator it = queriedCookieClasses.iterator(); + instances.add (node); + while (it.hasNext()) { + Class c = (Class)it.next (); + Object res = node.getCookie (c); + if (res != null) { + instances.add (res); + } + } + } + + ic.set (instances, null); + } + + /** Fired when the node is deleted. + * @param ev event describing the node + */ + public void nodeDestroyed(NodeEvent ev) { + } + + /** Fired when a set of children is removed. + * @param ev event describing the action + */ + public void childrenRemoved(NodeMemberEvent ev) { + } + + /** Fired when the order of children is changed. + * @param ev event describing the change + */ + public void childrenReordered(NodeReorderEvent ev) { + } + + /** Fired when a set of new children is added. + * @param ev event describing the action + */ + public void childrenAdded(NodeMemberEvent ev) { + } + + // + // Not important Collection methods, just add (Object is important)! + // + + /** Adds value to the lookup + */ + public boolean add(Object obj) { + ic.add (obj); + return true; + } + + public boolean retainAll(java.util.Collection collection) { + return false; + } + + public boolean contains(Object obj) { + return false; + } + + public Object[] toArray(Object[] obj) { + return obj; + } + + public java.util.Iterator iterator() { + return null; + } + + public boolean removeAll(java.util.Collection collection) { + return false; + } + + public Object[] toArray() { + return null; + } + + public boolean remove(Object obj) { + return false; + } + + public void clear() { + } + + public boolean addAll(java.util.Collection collection) { + return false; + } + + public int size() { + return 0; + } + + public boolean containsAll(java.util.Collection collection) { + return false; + } + + public boolean isEmpty() { + return false; + } + + + } Index: test/unit/src/org/openide/nodes/NodeLookupTest.java =================================================================== RCS file: test/unit/src/org/openide/nodes/NodeLookupTest.java diff -N test/unit/src/org/openide/nodes/NodeLookupTest.java *** /dev/null 1 Jan 1970 00:00:00 -0000 --- test/unit/src/org/openide/nodes/NodeLookupTest.java 3 Oct 2002 08:11:38 -0000 *************** *** 0 **** --- 1,307 ---- + /* + * 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-2001 Sun + * Microsystems, Inc. All Rights Reserved. + */ + + package org.openide.nodes; + + import junit.framework.*; + import junit.textui.TestRunner; + import java.util.*; + import org.openide.nodes.*; + + import org.netbeans.junit.*; + import org.openide.util.Lookup; + + import org.openide.util.LookupListener; + import org.openide.util.lookup.*; + + /** Tests whether notification to NodeListener is fired under Mutex.writeAccess + * + * @author Jaroslav Tulach + */ + public class NodeLookupTest extends NbTestCase { + public NodeLookupTest(String name) { + super(name); + } + + public static void main(String[] args) { + TestRunner.run(new NbTestSuite(NodeLookupTest.class)); + } + + public void testChangesAreFiredFromLookup () { + InstanceContent ic = new InstanceContent (); + AbstractLookup lookup = new AbstractLookup (ic); + Node node = new AbstractNode (Children.LEAF, lookup); + + checkInstanceInGetCookie (new Node.Cookie () {}, ic, node); + checkInstanceInGetLookup (new Node.Cookie () {}, ic, node, true); + checkInstanceInGetLookup ("Some string", ic, node, true); + } + + public void testChangesAreFiredFromLookupThruFilterNode () { + InstanceContent ic = new InstanceContent (); + AbstractLookup lookup = new AbstractLookup (ic); + Node node = new FilterNode (new AbstractNode (Children.LEAF, lookup)); + + //checkInstanceInGetCookie (new Node.Cookie () {}, ic, node); + checkInstanceInGetLookup (new Node.Cookie () {}, ic, node, true); + checkInstanceInGetLookup ("Some string", ic, node, true); + } + + public void testChangesAreFiredFromLookupThruFilterNodeWithOverWrittenGetCookie () { + final Node.Cookie myInstance = new Node.Cookie () { }; + + + InstanceContent ic = new InstanceContent (); + AbstractLookup lookup = new AbstractLookup (ic); + Node node = new FilterNode (new AbstractNode (Children.LEAF, lookup)) { + public Node.Cookie getCookie (Class clazz) { + if (clazz == myInstance.getClass ()) { + return myInstance; + } + return super.getCookie (clazz); + } + }; + + checkInstanceInGetCookie (new Node.Cookie () {}, ic, node); + checkInstanceInGetLookup (new Node.Cookie () {}, ic, node, true); + // by overwriting the FilterNode.getCookie we disable enhanced support + // for non-cookie objects in original lookup + checkInstanceInGetLookup ("Some string", ic, node, false); + + assertEquals ("It is possible to get myInstance from getCookie", myInstance, node.getCookie (myInstance.getClass ())); + assertEquals ("It also possible to get it from getLookup", myInstance, node.getLookup ().lookup (myInstance.getClass ())); + } + + private void checkInstanceInGetCookie (Object obj, InstanceContent ic, Node node) { + Listener listener = new Listener (); + node.addNodeListener(listener); + + ic.add (obj); + listener.assertEvents ("One change in node", 1, -1); + + if (obj instanceof Node.Cookie) { + assertEquals ("Can access cookie in the content", obj, node.getCookie (obj.getClass ())); + } else { + assertNull ("Cannot access noncookie in the content", node.getCookie (obj.getClass ())); + } + + ic.remove (obj); + listener.assertEvents ("One change in node", 1, -1); + } + + private void checkInstanceInGetLookup (Object obj, InstanceContent ic, Node node, boolean shouldBeThere) { + Listener listener = new Listener (); + Lookup.Result res = node.getLookup ().lookup (new Lookup.Template (obj.getClass ())); + Collection ignore = res.allItems (); + res.addLookupListener(listener); + + ic.add (obj); + if (shouldBeThere) { + listener.assertEvents ("One change in node's lookup", -1, 1); + assertEquals ("Can access object in content via lookup", obj, node.getLookup ().lookup (obj.getClass ())); + } else { + assertNull ("Cannot access object in content via lookup", node.getLookup ().lookup (obj.getClass ())); + } + + + ic.remove (obj); + if (shouldBeThere) { + listener.assertEvents ("One change in node's lookup", -1, 1); + } + assertNull ("Cookie is removed", node.getLookup ().lookup (obj.getClass ())); + } + + + // + // Test to see correct behaviour from getCookie to lookup + // + + public void testNodeIsInItsLookup () { + CookieNode n = new CookieNode (); + assertEquals ("Node is there", n, n.getLookup ().lookup (Node.class)); + } + + public void testChangeInCookieVisibleInLookup () { + CookieNode n = new CookieNode (); + checkInstanceInLookup (new Node.Cookie() {}, n.cookieSet(), n.getLookup ()); + } + + public void testChangeInCookieVisibleInLookupThruFilterNode () { + CookieNode n = new CookieNode (); + FilterNode f = new FilterNode (n); + checkInstanceInLookup (new Node.Cookie() {}, n.cookieSet(), f.getLookup ()); + } + + public void testChangeInCookieVisibleInLookupThruFilterNodeWhenItOverridesGetCookie () { + CookieNode n = new CookieNode (); + + class MyFilterNode extends FilterNode implements javax.swing.event.ChangeListener { + public CookieSet set = new CookieSet (); + + public MyFilterNode (Node n) { + super (n); + set.addChangeListener(this); + } + + public Node.Cookie getCookie (Class cl) { + Node.Cookie c = super.getCookie (cl); + if (c != null) { + return c; + } + return set.getCookie (cl); + } + + public void stateChanged (javax.swing.event.ChangeEvent ev) { + fireCookieChange (); + } + } + + MyFilterNode f = new MyFilterNode (n); + + checkInstanceInLookup (new Node.Cookie() {}, n.cookieSet(), f.getLookup ()); + checkInstanceInLookup (new Node.Cookie() {}, f.set, f.getLookup ()); + } + + private void checkInstanceInLookup (Node.Cookie obj, CookieSet ic, Lookup l) { + Listener listener = new Listener (); + Lookup.Result res = l.lookup (new Lookup.Template (Object.class)); + Collection justToEnsureChangesToListenerWillBeFired = res.allItems (); + res.addLookupListener(listener); + + ic.add (obj); + listener.assertEvents ("One change in lookup", -1, 1); + + assertEquals ("Can access cookie in the content", obj, l.lookup (obj.getClass ())); + + ic.remove (obj); + listener.assertEvents ("One change in lookup", -1, 1); + + ic.add (obj); + listener.assertEvents ("One change in lookup", -1, 1); + + assertEquals ("Can access cookie in the content", obj, l.lookup (obj.getClass ())); + + ic.remove (obj); + listener.assertEvents ("One change in lookup", -1, 1); + + } + + // + // Garbage collect + // + + public void testAbstractNodeWithoutLookupHasCookieSet () { + CookieNode n = new CookieNode (); + try { + n.cookieSet (); + } catch (RuntimeException ex) { + fail ("cannot obtain cookie set"); + } + } + + public void testAbstractNodeWithLookupDoesNotHaveCookieSet () { + CookieNode n = new CookieNode (Lookup.EMPTY); + try { + n.cookieSet (); + fail ("It should not be possible to obtain cookieSet it should throw an exception"); + } catch (RuntimeException ex) { + } + try { + n.setSet (null); + fail ("It should not be possible to obtain setCookieSet it should throw an exception"); + } catch (RuntimeException ex) { + } + } + + + public void testBackwardCompatibleAbstractNodeLookupCanBeGarbageCollected () { + AbstractNode n = new AbstractNode (Children.LEAF); + + Lookup l = n.getLookup (); + assertEquals ("Two invocations share the same lookup", l, n.getLookup ()); + + java.lang.ref.WeakReference ref = new java.lang.ref.WeakReference (l); + l = null; + assertGC ("Lookup can be GCed", ref); + } + + /** Assert GC. + */ + private static void assertGC (String text, java.lang.ref.Reference ref) { + for (int i = 0; i < 10; i++) { + if (ref.get () == null) { + return; + } + System.gc (); + System.runFinalization(); + } + fail (text + " " + ref.get ()); + } + + + private static class Listener extends Object + implements LookupListener, NodeListener { + private int cookies; + private int lookups; + + public void assertEvents (String txt, int cookies, int lookups) { + if (cookies != -1) + assertEquals (txt + " cookies", cookies, this.cookies); + if (lookups != -1) + assertEquals (txt + " lookups", lookups, this.lookups); + + this.cookies = 0; + this.lookups = 0; + } + + public void childrenAdded(NodeMemberEvent ev) { + } + + public void childrenRemoved(NodeMemberEvent ev) { + } + + public void childrenReordered(NodeReorderEvent ev) { + } + + public void nodeDestroyed(NodeEvent ev) { + } + + public void propertyChange(java.beans.PropertyChangeEvent evt) { + if (Node.PROP_COOKIE == evt.getPropertyName()) { + cookies++; + } + } + + public void resultChanged(org.openide.util.LookupEvent ev) { + lookups++; + } + + } // end of Listener + + private static class CookieNode extends AbstractNode { + public CookieNode () { + super (Children.LEAF); + } + public CookieNode (Lookup l) { + super (Children.LEAF, l); + } + + public CookieSet cookieSet () { + return getCookieSet (); + } + public void setSet (CookieSet s) { + super.setCookieSet (s); + } + } // end of CookieNode + } +