]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.g2d/src/org/simantics/g2d/canvas/impl/AbstractCanvasParticipant.java
Merge "Adding more detailed message to thrown exceptions in SyncElementFactory"
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / canvas / impl / AbstractCanvasParticipant.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.g2d.canvas.impl;\r
13 \r
14 import java.lang.reflect.Field;\r
15 \r
16 import org.simantics.g2d.canvas.ICanvasContext;\r
17 import org.simantics.g2d.canvas.ICanvasParticipant;\r
18 import org.simantics.g2d.canvas.IContentContext;\r
19 import org.simantics.g2d.canvas.impl.DependencyReflection.ReferenceDefinition;\r
20 import org.simantics.g2d.canvas.impl.DependencyReflection.ReferenceType;\r
21 import org.simantics.g2d.canvas.impl.HintReflection.HintListenerDefinition;\r
22 import org.simantics.g2d.canvas.impl.SGNodeReflection.CanvasSGNodeDefinition;\r
23 import org.simantics.scenegraph.g2d.events.EventHandlerReflection;\r
24 import org.simantics.scenegraph.g2d.events.IEventHandler;\r
25 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandlerDefinition;\r
26 import org.simantics.utils.datastructures.context.IContext;\r
27 import org.simantics.utils.datastructures.context.IContextListener;\r
28 import org.simantics.utils.datastructures.hints.HintContext;\r
29 import org.simantics.utils.datastructures.hints.IHintContext;\r
30 import org.simantics.utils.datastructures.hints.IHintStack;\r
31 import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
32 import org.simantics.utils.datastructures.prioritystack.IPriorityStack;\r
33 import org.simantics.utils.threads.IThreadWorkQueue;\r
34 import org.simantics.utils.threads.ThreadUtils;\r
35 \r
36 \r
37 /**\r
38  * AbstractCanvasParticipant is base implementation for canvas participants.\r
39  * There is an assertion that states AbstractCanvasParticipant can be added\r
40  * only once to a canvas.\r
41  * \r
42  * <p>\r
43  * There is convenience mechanism for adding painter and event handler\r
44  * listeners. Subclasses create listeners with usage of Painter and\r
45  * EventHandler annotations. Listeners are automatically added and\r
46  * removed to/from canvas context.\r
47  * </p>\r
48  * \r
49  * <p>\r
50  * Example:\r
51  * </p>\r
52  * <blockquote><pre>\r
53  *   @EventHandler(priority=200)\r
54  *   public boolean handleEvent(Event e) {\r
55  *       return false;\r
56  *   }\r
57  * \r
58  *   @EventHandler(priority=400)\r
59  *   public boolean handleMousePressEvent(MouseButtonPressedEvent e) {\r
60  *       return false;\r
61  *   }\r
62  * \r
63  *   IG2DNode node;\r
64  * \r
65  *   @SGInit\r
66  *   public void initSG(G2DParentNode parent) {\r
67  *       // Insert a node into the scene graph rendered by the canvas\r
68  *       // this participant is attached to and initialize it to render\r
69  *       // something. Later on, to modify what the node will render, just\r
70  *       // update the node's attributes.\r
71  *       node = parent.addNode("myNode", MyNode.class);\r
72  *       node.setZIndex(Integer.MIN_VALUE);\r
73  *   }\r
74  * \r
75  *   @SGCleanup\r
76  *   public void cleanup() {\r
77  *       // Remove our node from the scene graph where it belonged.\r
78  *       // The node must not be used after this anymore.\r
79  *       if (node != null) {\r
80  *           node.remove();\r
81  *           node = null;\r
82  *       }\r
83  *   }\r
84  * </pre></blockquote>\r
85  * \r
86  * <p>\r
87  * Local fields are automatically assigned with ICanvasParticipant instances if they\r
88  * are annotated with either <code>@Dependency</code> or <code>@Reference</code> annotation tag.\r
89  * <code>@Depedency</code> is a requirement, <code>@Reference</code> is optional.\r
90  * \r
91  * assertDependencies() verifies that dependencies are satisfied.\r
92  * Local depsSatisfied field is true when dependencies are satisfied.\r
93  * \r
94  * <p>\r
95  * Example:\r
96  * </p>\r
97  * <blockquote><pre>\r
98  *   class MyParticipant implements ICanvasParticipant {\r
99  *       @Reference MouseMonitor mouseMonitor;\r
100  *       @Dependency TimeParticipant timeParticipant;\r
101  *\r
102  *       @Painter(priority=100)\r
103  *       public void paint(GraphicsContext gc) {\r
104  *           assertDependencies(); // timeParticipant != null\r
105  * \r
106  *           timeParticipant.doSomething();\r
107  * \r
108  *           if (mouseMonitor!=null) doSomethingElse();\r
109  *       }\r
110  *   }</pre></blockquote>\r
111  * \r
112  * <p>\r
113  * Hint listener annotation.\r
114  * </p>\r
115  * \r
116  * <p>\r
117  * Example:\r
118  * </p>\r
119  * <blockquote><pre>\r
120  *   @HintListener(Class=Hints.class, Field="KEY_CANVAS_TRANSFORM")\r
121  *   public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {\r
122  *       ...\r
123  *   }\r
124  *   @HintListener(Class=Hints.class, Field="KEY_CANVAS_TRANSFORM")\r
125  *   public void hintRemoved(IHintObservable sender, Key key, Object oldValue) {\r
126  *       ...\r
127  *   }\r
128  * </pre></blockquote>\r
129  * \r
130  * @author Toni Kalajainen\r
131  */\r
132 public abstract class AbstractCanvasParticipant implements ICanvasParticipant {\r
133 \r
134     /** The interactor/canvas context */\r
135     private ICanvasContext context;\r
136 \r
137     /** The thread used in the context */\r
138     private IThreadWorkQueue thread;\r
139 \r
140     /** the local hint context */\r
141     protected IHintContext localHintCtx = null;\r
142     protected int localPriority = 0;\r
143 \r
144     /** wrapped local hint context. reads from hint stack */\r
145     protected IHintContext hintCtx = null;\r
146 \r
147     /** Cached hint stack value */\r
148     protected IHintStack hintStack;\r
149 \r
150     /** Painters found with reflection */\r
151     protected CanvasSGNodeDefinition[] sghandlers;\r
152 \r
153     /** Painters found with reflection */\r
154     protected EventHandlerDefinition[] eventHandlers;\r
155 \r
156     protected HintListenerDefinition[] hintListeners;\r
157 \r
158     /** Reference definitions */\r
159     protected ReferenceDefinition[] refDefs;\r
160     protected boolean depsSatisfied = false;\r
161     private final IContextListener<ICanvasParticipant> ctxListener =\r
162         new IContextListener<ICanvasParticipant>() {\r
163         @Override\r
164         public void itemAdded(IContext<ICanvasParticipant> sender, ICanvasParticipant item) {\r
165             _itemAdded(sender, item);\r
166             if (!depsSatisfied) depsSatisfied = checkDependencies();\r
167         }\r
168         @Override\r
169         public void itemRemoved(IContext<ICanvasParticipant> sender, ICanvasParticipant item) {\r
170             _itemRemoved(sender, item);\r
171             AbstractCanvasParticipant.this.depsSatisfied = depsSatisfied;\r
172         }\r
173     };\r
174     @SuppressWarnings("unchecked")\r
175     private void _itemAdded(IContext<ICanvasParticipant> sender, ICanvasParticipant item)\r
176     {\r
177         // This code handles @Dependency annotation\r
178         // Synchronizing because the calling thread may be anything\r
179         synchronized ( ctxListener ) {\r
180                 Class<ICanvasParticipant> c = (Class<ICanvasParticipant>) item.getClass();\r
181                 try {\r
182                     for (ReferenceDefinition def : refDefs)\r
183                     {\r
184                         Class<?>        defClass = def.requirement;\r
185                         if (!defClass.isAssignableFrom(c)) continue;\r
186                         Field   f = def.field;\r
187                         Object  value = f.get(AbstractCanvasParticipant.this);\r
188                         assert(value==null);\r
189                         f.set(AbstractCanvasParticipant.this, item);\r
190                     }\r
191                 } catch (Exception e) {\r
192                     throw new RuntimeException(e);\r
193                 }\r
194         }\r
195     }\r
196     @SuppressWarnings("unchecked")\r
197     private void _itemRemoved(IContext<ICanvasParticipant> sender, ICanvasParticipant item)\r
198     {\r
199         synchronized ( ctxListener ) {          \r
200                 Class<ICanvasParticipant> c = (Class<ICanvasParticipant>) item.getClass();\r
201                 //boolean depsSatisfied = true;\r
202                 try {\r
203                     for (ReferenceDefinition def : refDefs)\r
204                     {\r
205                         Class<?>        defClass = def.requirement;\r
206                         Field   f = def.field;\r
207                         //Object        value = f.get(AbstractCanvasParticipant.this);\r
208                         if (defClass.isAssignableFrom(c)) {\r
209                             //value = null;\r
210                             f.set(AbstractCanvasParticipant.this, null);\r
211                         }\r
212                         //depsSatisfied &= !def.dependency || value!=null;\r
213                     }\r
214                 } catch (Exception e) {\r
215                     throw new RuntimeException(e);\r
216                 }\r
217         }\r
218     }\r
219 \r
220     /**\r
221      * AbstractInteractor adds itself to the context set in the constructor\r
222      * argument.\r
223      * \r
224      * The constructor creates hint constructor and adds it into the context\r
225      * with priority value Integer.MIN_VALUE. Generally, base service interactors\r
226      * only read hints. Those hints are usually overrideable and thus the default\r
227      * hint context priority is as small value as possible. Use this constructor for\r
228      * BASE SERVICE interactors that only read values.\r
229      * \r
230      * @param ctx\r
231      */\r
232     public AbstractCanvasParticipant()\r
233     {\r
234         sghandlers = SGNodeReflection.getSGHandlers(this);\r
235         eventHandlers = EventHandlerReflection.getEventHandlers(this);\r
236         refDefs = DependencyReflection.getDependencies(this, ReferenceType.CanvasParticipant);\r
237         hintListeners = HintReflection.getDependencies(this);\r
238         if (refDefs.length==0) depsSatisfied = true;\r
239     }\r
240 \r
241     @Override\r
242     public void addedToContext(ICanvasContext ctx)\r
243     {\r
244         assert(ctx!=null);\r
245         assert(context==null);\r
246         this.context = ctx;\r
247         this.hintStack = ctx.getHintStack();\r
248         this.thread = ctx.getThreadAccess();\r
249 \r
250         // Add event handlers\r
251         IPriorityStack<IEventHandler> eventHandlerStack = getContext().getEventHandlerStack();\r
252         for (EventHandlerDefinition eventHandler : eventHandlers)\r
253             eventHandlerStack.add(eventHandler.eventHandler, eventHandler.priority);\r
254 \r
255         // Fill References & Monitor for changes\r
256         if (refDefs.length!=0) {\r
257             getContext().addContextListener(ctxListener);\r
258             for (ICanvasParticipant cp : getContext().toArray())\r
259                 _itemAdded(getContext(), cp);\r
260             depsSatisfied = checkDependencies();\r
261         }\r
262 \r
263         // Add hint reflections\r
264         IHintStack stack = getContext().getHintStack();\r
265         IThreadWorkQueue thread = getContext().getThreadAccess();\r
266         for (HintListenerDefinition def : hintListeners)\r
267             stack.addKeyHintListener(thread, def.key, def);\r
268 \r
269 \r
270         // Create SceneGraph nodes\r
271         for (CanvasSGNodeDefinition sg : sghandlers) {\r
272             if (sg.initDesignation != null) {\r
273                 switch (sg.initDesignation) {\r
274                     case CONTROL:\r
275                         sg.init(ctx.getSceneGraph());\r
276                         break;\r
277                     case CANVAS:\r
278                         sg.init(ctx.getCanvasNode());\r
279                         break;\r
280                 }\r
281             }\r
282         }\r
283     }\r
284 \r
285     @Override\r
286     public void removedFromContext(ICanvasContext ctx)\r
287     {\r
288         assert(ctx!=null);\r
289         if (context==null)\r
290             throw new RuntimeException("Interactor was not in any context");\r
291 \r
292         // Remove context listener\r
293         if (refDefs.length!=0) {\r
294             getContext().removeContextListener(ctxListener);\r
295         }\r
296 \r
297         // Clean up SceneGraph nodes\r
298         for (CanvasSGNodeDefinition sg : sghandlers) {\r
299             sg.cleanup();\r
300         }\r
301 \r
302         IPriorityStack<IEventHandler> eventHandlerStack = context.getEventHandlerStack();\r
303         for (EventHandlerDefinition eventHandler : eventHandlers)\r
304             eventHandlerStack.remove(eventHandler.eventHandler);\r
305 \r
306         IHintStack stack = getContext().getHintStack();\r
307         IThreadWorkQueue thread = getContext().getThreadAccess();\r
308         for (HintListenerDefinition def : hintListeners)\r
309             stack.removeKeyHintListener(thread, def.key, def);\r
310 \r
311         if (localHintCtx!=null) {\r
312             context.getHintStack().removeHintContext(localHintCtx);\r
313         }\r
314         context = null;\r
315     }\r
316 \r
317     /**\r
318      * Has this interactor been removed from the context\r
319      * @return true if interactor has been removed from the context\r
320      */\r
321     public boolean isRemoved()\r
322     {\r
323         return context==null;\r
324     }\r
325 \r
326     /**\r
327      * Remove Self.\r
328      * \r
329      * Removes this interactor from its designed context.\r
330      * This methods can be invoked only once.\r
331      */\r
332     public void remove()\r
333     {\r
334         if (isRemoved())\r
335             throw new RuntimeException("Interactor has already been removed from the context");\r
336         context.remove(this);\r
337     }\r
338 \r
339     /**\r
340      * Returns the hint stack of the context.\r
341      * @return hint stack\r
342      */\r
343     public IHintStack getHintStack()\r
344     {\r
345         return hintStack;\r
346     }\r
347 \r
348     /**\r
349      * Get local hint context. This context contains all hints set by this\r
350      * particular interactor. Modifications affect the hint stack of the\r
351      * interaction context, although some modifications may not become effective\r
352      * in case the key is overridden by another hint context in the stack.\r
353      * <p>\r
354      * Local context must be created with createLocalHintContext() method.\r
355      * constructor has defualt value Integer.MIN_VALUE.\r
356      * <p>\r
357      * Reading from this context returns only the local hint and not\r
358      * from the shared hint stack of the canvas context.\r
359      * <p>\r
360      * To read from the shared hint stack, use getHint() or getHintStack() instead.\r
361      * \r
362      * @return the local hint context\r
363      */\r
364     public synchronized IHintContext getLocalHintContext()\r
365     {\r
366         return localHintCtx;\r
367     }\r
368 \r
369     /**\r
370      * Get hint context. Read operations are to hint stack, writes to local stack.\r
371      * \r
372      * @return\r
373      */\r
374     public synchronized IHintContext getWriteableHintStack()\r
375     {\r
376         if (hintCtx==null)\r
377             hintCtx = getHintStack().createStackRead(getLocalHintContext());\r
378         return hintCtx;\r
379     }\r
380 \r
381     /**\r
382      * Get hint context; local if exists, otherwise stack's default context.\r
383      * \r
384      * @return\r
385      */\r
386     public IHintContext getWriteableHintContext()\r
387     {\r
388         ICanvasContext ctx = context;\r
389         assert(ctx!=null);\r
390         if (localHintCtx!=null) return localHintCtx;\r
391         return ctx.getDefaultHintContext();\r
392     }\r
393 \r
394     /**\r
395      * Read hint from the hint stack.\r
396      * \r
397      * @param key\r
398      * @return\r
399      */\r
400     public <E> E getHint(Key key)\r
401     {\r
402         return hintStack.getHint(key);\r
403     }\r
404 \r
405     public boolean hasHint(Key key)\r
406     {\r
407         return hintStack.getHint(key)!=null;\r
408     }\r
409 \r
410     /**\r
411      * Set hint to the local hint stack.\r
412      * \r
413      * Thread safe - switches to context thread\r
414      * \r
415      * @param key the key\r
416      * @param value the value\r
417      */\r
418     public void setHint(final Key key, final Object value)\r
419     {\r
420         if (getThread().currentThreadAccess())\r
421             getWriteableHintContext().setHint(key, value);\r
422         else {\r
423 //                      syncExec(new Runnable() {\r
424 //                              @Override\r
425 //                              public void run() {\r
426 //                                      getWriteableHintContext().setHint(key, value);\r
427 //                              }});\r
428             throw new IllegalStateException("illegal thread access, expected " + getThread().getThread() + ", was in "\r
429                     + Thread.currentThread());\r
430         }\r
431     }\r
432 \r
433     /**\r
434      * Remove hint from local hint context / stack's default context\r
435      * \r
436      * Thread safe - switches to context thread\r
437      * \r
438      * @param key the key\r
439      */\r
440     public void removeHint(final Key key)\r
441     {\r
442         if (getThread().currentThreadAccess())\r
443             getWriteableHintContext().removeHint(key);\r
444         else\r
445 //                      syncExec(new Runnable() {\r
446 //                              @Override\r
447 //                              public void run() {\r
448 //                                      getWriteableHintContext().removeHint(key);\r
449 //                              }});\r
450             throw new IllegalStateException("illegal thread access");\r
451     }\r
452 \r
453 \r
454     public void syncExec(Runnable r)\r
455     {\r
456         ThreadUtils.syncExec(getThread(), r);\r
457     }\r
458 \r
459     public void asyncExec(Runnable r)\r
460     {\r
461         ThreadUtils.asyncExec(getThread(), r);\r
462     }\r
463 \r
464     /**\r
465      * Create local hint context and add it to the hint stack. It will be\r
466      * automatically removed when this participant is removed from the canvas.\r
467      * <p>\r
468      * Local hint context overrides default context in the following convenience methods:\r
469      *  getHint(), setHint(), setHintAsync()\r
470      * <p>\r
471      * Q: What is the purpose of local hint stack?\r
472      * A: To override hints for the lifetime of the participant.\r
473      *    For example, Special editing mode overrides viewport and toolmode.\r
474      * \r
475      * @param priority\r
476      */\r
477     public void createLocalHintContext(int priority)\r
478     {\r
479         localHintCtx = new HintContext();\r
480     }\r
481 \r
482     /**\r
483      * Set hint to the local hint stack if one exists otherwise to the default context.\r
484      * Switches thread to the appropriate context thread.\r
485      * \r
486      * @param key the key\r
487      * @param value the value\r
488      */\r
489     public void setHintAsync(final Key key, final Object value)\r
490     {\r
491         assert(context!=null);\r
492         asyncExec(new Runnable() {\r
493             @Override\r
494             public void run() {\r
495                 if (isRemoved())\r
496                     return;\r
497                 if (localHintCtx!=null)\r
498                     localHintCtx.setHint(key, value);\r
499                 else\r
500                     context.getDefaultHintContext().setHint(key, value);\r
501             }});\r
502     }\r
503 \r
504     /**\r
505      * Get the interactor context.\r
506      * @return the context\r
507      */\r
508     public ICanvasContext getContext()\r
509     {\r
510         return context;\r
511     }\r
512 \r
513     public IThreadWorkQueue getThread()\r
514     {\r
515         return thread;\r
516     }\r
517 \r
518 \r
519     /**\r
520      * Asserts that dependencies of participants are satisfied.\r
521      */\r
522     public void assertDependencies()\r
523     {\r
524         assert(depsSatisfied);\r
525     }\r
526 \r
527     private boolean checkDependencies() {\r
528         synchronized ( ctxListener ) {          \r
529                 try {\r
530                     for (ReferenceDefinition rd : refDefs) {\r
531                         if (!rd.dependency)\r
532                             continue;\r
533                         Field f = rd.field;\r
534                         Object o = f.get(this);\r
535                         if (o == null)\r
536                             return false;\r
537                     }\r
538                 } catch (Exception e) {\r
539                     throw new RuntimeException(e);\r
540                 }\r
541                 return true;\r
542         }\r
543     }\r
544 \r
545     public void setDirty()\r
546     {\r
547         ICanvasContext ctx = getContext();\r
548         if ( ctx==null ) return;\r
549         IContentContext cctx = ctx.getContentContext();\r
550         if ( cctx==null ) return;\r
551         cctx.setDirty();\r
552     }\r
553 \r
554 }\r