From: jsimomaa Date: Fri, 27 Jul 2018 07:12:54 +0000 (+0300) Subject: Performance improvements for ParentNode in scene graph X-Git-Tag: v1.43.0~136^2~343 X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=commitdiff_plain;h=7e2197edb8015728a84e9e5c61ccd7eea8eedade;p=simantics%2Fplatform.git Performance improvements for ParentNode in scene graph gitlab #65 Change-Id: Ib1cb9da01e8531b1d9617f0696a20f806037dc38 --- diff --git a/bundles/org.simantics.document/src/org/simantics/document/function/WikiDocumentNodeImpl.java b/bundles/org.simantics.document/src/org/simantics/document/function/WikiDocumentNodeImpl.java index 898729828..e3c52294c 100644 --- a/bundles/org.simantics.document/src/org/simantics/document/function/WikiDocumentNodeImpl.java +++ b/bundles/org.simantics.document/src/org/simantics/document/function/WikiDocumentNodeImpl.java @@ -24,18 +24,18 @@ import org.simantics.scenegraph.ScenegraphUtils; import org.simantics.scl.runtime.function.Function1; import org.simantics.scl.runtime.function.Function2; -abstract public class WikiDocumentNodeImpl extends ParentNode implements WikiDocumentNode { +public abstract class WikiDocumentNodeImpl extends ParentNode implements WikiDocumentNode { public Boolean printInPDF = false; public String editText = null; private static final long serialVersionUID = 3394059912639648935L; - class M implements Map { - - ArrayList list = new ArrayList(); - Hashtable table = new Hashtable(); - + static class M implements Map { + + ArrayList list = new ArrayList<>(); + Hashtable table = new Hashtable<>(); + @Override public void clear() { table.clear(); @@ -50,11 +50,11 @@ abstract public class WikiDocumentNodeImpl extends ParentNode return table.containsValue(arg0); } @Override - public Set> entrySet() { + public Set> entrySet() { return table.entrySet(); } @Override - public WikiDocumentNode get(Object arg0) { + public INode get(Object arg0) { return table.get(arg0); } @Override @@ -66,20 +66,20 @@ abstract public class WikiDocumentNodeImpl extends ParentNode return table.keySet(); } @Override - public WikiDocumentNode put(String arg0, WikiDocumentNode arg1) { - WikiDocumentNode exist = table.put(arg0, arg1); + public INode put(String arg0, INode arg1) { + INode exist = table.put(arg0, arg1); if(exist != null) list.remove(exist); list.add(arg1); return exist; } @Override - public void putAll(Map arg0) { - for(Map.Entry entry : arg0.entrySet()) + public void putAll(Map arg0) { + for(Map.Entry entry : arg0.entrySet()) put(entry.getKey(), entry.getValue()); } @Override - public WikiDocumentNode remove(Object arg0) { - WikiDocumentNode node = table.remove(arg0); + public INode remove(Object arg0) { + INode node = table.remove(arg0); if(node != null) list.remove(node); return node; } @@ -88,22 +88,22 @@ abstract public class WikiDocumentNodeImpl extends ParentNode return table.size(); } @Override - public Collection values() { + public Collection values() { return list; } - + } - + @Override final public void asyncRemoveNode(INode node) { throw new Error(); } @Override - protected Map createChildMap() { + protected Map createChildMap(int initialCapacity) { return new M(); } - + @Override public Function1 getPropertyFunction(String propertyName) { return ScenegraphUtils.getMethodPropertyFunction(null, this, propertyName); @@ -119,7 +119,7 @@ abstract public class WikiDocumentNodeImpl extends ParentNode } protected void createChildren(StringBuilder b, boolean isPDF) { - for(WikiDocumentNode node : children.values()) node.create(b, isPDF); + for(WikiDocumentNode node : getNodes()) node.create(b, isPDF); } public void synchronizePrintInPDF(Boolean value) { diff --git a/bundles/org.simantics.scenegraph.ui/src/org/simantics/scenegraph/ui/NodeProxy.java b/bundles/org.simantics.scenegraph.ui/src/org/simantics/scenegraph/ui/NodeProxy.java index 6ed294b62..a9307c6e2 100644 --- a/bundles/org.simantics.scenegraph.ui/src/org/simantics/scenegraph/ui/NodeProxy.java +++ b/bundles/org.simantics.scenegraph.ui/src/org/simantics/scenegraph/ui/NodeProxy.java @@ -62,7 +62,7 @@ class NodeProxy { INode n = getNode(); if (n == null) return "-"; - return n.getId().toString(); + return Long.toString(n.getId()); } public String getId() { diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/INode.java b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/INode.java index c14edca84..755bb755f 100644 --- a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/INode.java +++ b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/INode.java @@ -78,7 +78,7 @@ public interface INode extends Serializable { * * @return unique node identifier */ - public Long getId(); + public long getId(); /** * @return root node of the scene graph or null if this node is @@ -109,8 +109,7 @@ public interface INode extends Serializable { * @see ParentNode#getOrCreateNode(String, Class) */ public void init(); - public void attach(); - + /** * Perform cleanup for this node and for the child nodes. Any resources * (including child nodes) related to this node are unusable after this diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/Node.java b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/Node.java index 7ce4611a3..b9fb1b67f 100644 --- a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/Node.java +++ b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/Node.java @@ -22,7 +22,13 @@ public abstract class Node implements INode { private static final long serialVersionUID = -5540999051056414851L; + /** + * The scene graph is essentially single-threaded, meaning nodes should be + * created in one thread only. For this reason, we are not using AtomicLong + * here. + */ public transient static long IDCOUNTER = 1; + protected long id = IDCOUNTER++; protected transient ParentNode parent = null; protected transient Location location = Location.LOCAL; @@ -31,12 +37,10 @@ public abstract class Node implements INode { // Support for only one listener should be enough, thus we don't need PropertyChangeSupport protected transient PropertyChangeListener propertyChangeListener = null; - protected Long id = IDCOUNTER++; - /** * @see org.simantics.scenegraph.INode#getId() */ - public Long getId() { + public long getId() { return id; } @@ -78,10 +82,6 @@ public abstract class Node implements INode { */ public void init() { } - - public void attach() { - } - /** * @see org.simantics.scenegraph.INode#cleanup() diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/ParentNode.java b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/ParentNode.java index 866e8d36c..e9e0b4be3 100644 --- a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/ParentNode.java +++ b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/ParentNode.java @@ -11,14 +11,14 @@ *******************************************************************************/ package org.simantics.scenegraph; -import gnu.trove.map.hash.THashMap; - import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Collection; -import java.util.Collections; +import java.util.HashMap; import java.util.Map; +import gnu.trove.map.hash.TLongObjectHashMap; + /** * Base class of all scene graph nodes which can have a set of sub-nodes * (children). This class only provides support for unordered children. @@ -33,11 +33,6 @@ public abstract class ParentNode extends Node { public static final String UNLINK = "#UNLINK#"; public static final String NULL = "#NULL#"; - protected static final String[] EMPTY_STRING_ARRAY = {}; - - @SuppressWarnings("rawtypes") - private static final Map DISPOSED_CHILDREN = Collections.emptyMap(); - /** * A value used for {@link #rootNodeCache} to indicate the node has been * disposed and the root node cache is to be considered indefinitely @@ -51,7 +46,8 @@ public abstract class ParentNode extends Node { } }; - protected transient Map children = createChildMap(); + protected transient Map children = createChildMap(); + private transient TLongObjectHashMap childrenIdMap = new TLongObjectHashMap<>(); /** * A cached value for the root node of this parent node. This makes it @@ -64,50 +60,57 @@ public abstract class ParentNode extends Node { */ protected transient volatile ParentNode rootNodeCache; - protected Map createChildMap() { - return new THashMap(1); + protected Map createChildMap() { + return createChildMap(1); } - + + protected Map createChildMap(int initialCapacity) { + // With JDK 1.8 HashMap is faster than Trove + return new HashMap<>(initialCapacity); + } + public final TC addNode(Class a) { return addNode(java.util.UUID.randomUUID().toString(), a); } - @SuppressWarnings("unchecked") public TC addNode(String id, TC child) { + return addNodeInternal(id, child, true, true); + } + private TC addNodeInternal(String id, TC child, boolean init, boolean addToChildren) { child.setParent(this); - children.put(id, (T)child); + if (addToChildren) + children.put(id, child); + + childrenIdMap.put(child.getId(), id); - child.init(); + if (init) + child.init(); childrenChanged(); return (TC)child; - } - @SuppressWarnings("unchecked") public TC attachNode(String id, TC child) { - - child.setParent(this); - children.put(id, (T)child); - - child.attach(); - childrenChanged(); - return (TC)child; - + return addNodeInternal(id, child, false, true); } @SuppressWarnings("unchecked") public TC detachNode(String id) { - T child = children.remove(id); + INode child = children.remove(id); if (child == null) return null; + childrenIdMap.remove(child.getId()); child.setParent(null); childrenChanged(); return (TC) child; } - @SuppressWarnings("unchecked") public TC addNode(String id, Class a) { + return addNodeInternal0(id, a, true); + } + + @SuppressWarnings("unchecked") + private TC addNodeInternal0(String id, Class a, boolean addToChildren) { // a must be extended from Node if(!Node.class.isAssignableFrom(a)) { throw new IllegalArgumentException(a + " is not extended from org.simantics.scenegraph.Node"); @@ -120,75 +123,32 @@ public abstract class ParentNode extends Node { } catch (IllegalAccessException e) { throw new NodeException("Node " + Node.getSimpleClassName(a) + " instantiation failed, see exception for details.", e); } - - child.setParent(this); - children.put(id, (T)child); - - child.init(); - childrenChanged(); - return (TC)child; + return (TC) addNodeInternal(id, child, true, addToChildren); } @SuppressWarnings("unchecked") public final TC getOrAttachNode(String id, TC a) { - synchronized (children) { - if (children.containsKey(id)) - return (TC) children.get(id); - } - return attachNode(id, a); + return (TC) children.computeIfAbsent(id, key -> { + return (T) addNodeInternal(id, a, false, false); + }); } @SuppressWarnings("unchecked") public final TC getOrCreateNode(String id, Class a) { - synchronized (children) { - if (children.containsKey(id)) - return (TC) children.get(id); - } - return addNode(id, a); + return (TC) children.computeIfAbsent(id, key -> { + return (T) addNodeInternal0(id, a, false); + }); } public final void removeNode(String id) { - INode child = null; - synchronized (children) { - child = children.remove(id); - } - if (child != null) { - if (child instanceof ParentNode) { - ((ParentNode) child).removeNodes(); - } - child.cleanup(); - child.setParent(null); - childrenChanged(); - if (propertyChangeListener != null) { - propertyChangeListener.propertyChange(new PropertyChangeEvent(this, "children["+child.getId()+"]", child.getClass(), NULL)); // "children" is a special field name - } - } + INode child = children.remove(id); + if (child != null) + removeNodeInternal(child, true); } public final void removeNode(INode child) { - synchronized (children) { - String id = null; - // FIXME: damn slow, needs more data structure to be supported well - // One option would be to store the id<->node mappings in a BidiMap. - for (String tmp : children.keySet()) { - if (children.get(tmp).equals(child)) { - id = tmp; - break; - } - } - if(id == null) return; - children.remove(id); - childrenChanged(); - } - if (child instanceof ParentNode) { - ((ParentNode) child).removeNodes(); - } - child.cleanup(); - child.setParent(null); - - if (propertyChangeListener != null) { - propertyChangeListener.propertyChange(new PropertyChangeEvent(this, "children["+child.getId()+"]", child.getClass(), NULL)); // "children" is a special field name - } + String key = childrenIdMap.get(child.getId()); + removeNode(key); } /** @@ -196,25 +156,25 @@ public abstract class ParentNode extends Node { * children). */ public final void removeNodes() { - synchronized (children) { - boolean changed = false; - String[] keys = children.keySet().toArray(EMPTY_STRING_ARRAY); - for (String key : keys) { - INode child = children.remove(key); - if (child != null) { - changed = true; - if (child instanceof ParentNode) { - ((ParentNode) child).removeNodes(); - } - child.cleanup(); - child.setParent(null); - if (propertyChangeListener != null) { - propertyChangeListener.propertyChange(new PropertyChangeEvent(this, "children["+child.getId()+"]", child.getClass(), NULL)); // "children" is a special field name - } - } + boolean changed = children.size() > 0; + children.forEach((id, child) -> removeNodeInternal(child, false)); + children.clear(); + childrenIdMap.clear(); + + if (changed) + childrenChanged(); + } + + private void removeNodeInternal(INode child, boolean triggerChildrenChanged) { + if (child != null) { + if (child instanceof ParentNode) { + ((ParentNode) child).removeNodes(); } - if (changed) + child.cleanup(); + child.setParent(null); + if (triggerChildrenChanged) childrenChanged(); + triggerPropertyChangeEvent(child); } } @@ -228,8 +188,9 @@ public abstract class ParentNode extends Node { public abstract void asyncRemoveNode(INode node); + @SuppressWarnings("unchecked") public T getNode(String id) { - return children.get(id); + return (T) children.get(id); } /** @@ -243,8 +204,9 @@ public abstract class ParentNode extends Node { /** * @return the collection of this node's children in an unspecified order */ + @SuppressWarnings("unchecked") public Collection getNodes() { - return children.values(); + return (Collection) children.values(); } public int getNodeCount() { @@ -258,32 +220,28 @@ public abstract class ParentNode extends Node { */ public void setPropertyChangeListener(PropertyChangeListener propertyChangeListener) { this.propertyChangeListener = propertyChangeListener; - synchronized(children) { - for(T t : children.values()) { - INode child = t; - if(child instanceof ParentNode) { - ((ParentNode)child).setPropertyChangeListener(propertyChangeListener); - } else { - ((Node)child).propertyChangeListener = propertyChangeListener; // FIXME - } + children.forEach((id, child) -> { + if(child instanceof ParentNode) { + ((ParentNode)child).setPropertyChangeListener(propertyChangeListener); + } else { + ((Node)child).propertyChangeListener = propertyChangeListener; // FIXME } - } + }); } - @SuppressWarnings("unchecked") @Override public void cleanup() { retractMapping(); - if (children != DISPOSED_CHILDREN) { - synchronized(children) { - for(T child : children.values()) { - ((INode)child).cleanup(); - ((INode)child).setParent(null); - } - children.clear(); - children = DISPOSED_CHILDREN; - childrenChanged(); - } + if (children != null) { + children.forEach((id, child) -> { + child.cleanup(); + child.setParent(null); + }); + children.clear(); + childrenIdMap.clear(); + children = null; + childrenIdMap = null; + childrenChanged(); rootNodeCache = DISPOSED; } } @@ -294,13 +252,12 @@ public abstract class ParentNode extends Node { return; } - synchronized (children) { - // 1. Add children under parent - parent.appendChildren(children); + // 1. Add children under parent + parent.appendChildren(children); - // 2. Clear children - children.clear(); - } + // 2. Clear child maps to prevent cleanup from deleting them in step 4. + children.clear(); + childrenIdMap.clear(); // 3. Remove this node from parent parent.unlinkChild(this); @@ -313,30 +270,21 @@ public abstract class ParentNode extends Node { * Helper method for delete() * @param children */ - @SuppressWarnings("unchecked") - protected void appendChildren(Map children) { - synchronized(this.children) { - for(String id : children.keySet()) { - INode child = (INode)children.get(id); - this.children.put(id, (T)child); // Hopefully cast works - child.setParent(this); - - // Send notify only if we are on server side (or standalone) - if (propertyChangeListener != null && location.equals(Location.LOCAL)) { - propertyChangeListener.propertyChange(new PropertyChangeEvent(this, "children["+child.getId()+"]", null, EXISTING)); // "children" is a special field name - } - } - } + protected void appendChildren(Map children) { + children.forEach((key, value) -> { + appendChildInternal(key, value); + }); } - @SuppressWarnings("unchecked") protected void appendChild(String id, INode child) { - children.put(id, (T)child); + appendChildInternal(id, child); + } + + private void appendChildInternal(String id, INode child) { + children.put(id, child); + childrenIdMap.put(child.getId(), id); child.setParent(this); - // Send notify only if we are on server side (or standalone) - if (propertyChangeListener != null && location.equals(Location.LOCAL)) { - propertyChangeListener.propertyChange(new PropertyChangeEvent(this, "children["+(child).getId()+"]", null, EXISTING)); // "children" is a special field name - } + triggerPropertyChangeEvent(child); } /** @@ -344,22 +292,17 @@ public abstract class ParentNode extends Node { * @param child */ protected void unlinkChild(INode child) { - synchronized(children) { - String id = null; - // FIXME: damn slow, needs more data structure to be supported well - // One option would be to store the id<->node mappings in a BidiMap. - for(String tmp : children.keySet()) { - if(children.get(tmp).equals(child)) { - id = tmp; - break; - } - } - if(id == null) return; + String id = childrenIdMap.remove(child.getId()); + if(id != null) { children.remove(id); childrenChanged(); + triggerPropertyChangeEvent(child); } + } - if(propertyChangeListener != null && location.equals(Location.LOCAL)) { + private void triggerPropertyChangeEvent(INode child) { + // Send notify only if we are on server side (or standalone) + if (propertyChangeListener != null && location.equals(Location.LOCAL)) { propertyChangeListener.propertyChange(new PropertyChangeEvent(this, "children["+child.getId()+"]", child.getClass(), UNLINK)); // "children" is a special field name } } diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/spatial/RTreeNode.java b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/spatial/RTreeNode.java index 52403edd0..56b6be302 100644 --- a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/spatial/RTreeNode.java +++ b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/spatial/RTreeNode.java @@ -20,9 +20,11 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Properties; import java.util.Set; +import org.simantics.scenegraph.INode; import org.simantics.scenegraph.g2d.G2DParentNode; import org.simantics.scenegraph.g2d.IG2DNode; import org.simantics.scenegraph.g2d.events.Event; @@ -114,6 +116,11 @@ public class RTreeNode extends G2DParentNode implements INodeEventHandlerProvide private transient ArrayList collected = new ArrayList(); private transient Set simplified = new HashSet(); + @Override + protected Map createChildMap() { + return super.createChildMap(1 << 15); + } + @Override public void render(Graphics2D g) { if (DISABLE_RTREE) { diff --git a/bundles/org.simantics.views.swt.client/src/org/simantics/views/swt/client/base/SWTParentNode.java b/bundles/org.simantics.views.swt.client/src/org/simantics/views/swt/client/base/SWTParentNode.java index 1d0a25442..3fc213126 100644 --- a/bundles/org.simantics.views.swt.client/src/org/simantics/views/swt/client/base/SWTParentNode.java +++ b/bundles/org.simantics.views.swt.client/src/org/simantics/views/swt/client/base/SWTParentNode.java @@ -13,17 +13,17 @@ import org.eclipse.ui.IWorkbenchSite; import org.simantics.scenegraph.INode; import org.simantics.scenegraph.ParentNode; -abstract public class SWTParentNode extends ParentNode implements ISWTViewNode { +public abstract class SWTParentNode extends ParentNode implements ISWTViewNode { private boolean disposed = false; - + private static final long serialVersionUID = -3548136282051185971L; - class M implements Map { - - ArrayList list = new ArrayList(); - Hashtable table = new Hashtable(); - + static class M implements Map { + + ArrayList list = new ArrayList<>(); + Hashtable table = new Hashtable<>(); + @Override public void clear() { table.clear(); @@ -38,11 +38,11 @@ abstract public class SWTParentNode extends ParentNode implements return table.containsValue(arg0); } @Override - public Set> entrySet() { + public Set> entrySet() { return table.entrySet(); } @Override - public ISWTViewNode get(Object arg0) { + public INode get(Object arg0) { return table.get(arg0); } @Override @@ -54,20 +54,20 @@ abstract public class SWTParentNode extends ParentNode implements return table.keySet(); } @Override - public ISWTViewNode put(String arg0, ISWTViewNode arg1) { - ISWTViewNode exist = table.put(arg0, arg1); + public INode put(String arg0, INode arg1) { + INode exist = table.put(arg0, arg1); if(exist != null) list.remove(exist); list.add(arg1); return exist; } @Override - public void putAll(Map arg0) { - for(Map.Entry entry : arg0.entrySet()) + public void putAll(Map arg0) { + for(Map.Entry entry : arg0.entrySet()) put(entry.getKey(), entry.getValue()); } @Override - public ISWTViewNode remove(Object arg0) { - ISWTViewNode node = table.remove(arg0); + public INode remove(Object arg0) { + INode node = table.remove(arg0); if(node != null) list.remove(node); return node; } @@ -76,12 +76,12 @@ abstract public class SWTParentNode extends ParentNode implements return table.size(); } @Override - public Collection values() { + public Collection values() { return list; } - + } - + @Override public SWTRoot getRootNode() { ParentNode root = super.getRootNode(); @@ -104,18 +104,18 @@ abstract public class SWTParentNode extends ParentNode implements final public void asyncRemoveNode(INode node) { throw new Error(); } - + @Override - protected Map createChildMap() { + protected Map createChildMap(int initialCapacity) { return new M(); } - + protected void createChildComposites() { createChildComposites((Composite)getControl()); } protected void createChildComposites(Composite composite) { - for(ISWTViewNode node : children.values()) node.createControls(composite); + for(ISWTViewNode node : getNodes()) node.createControls(composite); } @Override @@ -133,7 +133,7 @@ abstract public class SWTParentNode extends ParentNode implements } public Collection getChildComposites() { - return children.values(); + return getNodes(); } @Override diff --git a/bundles/org.simantics.views.swt.client/src/org/simantics/views/swt/client/base/SWTRoot.java b/bundles/org.simantics.views.swt.client/src/org/simantics/views/swt/client/base/SWTRoot.java index 36f874d78..59c0dde6e 100644 --- a/bundles/org.simantics.views.swt.client/src/org/simantics/views/swt/client/base/SWTRoot.java +++ b/bundles/org.simantics.views.swt.client/src/org/simantics/views/swt/client/base/SWTRoot.java @@ -62,12 +62,12 @@ public class SWTRoot extends SWTParentNode implements ISWTViewNode { public void createControls(Composite parent) { body = parent; resourceManager = new LocalResourceManager(JFaceResources.getResources(), body); - for(ISWTViewNode child : children.values()) child.createControls(body); + for(ISWTViewNode child : getNodes()) child.createControls(body); } public Control getSingleChild() { if(children.size() != 1) throw new IllegalStateException("SWTRoot should have exactly one child (has " + children.size() + ")."); - return children.values().iterator().next().getControl(); + return getNodes().iterator().next().getControl(); } } diff --git a/bundles/org.simantics.views.swt.client/src/org/simantics/views/swt/client/impl/SWTAlternative.java b/bundles/org.simantics.views.swt.client/src/org/simantics/views/swt/client/impl/SWTAlternative.java index cd4720636..f311f4369 100644 --- a/bundles/org.simantics.views.swt.client/src/org/simantics/views/swt/client/impl/SWTAlternative.java +++ b/bundles/org.simantics.views.swt.client/src/org/simantics/views/swt/client/impl/SWTAlternative.java @@ -28,7 +28,7 @@ public class SWTAlternative extends SingleSWTViewNode { GridLayoutFactory.fillDefaults().applyTo(control); GridDataFactory.fillDefaults().grab(true, true).applyTo(control); - Collection nodes = children.values(); + Collection nodes = getNodes(); if(nodes.size() > 0) { Iterator it = nodes.iterator(); if(condition == null || !condition) {