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