1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.scenegraph;
14 import java.beans.PropertyChangeEvent;
15 import java.beans.PropertyChangeListener;
16 import java.util.Collection;
17 import java.util.HashMap;
20 import gnu.trove.map.hash.TLongObjectHashMap;
23 * Base class of all scene graph nodes which can have a set of sub-nodes
24 * (children). This class only provides support for unordered children.
28 public abstract class ParentNode<T extends INode> extends Node {
30 private static final long serialVersionUID = 8519410262849626534L;
32 public static final String EXISTING = "#EXISTING#";
33 public static final String UNLINK = "#UNLINK#";
34 public static final String NULL = "#NULL#";
37 * A value used for {@link #rootNodeCache} to indicate the node has been
38 * disposed and the root node cache is to be considered indefinitely
41 protected static final ParentNode<?> DISPOSED = new ParentNode<INode>() {
42 private static final long serialVersionUID = 6155494069158034123L;
44 public void asyncRemoveNode(INode node) {
49 protected transient Map<String, INode> children = createChildMap();
50 private transient TLongObjectHashMap<String> childrenIdMap = new TLongObjectHashMap<>();
53 * A cached value for the root node of this parent node. This makes it
54 * possible to optimize {@link #getRootNode()} so that not all invocations
55 * go through the whole node hierarchy to find the root. This helps in
56 * making {@link ILookupService} located in the root node perform better and
57 * therefore be more useful.
61 protected transient volatile ParentNode<?> rootNodeCache;
63 protected Map<String, INode> createChildMap() {
64 return createChildMap(1);
67 protected Map<String, INode> createChildMap(int initialCapacity) {
68 // With JDK 1.8 HashMap is faster than Trove
69 return new HashMap<>(initialCapacity);
72 public final <TC> TC addNode(Class<TC> a) {
73 return addNode(java.util.UUID.randomUUID().toString(), a);
76 public <TC extends INode> TC addNode(String id, TC child) {
77 return addNodeInternal(id, child, true, true);
80 private <TC extends INode> TC addNodeInternal(String id, TC child, boolean init, boolean addToChildren) {
81 child.setParent(this);
83 children.put(id, child);
85 childrenIdMap.put(child.getId(), id);
93 public <TC extends INode> TC attachNode(String id, TC child) {
94 return addNodeInternal(id, child, false, true);
97 @SuppressWarnings("unchecked")
98 public <TC extends INode> TC detachNode(String id) {
99 INode child = children.remove(id);
102 childrenIdMap.remove(child.getId());
103 child.setParent(null);
108 public <TC> TC addNode(String id, Class<TC> a) {
109 return addNodeInternal0(id, a, true);
112 @SuppressWarnings("unchecked")
113 private <TC> TC addNodeInternal0(String id, Class<TC> a, boolean addToChildren) {
114 // a must be extended from Node
115 if(!Node.class.isAssignableFrom(a)) {
116 throw new IllegalArgumentException(a + " is not extended from org.simantics.scenegraph.Node");
120 child = (Node) a.newInstance();
121 } catch (InstantiationException e) {
122 throw new NodeException("Node " + Node.getSimpleClassName(a) + " instantiation failed, see exception for details.", e);
123 } catch (IllegalAccessException e) {
124 throw new NodeException("Node " + Node.getSimpleClassName(a) + " instantiation failed, see exception for details.", e);
126 return (TC) addNodeInternal(id, child, true, addToChildren);
129 @SuppressWarnings("unchecked")
130 public final <TC extends INode> TC getOrAttachNode(String id, TC a) {
131 return (TC) children.computeIfAbsent(id, key -> {
132 return (T) addNodeInternal(id, a, false, false);
136 @SuppressWarnings("unchecked")
137 public final <TC> TC getOrCreateNode(String id, Class<TC> a) {
138 return (TC) children.computeIfAbsent(id, key -> {
139 return (T) addNodeInternal0(id, a, false);
143 public final void removeNode(String id) {
144 INode child = children.remove(id);
146 removeNodeInternal(child, true);
149 public final void removeNode(INode child) {
150 String key = childrenIdMap.get(child.getId());
155 * This method removes and disposes all children of the node (and their
158 public final void removeNodes() {
159 boolean changed = children.size() > 0;
160 children.forEach((id, child) -> removeNodeInternal(child, false));
162 childrenIdMap.clear();
168 private void removeNodeInternal(INode child, boolean triggerChildrenChanged) {
170 if (child instanceof ParentNode<?>) {
171 ((ParentNode<?>) child).removeNodes();
174 child.setParent(null);
175 if (triggerChildrenChanged)
177 triggerPropertyChangeEvent(child);
182 * Invoked every time the set of child changes for a {@link ParentNode}.
183 * Extending implementations may override to perform their own actions, such
184 * as invalidating possible caches.
186 protected void childrenChanged() {
189 public abstract void asyncRemoveNode(INode node);
191 @SuppressWarnings("unchecked")
192 public T getNode(String id) {
193 return (T) children.get(id);
197 * @return the IDs of the child nodes as a collection. Returns internal
198 * state which must not be directly modified by client code!
200 public Collection<String> getNodeIds() {
201 return children.keySet();
205 * @return the collection of this node's children in an unspecified order
207 @SuppressWarnings("unchecked")
208 public Collection<T> getNodes() {
209 return (Collection<T>) children.values();
212 public int getNodeCount() {
213 return children.size();
217 * Recursively set the PropertyChangeListener
219 * @param propertyChangeListener
221 public void setPropertyChangeListener(PropertyChangeListener propertyChangeListener) {
222 this.propertyChangeListener = propertyChangeListener;
223 children.forEach((id, child) -> {
224 if(child instanceof ParentNode<?>) {
225 ((ParentNode<?>)child).setPropertyChangeListener(propertyChangeListener);
227 ((Node)child).propertyChangeListener = propertyChangeListener; // FIXME
233 public void cleanup() {
235 if (children != null) {
236 children.forEach((id, child) -> {
238 child.setParent(null);
241 childrenIdMap.clear();
243 childrenIdMap = null;
245 rootNodeCache = DISPOSED;
250 public void delete() {
255 // 1. Add children under parent
256 parent.appendChildren(children);
258 // 2. Clear child maps to prevent cleanup from deleting them in step 4.
260 childrenIdMap.clear();
262 // 3. Remove this node from parent
263 parent.unlinkChild(this);
265 // 4. Cleanup, this node is now disposed
270 * Helper method for delete()
273 protected void appendChildren(Map<String, INode> children) {
274 children.forEach((key, value) -> {
275 appendChildInternal(key, value);
279 protected void appendChild(String id, INode child) {
280 appendChildInternal(id, child);
283 private void appendChildInternal(String id, INode child) {
284 children.put(id, child);
285 childrenIdMap.put(child.getId(), id);
286 child.setParent(this);
287 triggerPropertyChangeEvent(child);
291 * Same as removeNode, but does not perform cleanup for the child
294 protected void unlinkChild(INode child) {
295 String id = childrenIdMap.remove(child.getId());
299 triggerPropertyChangeEvent(child);
303 private void triggerPropertyChangeEvent(INode child) {
304 // Send notify only if we are on server side (or standalone)
305 if (propertyChangeListener != null && location.equals(Location.LOCAL)) {
306 propertyChangeListener.propertyChange(new PropertyChangeEvent(this, "children["+child.getId()+"]", child.getClass(), UNLINK)); // "children" is a special field name
311 public String toString() {
312 return super.toString() + " [#child=" + children.size() + "]";
316 public ParentNode<?> getRootNode() {
317 // Note: using double-checked locking idiom with volatile keyword.
318 // Works with Java 1.5+
319 ParentNode<?> result = rootNodeCache;
320 if (result == DISPOSED)
323 if (result == null) {
324 synchronized (this) {
325 result = rootNodeCache;
326 if (result == null) {
327 if (parent != null) {
328 result = parent.getRootNode();
329 rootNodeCache = result;