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