diff -r 4bda945ac0d2 openide.nodes/apichanges.xml --- a/openide.nodes/apichanges.xml Wed Feb 10 19:19:24 2016 +0300 +++ b/openide.nodes/apichanges.xml Thu Feb 11 12:18:42 2016 +0100 @@ -49,6 +49,30 @@ 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 nodes created + by the ChildFactory are no longer in use and should clean up any + resources. +

+ ChildFactory.DestroyableNodes is an abstract class which adds + destroyNodes methods to ChildFactory.Detachable. +

+
+ + +
Adding @BeanInfoSearchPath annotation diff -r 4bda945ac0d2 openide.nodes/manifest.mf --- a/openide.nodes/manifest.mf Wed Feb 10 19:19:24 2016 +0300 +++ b/openide.nodes/manifest.mf Thu Feb 11 12:18:42 2016 +0100 @@ -2,5 +2,5 @@ OpenIDE-Module: org.openide.nodes OpenIDE-Module-Localizing-Bundle: org/openide/nodes/Bundle.properties AutoUpdate-Essential-Module: true -OpenIDE-Module-Specification-Version: 7.43 +OpenIDE-Module-Specification-Version: 7.44 diff -r 4bda945ac0d2 openide.nodes/src/org/openide/nodes/AsynchChildren.java --- a/openide.nodes/src/org/openide/nodes/AsynchChildren.java Wed Feb 10 19:19:24 2016 +0300 +++ b/openide.nodes/src/org/openide/nodes/AsynchChildren.java Thu Feb 11 12:18:42 2016 +0100 @@ -161,6 +161,12 @@ } } + @Override + protected void destroyNodes(Node[] arr) { + super.destroyNodes(arr); + factory.destroyNodes(arr); + } + volatile boolean cancelled = false; volatile boolean notified; private final Object notifyLock = new Object(); diff -r 4bda945ac0d2 openide.nodes/src/org/openide/nodes/ChildFactory.java --- a/openide.nodes/src/org/openide/nodes/ChildFactory.java Wed Feb 10 19:19:24 2016 +0300 +++ b/openide.nodes/src/org/openide/nodes/ChildFactory.java Thu Feb 11 12:18:42 2016 +0100 @@ -220,6 +220,10 @@ //do nothing } + void destroyNodes(Node[] arr) { + //do nothing + } + interface Observer { public void refresh(boolean immediate); } @@ -270,4 +274,26 @@ } } + + /** + * Subclass of {@link Detachable} with lifecycle method invoked when + * nodes created by the factory are no longer needed. + * + * @param The key type for this child factory + * @since org.openide.nodes 7.44 + */ + public static abstract class DestroyableNodes extends Detachable{ + + /** + * Called when nodes created previously by this factory are no longer + * present in the node hierarchy. + * + * @param arr nodes which are no longer needed + */ + @Override + protected void destroyNodes(Node[] arr) { + //do nothing + } + + } } diff -r 4bda945ac0d2 openide.nodes/src/org/openide/nodes/SynchChildren.java --- a/openide.nodes/src/org/openide/nodes/SynchChildren.java Wed Feb 10 19:19:24 2016 +0300 +++ b/openide.nodes/src/org/openide/nodes/SynchChildren.java Thu Feb 11 12:18:42 2016 +0100 @@ -79,6 +79,13 @@ protected Node[] createNodes(T key) { return factory.createNodesForKey(key); } + + @Override + protected void destroyNodes(Node[] arr) { + super.destroyNodes(arr); + factory.destroyNodes(arr); + } + public void refresh(boolean immediate) { if (active) { diff -r 4bda945ac0d2 openide.nodes/test/unit/src/org/openide/nodes/ChildFactoryTest.java --- a/openide.nodes/test/unit/src/org/openide/nodes/ChildFactoryTest.java Wed Feb 10 19:19:24 2016 +0300 +++ b/openide.nodes/test/unit/src/org/openide/nodes/ChildFactoryTest.java Thu Feb 11 12:18:42 2016 +0100 @@ -278,6 +278,47 @@ ch = Children.create(b, true); assertEquals(4, ch.getNodesCount(true)); } + + public void testDestroyNodesSynch() throws Exception { + DestroyableImpl r = new DestroyableImpl(); + Children ch = Children.create(r, false); + new AbstractNode (ch); + Node[] n = ch.getNodes(true); + assertEquals (2, n.length); + assertEquals ("foo", n[0].getDisplayName()); + assertEquals ("bar", n[1].getDisplayName()); + r.refresh(true); + n = ch.getNodes(true); + assertEquals (0, n.length); + Set destroyed = r.getDestroyed(); + Set expected = new HashSet(); + Collections.addAll(expected, "foo", "bar"); + for (Node node : destroyed) { + assertTrue(node.getDisplayName(), expected.contains(node.getDisplayName())); + } + } + + public void testDestroyNodesAsynch() throws Exception { + DestroyableImpl r = new DestroyableImpl(); + Children ch = Children.create(r, true); + new AbstractNode (ch); + Node[] n = ch.getNodes(true); + assertEquals (2, n.length); + assertEquals ("foo", n[0].getDisplayName()); + assertEquals ("bar", n[1].getDisplayName()); + r.refresh(false); + synchronized(r) { + r.wait(1000); + } + n = ch.getNodes(true); + assertEquals (0, n.length); + Set destroyed = r.getDestroyed(); + Set expected = new HashSet(); + Collections.addAll(expected, r.createWaitNode().getDisplayName(), "foo", "bar"); + for (Node node : destroyed) { + assertTrue(node.getDisplayName(), expected.contains(node.getDisplayName())); + } + } public void testIncrementalDisplay() throws Exception { // #206556 final Semaphore s1 = new Semaphore(0); @@ -674,4 +715,46 @@ assertTrue (removed); } } + + private static final class DestroyableImpl extends ChildFactory.DestroyableNodes { + + private boolean empty; + + private final Set destroyed = Collections.synchronizedSet(new HashSet()); + + @Override + protected boolean createKeys(List toPopulate) { + if (empty) { + return true; + } + + toPopulate.add("foo"); + toPopulate.add("bar"); + synchronized (this) { + notifyAll(); + } + empty = true; + return true; + } + + @Override + protected Node createNodeForKey(String key) { + AbstractNode nd = new AbstractNode(Children.LEAF); + nd.setDisplayName(key); + return nd; + } + + @Override + protected void destroyNodes(Node[] arr) { + synchronized (destroyed) { + Collections.addAll(destroyed, arr); + } + } + + public Set getDestroyed() { + synchronized (destroyed) { + return new HashSet(destroyed); + } + } + } }