diff --git a/openide.nodes/apichanges.xml b/openide.nodes/apichanges.xml --- a/openide.nodes/apichanges.xml +++ b/openide.nodes/apichanges.xml @@ -46,6 +46,39 @@ Nodes API + + + Adding SimpleNode, an AbstractNode subclass which handles + a number of common cases and can eliminate some AbstractNode subclasses + + + + + + + SimpleNode is + an AbstractNode subclass which simplifies some common usage patterns + of Nodes: +
    +
  • Children object not necessary - simply override + createKeys() and createNodeForKey() to handle child nodes
  • +
  • No lookup, or fixed lookup contents may be passed into constructor, + or createLookup() can be overridden (but not both)
  • +
  • Icon can be passed as constructor parameter, without a subclass being + required
  • +
  • Display name can be annotated with HTML without subclassing, either + using setValue (SimpleNode.ERROR, Boolean.TRUE) or, e.g., + setValue(SimpleNode.HTML_PREFIX, "<b>")
  • +
  • Children objects do not need to be dealt with directly - simply + override createKeys() and createNodeForKey()
  • +
  • Actions can be handled more simply, and the default Properties action + is suppressed
  • +
  • Fixed Lookup contents can be passed in directly
  • +
+
+ + +
Support for declarative Node registrations diff --git a/openide.nodes/nbproject/project.properties b/openide.nodes/nbproject/project.properties --- a/openide.nodes/nbproject/project.properties +++ b/openide.nodes/nbproject/project.properties @@ -44,4 +44,4 @@ javadoc.arch=${basedir}/arch.xml javadoc.apichanges=${basedir}/apichanges.xml -spec.version.base=7.11.0 +spec.version.base=7.12.0 diff --git a/openide.nodes/src/org/openide/nodes/AbstractNode.java b/openide.nodes/src/org/openide/nodes/AbstractNode.java --- a/openide.nodes/src/org/openide/nodes/AbstractNode.java +++ b/openide.nodes/src/org/openide/nodes/AbstractNode.java @@ -571,6 +571,10 @@ */ private boolean overridesAMethod(String name, Class[] arguments) { // we are subclass of AbstractNode + if (getClass() == SimpleNode.class) { + return false; + } + try { java.lang.reflect.Method m = getClass().getMethod(name, arguments); diff --git a/openide.nodes/src/org/openide/nodes/SimpleNode.java b/openide.nodes/src/org/openide/nodes/SimpleNode.java new file mode 100644 --- /dev/null +++ b/openide.nodes/src/org/openide/nodes/SimpleNode.java @@ -0,0 +1,603 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, 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-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2009 Sun Microsystems, Inc. + */ + +package org.openide.nodes; + +import java.awt.Graphics; +import java.awt.Image; +import java.awt.image.ImageObserver; +import java.awt.image.ImageProducer; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; +import javax.swing.Action; +import org.openide.util.Exceptions; +import org.openide.util.ImageUtilities; +import org.openide.util.Lookup; +import org.openide.util.lookup.Lookups; +import org.openide.util.lookup.ProxyLookup; + +/** + * Node subclass for simple use-cases where the icon and display name are + * known ahead of time, lookup contents may be known ahead of time, which + * takes care of computing its child nodes without exposing a Children + * object. + *

+ * This class basically simplifies the following common cases for using + * AbstractNode + Children.Keys: + *

    + *
  • Icon can be passed as constructor parameter, without a subclass being + * required
  • + *
  • Display name can be annotated with HTML without subclassing, either + * using setValue (SimpleNode.ERROR, Boolean.TRUE) or, e.g., + * setValue(SimpleNode.HTML_PREFIX, "<b>")
  • + *
  • Children objects do not need to be dealt with directly - simply + * override createKeys(List) and createNodeForKey(KeyType)
  • + *
  • Actions can be handled more simply, and the default Properties action + * is suppressed
  • + *
  • Fixed Lookup contents can be passed in directly using one of the + * static factory methods
  • + *
  • No lookup, or fixed lookup contents may be passed into constructor, + * or createLookup() can be overridden (but not both). The Lookup does not + * need to exist at the time the node is constructed, eliminating some + * awkward code constructs that would otherwise be needed. + *
  • + *
+ * Note: Do not call getCookieSet() to alter the contents of the + * lookup of a SimpleNode - it is not used. Instead, pass an appropriate + * lookup as a constructor parameter. + *

+ *

Managing Child Nodes

+ * If your node should have a set of child nodes that can be shown when your + * node is expanded, subclass SimpleNode and override createKeys(List) + * and createNodeForKey(KeyType). + *

+ * How this works: In createKeys(List), you create (or fetch + * somehow) model objects each of which represents one child node, and add them + * to the passed list. Later, when the nodes really need to be shown, + * createNodeForKey(KeyType) is called once for every object you + * added to the list in createKeys(List). + *

+ * If you need to update the set of child nodes because something has changed, + * simply call refreshChildren() - this will trigger another call + * to createKeys(List) and so forth. + *

+ * If the set of child nodes can change due to external code, and you can + * listen for those changes, override onStartListeningForChildChanges() + * to attach your listeners and onStopListeningForChildChanges() + *

+ * If you override createKeys(List), you must also override + * createNodeForKey(KeyType). + * + * + * @since 7.12 + * @author Tim Boudreau + * @param KeyType the type of object used for the key objects that + * represent child nodes. If your node has no children, use + * SimpleNode<Void> + */ +public class SimpleNode extends AbstractNode { + private final Image icon; + /** + * Key which can be used in a call to setValue(ERROR, Boolean), to + * specify that this node should receive error badging in its display + * name and/or icon + */ + public static final String ERROR = "_error"; //NOI18N + + /** + * Key which can be used in a call to setValue(HTML_PREFIX, String), to + * specify an HTML prefix for the display name of this node, to affect + * its appearance somehow + */ + public static final String HTML_PREFIX = "_prefix"; //NOI18N + /** + * Flag which is true if setValue() has ever been called - an optimization + * for handling getHtmlDisplayName() + */ + private volatile boolean valueSet; + private final CF childFactory; + + /** + * Create a new SimpleNode with unspecified lookup, display name, + * and lookup contents + */ + public SimpleNode() { + this (null, (Image) null, (Lookup) null); + } + + /** + * Create a new SimpleNode with the passed display name and + * icon resource path + * @param displayName A localized display name + * @param iconResourcePath A resource path to an icon, e.g. com/foo/bar/Icon.png + */ + public SimpleNode (String displayName, String iconResourcePath) { + this (displayName, new PathIcon(iconResourcePath)); + } + + /** + * Create a new SimpleNode with the specified display name and icon + * @param displayName The display name, or null + * @param icon The icon, or null + */ + public SimpleNode (String displayName, Image icon) { + this (displayName, icon, (Lookup) null); + } + + /** + * Create a new SimpleNode with the specified display name and lookup + * contents + * @param displayName The display name, or null + * @param lookupContents The lookup contents, or null + */ + public static Node create (String displayName, Object... lookupContents) { + return new SimpleNode(displayName, lookupContents); + } + + /** + * Create a new SimpleNode with the specified display name, icon and lookup + * contents + * @param displayName The display name, or null + * @param lookupContents The lookup contents, or null + */ + public static Node create (String displayName, Image icon, Object... lookupContents) { + return new SimpleNode(displayName, icon, lookupContents); + } + + /** + * Create a new SimpleNode with the specified display name, icon and lookup + * contents + * @param displayName The display name, or null + * @param iconResourcePath A resource path to an icon, e.g. com/foo/bar/Icon.png + * @param lookupContents The lookup contents, or null + */ + public static Node create (String displayName, String iconResourcePath, Object... lookupContents) { + return new SimpleNode(displayName, iconResourcePath == null ? null : new PathIcon(iconResourcePath), lookupContents); + } + + + /** + * Create a new SimpleNode with the specified display name and lookup + * contents + * @param displayName The display name, or null + * @param lookupContents The lookup contents, or null + */ + SimpleNode (String displayName, Object... lookupContents) { + //package private to avoid constructor calls requiring extra casts + this (displayName, null, lookupContents); + } + + /** + * Create a new SimpleNode with the specified display name, icon and lookup contents + * @param displayName The display name, or null + * @param icon The icon, or null + * @param lookupContents the contents of the lookup (may be empty but not + * null) + */ + SimpleNode (String displayName, Image icon, Object... lookupContents) { + //package private to avoid constructor calls requiring extra casts + this (displayName, icon, Lookups.fixed(lookupContents)); + } + + /** + * Create a new SimpleNode with the specified display name, icon and lookup + * @param displayName The display name, or null + * @param iconResourcePath A resource path to an icon, e.g. com/foo/bar/Icon.png + * @param lookup The lookup, or null + */ + public SimpleNode (String displayName, String iconResourcePath, Lookup lookup) { + this (displayName, iconResourcePath == null ? null : new PathIcon (iconResourcePath), lookup); + } + + /** + * Create a new SimpleNode with the specified display name, icon and lookup + * @param displayName The display name, or null + * @param icon The icon, or null + * @param lookup The lookup, or null + */ + public SimpleNode (String displayName, Image icon, Lookup lookup) { + super(Children.LEAF, new L()); + if (lookup != null) assert !overridesCreateLookup() : "Passing a " + //NOI18N + "non-null lookup to a SimpleNode which overrides createLookup." + //NOI18N + "Passed lookup will not be used"; //NOI18N + if (displayName != null) { + setName (displayName); + setDisplayName(displayName); + } + ((L) getLookup()).set(lookup == null ? createLookup() : lookup); + if (overridesCreateKeys()) { + setChildren (Children.create(childFactory = new CF(this), true)); + } else { + childFactory = null; + } + this.icon = icon; + } + + /** + * Create this node's lookup. This method is called once, from the + * superclass constructor (be careful not to refer to instance fields + * if you override this method), and only if no Lookup or array of + * Lookup contents was passed to the constructor. + * + * @return A Lookup. The default implementation returns + * Lookups.singleton(this) + */ + protected Lookup createLookup() { + return Lookups.singleton(this); + } + + /** + * Populate a list of "key" objects which will be passed + * individually to createNodeForKey() to create this node's child nodes. + * Think of this as keys in a map, where the values are Nodes. Create + * your collection of keys in this method, and add them all to the passed + * list. Later, when the nodes need to be displayed, the system will + * call createNodeForKey(), passing each key you provided individually, + * at which point you create a child node. + *

+ * If you override this method, you must also override createNodeForKey() + * or an exception will be thrown if your node is expanded at runtime. + *

+ * Note that this method is not called on the AWT event thread, but on a + * background thread. Code inside this method should not directly + * call other code that is not thread-safe. + *

+ * If your node should not have child nodes, simply do not override this + * method. + * @param toPopulate A list which can be added to + * @return true if the list has been fully populated, false if another call + * to createKeys() should be enqueued to add more key objects to the list + * (do this if computing the keys is very slow, perhaps involving I/O, + * and you want to show a partial result). + */ + protected boolean createKeys(List toPopulate) { + return true; + } + + /** + * Call this method if the list of children of this node changes, to trigger + * an update of its children. + * + * @param immediate If true, the children should be updated immediately + * (resulting in a synchronous call to createKeys() - if creating the keys + * is slow, do not call this method with an argument of true from the + * AWT event thread) + */ + protected final void refreshChildren(boolean immediate) { + if (childFactory != null) { + childFactory.refresh(immediate); + } + } + + /** + * Create a child node for the passed key object. The key abject is one + * of the objects created and added to the passed list in toPopulate. + * + * @param key The key object + * @return A node + */ + protected Node createChildNodeFor(KeyType key) { + // XXX new BeanNode(key) ? + throw new UnsupportedOperationException("Must override createNodeForKey"); //NOI18N + } + + /** + * Called when the user expands this node in the UI, or something + * programmatically expresses an interest in the children of this node. + * If the set of child nodes can change due to external events, such as + * a file being deleted or created, start listening for changes in whatever + * model object determines the set of child nodes here. + *

+ * The default implementation does nothing. This method will only ever + * be called if you are overriding createKeys() and + * createNodeForKey() + */ + protected void onStartListeningForChildChanges() { + //do nothing + } + + /** + * Called some time after the user has collapsed this node in the UI, or the + * last listener interested in the children of this node is removed or + * garbage collected.. + * If the set of child nodes can change due to external events, such as + * a file being deleted or created, detach your listeners from whatever + * model object determines the children here. + *

+ * The default implementation does nothing. This method will only ever + * be called if you are overriding createKeys() and + * createNodeForKey() + */ + protected void onStopListeningForChildChanges() { + //do nothing + } + + + /** + * Overridden to be final, to prevent overriding in subclasses. To + * affect the contents of this Node's Lookup (and thus the return values + * of getCookie()), provide your own Lookup as a constructor parameter or + * as the return value from createLookup() + * @param the cookie type + * @param type the cookie type + * @return a object of type T + * @deprecated use getLookup().lookup(Class) instead + */ + @Deprecated + @Override + public final T getCookie(Class type) { + return super.getCookie(type); + } + + /** + * Overridden to make use of flags that can be set on this node. + * Call setValue(SimpleNode.ERROR, Boolean.TRUE) to have this node's + * title show up in the default error color (usually red). Call + * setValue (SimpleNode.HTML_PREFIX, "") with an html prefix + * to prepend to the display name. + *

+ * Note that if you override this method, calls to setValue(HTML_PREFIX, String) + * and setValue(ERROR, Boolean) will have no effect unless you handle these + * values in your own code or call super.getHtmlDisplayName() and decorate + * but do not replace the result. + * + * @return The node's display name decorated with HTML or null if HTML + * is not needed + */ + @Override + public String getHtmlDisplayName() { + if (valueSet) { + String pfix = (String) getValue(HTML_PREFIX); + boolean err = Boolean.TRUE.equals(getValue(ERROR)); + if (err) { + String errHead = ""; //NOI18N + pfix = pfix == null ? errHead : pfix + errHead; + return pfix + getDisplayName(); + } else if (pfix != null) { + return pfix + getDisplayName(); + } + } + return super.getHtmlDisplayName(); + } + + /** + * Allows nodes to contain ad-hoc key/value pairs. Will trigger a + * display name change event if a key which affects html display name + * is passed + * @param key The key + * @param o The value + */ + @Override + public final void setValue(String key, Object o) { + valueSet = true; + boolean isChange = ERROR.equals(key) || HTML_PREFIX.equals(key); + String oldName = isChange ? getDisplayName() : null; + super.setValue(key, o); + if (isChange) { + fireDisplayNameChange(oldName, getDisplayName()); + if (ERROR.equals(key)) { + fireIconChange(); + } + } + } + + /** + * Overridden to return the icon passed as a constructor argument, if any. + * @param type The icon type, + * @return the icon passed to the constructor, or the return value of + * a call to super.getIcon(type) if that was null + */ + @Override + public Image getIcon(int type) { + Image result = icon == null ? super.getIcon(type) : + icon instanceof PathIcon ? ((PathIcon) icon).actualImage() : icon; + return badge(result); + } + + /** + * Overridden to return the icon passed as a constructor argument, or + * if null, the return value of super.getOpenedIcon(type). + * @param type The icon type, + * @return the icon passed to the constructor, or the return value of + * a call to super.getOpenedIcon(type) if that was null. + */ + @Override + public Image getOpenedIcon(int type) { + Image result = icon == null ? super.getOpenedIcon(type) : + icon instanceof PathIcon ? ((PathIcon) icon).actualImage() : icon; + return badge(result); + } + + private Image badge(Image img) { + if (img != null && valueSet && Boolean.TRUE.equals(getValue(ERROR))) { + Image badge = ImageUtilities.loadImage( + "org/openide/nodes/errorBadge.png"); //NOI18N + img = ImageUtilities.mergeImages(img, badge, 8, 8); + } + return img; + } + + /** + * Populate the passed list with any actions which should be available + * @param actions A list of actions which can be added to + */ + public void createActions(List actions) { + //do nothing + } + + /** + * Overridden as final to delegate to createActions(List) + * @param context ignored + * @return an array of actions + */ + @Override + public final Action[] getActions(boolean context) { + List l = new LinkedList(); + createActions(l); + return l.toArray(new Action[0]); + } + + private static class CF extends ChildFactory.Detachable { + private final SimpleNode nd; + CF (SimpleNode nd) { + this.nd = nd; + } + + @Override + protected boolean createKeys(List toPopulate) { + return nd.createKeys(toPopulate); + } + + @Override + protected Node createNodeForKey(KeyType key) { + return nd.createChildNodeFor(key); + } + + @Override + protected void addNotify() { + nd.onStartListeningForChildChanges(); + } + + @Override + protected void removeNotify() { + nd.onStopListeningForChildChanges(); + } + } + + private static final class L extends ProxyLookup { + void set(Lookup real) { + setLookups(real); + } + } + + private static final Map, Boolean> CREATE_KEYS_OVERRIDERS = + Collections.synchronizedMap(new WeakHashMap,Boolean>()); + /** + * Used to determine if Children.LEAF should be retained as the + * Children object + * @return true if this class overrides the createKeys() method + */ + private boolean overridesCreateKeys() { + if (getClass() == SimpleNode.class) return false; + Boolean val = CREATE_KEYS_OVERRIDERS.get(getClass()); + if (val == null) { + try { + val = overrides ("createKeys", List.class); + CREATE_KEYS_OVERRIDERS.put (getClass(), val); + } catch (SecurityException ex) { + Exceptions.printStackTrace(ex); + val = false; + } + } + return val; + } + + private boolean overrides (String name, Class... ptypes) { + Class type = getClass(); + boolean result = false; + while (type != SimpleNode.class) { + try { + type.getDeclaredMethod(name, ptypes); + result = true; + break; + } catch (NoSuchMethodException e) { + //do nothing + } + type = type.getSuperclass(); + } + return result; + } + + /** + * Used to trigger an assertion error of createLookup() is overridden + * and a non-null lookup is also passed to the constructor + * @return whether or not this subclass overrides createLookup() + */ + private boolean overridesCreateLookup() { + if (getClass() == SimpleNode.class) return false; + try { + return overrides ("createLookup"); + } catch (SecurityException ex) { + Exceptions.printStackTrace(ex); + } + return false; + } + + private static final class PathIcon extends Image { + private final String path; + PathIcon(String path) { + this.path = path; + } + + Image actualImage() { + return ImageUtilities.loadImage(path); + } + + + @Override + public int getWidth(ImageObserver observer) { + throw new AssertionError(); + } + + @Override + public int getHeight(ImageObserver observer) { + throw new AssertionError(); + } + + @Override + public ImageProducer getSource() { + throw new AssertionError(); + } + + @Override + public Graphics getGraphics() { + throw new AssertionError(); + } + + @Override + public Object getProperty(String name, ImageObserver observer) { + throw new AssertionError(); + } + + } +} diff --git a/openide.nodes/src/org/openide/nodes/errorBadge.png b/openide.nodes/src/org/openide/nodes/errorBadge.png new file mode 100644 index 0000000000000000000000000000000000000000..a903aa2b56245164715b9f81c9893eaf523474af GIT binary patch literal 373 zc%17D@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqoCO|{#S9GG!XV7ZFl&wkP>?0v z(btiIVPjv-@4(4GzCyA`kS_y6l_~>6Lo)-z&;LOBB?CjL0RzLU1O^7H84L{K`IF+0 zx&hU`_H=O!skoK2$8;_u&%ZxO0x1vED%jIL8+L5kV6caABO_DC%gf?yZQtb^bi~?J zuBMo>T}@$SYkSV~F@L?0LCubn2N(n#eBRp|8|Ec1&B5UW$i~cJ( z-z0=*udLx^znzB3VotXU?Ad(AJ@Wk>RINK-RtHjo*OY OX7F_Nb6Mw<&;$UY8ialT diff --git a/openide.nodes/test/unit/src/org/openide/nodes/SimpleNodeTest.java b/openide.nodes/test/unit/src/org/openide/nodes/SimpleNodeTest.java new file mode 100644 --- /dev/null +++ b/openide.nodes/test/unit/src/org/openide/nodes/SimpleNodeTest.java @@ -0,0 +1,327 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, 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-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2009 Sun Microsystems, Inc. + */ + +package org.openide.nodes; + +import java.awt.Graphics; +import java.awt.Image; +import java.awt.event.ActionEvent; +import java.awt.image.ImageObserver; +import java.awt.image.ImageProducer; +import java.util.Collection; +import java.util.List; +import javax.swing.AbstractAction; +import javax.swing.Action; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.openide.util.ImageUtilities; +import static org.junit.Assert.*; +import org.openide.util.Lookup; +import org.openide.util.lookup.Lookups; + +/** + * + * @author tim + */ +public class SimpleNodeTest { + + public SimpleNodeTest() { + } + + @BeforeClass + public static void setUpClass() throws Exception { + } + + @AfterClass + public static void tearDownClass() throws Exception { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + /** + * Test of create method, of class SimpleNode. + */ + @Test + public void testCreate_String_ObjectArr() { + System.out.println("create"); + String displayName = "Boo"; + Object[] lookupContents = new String[] { "A", "B", "C" }; + Node n = SimpleNode.create(displayName, lookupContents); + assertEquals ("Boo", n.getDisplayName()); + Collection c = n.getLookup().lookupAll(String.class); + assertTrue (c.contains("A")); + assertTrue (c.contains("B")); + assertTrue (c.contains("C")); + assertFalse (c.contains("D")); + assertEquals(3, c.size()); + } + + /** + * Test of create method, of class SimpleNode. + */ + @Test + public void testCreate_3args_1() { + System.out.println("create"); + String displayName = "X"; + Image icon = ImageUtilities.loadImage("org/openide/nodes/beans.gif"); + Object[] lookupContents = new Object [] { displayName, icon }; + Node n = SimpleNode.create(displayName, icon, lookupContents); + assertSame (icon, n.getLookup().lookup(Image.class)); + assertEquals (displayName, n.getLookup().lookup(String.class)); + assertEquals (displayName, n.getDisplayName()); + assertNull (n.getHtmlDisplayName()); + assertSame (icon, n.getOpenedIcon(0)); + } + + /** + * Test of create method, of class SimpleNode. + */ + @Test + public void testCreate_3args_2() { + System.out.println("create"); + String displayName = "X"; + String iconResourcePath = "org/openide/nodes/beans.gif"; + Object[] lookupContents = new Object[] { displayName, iconResourcePath }; + Node n = SimpleNode.create(displayName, iconResourcePath, lookupContents); + assertNotNull (n.getIcon(0)); + assertEquals (displayName, n.getDisplayName()); + assertNotNull (n.getDisplayName()); + Collection c = n.getLookup().lookupAll(String.class); + assertTrue (c.contains(displayName)); + assertTrue (c.contains(iconResourcePath)); + assertEquals(2, c.size()); + } + + /** + * Test of createLookup method, of class SimpleNode. + */ + @Test + public void testCreateLookup() { + System.out.println("createLookup"); + StringBuilder thing1 = new StringBuilder("Thing 1"); + StringBuilder thing2 = new StringBuilder("Thing 2"); + final Lookup lkp = Lookups.fixed(thing1, thing2); + final boolean[] called = new boolean[1]; + SimpleNode instance = new SimpleNode() { + @Override + protected Lookup createLookup() { + called[0] = true; + return lkp; + } + }; + Lookup lookup = instance.getLookup(); + assertTrue(called[0]); + Collection c = lookup.lookupAll(StringBuilder.class); + assertTrue (c.contains(thing1)); + assertTrue (c.contains(thing2)); + assertSame (Children.LEAF, instance.getChildren()); + } + + /** + * Test of createKeys method, of class SimpleNode. + */ + @Test + public void testCreateKeys() { + System.out.println("createKeys"); + } + + /** + * Test of getHtmlDisplayName method, of class SimpleNode. + */ + @Test + public void testGetHtmlDisplayName() { + System.out.println("getHtmlDisplayName"); + SimpleNode instance = new SimpleNode("Foo"); + assertNull (instance.getHtmlDisplayName()); + instance.setValue(SimpleNode.ERROR, Boolean.TRUE); + assertNotNull (instance.getHtmlDisplayName()); + instance.setValue(SimpleNode.ERROR, Boolean.FALSE); + assertNull (instance.getHtmlDisplayName()); + instance.setValue(SimpleNode.HTML_PREFIX, ""); + assertNotNull (instance.getHtmlDisplayName()); + assertEquals ("Foo", instance.getHtmlDisplayName()); + } + + /** + * Test of getIcon method, of class SimpleNode. + */ + @Test + public void testGetIcon() { + System.out.println("getIcon"); + Image img = new Image() { + + @Override + public int getWidth(ImageObserver observer) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public int getHeight(ImageObserver observer) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public ImageProducer getSource() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Graphics getGraphics() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Object getProperty(String name, ImageObserver observer) { + throw new UnsupportedOperationException("Not supported yet."); + } + }; + + SimpleNode nd = new SimpleNode ("Foo", img); + assertSame (img, nd.getIcon(0)); + assertSame (img, nd.getOpenedIcon(0)); + } + + /** + * Test of createActions method, of class SimpleNode. + */ + @Test + public void testCreateActions() { + System.out.println("createActions"); + class A extends AbstractAction { + + public void actionPerformed(ActionEvent e) { + throw new UnsupportedOperationException("Not supported yet."); + } + + } + final A[] as = new A[] { new A() }; + SimpleNode instance = new SimpleNode() { + + @Override + public void createActions(List actions) { + actions.add (as[0]); + } + }; + Action[] acts = instance.getActions(true); + assertEquals (1, acts.length); + assertSame (as[0], acts[0]); + } + + @Test + public void testChildren() throws Exception { + N n = new N(); + Children k = n.getChildren(); + assertFalse (k == Children.LEAF); + k.addNotify(); + Node[] nds = k.getNodes(true); + synchronized(n) { + n.wait(10000); + } + nds = k.getNodes(true); + assertTrue (n.keysCalled); + assertTrue (n.nodeCalled); + assertEquals(3, nds.length); + for (int i = 0; i < nds.length; i++) { + Node nd = nds[i]; + switch(i) { + case 0: + assertEquals("A", nd.getDisplayName()); + break; + case 1: + assertEquals("B", nd.getDisplayName()); + break; + case 2: + assertEquals("C", nd.getDisplayName()); + break; + } + } + } + + private static final class N extends SimpleNode { + boolean keysCalled; + boolean nodeCalled; + N() { + super ("N", (String) null, Lookups.fixed("Foo")); + } + + public void bibbleRocket() { + + } + + @Override + protected boolean createKeys(List toPopulate) { + keysCalled = true; + toPopulate.add("A"); + toPopulate.add("B"); + toPopulate.add("C"); + return true; + } + + int nds; + @Override + protected Node createChildNodeFor(String key) { + nodeCalled = true; + nds++; + Node result = new SimpleNode(key); + if (nds == 3) { + Thread t = new Thread(new Runnable() { + public void run() { + synchronized(N.this) { + N.this.notifyAll(); + } + } + + }); + t.setDaemon(true); + t.setPriority(Thread.MIN_PRIORITY); + t.start(); + } + return result; + } + } +}