Index: openide/arch/arch-openide-nodes.xml =================================================================== RCS file: /cvs/openide/arch/arch-openide-nodes.xml,v retrieving revision 1.18 diff -u -r1.18 arch-openide-nodes.xml --- openide/arch/arch-openide-nodes.xml 15 Aug 2006 15:21:26 -0000 1.18 +++ openide/arch/arch-openide-nodes.xml 21 Mar 2007 04:01:56 -0000 @@ -14,7 +14,7 @@ "Portions Copyrighted [year] [name of copyright owner]" The Original Software is NetBeans. The Initial Developer of the Original -Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun +Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun Microsystems, Inc. All Rights Reserved. --> --> -No +No. An API is provided for clients of this API which wish to create children +asynchronously, in which case the work is done on a background thread under +the control of this library. Index: openide/nodes/apichanges.xml =================================================================== RCS file: /cvs/openide/nodes/apichanges.xml,v retrieving revision 1.8 diff -u -r1.8 apichanges.xml --- openide/nodes/apichanges.xml 6 Nov 2006 08:23:48 -0000 1.8 +++ openide/nodes/apichanges.xml 21 Mar 2007 04:02:06 -0000 @@ -14,7 +14,7 @@ "Portions Copyrighted [year] [name of copyright owner]" The Original Software is NetBeans. The Initial Developer of the Original -Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun +Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun Microsystems, Inc. All Rights Reserved. --> @@ -23,6 +23,36 @@ Nodes API + + + API for Children objects that asynchronously compute keys/child + nodes and simplifies implementation of Children.Keys usages + + + + + + Added the class + ChildFactory + and the method + Children.create(ChildFactory factory, boolean useWaitNode) + to the API. This simplifies creation of Node children which need + to be computed on a background thread for performance reasons. + Anyone wishing to do this can simply extend ChildFactory and + pass that to Children.create() to automatically get a Node that will + display a Please Wait child node when first expanded. A ChildFactory + can either compute all child nodes, or batch them in multiple + passes. +

+ ChildFactory can also be used to implement synchronous children, + by setting the useWaitNode parameter passed to + Children.create() to true. This could replace most + common usages of Children.Keys, and make it easy to switch to + asynchronous child computation if that is determined to be + necessary for performance reasons. +

