diff -r a099953455bb openide.nodes/apichanges.xml --- a/openide.nodes/apichanges.xml Mon Nov 17 20:38:18 2008 +0300 +++ b/openide.nodes/apichanges.xml Mon Nov 17 17:40:04 2008 +0000 @@ -46,6 +46,30 @@ Nodes API + + + Adding public int Children.getNodesCount(boolean optimalResult), protected Children.Keys(boolean lazy), + Children.snapshot(), NodeMemberEvent.getSnapshot(), NodeReorderEvent.getSnapshot() + + + + + + 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 a099953455bb openide.nodes/src/org/openide/nodes/AsynchChildren.java --- a/openide.nodes/src/org/openide/nodes/AsynchChildren.java Mon Nov 17 20:38:18 2008 +0300 +++ b/openide.nodes/src/org/openide/nodes/AsynchChildren.java Mon Nov 17 17:40:04 2008 +0000 @@ -85,10 +85,17 @@ } protected @Override void removeNotify() { - cancelled = true; - task.cancel(); - initialized = false; - setKeys (Collections.emptyList()); + try { + cancelled = true; + task.cancel(); + initialized = false; + setKeys (Collections.emptyList()); + factory.removeNotify(); + } finally { + if (notified) { + factory.removeNotify(); + } + } } /** @@ -136,6 +143,7 @@ } volatile boolean cancelled = false; + volatile boolean notified; public void run() { if (Thread.interrupted()) { setKeys (Collections.emptyList()); @@ -144,6 +152,10 @@ 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 a099953455bb openide.nodes/src/org/openide/nodes/ChildFactory.java --- a/openide.nodes/src/org/openide/nodes/ChildFactory.java Mon Nov 17 20:38:18 2008 +0300 +++ b/openide.nodes/src/org/openide/nodes/ChildFactory.java Mon Nov 17 17:40:04 2008 +0000 @@ -198,6 +198,14 @@ } this.observer = new WeakReference (observer); } + + void removeNotify() { + //do nothing + } + + void addNotify() { + //do nothing + } interface Observer { public void refresh(boolean immediate); @@ -217,4 +225,32 @@ 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 a099953455bb openide.nodes/src/org/openide/nodes/SynchChildren.java --- a/openide.nodes/src/org/openide/nodes/SynchChildren.java Mon Nov 17 20:38:18 2008 +0300 +++ b/openide.nodes/src/org/openide/nodes/SynchChildren.java Mon Nov 17 17:40:04 2008 +0000 @@ -64,12 +64,14 @@ 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 a099953455bb openide.nodes/test/unit/src/org/openide/nodes/ChildFactoryTest.java --- a/openide.nodes/test/unit/src/org/openide/nodes/ChildFactoryTest.java Mon Nov 17 20:38:18 2008 +0300 +++ b/openide.nodes/test/unit/src/org/openide/nodes/ChildFactoryTest.java Mon Nov 17 17:40:04 2008 +0000 @@ -44,8 +44,7 @@ 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. @@ -214,7 +213,42 @@ 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(); + 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 +455,47 @@ 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() { + synchronized(this) { + notifyAll(); + } + removed = true; + } + + @Override + protected Node createNodeForKey(String key) { + AbstractNode nd = new AbstractNode(Children.LEAF); + nd.setDisplayName(key); + return nd; + } + + @Override + protected void addNotify() { + added = true; + } + + void assertAdded() { + assertTrue (added); + } + + void assertRemoved() { + assertTrue (removed); + } + } }