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