+
+
CookieSet can hold any objects and not just cookies Index: openide/nodes/nbproject/project.properties =================================================================== RCS file: /cvs/openide/nodes/nbproject/project.properties,v retrieving revision 1.10 diff -u -r1.10 project.properties --- openide/nodes/nbproject/project.properties 6 Nov 2006 08:23:48 -0000 1.10 +++ openide/nodes/nbproject/project.properties 21 Mar 2007 04:02:06 -0000 @@ -22,4 +22,4 @@ javadoc.arch=${basedir}/../arch/arch-openide-nodes.xml javadoc.apichanges=${basedir}/apichanges.xml -spec.version.base=7.0 +spec.version.base=7.1 Index: openide/nodes/src/org/openide/nodes/AsynchChildren.java =================================================================== RCS file: openide/nodes/src/org/openide/nodes/AsynchChildren.java diff -N openide/nodes/src/org/openide/nodes/AsynchChildren.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openide/nodes/src/org/openide/nodes/AsynchChildren.java 21 Mar 2007 04:02:06 -0000 @@ -0,0 +1,149 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (the License). You may not use this file except in + * compliance with the License. + * + * You can obtain a copy of the License at http://www.netbeans.org/cddl.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun + * Microsystems, Inc. All Rights Reserved. + */ +package org.openide.nodes; + +import java.awt.EventQueue; +import java.awt.Image; +import java.lang.Thread; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import org.openide.util.NbBundle; +import org.openide.util.RequestProcessor; +import org.openide.util.Utilities; + +/** + * Children object which creates its keys on a background thread. To use, + * implement AsynchChildren.Provider and pass that to the constructor. + * + * @author Tim Boudreau + */ +final class AsynchChildren extends Children.Keys implements + ChildFactory.Observer, + Runnable { + private final ChildFactory factory; + private final RequestProcessor.Task task; + private final RequestProcessor PROC = new RequestProcessor("Asynch " //NOI18N + + "children creator " + this, Thread.NORM_PRIORITY, true); //NOI18N + /** + * Create a new AsyncChildren instance with the passed provider object + * which will manufacture key objects and nodes. + * @param provider An object which can provide a list of keys and make + * Nodes for them + */ + AsynchChildren(ChildFactory factory) { + factory.setObserver (this); + this.factory = factory; + task = PROC.create(this, true); + } + + volatile boolean initialized = false; + protected void addNotify() { + if ((!initialized && task.isFinished()) || cancelled) { + cancelled = false; + setKeys (new Object[] { new WaitNode() }); + task.schedule(0); + } + } + + protected void removeNotify() { + cancelled = true; + task.cancel(); + initialized = false; + setKeys (Collections.emptyList()); + } + + /** + * Notify this AsynchChildren that it should reconstruct its children, + * calling provider.asynchCreateKeys() and setting the + * keys to that. Call this method if the list of child objects is known + * or likely to have changed. + * @param immediate If true, the keys are updated synchronously from the + * calling thread. Set this to true only if you know that updating + * the keys will not be an expensive or time-consuming operation. + */ + @SuppressWarnings("unchecked") //NOI18N + public void refresh(boolean immediate) { + immediate &= !EventQueue.isDispatchThread(); + if (immediate) { + boolean done; + List keys = new LinkedList (); + do { + done = factory.createKeys(keys); + } while (!done); + setKeys (keys); + } else { + task.schedule (0); + } + } + + public Node[] getNodes(boolean optimalResult) { + if (optimalResult) { + task.waitFinished(); + } + return super.getNodes(); + } + + @SuppressWarnings("unchecked") + protected Node[] createNodes(Object key) { + if (key instanceof WaitNode) { + return new Node[] { (Node) key }; + } else { + return factory.createNodesForKey (key); + } + } + + volatile boolean cancelled = false; + @SuppressWarnings("unchecked") + public void run() { + if (Thread.interrupted()) { + doSetKeys (Collections.EMPTY_LIST); + return; + } + List keys = new LinkedList (); + boolean done; + int ct = 0; + do { + done = factory.createKeys (keys); + if (cancelled || Thread.interrupted()) { + doSetKeys (Collections.emptyList()); + return; + } + doSetKeys (new ArrayList (keys)); + } while (!done); + initialized = done; + } + + private void doSetKeys (List keys) { + setKeys (keys); + } + + //We need a type that no Children class will use as a key + private static final class WaitNode extends AbstractNode { + public WaitNode () { + super (Children.LEAF); + setDisplayName (NbBundle.getMessage (WaitNode.class, "LBL_WAIT")); //NOI18N + } + + public Image getIcon (int type) { + return Utilities.loadImage ("org/openide/nodes/wait.gif"); //NOI18N + } + } +} Index: openide/nodes/src/org/openide/nodes/Bundle.properties =================================================================== RCS file: /cvs/openide/nodes/src/org/openide/nodes/Bundle.properties,v retrieving revision 1.3 diff -u -r1.3 Bundle.properties --- openide/nodes/src/org/openide/nodes/Bundle.properties 1 Jul 2006 09:08:57 -0000 1.3 +++ openide/nodes/src/org/openide/nodes/Bundle.properties 21 Mar 2007 04:02:06 -0000 @@ -71,3 +71,5 @@ # NodeMimeTypeCut=application/x-java-openide-nodecut;class=org.openide.nodes.NodeTransfer$Cut # NodeMimeTypeCopy=application/x-java-openide-nodecopy;class=org.openide.nodes.NodeTransfer$Copy # NodeMimeTypePaste=application/x-java-openide-nodepaste;class=org.openide.nodes.NodeTransfer$Paste +#AsynchChildren +LBL_WAIT=Please Wait... Index: openide/nodes/src/org/openide/nodes/ChildFactory.java =================================================================== RCS file: openide/nodes/src/org/openide/nodes/ChildFactory.java diff -N openide/nodes/src/org/openide/nodes/ChildFactory.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openide/nodes/src/org/openide/nodes/ChildFactory.java 21 Mar 2007 04:02:06 -0000 @@ -0,0 +1,120 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (the License). You may not use this file except in + * compliance with the License. + * + * You can obtain a copy of the License at http://www.netbeans.org/cddl.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun + * Microsystems, Inc. All Rights Reserved. + */ +package org.openide.nodes; + +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.util.List; + +/** + * Factory used to create Children + * objects. Children objects supply child Nodes for a + * Node. Usage is to write a class that extends ChildFactory and + * pass that to Children.create(). When the Node is expanded or its + * children are programmatically requested, the + * createKeys(List <T>) method + * will be invoked to create the List of objects to be modelled as Nodes. + * Later, on demand, each object from the List will be passed in turn to + * createNodes(T), + * which may return an array of zero or more Nodes for the object. + *

