]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/NodeUtil.java
Sync git svn branch with SVN repository r33144.
[simantics/platform.git] / bundles / org.simantics.scenegraph / src / org / simantics / scenegraph / utils / NodeUtil.java
1 /*******************************************************************************\r
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
3  * in Industry THTH ry.\r
4  * All rights reserved. This program and the accompanying materials\r
5  * are made available under the terms of the Eclipse Public License v1.0\r
6  * which accompanies this distribution, and is available at\r
7  * http://www.eclipse.org/legal/epl-v10.html\r
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.scenegraph.utils;\r
13 \r
14 import java.awt.AWTEvent;\r
15 import java.awt.Container;\r
16 import java.awt.event.MouseEvent;\r
17 import java.awt.event.MouseWheelEvent;\r
18 import java.awt.geom.AffineTransform;\r
19 import java.awt.geom.NoninvertibleTransformException;\r
20 import java.awt.geom.Point2D;\r
21 import java.awt.geom.Rectangle2D;\r
22 import java.io.PrintStream;\r
23 import java.lang.reflect.InvocationTargetException;\r
24 import java.lang.reflect.Method;\r
25 import java.util.ArrayList;\r
26 import java.util.Collection;\r
27 import java.util.HashSet;\r
28 import java.util.Iterator;\r
29 import java.util.List;\r
30 import java.util.Set;\r
31 import java.util.concurrent.TimeUnit;\r
32 import java.util.concurrent.locks.Condition;\r
33 import java.util.concurrent.locks.Lock;\r
34 import java.util.concurrent.locks.ReentrantLock;\r
35 \r
36 import org.simantics.scenegraph.IDynamicSelectionPainterNode;\r
37 import org.simantics.scenegraph.ILookupService;\r
38 import org.simantics.scenegraph.INode;\r
39 import org.simantics.scenegraph.INode.PropertySetter;\r
40 import org.simantics.scenegraph.ISelectionPainterNode;\r
41 import org.simantics.scenegraph.ParentNode;\r
42 import org.simantics.scenegraph.g2d.G2DParentNode;\r
43 import org.simantics.scenegraph.g2d.G2DSceneGraph;\r
44 import org.simantics.scenegraph.g2d.IG2DNode;\r
45 import org.simantics.scenegraph.g2d.events.EventDelegator;\r
46 import org.simantics.scenegraph.g2d.events.NodeEventHandler;\r
47 import org.simantics.scenegraph.g2d.events.SGMouseEvent;\r
48 import org.simantics.scenegraph.g2d.events.SGMouseWheelEvent;\r
49 import org.simantics.scenegraph.g2d.nodes.ConnectionNode;\r
50 import org.simantics.scenegraph.g2d.nodes.FlagNode;\r
51 import org.simantics.scenegraph.g2d.nodes.SingleElementNode;\r
52 import org.simantics.scl.runtime.function.Function1;\r
53 import org.simantics.scl.runtime.function.FunctionImpl1;\r
54 import org.simantics.utils.datastructures.Pair;\r
55 import org.simantics.utils.threads.IThreadWorkQueue;\r
56 \r
57 /**\r
58  * Utilities for debugging/printing the contents of a scenegraph.\r
59  * \r
60  * @author Tuukka Lehtonen\r
61  */\r
62 public final class NodeUtil {\r
63 \r
64     /**\r
65      * @param <T>\r
66      */\r
67     public static interface Filter<T> {\r
68         public boolean accept(T t);\r
69     }\r
70 \r
71     public static class PrefixFilter implements Filter<String> {\r
72         private final String prefix;\r
73         public PrefixFilter(String prefix) {\r
74             this.prefix = prefix;\r
75         }\r
76         @Override\r
77         public boolean accept(String t) {\r
78             return t.startsWith(prefix);\r
79         }\r
80     }\r
81 \r
82     /**\r
83      * The name of the sibling-node that is used to represent that a node is selected.\r
84      */\r
85     public static final String SELECTION_NODE_NAME = "selection";\r
86 \r
87     public static INode getNearestParentOfType(INode node, Class<?> clazz) {\r
88         ParentNode<?> parent = null;\r
89         while (true) {\r
90             parent = node.getParent();\r
91             if (parent == null)\r
92                 return node;\r
93             node = parent;\r
94             if (clazz.isInstance(node))\r
95                 return node;\r
96         }\r
97     }\r
98 \r
99     public static INode getRootNode(INode node) {\r
100         ParentNode<?> parent = null;\r
101         while (true) {\r
102             parent = node.getParent();\r
103             if (parent == null)\r
104                 return node;\r
105             node = parent;\r
106         }\r
107     }\r
108 \r
109     public static G2DSceneGraph getRootNode(IG2DNode node) {\r
110         INode root = getRootNode((INode) node);\r
111         return (G2DSceneGraph) root;\r
112     }\r
113 \r
114     public static G2DSceneGraph getPossibleRootNode(IG2DNode node) {\r
115         INode root = getRootNode((INode) node);\r
116         return (root instanceof G2DSceneGraph) ? (G2DSceneGraph) root : null;\r
117     }\r
118 \r
119     /**\r
120      * Method for seeking node from scenegraph by class\r
121      * \r
122      * @param <T>\r
123      * @param parent\r
124      * @param clazz\r
125      * @return\r
126      */\r
127     public static <T> T getNearestChildByClass(G2DParentNode parent, Class<T> clazz) {\r
128         return getNearestChildByClass(parent.getNodes(), clazz);\r
129     }\r
130 \r
131     /**\r
132      * Breadth-first-search implementation to be used by getNearestChildByClass method\r
133      * \r
134      * @param <T>\r
135      * @param nodes\r
136      * @param clazz\r
137      * @return\r
138      */\r
139     @SuppressWarnings("unchecked")\r
140     public static <T> T getNearestChildByClass(Collection<IG2DNode> nodes, Class<T> clazz) {\r
141         Collection<IG2DNode> list = null;\r
142         for (IG2DNode n : nodes) {\r
143             if (clazz.isInstance(n)) {\r
144                 return (T) n;\r
145             } else if (n instanceof G2DParentNode) {\r
146                 if (list == null)\r
147                     list = new ArrayList<IG2DNode>();\r
148                 list.addAll(((G2DParentNode)n).getNodes());\r
149             }\r
150         }\r
151         if (list == null || list.isEmpty()) return null;\r
152         return getNearestChildByClass(list, clazz);\r
153     }\r
154 \r
155     /**\r
156      * Tries to look for a child node from the specified node with the specified\r
157      * ID. Returns <code>null</code> if the specified node is a not a\r
158      * {@link ParentNode}.\r
159      * \r
160      * @param node\r
161      * @param id\r
162      * @return\r
163      */\r
164     public static INode getChildById(INode node, String id) {\r
165         if (node instanceof ParentNode<?>) {\r
166             return ((ParentNode<?>) node).getNode(id);\r
167         }\r
168         return null;\r
169     }\r
170 \r
171     /**\r
172      * Looks for the first child node of the specified node based on 2D Z-order.\r
173      * The specified node must be a {@link G2DParentNode} for the method to\r
174      * succeed.\r
175      * \r
176      * @param node the node to get for the first child from\r
177      * @return <code>null</code> if the specified node is not a\r
178      *         {@link G2DParentNode} or has no children.\r
179      */\r
180     public static INode getFirstChild(INode node) {\r
181         if (node instanceof G2DParentNode) {\r
182             G2DParentNode pn = (G2DParentNode) node;\r
183             IG2DNode[] sorted = pn.getSortedNodes();\r
184             if (sorted.length > 0)\r
185                 return sorted[0];\r
186         }\r
187         return null;\r
188     }\r
189 \r
190     /**\r
191      * Returns a single child node of the specified node or <code>null</code> if\r
192      * there are more than one or zero children.\r
193      * \r
194      * @param node the node to get a possible single child from\r
195      * @return single child node or <code>null</code> if specified node has more\r
196      *         than one or zero children\r
197      */\r
198     public static INode getPossibleChild(INode node) {\r
199         if (node instanceof ParentNode<?>) {\r
200             ParentNode<?> pn = (ParentNode<?>) node;\r
201             if (pn.getNodeCount() == 1)\r
202                 return pn.getNodes().iterator().next();\r
203         }\r
204         return null;\r
205     }\r
206 \r
207     /**\r
208      * Counts the depth of the specified node in its scene graph node tree.\r
209      * Depth 1 equals root level.\r
210      * \r
211      * @param node the node for which to count a depth\r
212      * @return the depth of the node\r
213      */\r
214     public static int getDepth(INode node) {\r
215         int result = 1;\r
216         ParentNode<?> parent = null;\r
217         while (true) {\r
218             parent = node.getParent();\r
219             if (parent == null)\r
220                 return result;\r
221             node = parent;\r
222             ++result;\r
223         }\r
224     }\r
225 \r
226     private static final void printSceneGraph(PrintStream stream, int indentLevel, INode node, String id) {\r
227         for (int i = 0; i < indentLevel; ++i)\r
228             stream.print("\t");\r
229         stream.print(node.getSimpleClassName());\r
230         if (id != null) {               \r
231                 String lookupId = tryLookupId(node);\r
232                 if (lookupId != null) {\r
233                         stream.print(" {" + id + ", lookupId = "+lookupId+"}");\r
234                 } else {\r
235                         stream.print(" {" + id + "}");\r
236                 }\r
237         }\r
238         stream.println(node);\r
239         if (node instanceof G2DParentNode) {\r
240             G2DParentNode parentNode = (G2DParentNode) node;\r
241             for (String cid : parentNode.getSortedNodesById()) {\r
242                 Object child = parentNode.getNode(cid);\r
243                 if (child instanceof INode)\r
244                     printSceneGraph(stream, indentLevel + 1, (INode) child, cid);\r
245             }\r
246         } else if (node instanceof ParentNode<?>) {\r
247             ParentNode<? extends INode> parentNode = (ParentNode<?>) node;\r
248             for (String cid : parentNode.getNodeIds()) {\r
249                 INode child = parentNode.getNode(cid);\r
250                 printSceneGraph(stream, indentLevel + 1, (INode) child);\r
251             }\r
252         }\r
253     }\r
254 \r
255     public static final void printSceneGraph(PrintStream stream, int indentLevel, INode node) {\r
256         String id = null;\r
257         ParentNode<?> parent = node.getParent();\r
258         if (parent != null) {\r
259             Collection<String> ids = parent.getNodeIds();\r
260             for (String i : ids) {\r
261                 INode n = parent.getNode(i);\r
262                 if (n == node) {\r
263                     id = i;\r
264                     break;\r
265                 }\r
266             }\r
267         }\r
268         printSceneGraph(stream, indentLevel, node, id);\r
269     }\r
270 \r
271     public static final void printSceneGraph(int indentLevel, INode node) {\r
272         printSceneGraph(System.out, indentLevel, node);\r
273     }\r
274 \r
275     public static final void printSceneGraph(INode node) {\r
276         printSceneGraph(System.out, 0, node);\r
277     }\r
278 \r
279     public static interface NodeProcedure<T> {\r
280         T execute(INode node, String id);\r
281     }\r
282 \r
283     /**\r
284      * @param node the node to iterate possible children for\r
285      * @param procedure invoked for each child node, if returns a\r
286      *        <code>non-null</code> value, the return value is collected into the result\r
287      *        list\r
288      * @return the list of collected children\r
289      */\r
290     public static final <T> List<T> forChildren(INode node, NodeProcedure<T> procedure) {\r
291         return forChildren(node, procedure, new ArrayList<T>());\r
292     }\r
293 \r
294     /**\r
295      * @param node the node to iterate possible children for\r
296      * @param procedure invoked for each child node, if returns a\r
297      *        <code>non-null</code> value, the node is collected into the result\r
298      *        list\r
299      * @param result the result list into which selected children are collected\r
300      *        or <code>null</code> to not collect\r
301      * @return the list of collected children or null if provided result list\r
302      *         was <code>null</code>\r
303      */\r
304     public static final <T> List<T> forChildren(INode node, NodeProcedure<T> procedure, List<T> result) {\r
305         if (node instanceof ParentNode<?>) {\r
306             ParentNode<?> pn = (ParentNode<?>) node;\r
307             if (node instanceof G2DParentNode) {\r
308                 G2DParentNode g2dpn = (G2DParentNode) node;\r
309                 for (String id : g2dpn.getSortedNodesById()) {\r
310                     INode n = pn.getNode(id);\r
311                     T t = procedure.execute(n, id);\r
312                     if (t != null && result != null)\r
313                         result.add(t);\r
314                 }\r
315             } else {\r
316                 for (String id : pn.getNodeIds()) {\r
317                     INode n = pn.getNode(id);\r
318                     T t = procedure.execute(n, id);\r
319                     if (t != null && result != null)\r
320                         result.add(t);\r
321                 }\r
322             }\r
323         }\r
324         return result;\r
325     }\r
326 \r
327     public static final int countTreeNodes(INode node) {\r
328         int result = 1;\r
329         if (node instanceof ParentNode<?>) {\r
330             ParentNode<? extends INode> pn = (ParentNode<?>) node;\r
331             Collection<? extends INode> ns = pn.getNodes();\r
332             for (INode n : ns) {\r
333                 result += countTreeNodes(n);\r
334             }\r
335         }\r
336         return result;\r
337     }\r
338 \r
339     public static final void printTreeNodes(INode node, StringBuilder builder) {\r
340         printTreeNodes(node, 0, builder);\r
341     }\r
342 \r
343     public static final void printTreeNodes(INode node, int indent, StringBuilder builder) {\r
344         for (int i = 0; i < indent; i++)\r
345             builder.append(" ");\r
346         builder.append(node.toString() + "\n");\r
347         if (node instanceof ParentNode<?>) {\r
348             ParentNode<? extends INode> pn = (ParentNode<?>) node;\r
349             Collection<? extends INode> ns = pn.getNodes();\r
350             for (INode n : ns) {\r
351                 printTreeNodes(n, indent+2, builder);\r
352             }\r
353         }\r
354     }\r
355 \r
356     public static final <T extends INode> Set<T> collectNodes(INode node, Class<T> clazz) {\r
357         Set<T> result = new HashSet<T>();\r
358         collectNodes(node, clazz, result);\r
359         return result;\r
360     }\r
361 \r
362     @SuppressWarnings("unchecked")\r
363     public static final <T extends INode> void collectNodes(INode node, Class<T> clazz, Set<T> result) {\r
364         if (clazz.isInstance(node))\r
365             result.add((T) node);\r
366         if (node instanceof ParentNode<?>) {\r
367             ParentNode<? extends INode> pn = (ParentNode<?>) node;\r
368             Collection<? extends INode> ns = pn.getNodes();\r
369             for (INode n : ns) {\r
370                 collectNodes(n, clazz, result);\r
371             }\r
372         }\r
373     }\r
374     \r
375     public static <T extends INode> T getSingleNode(INode node, Class<T> clazz) {\r
376         Set<T> all = collectNodes(node, clazz);\r
377         if(all.size() != 1) throw new RuntimeException("Expected exactly 1 instance of class " + clazz.getCanonicalName() + ", got " + all.size());\r
378         return (T)all.iterator().next();\r
379     }\r
380     \r
381     public static final boolean hasChildren(INode node) {\r
382         if (node instanceof ParentNode<?>) {\r
383             ParentNode<?> pn = (ParentNode<?>) node;\r
384             return !pn.getNodes().isEmpty();\r
385         }\r
386         return false;\r
387     }\r
388 \r
389     /**\r
390      * Look for a single scene graph node by its ID in a path under a specified\r
391      * node.\r
392      * \r
393      * @param parent the parent node under which to start looking\r
394      * @param idPath the node ID path\r
395      * @return <code>null</code> if node was not found\r
396      * @throws ClassCastException if the found node was not of the expected type\r
397      *         T extending INode\r
398      */\r
399     @SuppressWarnings("unchecked")\r
400     public static <T extends INode> T findNodeById(INode parent, String... idPath) {\r
401         INode n = parent;\r
402         for (int i = 0;; ++i) {\r
403             if (i >= idPath.length)\r
404                 return (T) n;\r
405             if (n instanceof ParentNode<?>) {\r
406                 n = ((ParentNode<?>) n).getNode(idPath[i]);\r
407             } else {\r
408                 return null;\r
409             }\r
410         }\r
411     }\r
412 \r
413     /**\r
414      * Tries to find out whether a node is selected or not.\r
415      * \r
416      * DISCLAIMER: this is a hack for the current\r
417      * org.simantics.g2d.diagram.participant.ElementPainter implementation that\r
418      * will stop working if the implementation changes.\r
419      * \r
420      * @param node\r
421      * @param ascendLimit the max. amount of steps towards parent nodes to take\r
422      *        while looking for selection information\r
423      * @return <code>true</code> if evidence of selection is found\r
424      */\r
425     public static boolean isSelected(INode node, int ascendLimit) {\r
426         int steps = 0;\r
427         ParentNode<?> pn = null;\r
428         if (node instanceof ParentNode<?>) {\r
429             pn = (ParentNode<?>) node;\r
430         } else {\r
431             pn = node.getParent();\r
432             ++steps;\r
433         }\r
434         for (; pn != null && steps <= ascendLimit; pn = pn.getParent(), ++steps) {\r
435             INode child = pn.getNode(SELECTION_NODE_NAME);\r
436             if (child != null)\r
437                 return true;\r
438         }\r
439         return false;\r
440     }\r
441 \r
442     public static Container findRootPane(INode node) {\r
443         G2DSceneGraph parent = findNearestParentNode(node, G2DSceneGraph.class);\r
444         if (parent == null)\r
445             return null;\r
446         return ((G2DSceneGraph) parent).getRootPane();\r
447     }\r
448 \r
449     private static boolean isSelectionPainter(INode node) {\r
450         if (node instanceof ISelectionPainterNode) {\r
451             if (node instanceof IDynamicSelectionPainterNode)\r
452                 return ((IDynamicSelectionPainterNode)node).showsSelection();\r
453             return true;\r
454         }\r
455         return false;\r
456     }\r
457 \r
458     /**\r
459      * @param elementNode\r
460      * @return\r
461      */\r
462     public static boolean needSelectionPaint(INode elementNode) {\r
463         // FIXME: there should be a cleaner way to implement this.\r
464 \r
465         if (isSelectionPainter(elementNode)) {\r
466 //          System.out.println("skipped selection painting for connection node child ISelectionPainterNode");\r
467             return false;\r
468         }\r
469 \r
470         if (elementNode instanceof ConnectionNode) {\r
471 //          System.out.println("connectionNode");\r
472             for (IG2DNode child : ((ConnectionNode) elementNode).getNodes()) {\r
473 //              System.out.println(" child " + child);\r
474                 if (isSelectionPainter(child)) {\r
475 //                  System.out.println("skipped selection painting for connection node child ISelectionPainterNode");\r
476                     return false;\r
477                 }\r
478                 if (child instanceof SingleElementNode) {\r
479                     for(IG2DNode child2 : ((SingleElementNode) child).getNodes()) {\r
480 //                      System.out.println(" child2 " + child2);\r
481                         if (isSelectionPainter(child2)) {\r
482 //                          System.out.println("skipped selection painting for edge ISelectionPainterNode");\r
483                             return false;\r
484                         }\r
485                     }\r
486                 }\r
487             }\r
488         } else if (elementNode instanceof SingleElementNode) {\r
489             for (INode child : ((SingleElementNode) elementNode).getNodes()) {\r
490                 if (isSelectionPainter(child))\r
491                     return false;\r
492             }\r
493         }\r
494 \r
495         return true;\r
496     }\r
497 \r
498     private static final String SET_THREAD_CALLBACKS_NAME = "CGLIB$SET_THREAD_CALLBACKS"; // If this method is found, the class is enhanced by cglib, thus does not contain annotations\r
499 \r
500     public static Method getSetterForProperty(String property, INode node) {\r
501         assert(node != null);\r
502         Class<?> cl = node.getClass();\r
503 \r
504         while(true) {\r
505             boolean isEnhanced = false;\r
506             for(Method method : cl.getMethods()) {\r
507                 if(method.isAnnotationPresent(PropertySetter.class)) {\r
508                     PropertySetter ann = method.getAnnotation(PropertySetter.class);\r
509                     if(ann.value().equals(property)) {\r
510                         return method;\r
511                     }\r
512                 } else if(method.getName().equals(SET_THREAD_CALLBACKS_NAME) && method.getGenericParameterTypes().length == 1) {\r
513                     // The class seems to be enhanced by cglib, hence annotations are not present. Check superclass for annotations..\r
514                     isEnhanced = true;\r
515                     cl = cl.getSuperclass();\r
516                     break; // We are not going to find any annotations, stop loop and try with the parent class\r
517                 }\r
518             }\r
519             if(!isEnhanced || cl == null) break;\r
520         }\r
521         return null;\r
522     }\r
523 \r
524     /**\r
525      * TODO: log exceptions for debug purposes\r
526      * \r
527      * @param property name of the property\r
528      * @param value    can be null..\r
529      * @param node\r
530      * @return\r
531      */\r
532     public static boolean setPropertyIfSupported(String property, Object value, INode node) {\r
533         Method setter = getSetterForProperty(property, node);\r
534         if(setter != null) {\r
535             Class<?> pc[] = setter.getParameterTypes();\r
536             if(pc.length == 1 && (value == null || pc[0].isAssignableFrom(value.getClass()))) {\r
537                 try {\r
538                     setter.invoke(node, value);\r
539                     return true;\r
540                 } catch (IllegalArgumentException e) {\r
541                     // TODO Auto-generated catch block\r
542                     e.printStackTrace();\r
543                 } catch (IllegalAccessException e) {\r
544                     // TODO Auto-generated catch block\r
545                     e.printStackTrace();\r
546                 } catch (InvocationTargetException e) {\r
547                     // TODO Auto-generated catch block\r
548                     e.getCause().printStackTrace();\r
549                 }\r
550             } else {\r
551 \r
552                 if(pc.length > 0) {\r
553                     System.err.println("Method " + setter.getName() + " expects " + pc[0].getCanonicalName() + " (got " + value.getClass().getCanonicalName() + ").");\r
554                 }\r
555 \r
556             }\r
557         }\r
558 \r
559         return false;\r
560     }\r
561 \r
562     public static INode findChildById(ParentNode<?> parent, String key) {\r
563         INode result = parent.getNode(key);\r
564         if (result != null)\r
565             return result;\r
566 \r
567         for (String entry : parent.getNodeIds()) {\r
568             if (entry.startsWith(key))\r
569                 return parent.getNode(key);\r
570         }\r
571 \r
572         for (INode node : parent.getNodes()) {\r
573             if (node instanceof ParentNode) {\r
574                 result = findChildById((ParentNode<?>) node, key);\r
575                 if (result != null)\r
576                     return result;\r
577             }\r
578         }\r
579 \r
580         return null;\r
581     }\r
582 \r
583     \r
584     private static int getSegmentEnd(String suffix) {\r
585         int pos;\r
586         for(pos=1;pos<suffix.length();++pos) {\r
587             char c = suffix.charAt(pos);\r
588             if(c == '/' || c == '#')\r
589                 break;\r
590         }\r
591         return pos;\r
592     }\r
593     \r
594     public static String decodeString(String string) {\r
595         return string;\r
596     }\r
597     \r
598     public static INode browsePossible(INode node, String suffix) {\r
599         if(suffix.isEmpty()) \r
600             return node;        \r
601         switch(suffix.charAt(0)) {\r
602         case '.': {\r
603                 INode parent = node.getParent();\r
604             if(parent == null)\r
605                 return null;\r
606             return browsePossible(parent, suffix.substring(1));\r
607         }\r
608         case '#': {\r
609             /*int segmentEnd = getSegmentEnd(suffix);\r
610             Variable property = getPossibleProperty(graph, \r
611                     decodeString(suffix.substring(1, segmentEnd)));\r
612             if(property == null) \r
613                 return null;\r
614             return property.browsePossible(graph, suffix.substring(segmentEnd));*/\r
615                 return node;\r
616         }\r
617         case '/': {\r
618             int segmentEnd = getSegmentEnd(suffix);\r
619             INode child = findChildById((ParentNode<?>)node, decodeString(suffix.substring(1, segmentEnd)));\r
620             if(child == null) \r
621                 return null;\r
622             return browsePossible(child, suffix.substring(segmentEnd));\r
623         }\r
624         default:\r
625             return null;\r
626         }\r
627     }    \r
628     \r
629     public static Pair<INode, String> browsePossibleReference(INode node, String suffix) {\r
630         if(suffix.isEmpty()) \r
631             throw new RuntimeException("Did not find a reference.");        \r
632         switch(suffix.charAt(0)) {\r
633         case '.': {\r
634                 INode parent = node.getParent();\r
635             if(parent == null)\r
636                 return null;\r
637             return browsePossibleReference(parent, suffix.substring(1));\r
638         }\r
639         case '#': {\r
640                 return Pair.make(node, suffix.substring(1));\r
641         }\r
642         case '/': {\r
643             int segmentEnd = getSegmentEnd(suffix);\r
644             INode child = findChildById((ParentNode<?>)node, decodeString(suffix.substring(1, segmentEnd)));\r
645             if(child == null) \r
646                 return null;\r
647             return browsePossibleReference(child, suffix.substring(segmentEnd));\r
648         }\r
649         default:\r
650             return null;\r
651         }\r
652     }    \r
653 \r
654     public static INode findChildByPrefix(G2DParentNode parent, String prefix) {\r
655         INode result = parent.getNode(prefix);\r
656         if (result != null)\r
657             return result;\r
658 \r
659         for (String entry : parent.getNodeIds()) {\r
660             if (entry.startsWith(prefix))\r
661                 return parent.getNode(entry);\r
662         }\r
663 \r
664         for (IG2DNode node : parent.getNodes()) {\r
665             if (node instanceof G2DParentNode) {\r
666                 result = findChildByPrefix((G2DParentNode) node, prefix);\r
667                 if (result != null)\r
668                     return result;\r
669             }\r
670         }\r
671 \r
672         return null;\r
673     }\r
674 \r
675     /**\r
676      * @param parent\r
677      * @param prefix\r
678      * @return\r
679      */\r
680     public static Collection<String> filterDirectChildIds(ParentNode<?> parent, String prefix) {\r
681         return filterDirectChildIds(parent, new PrefixFilter(prefix));\r
682     }\r
683 \r
684     /**\r
685      * @param parent\r
686      * @param prefix\r
687      * @return\r
688      */\r
689     public static Collection<String> filterDirectChildIds(ParentNode<?> parent, Filter<String> childFilter) {\r
690         Collection<String> childIds = parent.getNodeIds();\r
691         ArrayList<String> result = new ArrayList<String>(childIds.size());\r
692 \r
693         for (String id : childIds)\r
694             if (childFilter.accept(id))\r
695                 result.add(id);\r
696 \r
697         return result;\r
698     }\r
699 \r
700     /**\r
701      * @param parent\r
702      * @param prefix\r
703      * @return\r
704      */\r
705     public static Collection<INode> filterDirectChildren(ParentNode<?> parent, Filter<String> childFilter) {\r
706         Collection<String> childIds = parent.getNodeIds();\r
707         ArrayList<INode> result = new ArrayList<INode>(childIds.size());\r
708 \r
709         for (String id : childIds)\r
710             if (childFilter.accept(id))\r
711                 result.add( parent.getNode(id) );\r
712 \r
713         return result;\r
714     }\r
715 \r
716     /**\r
717      * @param node\r
718      * @return the lookup service for the specified node\r
719      * @throws UnsupportedOperationException if ILookupService is not available\r
720      */\r
721     public static ILookupService getLookupService(INode node) {\r
722         ParentNode<?> root = node.getRootNode();\r
723         if (!(root instanceof ILookupService))\r
724             throw new UnsupportedOperationException("ILookupService not supported by root node " + root + " attained from " + node);\r
725         return (ILookupService) root;\r
726     }\r
727 \r
728     /**\r
729      * @param node\r
730      * @return <code>null</code> if lookup service is not available\r
731      */\r
732     public static ILookupService tryGetLookupService(INode node) {\r
733         ParentNode<?> root = node.getRootNode();\r
734         return root instanceof ILookupService ? (ILookupService) root : null;\r
735     }\r
736 \r
737     /**\r
738      * @param node\r
739      * @param id\r
740      * @return <code>null</code> if lookup failed, i.e. mapping does not exist\r
741      * @throws UnsupportedOperationException if lookup is not supported\r
742      * @see #getLookupService(INode)\r
743      * @see ILookupService\r
744      */\r
745     public static INode lookup(INode node, String id) {\r
746         ILookupService lookup = getLookupService(node);\r
747         return lookup.lookupNode(id);\r
748     }\r
749 \r
750     /**\r
751      * @param node\r
752      * @param id\r
753      * @return <code>null</code> if lookup not supported or lookup failed\r
754      * @see #tryGetLookupService(INode)\r
755      * @see ILookupService\r
756      */\r
757     public static INode tryLookup(INode node, String id) {\r
758         ILookupService lookup = tryGetLookupService(node);\r
759         return lookup != null ? lookup.lookupNode(id) : null;\r
760     }\r
761 \r
762     /**\r
763      * @param node\r
764      * @param id\r
765      * @param clazz\r
766      * @return <code>null</code> if lookup failed, i.e. mapping does not exist\r
767      * @throws UnsupportedOperationException if lookup is not supported\r
768      * @throws ClassCastException if the found node cannot be cast to the\r
769      *         specified class\r
770      * @see #getLookupService(INode)\r
771      * @see ILookupService\r
772      */\r
773     public static <T> T lookup(INode node, String id, Class<T> clazz) {\r
774         ILookupService lookup = getLookupService(node);\r
775         INode found = lookup.lookupNode(id);\r
776         return found != null ? clazz.cast(found) : null;\r
777     }\r
778 \r
779     /**\r
780      * @param node\r
781      * @param id\r
782      * @return <code>null</code> if lookup not supported or lookup failed\r
783      * @see #tryGetLookupService(INode)\r
784      * @see ILookupService\r
785      */\r
786     public static <T> T tryLookup(INode node, String id, Class<T> clazz) {\r
787         ILookupService lookup = tryGetLookupService(node);\r
788         if (lookup == null)\r
789             return null;\r
790         INode found = lookup.lookupNode(id);\r
791         return found != null ? clazz.cast(found) : null;\r
792     }\r
793 \r
794     /**\r
795      * @param node\r
796      * @param id\r
797      * @return <code>null</code> if lookup failed, i.e. mapping does not exist\r
798      * @throws UnsupportedOperationException if lookup is not supported\r
799      * @see #getLookupService(INode)\r
800      * @see ILookupService\r
801      */\r
802     public static String lookupId(INode node) {\r
803         ILookupService lookup = getLookupService(node);\r
804         return lookup.lookupId(node);\r
805     }\r
806 \r
807     /**\r
808      * @param node\r
809      * @param id\r
810      * @return <code>null</code> if lookup not supported or lookup failed\r
811      * @see #tryGetLookupService(INode)\r
812      * @see ILookupService\r
813      */\r
814     public static String tryLookupId(INode node) {\r
815         ILookupService lookup = tryGetLookupService(node);\r
816         return lookup != null ? lookup.lookupId(node) : null;\r
817     }\r
818 \r
819     /**\r
820      * Map the specified node to the specified ID in the {@link ILookupService}\r
821      * provided by the root node of the specified node.\r
822      * \r
823      * @param node\r
824      * @param id\r
825      * @throws UnsupportedOperationException if {@link ILookupService} is not\r
826      *         available for the specified node\r
827      * @see #getLookupService(INode)\r
828      * @see ILookupService\r
829      */\r
830     public static void map(INode node, String id) {\r
831         getLookupService(node).map(id, node);\r
832     }\r
833 \r
834     /**\r
835      * Remove possible ILookupService mapping for the specified node.\r
836      * \r
837      * @param node the node to try to remove mappings for\r
838      * @return mapped ID or <code>null</code> if no mapping existed\r
839      * @throws UnsupportedOperationException if {@link ILookupService} is not\r
840      *         available for the specified node\r
841      * @see ILookupService\r
842      * @see #getLookupService(INode)\r
843      */\r
844     public static String unmap(INode node) {\r
845         return getLookupService(node).unmap(node);\r
846     }\r
847 \r
848     /**\r
849      * Try to remove possible ILookupService mapping for the specified node.\r
850      * \r
851      * @param node the node to try to remove mappings for\r
852      * @return mapped ID or <code>null</code> if {@link ILookupService} is not\r
853      *         supported or no mapping existed\r
854      * @see ILookupService\r
855      * @see #tryGetLookupService(INode)\r
856      */\r
857     public static String tryUnmap(INode node) {\r
858         ILookupService lookup = tryGetLookupService(node);\r
859         return lookup != null ? lookup.unmap(node) : null;\r
860     }\r
861 \r
862     public static EventDelegator getEventDelegator(INode node) {\r
863         ParentNode<?> n = node.getRootNode();\r
864         if (n instanceof G2DSceneGraph) {\r
865             return ((G2DSceneGraph) n).getEventDelegator();\r
866         }\r
867         return null;\r
868     }\r
869 \r
870     public static NodeEventHandler getNodeEventHandler(INode node) {\r
871         ParentNode<?> n = node.getRootNode();\r
872         return (n instanceof G2DSceneGraph) ? ((G2DSceneGraph) n).getEventHandler() : null;\r
873 //        INodeEventHandlerProvider provider = findNearestParentNode(node, INodeEventHandlerProvider.class);\r
874 //        return provider != null ? provider.getEventHandler() : null;\r
875     }\r
876 \r
877     public static AWTEvent transformEvent(AWTEvent event, IG2DNode node) {\r
878         if (event instanceof MouseEvent) {\r
879             // Find node transform..\r
880             AffineTransform transform = getGlobalToLocalTransform(node, null);\r
881             if (transform == null) {\r
882                 System.err.println("WARNING: Non-invertible transform for node: " + node);\r
883                 return event;\r
884             }\r
885             MouseEvent me = (MouseEvent)event;\r
886             // Use double coordinates if available\r
887             Point2D p = new Point2D.Double((double)me.getX(), (double)me.getY());\r
888             transform.transform(p, p);\r
889 \r
890             MouseEvent e = null;\r
891             // Giving event.getSource() as a parameter for the new events will cause major delay for the event instantiation, hence dummy component is used\r
892             if (event instanceof MouseWheelEvent) {\r
893                 e = new SGMouseWheelEvent(new DummyComponent(), me.getID(), me.getWhen(), me.getModifiers(), p.getX(), p.getY(), me.getClickCount(), me.isPopupTrigger(), ((MouseWheelEvent)me).getScrollType(), ((MouseWheelEvent)me).getScrollAmount(), ((MouseWheelEvent)me).getWheelRotation(), me);\r
894             } else {\r
895                 e = new SGMouseEvent(new DummyComponent(), me.getID(), me.getWhen(), me.getModifiers(), p.getX(), p.getY(), me.getClickCount(), me.isPopupTrigger(), me.getButton(), me);\r
896             }\r
897             return e;\r
898         }\r
899         return event;\r
900     }\r
901 \r
902     private static final boolean DEBUG_BOUNDS = false;\r
903 \r
904     private static Rectangle2D getLocalBoundsImpl(INode node, Function1<INode, Boolean> filter, int indent) {\r
905         if (node instanceof IG2DNode) {\r
906             if (node instanceof G2DParentNode) {\r
907                 G2DParentNode pNode = (G2DParentNode)node;\r
908                 Iterator<IG2DNode> it = pNode.getNodes().iterator();\r
909                 if (!it.hasNext())\r
910                     return null;\r
911                 Rectangle2D bounds = null;\r
912                 while (it.hasNext()) {\r
913                     IG2DNode next = it.next();\r
914                     if (filter != null && !filter.apply(next))\r
915                         continue;\r
916 \r
917                     Rectangle2D bl = getLocalBoundsImpl(next, filter, indent+2);\r
918 \r
919                     if(DEBUG_BOUNDS) {\r
920                         for(int i=0;i<indent;i++) System.err.print(" ");\r
921                         System.err.println("+getLocalBoundsImpl " + next  + " => " + bl);\r
922                     }\r
923 \r
924                     if(bl != null) {\r
925                         if(bounds == null) {\r
926                             bounds = next.localToParent(bl.getFrame());\r
927                         } else {\r
928                             bounds.add(next.localToParent(bl));\r
929                         }\r
930                     }\r
931                 }\r
932 \r
933                 if(DEBUG_BOUNDS) {\r
934                     for(int i=0;i<indent;i++) System.err.print(" ");\r
935                     System.err.println("=getLocalBoundsImpl " + node  + " => " + bounds);\r
936                 }\r
937 \r
938                 return bounds;\r
939             } else {\r
940                 Rectangle2D result = ((IG2DNode)node).getBoundsInLocal(true);\r
941                 if(result != null) {\r
942                     if(DEBUG_BOUNDS) {\r
943                         for(int i=0;i<indent;i++) System.err.print(" ");\r
944                         System.err.println("=getLocalBoundsImpl " + node  + " => " + result);\r
945                     }\r
946                     return result;\r
947                 }\r
948             }\r
949         }\r
950         return null;\r
951     }\r
952 \r
953     public static Rectangle2D getLocalBounds(INode node) {\r
954         return getLocalBoundsImpl(node, null, 0);\r
955     }\r
956 \r
957     public static Rectangle2D getLocalBounds(INode node, final Set<INode> excluding) {\r
958         return getLocalBoundsImpl(node, new FunctionImpl1<INode, Boolean>() {\r
959 \r
960                         @Override\r
961                         public Boolean apply(INode node) {\r
962                                 return !excluding.contains(node);\r
963                         }\r
964         }, 0);\r
965     }\r
966 \r
967     public static Rectangle2D getLocalBounds(INode node, final Class<?> excluding) {\r
968         return getLocalBoundsImpl(node, new FunctionImpl1<INode, Boolean>() {\r
969 \r
970                         @Override\r
971                         public Boolean apply(INode node) {\r
972                                 return !excluding.isInstance(node);\r
973                         }\r
974         }, 0);\r
975     }\r
976 \r
977     public static Rectangle2D getLocalElementBounds(INode node) {\r
978         if(node instanceof ConnectionNode) {\r
979             return getLocalBounds(node);\r
980         } else if(node instanceof SingleElementNode) {\r
981             INode image = NodeUtil.findChildByPrefix((SingleElementNode)node, "composite_image");\r
982             if (image == null)\r
983                 image = NodeUtil.findChildByPrefix((SingleElementNode) node, "text");\r
984             if (image == null)\r
985                 image = NodeUtil.getNearestChildByClass((SingleElementNode) node, FlagNode.class);\r
986             if (image != null)\r
987                 return getLocalElementBounds(image);\r
988             else\r
989                 return getLocalBounds(node);\r
990         } else {\r
991             return getLocalBounds(node);\r
992         }\r
993     }\r
994 \r
995     public static <T> T findNearestParentNode(INode node, Class<T> ofClass) {\r
996         ParentNode<?> parent = null;\r
997         while (true) {\r
998             parent = node.getParent();\r
999             if (parent == null)\r
1000                 return null;\r
1001             if (ofClass.isInstance(parent))\r
1002                 return ofClass.cast(parent);\r
1003             node = parent;\r
1004         }\r
1005     }\r
1006  \r
1007     private static class PendingTester implements Runnable {\r
1008 \r
1009         private boolean             pending     = true;\r
1010         private final G2DSceneGraph sg;\r
1011 \r
1012         private final Lock          pendingLock = new ReentrantLock();\r
1013         private final Condition     pendingSet  = pendingLock.newCondition();\r
1014 \r
1015         public PendingTester(G2DSceneGraph sg) {\r
1016             this.sg = sg;\r
1017         }\r
1018 \r
1019         @Override\r
1020         public void run() {\r
1021             pendingLock.lock();\r
1022             try {\r
1023                 pending = sg.isPending();\r
1024                 pendingSet.signalAll();\r
1025             } finally {\r
1026                 pendingLock.unlock();\r
1027             }\r
1028         }\r
1029 \r
1030         public boolean isPending() {\r
1031             return pending;\r
1032         }\r
1033 \r
1034         public void await() {\r
1035             pendingLock.lock();\r
1036             try {\r
1037                 if (pending)\r
1038                     pendingSet.await(10, TimeUnit.MILLISECONDS);\r
1039             } catch (InterruptedException e) {\r
1040                 // Ignore.\r
1041             } finally {\r
1042                 pendingLock.unlock();\r
1043             }\r
1044         }\r
1045 \r
1046     }\r
1047 \r
1048     public static void waitPending(IThreadWorkQueue thread, G2DSceneGraph sg) {\r
1049         // Wait for 30s by default\r
1050         waitPending(thread, sg, 30000);\r
1051     }\r
1052 \r
1053     /**\r
1054      * Synchronously waits until the the specified scene graph is no longer in\r
1055      * pending state.\r
1056      * \r
1057      * @param thread the thread to schedule pending checks into\r
1058      * @param sg the scene graph to wait upon\r
1059      */\r
1060     public static void waitPending(IThreadWorkQueue thread, G2DSceneGraph sg, int timeoutMs) {\r
1061         PendingTester tester = new PendingTester(sg);\r
1062         long start = System.currentTimeMillis();\r
1063         while (tester.isPending()) {\r
1064             thread.asyncExec(tester);\r
1065             if (tester.isPending())\r
1066                 tester.await();\r
1067             long duration = System.currentTimeMillis() - start;\r
1068             if(duration > timeoutMs)\r
1069                 throw new IllegalStateException("Timeout in resolving pending nodes.");\r
1070         }\r
1071     }\r
1072 \r
1073     public static void increasePending(INode node) {\r
1074         G2DSceneGraph sg = (G2DSceneGraph) node.getRootNode();\r
1075         if(sg != null)\r
1076                 sg.increasePending(node);\r
1077     }\r
1078 \r
1079     public static void decreasePending(INode node) {\r
1080         G2DSceneGraph sg = (G2DSceneGraph) node.getRootNode();\r
1081         if(sg != null)\r
1082                 sg.decreasePending(node);\r
1083     }\r
1084 \r
1085     // TRANSFORMATIONS\r
1086 \r
1087     public static AffineTransform getLocalToGlobalTransform(IG2DNode node, AffineTransform result) {\r
1088         result.setToIdentity();\r
1089         ParentNode<?> parent = node.getParent();\r
1090         while (parent != null) {\r
1091             result.preConcatenate(((IG2DNode) parent).getTransform());\r
1092             parent = parent.getParent();\r
1093         }\r
1094         return result;\r
1095     }\r
1096 \r
1097     public static AffineTransform getLocalToGlobalTransform(IG2DNode node) {\r
1098         return getLocalToGlobalTransform(node, new AffineTransform());\r
1099     }\r
1100 \r
1101     public static AffineTransform getGlobalToLocalTransform(IG2DNode node) throws NoninvertibleTransformException {\r
1102         AffineTransform transform = getLocalToGlobalTransform(node);\r
1103         transform.invert();\r
1104         return transform;\r
1105     }\r
1106 \r
1107     public static AffineTransform getGlobalToLocalTransform(IG2DNode node, AffineTransform returnIfNonInvertible) {\r
1108         AffineTransform transform = getLocalToGlobalTransform(node);\r
1109         try {\r
1110             transform.invert();\r
1111             return transform;\r
1112         } catch (NoninvertibleTransformException e) {\r
1113             return returnIfNonInvertible;\r
1114         }\r
1115     }\r
1116 \r
1117     public static Point2D worldToLocal(IG2DNode local, Point2D pt, Point2D pt2) {\r
1118         AffineTransform at = getGlobalToLocalTransform(local, null);\r
1119         if (at == null) {\r
1120                 pt2.setLocation(pt);\r
1121             return pt2;\r
1122         }\r
1123         return at.transform(pt, pt2);\r
1124     }\r
1125 \r
1126     public static Point2D localToWorld(IG2DNode local, Point2D pt, Point2D pt2) {\r
1127         AffineTransform at = getLocalToGlobalTransform(local);\r
1128         return at.transform(pt, pt2);\r
1129     }\r
1130 \r
1131     public static String getNodeName(INode nn) {\r
1132         INode node = nn.getParent();\r
1133         ParentNode<?> pn = (ParentNode<?>) node;\r
1134         if (node instanceof G2DParentNode) {\r
1135             G2DParentNode g2dpn = (G2DParentNode) node;\r
1136             for (String id : g2dpn.getSortedNodesById()) \r
1137             {\r
1138                 INode n = pn.getNode(id);\r
1139                 if ( nn == n ) {\r
1140                         return id;\r
1141                 }\r
1142             }\r
1143         }\r
1144         return null;\r
1145     }\r
1146 \r
1147     /**\r
1148      * For asking whether specified parent node really is a parent of the\r
1149      * specified child node.\r
1150      * \r
1151      * @param parent\r
1152      *            the parent\r
1153      * @param child\r
1154      *            alleged child of parent\r
1155      * @return <code>true</code> if parent really is a parent of child\r
1156      */\r
1157     public static boolean isParentOf(INode parent, INode child) {\r
1158         while (true) {\r
1159             if (parent == child)\r
1160                 return true;\r
1161             child = child.getParent();\r
1162             if (child == null)\r
1163                 return false;\r
1164         }\r
1165     }\r
1166 \r
1167 }\r