diff -r 6b487c15857b openide.nodes/apichanges.xml --- a/openide.nodes/apichanges.xml Tue Nov 25 18:06:33 2008 -0500 +++ b/openide.nodes/apichanges.xml Tue Nov 25 19:06:10 2008 -0500 @@ -46,6 +46,31 @@ made subject to such option by the copyr Nodes API + + + Adding ChildFactory.Detachable to allow ChildFactory implementations to + attach and detach listeners more easily + + + + + + + ChildFactory + is useful for creating node children lazily on a background thread, + and for simplifying working with Children.Keys. One oversight in + the original API was providing for notification that the ChildFactory + is no longer in use and should clean up any resources and detach + any listeners it can. +

+ ChildFactory.Detachable is an abstract class which adds + addNotify() and removeNotify() methods to ChildFactory. addNotify() + is called immediately before the first call to createKeys() after + creation or a call to removeNotify(). + + + +

Adding public int Children.getNodesCount(boolean optimalResult), protected Children.Keys(boolean lazy), diff -r 6b487c15857b openide.nodes/src/org/openide/nodes/AsynchChildren.java --- a/openide.nodes/src/org/openide/nodes/AsynchChildren.java Tue Nov 25 18:06:33 2008 -0500 +++ b/openide.nodes/src/org/openide/nodes/AsynchChildren.java Tue Nov 25 19:06:10 2008 -0500 @@ -41,7 +41,6 @@ package org.openide.nodes; package org.openide.nodes; import java.awt.EventQueue; -import java.lang.Thread; import java.util.Collections; import java.util.LinkedList; import java.util.List; @@ -52,6 +51,7 @@ import org.openide.util.RequestProcessor * implement AsynchChildren.Provider and pass that to the constructor. * * @author Tim Boudreau + * @param T the type of key object used to create the child nodes */ final class AsynchChildren extends Children.Keys implements ChildFactory.Observer, @@ -85,10 +85,16 @@ final class AsynchChildren extends C } protected @Override void removeNotify() { - cancelled = true; - task.cancel(); - initialized = false; - setKeys (Collections.emptyList()); + try { + cancelled = true; + task.cancel(); + initialized = false; + setKeys (Collections.emptyList()); + } finally { + if (notified) { + factory.removeNotify(); + } + } } /** @@ -128,7 +134,7 @@ final class AsynchChildren extends C @SuppressWarnings("unchecked") // Union2 undesirable since refresh could not use raw keys list protected Node[] createNodes(Object key) { - if (factory.isWaitNode(key)) { + if (ChildFactory.isWaitNode(key)) { return new Node[] { (Node) key }; } else { return factory.createNodesForKey ((T) key); @@ -136,6 +142,7 @@ final class AsynchChildren extends C } volatile boolean cancelled = false; + volatile boolean notified; public void run() { if (Thread.interrupted()) { setKeys (Collections.emptyList()); @@ -144,6 +151,10 @@ final class AsynchChildren extends C List keys = new LinkedList (); boolean done; do { + if (!notified) { + notified = true; + factory.addNotify(); + } done = factory.createKeys (keys); if (cancelled || Thread.interrupted()) { setKeys (Collections.emptyList()); diff -r 6b487c15857b openide.nodes/src/org/openide/nodes/ChildFactory.java --- a/openide.nodes/src/org/openide/nodes/ChildFactory.java Tue Nov 25 18:06:33 2008 -0500 +++ b/openide.nodes/src/org/openide/nodes/ChildFactory.java Tue Nov 25 19:06:10 2008 -0500 @@ -109,11 +109,11 @@ public abstract class ChildFactory { * * @param key An object from the list returned by * asynchCreateKeys() - * @return zero or more Nodes to represent this key + * @return null if no nodes, or zero or more Nodes to represent this key */ protected Node[] createNodesForKey(T key) { Node n = createNodeForKey(key); - return n == null ? new Node[0] : new Node[] { n }; + return n == null ? null : new Node[] { n }; } /** * Create a list of keys which can be individually passed to @@ -198,6 +198,14 @@ public abstract class ChildFactory { } this.observer = new WeakReference (observer); } + + void removeNotify() { + //do nothing + } + + void addNotify() { + //do nothing + } interface Observer { public void refresh(boolean immediate); @@ -217,4 +225,32 @@ public abstract class ChildFactory { super(orig); } } + + /** + * Subclass of ChildFactory with lifecycle methods which will be called + * on first use and last use. + * + * @param The key type for this child factory + */ + public static abstract class Detachable extends ChildFactory{ + /** + * Called immediately before the first call to createKeys(). Override + * to set up listening for changes, allocating expensive-to-create + * resources, etc. + */ + @Override + protected void addNotify() { + //do nothing + } + /** + * Called when this child factory is no longer in use, to dispose of + * resources, detach listeners, etc. Does nothing by default; override + * if you need notification when not in use anymore. + */ + @Override + protected void removeNotify() { + //do nothing + } + + } } diff -r 6b487c15857b openide.nodes/src/org/openide/nodes/SynchChildren.java --- a/openide.nodes/src/org/openide/nodes/SynchChildren.java Tue Nov 25 18:06:33 2008 -0500 +++ b/openide.nodes/src/org/openide/nodes/SynchChildren.java Tue Nov 25 19:06:10 2008 -0500 @@ -64,12 +64,14 @@ final class SynchChildren extends Chi volatile boolean active = false; protected @Override void addNotify() { active = true; + factory.addNotify(); refresh(true); } protected @Override void removeNotify() { active = false; setKeys(Collections.emptyList()); + factory.removeNotify(); } protected Node[] createNodes(T key) { diff -r 6b487c15857b openide.nodes/test/unit/src/org/openide/nodes/ChildFactoryTest.java --- a/openide.nodes/test/unit/src/org/openide/nodes/ChildFactoryTest.java Tue Nov 25 18:06:33 2008 -0500 +++ b/openide.nodes/test/unit/src/org/openide/nodes/ChildFactoryTest.java Tue Nov 25 19:06:10 2008 -0500 @@ -44,8 +44,7 @@ import java.beans.*; import java.beans.*; import java.util.*; import junit.framework.TestCase; -import org.openide.nodes.*; -import org.openide.nodes.NodeAdapter; +import org.openide.nodes.ChildFactory.Detachable; import org.openide.util.NbBundle; /** Test for AsynchChildren, ChildFactory and SynchChildren. @@ -202,7 +201,7 @@ public class ChildFactoryTest extends Te Thread.interrupted(); factory.wait = true; kids.addNotify(); - Thread.currentThread().yield(); + Thread.yield(); synchronized (factory.lock) { factory.lock.wait(500); } @@ -214,7 +213,46 @@ public class ChildFactoryTest extends Te assertTrue(kids.cancelled); assertTrue(factory.cancelled); } - + + public void testAddRemoveNotifySynch() throws Exception { + DetachableImpl r = new DetachableImpl(); + Children ch = Children.create(r, false); + new AbstractNode (ch); + ch.addNotify(); + r.assertAdded(); + ch.removeNotify(); + r.assertRemoved(); + r = new DetachableImpl(); + ch = Children.create(r, false); + Node[] n = ch.getNodes(true); + assertEquals (2, n.length); + assertEquals ("foo", n[0].getDisplayName()); + assertEquals ("bar", n[1].getDisplayName()); + ch.removeNotify(); + r.assertRemoved(); + } + + public void testAddRemoveNotifyAsynch() throws Exception { + DetachableImpl r = new DetachableImpl(); + Children ch = Children.create(r, true); + new AbstractNode (ch); + ch.addNotify(); + synchronized(r) { + r.wait(1000); + } + r.assertAdded(); + Node[] n = ch.getNodes(true); + assertEquals (2, n.length); + assertEquals ("foo", n[0].getDisplayName()); + assertEquals ("bar", n[1].getDisplayName()); + ch.removeNotify(); + synchronized(r) { + r.wait(1000); + } + r.assertRemoved(); + } + + static final class ProviderImpl extends ChildFactory { Object lock = new Object(); volatile boolean wait = false; @@ -421,4 +459,50 @@ public class ChildFactoryTest extends Te refresh(true); } } + + private static final class DetachableImpl extends Detachable { + boolean removed; + boolean added; + + @Override + protected boolean createKeys(List toPopulate) { + toPopulate.add("foo"); + toPopulate.add("bar"); + synchronized(this) { + notifyAll(); + } + return true; + } + + @Override + protected void removeNotify() { + assertFalse (removed); + synchronized(this) { + notifyAll(); + } + removed = true; + added = false; + } + + @Override + protected Node createNodeForKey(String key) { + AbstractNode nd = new AbstractNode(Children.LEAF); + nd.setDisplayName(key); + return nd; + } + + @Override + protected void addNotify() { + assertFalse (added); + added = true; + } + + void assertAdded() { + assertTrue (added); + } + + void assertRemoved() { + assertTrue (removed); + } + } }