+ * A ChildFactory can be used either to create typical Children object, or + * one which will be initialized on a background thread (providing + * a "Please Wait" Node in the meantime). It can be used most simple cases + * that Children.Keys has been historically used for, and makes it easy to + * change a Children object to compute its keys asynchronously if that is + * needed for performance reasons. + *

+ * Only one ChildFactory object may be used per Children object; if you wish + * to have multiple Nodes modelling children produced by a single + * ChildFactory, use @link FilterNode to wrap the Node that owns the + * Children object for this ChildFactory. + * + * @param T The type of objects in the keys collection + * @author Tim Boudreau + * @see Children#create(ChildFactory, boolean) + */ +public abstract class ChildFactory { + /** + * Create Nodes for a given key object (one from the List + * returned by asynchCreateKeys()). + * @param key An object from the list returned by + * asynchCreateKeys() + * @return zero or more Nodes to represent this key + */ + protected abstract Node[] createNodesForKey (T key); + /** + * Create a list of keys which can be individually passed to + * createNodes() to create child Nodes. Implementations of + * this method should regularly check Thread.interrupted(), and + * if it returns true (meaning the parent Node was collapsed or + * destroyed), stop creating keys immediately and return + * true. This method is guaranteed not to be called on the + * AWT event thread if this ChildFactory was passed to + * Children.create() with the useWaitNode parameter + * set to true. If not, then no guarantees are made as to what + * the calling thread is. + * + * @param toPopulate A list to add key objects to + * @return true if the list of keys has been completely populated, + * false if the list has only been partially populated and + * this method should be called again to batch more keys + */ + protected abstract boolean createKeys (List toPopulate); + + /** + * Call this method when the list of objects being modelled by the + * has changed and the child Nodes of this Node should be updated. The + * boolean argument is a hint to the refresh mechanism (which + * will cause createKeys() to be invoked again) that it is safe to + * synchronously recreate. + * + * @param immediate If true, the refresh should occur in the calling + * thread (be careful not to be holding any locks that might + * deadlock with your key/child creation methods if you pass true). + * Note that this parameter is only meaningful when using an + * asynchronous children instance (i.e. true was passed as the + * second parameter to Children.create()). If the + * Children object for this ChildFactory is called with immediate + * true on the AWT event dispatch thread, and it is an asynchronous + * Children object, this parameter will be ignored and computation + * will be scheduled on a background thread. + */ + protected final void refresh (boolean immediate) { + Observer obs = observer == null ? null : observer.get(); + if (obs != null) { + obs.refresh(immediate); + } + } + + private Reference observer = null; + final void setObserver (Observer observer) { + if (this.observer != null) { + throw new IllegalStateException ("Attempting to create two Children" + //NOI18N + " objects for a single ChildFactory. Use " + //NOI18N + "FilterNode.Children over the existing Children object " + //NOI18N + "instead"); //NOI18N + } + this.observer = new WeakReference (observer); + } + + interface Observer { + public void refresh (boolean immediate); + } +} Index: openide/nodes/src/org/openide/nodes/Children.java =================================================================== RCS file: /cvs/openide/nodes/src/org/openide/nodes/Children.java,v retrieving revision 1.20 diff -u -r1.20 Children.java --- openide/nodes/src/org/openide/nodes/Children.java 14 Feb 2007 15:35:28 -0000 1.20 +++ openide/nodes/src/org/openide/nodes/Children.java 21 Mar 2007 04:02:08 -0000 @@ -39,14 +39,23 @@ import org.openide.util.Enumerations; import org.openide.util.Mutex; -/** Container for array of nodes. -* Can be {@link Node#Node associated} with a node and then -* all children in the array have that node set as a parent, and this list -* will be returned as the node's children. +/** +* Factory for the child Nodes of a Node. Every Node has a Children object. +* Children are initially un-initialized, and child Nodes are created on +* demand when, for example, the Node is expanded in an Explorer view. +* If you know your Node has no child nodes, pass Children.LEAF. +* Typically a Children object will create a Collection of objects from +* some data model, and create one or more Nodes for each object on demand. +* +* If initializing the list of children of a Node is time-consuming (i.e. it +* does I/O, parses a file or some other expensive operation), implement +* ChildFactory and pass it to Children.create (theFactory, true) to have +* the child nodes be computed asynchronously on a background thread. * -*

