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 24 Dec 2006 08:52:36 -0000 @@ -0,0 +1,162 @@ +/* + * 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.awt.Image; +import java.lang.Thread; +import java.util.Collections; +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 + */ +public final class AsynchChildren extends Children.Keys { + private final Provider provider; + private final RequestProcessor.Task task; + private static final RequestProcessor PROC = new RequestProcessor("Asynch " //NOI18N + + "children creator", Thread.NORM_PRIORITY, true); //NOI18N + private final R r = new R(); + /** + * 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 + */ + public AsynchChildren(Provider provider) { + this.provider = provider; + task = PROC.create(r, true); + } + + protected void addNotify() { + if (task.isFinished() || r.cancelled) { + r.cancelled = false; + setKeys (new Object[] { new WaitNode() }); + task.schedule(0); + } + } + + @SuppressWarnings("unchecked") + protected void removeNotify() { + r.cancelled = true; + task.cancel(); + setKeys (Collections.EMPTY_LIST); + } + + /** + * 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 update(boolean immediate) { + if (immediate) { + setKeys (provider.asynchCreateKeys()); + } 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 provider.createNodes (key); + } + } + + /** + * Factory for keys and child nodes for an instance of AsynchChildren. + */ + public static interface Provider { + /** + * 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 + */ + public Node[] createNodes (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 + * null. This method is guaranteed not to be called on the + * AWT event thread. + * + * @return A List of objects to use as keys, or null + * if cancelled + */ + public List asynchCreateKeys(); + } + + private final class R implements Runnable { + volatile boolean cancelled = false; + + @SuppressWarnings("unchecked") + public void run() { + if (Thread.interrupted()) { + doSetKeys (Collections.EMPTY_LIST); + return; + } + List keys = provider.asynchCreateKeys(); + if (keys == null || Thread.interrupted()) { + keys = Collections.EMPTY_LIST; + } + doSetKeys (keys); + } + + private void doSetKeys (List keys) { + setKeys (keys); + justComputeNodes(); + } + + } + + //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 24 Dec 2006 08:52:36 -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 + +LBL_WAIT=Please Wait... 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/AsynchChildrenTest.java =================================================================== RCS file: openide/nodes/test/unit/src/org/openide/nodes/AsynchChildrenTest.java diff -N openide/nodes/test/unit/src/org/openide/nodes/AsynchChildrenTest.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openide/nodes/test/unit/src/org/openide/nodes/AsynchChildrenTest.java 24 Dec 2006 08:52:36 -0000 @@ -0,0 +1,135 @@ +/* + * 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.awt.EventQueue; +import java.beans.*; +import java.util.*; +import junit.framework.TestCase; +import org.openide.nodes.*; +import org.openide.util.NbBundle; + +/** Test for AsynchChildren + * + * @author Tim Boudreau + */ +public class AsynchChildrenTest extends TestCase { + + public AsynchChildrenTest(String name) { + super(name); + } + + private ProviderImpl provider; + private AsynchChildren kids; + private Node n; + public void setUp() throws Exception { + provider = new ProviderImpl(); + kids = new AsynchChildren (provider); + n = new AbstractNode (kids); + } + + public void testGetNodesWaits() throws Exception { + System.out.println("testGetNodesWaits"); + + kids.addNotify(); + Node[] n = new Node[0]; + for (int i = 0; i < 5 && n.length != 4; i++) { + n = kids.getNodes(true); + java.lang.Thread.currentThread().sleep(400); + } + assertEquals (4, n.length); + } + + public void testInitialNodeIsWaitNode() throws Exception { + System.out.println("testInitialNodeIsWaitNode"); + Runnable r = new Runnable() { + public void run() { + provider.wait = true; + kids.addNotify(); + Node[] n = kids.getNodes (false); + assertEquals (1, n.length); + assertEquals (NbBundle.getMessage(AsynchChildren.class, "LBL_WAIT"), + n[0].getDisplayName()); + provider.wait = false; + for (int i = 0; i < 5 && n.length != 4; i++) { + try { + n = kids.getNodes(true); + java.lang.Thread.currentThread().sleep(200); + } catch (InterruptedException ex) { + } + } + assertTrue (n.length == 4); + } + }; + EventQueue.invokeAndWait (r); + } + + public void testCancel() throws Exception { + System.out.println("testCancel"); + Runnable r = new Runnable() { + public void run() { + provider.wait = true; + kids.addNotify(); + kids.removeNotify(); + provider.wait = false; + Node[] n = new Node[0]; + for (int i = 0; i < 5 && n.length != 0; i++) { + try { + n = kids.getNodes(true); + java.lang.Thread.currentThread().sleep(200); + } catch (InterruptedException ex) { + } + } + assertEquals (0, n.length); + } + }; + EventQueue.invokeAndWait (r); + } + + static final class ProviderImpl implements AsynchChildren.Provider { + + boolean wait = false; + + public Node[] createNodes(String key) { + AbstractNode nd = new AbstractNode (Children.LEAF); + nd.setDisplayName (key); + return new Node[] { nd }; + } + + public List asynchCreateKeys() { + while (wait) { + try { + Thread.currentThread().sleep (200); + } catch (Exception e) { + + } + } + if (Thread.interrupted()) { + return null; + } + List result = new ArrayList (); + result.add ("A"); + result.add ("B"); + result.add ("C"); + result.add ("D"); + return result; + } + } +}