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