Probably you want to subclass {@link Children.Keys}. +*

In almost all cases you want to subclass ChildFactory and pass it to +* Children.create(), or subclass {@link Children.Keys}. * Subclassing Children directly is not recommended. -* +* * @author Jaroslav Tulach */ public abstract class Children extends Object { @@ -219,6 +228,35 @@ PR.exitReadAccess(); } } + + /** + * Create a Children object using the passed ChildFactory + * object. The ChildFactory will be asked to create a list + * of model objects that are the children; then for each object in the list, + * ChildFactory.createNodesFor() will be called to instantiate + * one or more Nodes for that object. + * @param factory A ChildFactory which will provide child objects + * @param useWaitNode If true, the ChildFactory will always be called to + * create the list of keys on + * a background thread, displaying a "Please Wait" child Node until + * some or all child Nodes have been computed. If so, + * when it is expanded, the Node that owns + * the returned Children object will display a "please wait" + * node while the children are computed in the background. Pass true + * for any case where computing child nodes is expensive and should + * not be done in the event thread. + * @return A Children object which + * will invoke the ChildFactory instance as needed to supply model + * objects and child nodes for it + * @throws IllegalStateException if the passed ChildFactory has already + * been used in a previous call to Children.create(). + * @since 7.1 + */ + public static Children create (ChildFactory factory, boolean useWaitNode) { + if (factory == null) throw new NullPointerException ("Null factory"); + return useWaitNode ? new AsynchChildren (factory) : + new SynchChildren (factory); + } /** Get the parent node of these children. * @return the node attached to this children object, or null if there is none yet @@ -1913,6 +1951,12 @@ * given key changes (but the key stays the same), you can * call {@link #refreshKey}. Usually this is not necessary. * + * Note that for simple cases, it may be preferable to subclass + * ChildFactory and pass the result to + * + * create(); doing so makes it easy to switch to using child + * nodes computed on a background thread if necessary for performance + * reasons. * @param T the type of the key */ public static abstract class Keys extends Children.Array { Index: openide/nodes/src/org/openide/nodes/SynchChildren.java =================================================================== RCS file: openide/nodes/src/org/openide/nodes/SynchChildren.java diff -N openide/nodes/src/org/openide/nodes/SynchChildren.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openide/nodes/src/org/openide/nodes/SynchChildren.java 21 Mar 2007 04:02:08 -0000 @@ -0,0 +1,63 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (the License). You may not use this file except in + * compliance with the License. + * + * You can obtain a copy of the License at http://www.netbeans.org/cddl.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun + * Microsystems, Inc. All Rights Reserved. + */ +package org.openide.nodes; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * Synchronous children implementation that takes a ChildFactory. + * + * @author Tim Boudreau + */ +final class SynchChildren extends Children.Keys implements ChildFactory.Observer { + private final ChildFactory factory; + + /** Creates a new instance of SynchChildren */ + SynchChildren(ChildFactory factory) { + this.factory = factory; + factory.setObserver(this); + } + + volatile boolean active = false; + protected void addNotify() { + active = true; + refresh (true); + } + + protected void removeNotify() { + active = false; + setKeys (Collections.emptyList()); + } + + @SuppressWarnings("unchecked") + protected Node[] createNodes(Object key) { + return factory.createNodesForKey(key); + } + + @SuppressWarnings("unchecked") + public void refresh(boolean immediate) { + if (active) { + List toPopulate = new LinkedList(); + while (!factory.createKeys(toPopulate)) {} + setKeys (toPopulate); + } + } +} Index: openide/nodes/src/org/openide/nodes/wait.gif =================================================================== RCS file: openide/nodes/src/org/openide/nodes/wait.gif diff -N openide/nodes/src/org/openide/nodes/wait.gif Binary files /dev/null and wait.gif differ Index: openide/nodes/test/unit/src/org/openide/nodes/ChildFactoryTest.java =================================================================== RCS file: openide/nodes/test/unit/src/org/openide/nodes/ChildFactoryTest.java diff -N openide/nodes/test/unit/src/org/openide/nodes/ChildFactoryTest.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openide/nodes/test/unit/src/org/openide/nodes/ChildFactoryTest.java 21 Mar 2007 04:02:09 -0000 @@ -0,0 +1,372 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (the License). You may not use this file except in + * compliance with the License. + * + * You can obtain a copy of the License at http://www.netbeans.org/cddl.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun + * Microsystems, Inc. All Rights Reserved. + */ +package org.openide.nodes; + +import java.awt.EventQueue; +import java.beans.*; +import java.util.*; +import junit.framework.TestCase; +import org.openide.nodes.*; +import org.openide.nodes.NodeAdapter; +import org.openide.util.NbBundle; + +/** Test for AsynchChildren + * + * @author Tim Boudreau + */ +public class ChildFactoryTest extends TestCase { + + public ChildFactoryTest(String name) { + super(name); + } + + private ProviderImpl factory; + private BatchProviderImpl factory2; + private AsynchChildren kids2; + private Node node2; + private AsynchChildren kids; + private Node node; + public void setUp() throws Exception { + factory = new ProviderImpl(); + kids = new AsynchChildren (factory); + node = new AbstractNode (kids); + + factory2 = new BatchProviderImpl(); + kids2 = new AsynchChildren (factory2); + node2 = new AbstractNode (kids2); + } + + public void testChildrenCreate() { + System.out.println("testChildrenCreate"); + ChildFactory f = new ProviderImpl(); + Children kids = Children.create (f, true); + assertTrue (kids instanceof AsynchChildren); + + ChildFactory ff = new ProviderImpl(); + Children kids2 = Children.create (ff, false); + assertFalse (kids2 instanceof AsynchChildren); + assertTrue (kids2 instanceof SynchChildren); + + RuntimeException e = null; + Children kids3 = null; + try { + kids3 = Children.create (ff, true); + } catch (RuntimeException ex) { + e = ex; + } + assertNull (kids3); + assertNotNull ("Exception should have been thrown creating two " + + "Children objects over the same ChildFactory", e); + } + + //A word of caution re adding tests: + //Almost anything (getNodes(), justCreateNodes(), etc. can trigger a + //fresh call to Children.addNotify(). Any test that expects a synchronous + //change in the child nodes as a result of having triggered a call + //to setKeys() is probably testing a race condition, not the behavior + //of the children implementation + + public void testGetNodesWaits() throws Exception { + System.out.println("testGetNodesWaits"); + factory.wait = false; + kids.getNodes (false); + synchronized (factory.lock) { + factory.lock.wait (300); + } + Thread.currentThread().yield(); + new NL (node); + Node[] n = kids.getNodes(true); + assertEquals (4, n.length); + } + + public void testInitialNodeIsWaitNode() throws Exception { + System.out.println("testInitialNodeIsWaitNode"); + factory.wait = true; + kids.addNotify(); + Node[] n = kids.getNodes(false); + factory.wait = false; + assertEquals (1, n.length); + assertEquals (NbBundle.getMessage(AsynchChildren.class, "LBL_WAIT"), + n[0].getDisplayName()); + factory.wait = false; + synchronized (factory) { + factory.wait (2000); + } + for (int i = 0; i < 5 && n.length != 4; i++) { + n = kids.getNodes(true); + java.lang.Thread.currentThread().yield(); + } + assertEquals (4, n.length); + } + + public void testBatch() throws Exception { + System.out.println("testBatch"); + kids2.addNotify(); + Thread.currentThread().yield(); + synchronized (factory2.lock) { + factory2.lock.notifyAll(); + } + new NL (node2); + Node[] n = n = kids2.getNodes(true); + assertEquals (4, n.length); + assertEquals (2, factory2.callCount); + } + + public void testSynchChildren() throws Exception { + System.out.println("testSynchChildren"); + final SynchProviderImpl factory = new SynchProviderImpl(); + final Children ch = Children.create (factory, false); + assertTrue (ch instanceof SynchChildren); + factory.assertCreateKeysNotCalled(); + factory.assertCreateNodesForKeyNotCalled(); + final Node nd = new AbstractNode (ch); + NodeAdapter adap = new NodeAdapter() {}; + nd.addNodeListener (adap); + + EventQueue.invokeAndWait (new Runnable() { + public void run() { + ch.getNodes(true); + } + }); + ((SynchChildren) ch).active = true; + synchronized (factory) { + factory.wait (1000); + } + factory.assertCreateKeysCalled(); + factory.assertCreateNodesForKeyCalled(); + Node[] nodes = nd.getChildren().getNodes(true); + assertEquals (factory.CONTENTS1.size(), nodes.length); + int ix = 0; + for (String s : factory.CONTENTS1) { + assertEquals (s, nodes[ix].getName()); + ix++; + } + factory.switchChildren(); + nodes = nd.getChildren().getNodes(true); + assertEquals (factory.CONTENTS2.size(), nodes.length); + ix = 0; + for (String s : factory.CONTENTS2) { + assertEquals (s, nodes[ix].getName()); + ix++; + } + } + + public void testCancel() throws Exception { + System.out.println("testCancel"); + Thread.interrupted(); + factory.wait = true; + kids.addNotify(); + Thread.currentThread().yield(); + synchronized (factory.lock) { + factory.lock.wait (500); + } + kids.removeNotify(); + factory.wait = false; + synchronized (factory) { + factory.wait (2000); + } + assertTrue (kids.cancelled); + assertTrue (factory.cancelled); + } + + static final class ProviderImpl extends ChildFactory { + Object lock = new Object(); + volatile boolean wait = false; + + public Node[] createNodesForKey(String key) { + AbstractNode nd = new AbstractNode (Children.LEAF); + nd.setDisplayName (key); + return new Node[] { nd }; + } + + boolean cancelled = false; + public boolean createKeys(List result) { + try { + while (wait) { + Thread.currentThread().yield(); + } + synchronized (lock) { + lock.notifyAll(); + } + if (Thread.interrupted()) { + cancelled = true; + return true; + } + result.add ("A"); + result.add ("B"); + result.add ("C"); + result.add ("D"); + if (Thread.interrupted()) { + cancelled = true; + } + return true; + } finally { + synchronized (this) { + notifyAll(); + } + } + } + } + + static final class BatchProviderImpl extends ChildFactory { + boolean firstCycle = true; + + public Node[] createNodesForKey(String key) { + AbstractNode nd = new AbstractNode (Children.LEAF); + nd.setDisplayName (key); + return new Node[] { nd }; + } + + Object lock = new Object(); + int callCount = 0; + public boolean createKeys(List result) { + callCount++; + synchronized (lock) { + try { + lock.wait(500); + } catch (InterruptedException ex) { + //re-interrupt + Thread.currentThread().interrupt(); + } + } + if (Thread.interrupted()) { + return true; + } + boolean wasFirstCycle = firstCycle; + if (wasFirstCycle) { + result.add ("A"); + result.add ("B"); + firstCycle = false; + return false; + } else { + result.add ("C"); + result.add ("D"); + } + if (Thread.interrupted()) { + return true; + } + synchronized (this) { + notifyAll(); + } + return true; + } + } + + private static final class NL implements NodeListener { + NL (Node n) { + n.addNodeListener (this); + try { + waitFor(); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } catch (Exception e) { + throw new Error (e); + } + } + + NL () { + + } + + public void childrenAdded(NodeMemberEvent ev) { + go(); + } + + public void childrenRemoved(NodeMemberEvent ev) { + go(); + } + + public void childrenReordered(NodeReorderEvent ev) { + go(); + } + + public void nodeDestroyed(NodeEvent ev) { + } + + public void propertyChange(PropertyChangeEvent arg0) { + } + + private void go() { + synchronized (this) { + notifyAll(); + } + } + + void waitFor() throws Exception { + System.err.println("Enter waitfor"); + synchronized (this) { + wait (1000); + } + } + } + + private static final class SynchProviderImpl extends ChildFactory { + static List CONTENTS1 = Arrays.asList (new String[] { + "One", "Two", "Three", "Four" + }); + static List CONTENTS2 = Arrays.asList (new String[] { + "Five", "Six", "Seven", "Eight", "Nine" + }); + + boolean createNodesForKeyCalled = false; + public Node[] createNodesForKey(String key) { + createNodesForKeyCalled = true; + Node result = new AbstractNode(Children.LEAF); + result.setDisplayName (key); + result.setName (key); + return new Node[] { result }; + } + + boolean createKeysCalled = false; + public boolean createKeys(List toPopulate) { + createKeysCalled = true; + List l = switched ? CONTENTS2 : CONTENTS1; + toPopulate.addAll (l); + return true; + } + + void assertCreateNodesForKeyNotCalled() { + assertFalse (createNodesForKeyCalled); + } + + void assertCreateKeysNotCalled() { + assertFalse (createKeysCalled); + } + + boolean assertCreateNodesForKeyCalled() { + boolean result = createNodesForKeyCalled; + createNodesForKeyCalled = false; + assertTrue (result); + return result; + } + + boolean assertCreateKeysCalled() { + boolean result = createKeysCalled; + createKeysCalled = false; + assertTrue (result); + return result; + } + + volatile boolean switched = false; + void switchChildren() { + switched = !switched; + refresh(true); + } + } +}