]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram/src/org/simantics/diagram/profile/StyleBase.java
Fixed multiple issues causing dangling references to discarded queries
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / profile / StyleBase.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management in
3  * 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.diagram.profile;
13
14 import java.util.Arrays;
15
16 import org.simantics.databoard.Bindings;
17 import org.simantics.db.ReadGraph;
18 import org.simantics.db.RequestProcessor;
19 import org.simantics.db.Resource;
20 import org.simantics.db.Session;
21 import org.simantics.db.common.procedure.adapter.TransientCacheListener;
22 import org.simantics.db.common.request.TernaryRead;
23 import org.simantics.db.common.request.UnaryRead;
24 import org.simantics.db.common.utils.NameUtils;
25 import org.simantics.db.exception.DatabaseException;
26 import org.simantics.db.layer0.variable.Variable;
27 import org.simantics.db.procedure.Listener;
28 import org.simantics.db.request.Read;
29 import org.simantics.diagram.stubs.DiagramResource;
30 import org.simantics.g2d.canvas.Hints;
31 import org.simantics.g2d.canvas.ICanvasContext;
32 import org.simantics.g2d.diagram.IDiagram;
33 import org.simantics.g2d.diagram.handler.DataElementMap;
34 import org.simantics.g2d.element.IElement;
35 import org.simantics.scenegraph.INode;
36 import org.simantics.scenegraph.profile.DataNodeMap;
37 import org.simantics.scenegraph.profile.EvaluationContext;
38 import org.simantics.scenegraph.profile.Group;
39 import org.simantics.scenegraph.profile.Observer;
40 import org.simantics.scenegraph.profile.Style;
41 import org.simantics.scenegraph.profile.common.ObserverGroupListener;
42 import org.simantics.scenegraph.profile.common.ObserverGroupValueListener;
43 import org.simantics.scenegraph.profile.impl.DebugPolicy;
44 import org.simantics.scl.runtime.tuple.Tuple3;
45 import org.simantics.utils.datastructures.Pair;
46
47 /**
48  * For most style implementations it should be enough to override the following
49  * methods:
50  * <ul>
51  * <li>{@link #calculateStyle(ReadGraph, Resource, Resource, Variable)}</li>
52  * <li>{@link #applyStyleForElement(Observer, Object, IElement, Object)}</li>
53  * <li>{@link #cleanupStyleForElement(IDiagram, IElement)}</li>
54  * </ul>
55  * 
56  * <p>
57  * See each method for a specification of what they are intended for.
58  * 
59  * Optionally you can also override
60  * {@link #styleResultChanged(Observer, Resource, Object)} for optimization
61  * purposes but this is usually not necessary.
62  * 
63  * @author Tuukka Lehtonen
64  * f
65  * @param <Result> type of result objects for styled group items tracked by this
66  *        style implementation, see
67  *        {@link #calculateStyle(ReadGraph, Resource, Resource, Variable)}
68  */
69 public abstract class StyleBase<Result> implements Style {
70
71     private Object identity;
72     private double priority;
73
74     public StyleBase(Object identity) {
75         this.identity = identity;
76     }
77
78     public StyleBase() {
79         this.identity = getClass();
80     }
81
82     @SuppressWarnings("unchecked")
83     protected <T> T getIdentity() {
84         return (T)identity;
85     }
86
87     public void setPriority(double priority) {
88         this.priority = priority;
89     }
90     
91     public double getPriority() {
92         return priority;
93     }
94     
95     @Override
96     public int hashCode() {
97         final int prime = 31;
98         int result = 1;
99         result = prime * result + ((identity == null) ? 0 : identity.hashCode());
100         return result;
101     }
102
103     @Override
104     public boolean equals(Object obj) {
105         if (this == obj)
106             return true;
107         if (obj == null)
108             return false;
109         if (getClass() != obj.getClass())
110             return false;
111         StyleBase<?> other = (StyleBase<?>) obj;
112         if (identity == null) {
113             if (other.identity != null)
114                 return false;
115         } else if (!identity.equals(other.identity))
116             return false;
117         return true;
118     }
119
120     protected Resource getResource() {
121         return getIdentity();
122     }
123
124     /**
125      * For caching this simple base request that is done in every
126      * {@link StyleBase#calculateStyle(ReadGraph, Resource, Resource, Resource)}.
127      */
128     static class RuntimeDiagramVariableRequest extends UnaryRead<Resource, Variable> {
129         public RuntimeDiagramVariableRequest(Resource runtimeDiagram) {
130             super(runtimeDiagram);
131         }
132
133         @Override
134         public Variable perform(ReadGraph graph) throws DatabaseException {
135             DiagramResource DIA = DiagramResource.getInstance(graph);
136             String variableURI = graph.getPossibleRelatedValue(parameter, DIA.RuntimeDiagram_HasVariable, Bindings.STRING);
137             if (variableURI == null)
138                 return null;
139             Variable activeVariable = org.simantics.db.layer0.variable.Variables.getPossibleVariable(graph, variableURI);
140             return activeVariable;
141         }
142     }
143
144     /**
145      * Calculates a typed result to be used for applying the style to a diagram
146      * in
147      * {@link #applyStyleForElement(Observer, IDiagram, Object, IElement, Object)}
148      * . The graph request system will take care of only notifying other systems
149      * when the style result actually changes.
150      * 
151      * <p>
152      * This implementation uses {@link RuntimeDiagramVariableRequest} to
153      * discover the active composite variable related to the specified
154      * runtimeDiagram and invokes
155      * {@link StyleBase#calculateStyle(ReadGraph, Resource, Resource, Resource, Variable)}
156      * with it.
157      * 
158      * @param graph
159      *            database read access
160      * @param runtimeDiagram
161      *            resource describing the runtime data of the styled diagram
162      * @param entry
163      *            profile entry resource, don't care if now aware
164      * @param groupItem
165      *            an item belonging to the observed group
166      * @return the calculated style result object
167      * @throws DatabaseException
168      * @see {@link StyleBase#calculateStyle(ReadGraph, Resource, Resource, Resource, Variable)}
169      */
170     public Result calculateStyle(ReadGraph graph, Resource runtimeDiagram, Resource entry, Resource groupItem) throws DatabaseException {
171         Variable activeVariable = graph.syncRequest(new RuntimeDiagramVariableRequest(runtimeDiagram), TransientCacheListener.<Variable>instance());
172         if (activeVariable == null)
173             return null;
174
175         //System.out.println("URI1: " + configuration.getURI(graph));
176         //System.out.println("URI2: " + activeVariable.getURI(graph));
177         return calculateStyle(graph, runtimeDiagram, entry, groupItem, activeVariable);
178     }
179
180     /**
181      * Calculates a typed result to be used for applying the style to a diagram
182      * in
183      * {@link #applyStyleForElement(Observer, IDiagram, Object, IElement, Object)}
184      * . The graph request system will take care of only notifying other systems
185      * when the style result actually changes.
186      * 
187      * @param graph database read access
188      * @param runtimeDiagram resource describing the runtime data of the styled
189      *        diagram
190      * @param entry profile entry resource, don't care if now aware
191      * @param groupItem an item belonging to the observed group
192      * @param activeComposite variable for accessing the active realization of the
193      *        diagram's corresponding composite. This may change when
194      *        experiments are activate and deactivated. When there is no
195      *        experiment active, this is the base realization, i.e. the
196      *        configuration.
197      * @return the calculated style result object
198      * @throws DatabaseException
199      * @see {@link StyleBase#calculateStyle(ReadGraph, Resource, Resource, Resource, Variable)}
200      */
201     public Result calculateStyle(ReadGraph graph, Resource runtimeDiagram, Resource entry, Resource groupItem, Variable activeComposite) throws DatabaseException {
202         return null;
203     }
204
205     /**
206      * Invoked when the result calculated by
207      * {@link #calculateStyle(ReadGraph, Resource, Resource, Variable)} changes.
208      * Used for keeping track of the latest calculated style values that are
209      * applied in
210      * {@link #applyStyleForElement(Observer, IDiagram, Object, IElement, Object)}.
211      * 
212      * @param observer
213      * @param object
214      * @param result
215      */
216     public void styleResultChanged(Observer observer, Resource runtimeDiagram, Resource object, Result result) {
217         if (result == null)
218             StyleBaseData.getInstance().removeValue(new Tuple3(this, runtimeDiagram, object));
219         else
220             StyleBaseData.getInstance().putValue(new Tuple3(this, runtimeDiagram, object), result);
221         observer.update(this, object);
222     }
223
224     /**
225      * Apply the latest style result calculated by
226      * {@link #calculateStyle(ReadGraph, Resource, Resource, Variable)} to the
227      * scene graph of the specified diagram item (i.e. element).
228      * 
229      * <p>
230      * <code>StyleBase</code> ensures that this method is invoked in the AWT
231      * thread only.
232      * 
233      * @param observer profile system observer
234      * @param item the styled diagram item data
235      * @param element the styled diagram element representing the data item
236      * @param result the latest result calculated by
237      *        {@link #calculateStyle(ReadGraph, Resource, Resource, Variable)}
238      */
239     public void applyStyleForNode(EvaluationContext evaluationContext, INode node, Result result) {
240     }
241     
242     public void applyStyleForItem(EvaluationContext evaluationContext, DataNodeMap map, Object item, Result value) {
243         
244         final INode node = map.getNode(item);
245         if (node == null) {
246             evaluationContext.update(this, item);
247             // TODO: continue or return?
248             return;
249         }
250
251         if (DebugPolicy.DEBUG_PROFILE_STYLE_APPLICATION)
252             System.out.println(StyleBase.this + ": applying style for item " + item + " and element " + node + " with result " + value);
253
254         applyStyleForNode(evaluationContext, node, value);
255     }
256
257     /**
258      * This method is invoked by
259      * {@link #cleanupStyleForNode(EvaluationContext, INode)} when the style is
260      * deactivated. It is invoked for each diagram element tracked by the style
261      * before deactivation.
262      * 
263      * @param node a previously tracked and styled scene graph node
264      */
265     protected void cleanupStyleForNode(INode node) {
266     }
267
268     /**
269      * This method is invoked by
270      * {@link #cleanupStyleForItem(EvaluationContext, DataNodeMap, Object)} when the style is
271      * deactivated. It is invoked for each diagram element tracked by the style
272      * before deactivation.
273      * @param evaluationContext the context of this style evaluation
274      * @param node a previously tracked and styled scene graph node
275      */
276     protected void cleanupStyleForNode(EvaluationContext evaluationContext, INode node) {
277         cleanupStyleForNode(node);
278     }
279
280     protected void cleanupStyleForItem(EvaluationContext evaluationContext, DataNodeMap map, Object item) {
281
282         final INode node = map.getNode(item);
283         if (node != null) {
284             if (DebugPolicy.DEBUG_PROFILE_STYLE_ACTIVATION)
285                 System.out.println(this + ".cleanupStyleForItem(" + item + " = " + node + ")");
286             cleanupStyleForNode(evaluationContext, node);
287         }
288
289     }
290     
291     
292     static class GroupListener<T> extends ObserverGroupListener {
293         
294         private StyleBase<T> style;
295         private Session session;
296         private Resource runtimeDiagram;
297         private Resource entry;
298         
299         GroupListener(Session session, Resource runtimeDiagram, Resource entry, StyleBase<T> style, Group group, Observer observer) {
300                 super(style, group, observer);
301                 this.style = style;
302                 this.session = session;
303                 this.runtimeDiagram = runtimeDiagram;
304                 this.entry = entry;
305         }
306         
307           @Override
308           public void add(final Resource item) {
309
310                   if (DebugPolicy.DEBUG_PROFILE_STYLE_GROUP_TRACKING)
311                   System.out.println(style + ": added to group " + group + ": " + item);
312
313               session.asyncRequest(
314                       style.getStyleCalculationRequest(runtimeDiagram, entry, item),
315                       style.getStyleResultListener(this, item, group, observer, runtimeDiagram)
316               );
317
318               super.add(item);
319           }
320           @Override
321           public void remove(Resource item) {
322               if (DebugPolicy.DEBUG_PROFILE_STYLE_GROUP_TRACKING)
323                   System.out.println(style + ": removed from group " + group + ": " + item);
324
325               StyleBaseData.getInstance().removeItem(style, item);
326
327               // TODO: do something here to dispose of ObserverGroupValueListeners?
328               super.remove(item);
329           }  
330           
331     }
332     
333     /* (non-Javadoc)
334      * @see org.simantics.diagram.profile.Style#activate(org.simantics.db.RequestProcessor, org.simantics.db.Resource, org.simantics.db.layer0.variable.Variable, org.simantics.diagram.profile.Group, org.simantics.diagram.profile.Observer)
335      */
336     @Override
337     public final void activate(RequestProcessor backend, final Resource runtimeDiagram, final Resource entry, final Group group, final EvaluationContext observer) throws DatabaseException {
338
339         ObserverGroupListener listener = getListener(runtimeDiagram, group);
340
341         if (listener == null || listener.isDisposed()) {
342
343             if (DebugPolicy.DEBUG_PROFILE_STYLE_ACTIVATION)
344                 System.out.println("activate(" + runtimeDiagram + ", " + group + ", " + observer);
345
346             listener = new GroupListener<Result>(backend.getSession(), runtimeDiagram, entry, this, group, observer);
347
348             StyleBaseData.getInstance().putListener(new Tuple3(this, runtimeDiagram, group), listener);
349
350             group.trackItems(backend, runtimeDiagram, listener);
351
352         }
353
354         // Register this entry in the listener
355         listener.addEntry(entry);
356     }
357
358     /**
359      * Used to customize the identity given to graph requests made for this
360      * style. Default identity is getClass().
361      * 
362      * @return identity object used in graph requests made by this style
363      */
364     protected Object getIdentity(Resource entry) {
365         return new Pair<Class<?>, Resource>(getClass(), entry);
366     }
367
368     /**
369      * @param configuration
370      * @param runtimeDiagram
371      * @param item
372      * @return
373      */
374     protected Read<Result> getStyleCalculationRequest(Resource runtimeDiagram, final Resource entry, Resource item) {
375         return new TernaryRead<Object, Resource, Resource, Result>(getIdentity(entry), runtimeDiagram, item) {
376             @Override
377             public Result perform(ReadGraph graph) throws DatabaseException {
378                 boolean oldSynchronous = graph.getSynchronous();
379                 try {
380                     graph.setSynchronous(false);
381                     Result result = calculateStyle(graph, parameter2, entry, parameter3);
382                     if (DebugPolicy.DEBUG_PROFILE_STYLE_GROUP_TRACKING)
383                         System.out.println(StyleBase.this + ": calculated style result for " + NameUtils.getSafeName(graph, parameter3, true) + ": " + result);
384                     return result;
385                 } finally {
386                     graph.setSynchronous(oldSynchronous);
387                 }
388             }
389         };
390     }
391
392     /**
393      * @param groupListener
394      * @param item
395      * @param group
396      * @param observer
397      * @param runtimeDiagram 
398      * @return
399      */
400     protected Listener<Result> getStyleResultListener(ObserverGroupListener groupListener, final Resource item,
401             Group group, Observer observer, final Resource runtimeDiagram) {
402         return new ObserverGroupValueListener<Result>(groupListener, observer, group, item) {
403             @Override
404             public void execute(Result result) {
405                 if (DebugPolicy.DEBUG_PROFILE_STYLE_GROUP_TRACKING)
406                     System.out.println(StyleBase.this + ": style result changed for " + item + ": " + result);
407                 styleResultChanged(observer, runtimeDiagram, item, result);
408             }
409         };
410     }
411
412     /* (non-Javadoc)
413      * @see org.simantics.diagram.profile.Style#deactivate(org.simantics.db.RequestProcessor, org.simantics.db.Resource, org.simantics.db.layer0.variable.Variable, org.simantics.diagram.profile.Group, org.simantics.diagram.profile.Observer)
414      */
415     @Override
416     public final void deactivate(Resource runtimeDiagram, Resource entry, Group group,
417             EvaluationContext observer) {
418
419         ObserverGroupListener listener = getListener(runtimeDiagram, group);
420         if (listener != null) {
421
422             if (DebugPolicy.DEBUG_PROFILE_STYLE_ACTIVATION)
423                 System.out.println("deactivate(" + runtimeDiagram + ", " + group + ", " + observer);
424
425             IDiagram diagram = observer.getConstant(ProfileKeys.DIAGRAM);
426
427             listener.removeEntry(entry);
428             if (!listener.hasEntries()) {
429                 listener.dispose();
430                 StyleBaseData.getInstance().removeListener(new Tuple3(this, runtimeDiagram, group));
431             }
432
433             // This was too eager when multiple groups were tracked!
434             //values.clear();
435             if (diagram != null) {
436                 cleanupItems(observer, diagram, listener.getItems().toArray());
437                 diagram = null;
438             }
439             
440             //observer.update(); TODO: Check if this is required!
441         }
442
443     }
444
445     /* (non-Javadoc)
446      * @see org.simantics.diagram.profile.Style#apply(org.simantics.g2d.diagram.IDiagram, org.simantics.diagram.profile.Group, org.simantics.diagram.profile.Observer)
447      */
448     @Override
449     public final void apply(Resource entry, Group group, final EvaluationContext evaluationContext) {
450         ICanvasContext context = evaluationContext.getConstant(ProfileKeys.CANVAS);
451         
452         assert context.getThreadAccess().currentThreadAccess();
453
454         ObserverGroupListener listener = getListener(evaluationContext.getResource(), group);
455         if (listener == null) {
456             System.out.println(this + "(" + getClass().getSimpleName() + ") had no listener for " + evaluationContext.getResource() + " " + group);
457             return;
458         }
459         
460         final DataNodeMap map = evaluationContext.getConstant(ProfileKeys.NODE_MAP);
461
462         if (DebugPolicy.DEBUG_PROFILE_STYLE_APPLICATION)
463             System.out.println(StyleBase.this + ": applying style for items: " + listener.getItems());
464
465         
466         StyleBaseData data = StyleBaseData.getInstance();
467         
468         data.applyRemovals(evaluationContext, this);
469  
470         IDiagram diagram = evaluationContext.getConstant(ProfileKeys.DIAGRAM);
471         assert diagram != null;
472         DataElementMap emap = diagram.getDiagramClass().getSingleItem(DataElementMap.class);
473       
474         for (Object item : listener.getItems()) {
475             Result value = data.getValue(new Tuple3(this, evaluationContext.getResource(), item));
476             applyStyleForItem(evaluationContext, map, item, value);
477
478             IElement element = emap.getElement(diagram, item);
479             if (element != null)
480                 element.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
481         }
482         
483     }
484
485     @Override
486     public final void apply2(Object item, final EvaluationContext evaluationContext) {
487         final DataNodeMap map = evaluationContext.getConstant(ProfileKeys.NODE_MAP);
488         
489         StyleBaseData data = StyleBaseData.getInstance();
490         
491         data.applyRemovals(evaluationContext, this);
492
493         Result value = data.getValue(new Tuple3(this, evaluationContext.getResource(), item));
494         applyStyleForItem(evaluationContext, map, item, value);
495         
496         IDiagram diagram = evaluationContext.getConstant(ProfileKeys.DIAGRAM);
497         assert diagram != null;
498         DataElementMap emap = diagram.getDiagramClass().getSingleItem(DataElementMap.class);
499         IElement element = emap.getElement(diagram, item);
500         if (element != null)
501             element.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
502     }
503     
504     /**
505      * This is ran when this profile entry gets deactivated after being first
506      * active. It allows cleaning up scene graph left-overs for the listened set
507      * of items before deactivation. It will invoke
508      * {@link #cleanupStyleForElement(IDiagram, IElement)} for each diagram element observed
509      * before deactivation in the AWT thread. If the profile observer is
510      * disposed in between scheduling to AWT thread, the method will do nothing.
511      * 
512      * @param observer the diagram profile observer
513      * @param diagram the diagram this profile system is working with
514      * @param items the diagram data items that need to be cleaned up
515      */
516     protected final void cleanupItems(final EvaluationContext evaluationContext, final IDiagram diagram, final Object[] items) {
517
518         ICanvasContext context = evaluationContext.getConstant(ProfileKeys.CANVAS);
519
520         context.getThreadAccess().asyncExec(new Runnable() {
521                 
522             @Override
523             public void run() {
524                 
525                 if (evaluationContext.isDisposed())
526                     return;
527
528                 final DataNodeMap map = evaluationContext.getConstant(ProfileKeys.NODE_MAP);
529                 
530                 if (DebugPolicy.DEBUG_PROFILE_STYLE_ACTIVATION)
531                     System.out.println(this + ".cleanupItems(" + evaluationContext + ", " + diagram + ", " + Arrays.toString(items));
532
533                 IDiagram diagram = evaluationContext.getConstant(ProfileKeys.DIAGRAM);
534                 assert diagram != null;
535                 DataElementMap emap = diagram.getDiagramClass().getSingleItem(DataElementMap.class);
536
537                 for (Object item : items) {
538                     cleanupStyleForItem(evaluationContext, map, item);
539
540                     IElement element = emap.getElement(diagram, item);
541                     if (element != null)
542                         element.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
543                 }
544             }
545         });
546     }
547     
548     private ObserverGroupListener getListener(Resource runtime, Group group) {
549         return StyleBaseData.getInstance().getListener(new Tuple3(this, runtime, group));
550     }
551
552 }