]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/ParentNode.java
Documented difference of RuntimeEnvironmentRequest and Runti...quest2
[simantics/platform.git] / bundles / org.simantics.scenegraph / src / org / simantics / scenegraph / ParentNode.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
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
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.scenegraph;
13
14 import gnu.trove.map.hash.THashMap;
15
16 import java.beans.PropertyChangeEvent;
17 import java.beans.PropertyChangeListener;
18 import java.util.Collection;
19 import java.util.Collections;
20 import java.util.Map;
21
22 /**
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.
25  * 
26  * @param <T>
27  */
28 public abstract class ParentNode<T extends INode> extends Node {
29
30     private static final long                  serialVersionUID     = 8519410262849626534L;
31
32     public static final String                 EXISTING             = "#EXISTING#";
33     public static final String                 UNLINK               = "#UNLINK#";
34     public static final String                 NULL                 = "#NULL#";
35
36     protected static final String[]            EMPTY_STRING_ARRAY   = {};
37
38     @SuppressWarnings("rawtypes")
39     private static final Map                   DISPOSED_CHILDREN    = Collections.emptyMap();
40
41     /**
42      * A value used for {@link #rootNodeCache} to indicate the node has been
43      * disposed and the root node cache is to be considered indefinitely
44      * invalid.
45      */
46     protected static final ParentNode<?> DISPOSED = new ParentNode<INode>() {
47         private static final long serialVersionUID = 6155494069158034123L;
48         @Override
49         public void asyncRemoveNode(INode node) {
50             throw new Error();
51         }
52     };
53
54     protected transient Map<String, T>         children             = createChildMap();
55
56     /**
57      * A cached value for the root node of this parent node. This makes it
58      * possible to optimize {@link #getRootNode()} so that not all invocations
59      * go through the whole node hierarchy to find the root. This helps in
60      * making {@link ILookupService} located in the root node perform better and
61      * therefore be more useful.
62      * 
63      * @see #DISPOSED
64      */
65     protected transient volatile ParentNode<?> rootNodeCache;
66
67     protected Map<String, T> createChildMap() {
68         return new THashMap<String, T>(1);
69     }
70     
71     public final <TC> TC addNode(Class<TC> a) {
72         return addNode(java.util.UUID.randomUUID().toString(), a);
73     }
74
75     @SuppressWarnings("unchecked")
76     public <TC extends INode> TC addNode(String id, TC child) {
77
78         child.setParent(this);
79         children.put(id, (T)child);
80
81         child.init();
82         childrenChanged();
83         return (TC)child;
84         
85     }
86
87     @SuppressWarnings("unchecked")
88     public <TC extends INode> TC attachNode(String id, TC child) {
89
90         child.setParent(this);
91         children.put(id, (T)child);
92
93         child.attach();
94         childrenChanged();
95         return (TC)child;
96         
97     }
98
99     @SuppressWarnings("unchecked")
100     public <TC extends INode> TC detachNode(String id) {
101         T child = children.remove(id);
102         if (child == null)
103             return null;
104         child.setParent(null);
105         childrenChanged();
106         return (TC) child;
107     }
108
109     @SuppressWarnings("unchecked")
110     public <TC> TC addNode(String id, Class<TC> a) {
111         // a must be extended from Node
112         if(!Node.class.isAssignableFrom(a)) {
113             throw new IllegalArgumentException(a + " is not extended from org.simantics.scenegraph.Node");
114         }
115         INode child = null;
116         try {
117             child = (Node) a.newInstance();
118         } catch (InstantiationException e) {
119             throw new NodeException("Node " + Node.getSimpleClassName(a) + " instantiation failed, see exception for details.", e);
120         } catch (IllegalAccessException e) {
121             throw new NodeException("Node " + Node.getSimpleClassName(a) + " instantiation failed, see exception for details.", e);
122         }
123
124         child.setParent(this);
125         children.put(id, (T)child);
126
127         child.init();
128         childrenChanged();
129         return (TC)child;
130     }
131
132     @SuppressWarnings("unchecked")
133     public final <TC extends INode> TC getOrAttachNode(String id, TC a) {
134         synchronized (children) {
135             if (children.containsKey(id))
136                 return (TC) children.get(id);
137         }
138         return attachNode(id, a);
139     }
140     
141     @SuppressWarnings("unchecked")
142     public final <TC> TC getOrCreateNode(String id, Class<TC> a) {
143         synchronized (children) {
144             if (children.containsKey(id))
145                 return (TC) children.get(id);
146         }
147         return addNode(id, a);
148     }
149
150     public final void removeNode(String id) {
151         INode child = null;
152         synchronized (children) {
153             child = children.remove(id);
154         }
155         if (child != null) {
156             if (child instanceof ParentNode<?>) {
157                 ((ParentNode<?>) child).removeNodes();
158             }
159             child.cleanup();
160             child.setParent(null);
161             childrenChanged();
162             if (propertyChangeListener != null) {
163                 propertyChangeListener.propertyChange(new PropertyChangeEvent(this, "children["+child.getId()+"]", child.getClass(), NULL)); // "children" is a special field name
164             }
165         }
166     }
167
168     public final void removeNode(INode child) {
169         synchronized (children) {
170             String id = null;
171             // FIXME: damn slow, needs more data structure to be supported well
172             // One option would be to store the id<->node mappings in a BidiMap.
173             for (String tmp : children.keySet()) {
174                 if (children.get(tmp).equals(child)) {
175                     id = tmp;
176                     break;
177                 }
178             }
179             if(id == null) return;
180             children.remove(id);
181             childrenChanged();
182         }
183         if (child instanceof ParentNode<?>) {
184             ((ParentNode<?>) child).removeNodes();
185         }
186         child.cleanup();
187         child.setParent(null);
188
189         if (propertyChangeListener != null) {
190             propertyChangeListener.propertyChange(new PropertyChangeEvent(this, "children["+child.getId()+"]", child.getClass(), NULL)); // "children" is a special field name
191         }
192     }
193
194     /**
195      * This method removes and disposes all children of the node (and their
196      * children).
197      */
198     public final void removeNodes() {
199         synchronized (children) {
200             boolean changed = false;
201             String[] keys = children.keySet().toArray(EMPTY_STRING_ARRAY);
202             for (String key : keys) {
203                 INode child = children.remove(key);
204                 if (child != null) {
205                     changed = true;
206                     if (child instanceof ParentNode<?>) {
207                         ((ParentNode<?>) child).removeNodes();
208                     }
209                     child.cleanup();
210                     child.setParent(null);
211                     if (propertyChangeListener != null) {
212                         propertyChangeListener.propertyChange(new PropertyChangeEvent(this, "children["+child.getId()+"]", child.getClass(), NULL)); // "children" is a special field name
213                     }
214                 }
215             }
216             if (changed)
217                 childrenChanged();
218         }
219     }
220
221     /**
222      * Invoked every time the set of child changes for a {@link ParentNode}.
223      * Extending implementations may override to perform their own actions, such
224      * as invalidating possible caches.
225      */
226     protected void childrenChanged() {
227     }
228
229     public abstract void asyncRemoveNode(INode node);
230
231     public T getNode(String id) {
232         return children.get(id);
233     }
234
235     /**
236      * @return the IDs of the child nodes as a collection. Returns internal
237      *         state which must not be directly modified by client code!
238      */
239     public Collection<String> getNodeIds() {
240         return children.keySet();
241     }
242
243     /**
244      * @return the collection of this node's children in an unspecified order
245      */
246     public Collection<T> getNodes() {
247         return children.values();
248     }
249
250     public int getNodeCount() {
251         return children.size();
252     }
253
254     /**
255      * Recursively set the PropertyChangeListener
256      * 
257      * @param propertyChangeListener
258      */
259     public void setPropertyChangeListener(PropertyChangeListener propertyChangeListener) {
260         this.propertyChangeListener = propertyChangeListener;
261         synchronized(children) {
262             for(T t : children.values()) {
263                 INode child = t;
264                 if(child instanceof ParentNode<?>) {
265                     ((ParentNode<?>)child).setPropertyChangeListener(propertyChangeListener);
266                 } else {
267                     ((Node)child).propertyChangeListener = propertyChangeListener; // FIXME
268                 }
269             }
270         }
271     }
272
273     @SuppressWarnings("unchecked")
274     @Override
275     public void cleanup() {
276         retractMapping();
277         if (children != DISPOSED_CHILDREN) { 
278             synchronized(children) {
279                 for(T child : children.values()) {
280                     ((INode)child).cleanup();
281                     ((INode)child).setParent(null);
282                 }
283                 children.clear();
284                 children = DISPOSED_CHILDREN;
285                 childrenChanged();
286             }
287             rootNodeCache = DISPOSED;
288         }
289     }
290
291     @Override
292     public void delete() {
293         if(parent == null) {
294             return;
295         }
296
297         synchronized (children) {
298             // 1. Add children under parent
299             parent.appendChildren(children);
300
301             // 2. Clear children
302             children.clear();
303         }
304
305         // 3. Remove this node from parent
306         parent.unlinkChild(this);
307
308         // 4. Cleanup, this node is now disposed
309         cleanup();
310     }
311
312     /**
313      * Helper method for delete()
314      * @param children
315      */
316     @SuppressWarnings("unchecked")
317     protected void appendChildren(Map<String, ?> children) {
318         synchronized(this.children) {
319             for(String id : children.keySet()) {
320                 INode child = (INode)children.get(id);
321                 this.children.put(id, (T)child); // Hopefully cast works
322                 child.setParent(this);
323
324                 // Send notify only if we are on server side (or standalone)
325                 if (propertyChangeListener != null && location.equals(Location.LOCAL)) {
326                     propertyChangeListener.propertyChange(new PropertyChangeEvent(this, "children["+child.getId()+"]", null, EXISTING)); // "children" is a special field name
327                 }
328             }
329         }
330     }
331
332     @SuppressWarnings("unchecked")
333     protected void appendChild(String id, INode child) {
334         children.put(id, (T)child);
335         child.setParent(this);
336         // Send notify only if we are on server side (or standalone)
337         if (propertyChangeListener != null && location.equals(Location.LOCAL)) {
338             propertyChangeListener.propertyChange(new PropertyChangeEvent(this, "children["+(child).getId()+"]", null, EXISTING)); // "children" is a special field name
339         }
340     }
341
342     /**
343      * Same as removeNode, but does not perform cleanup for the child
344      * @param child
345      */
346     protected void unlinkChild(INode child) {
347         synchronized(children) {
348             String id = null;
349             // FIXME: damn slow, needs more data structure to be supported well
350             // One option would be to store the id<->node mappings in a BidiMap.
351             for(String tmp : children.keySet()) {
352                 if(children.get(tmp).equals(child)) {
353                     id = tmp;
354                     break;
355                 }
356             }
357             if(id == null) return;
358             children.remove(id);
359             childrenChanged();
360         }
361
362         if(propertyChangeListener != null && location.equals(Location.LOCAL)) {
363             propertyChangeListener.propertyChange(new PropertyChangeEvent(this, "children["+child.getId()+"]", child.getClass(), UNLINK)); // "children" is a special field name
364         }
365     }
366
367     @Override
368     public String toString() {
369         return super.toString() + " [#child=" + children.size() + "]";
370     }
371
372     @Override
373     public ParentNode<?> getRootNode() {
374         // Note: using double-checked locking idiom with volatile keyword.
375         // Works with Java 1.5+
376         ParentNode<?> result = rootNodeCache;
377         if (result == DISPOSED)
378             return null;
379
380         if (result == null) {
381             synchronized (this) {
382                 result = rootNodeCache;
383                 if (result == null) {
384                     if (parent != null) {
385                         result = parent.getRootNode();
386                         rootNodeCache = result;
387                     }
388                 }
389             }
390         }
391         return result;
392     }
393
394 }