X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.scenegraph%2Fsrc%2Forg%2Fsimantics%2Fscenegraph%2FParentNode.java;h=e9e0b4be3e1f5beb2107b89733a1ac72ba04bc2d;hp=866e8d36cba6718fe20b5eebb926fe9698228e2a;hb=refs%2Fchanges%2F63%2F1963%2F9;hpb=7e66fdd1670687a56d8ef980d14d83bfc5f16583 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 } }