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