1 /*******************************************************************************
\r
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management in
\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
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.diagram.profile;
\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
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
53 * For most style implementations it should be enough to override the following
\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
62 * See each method for a specification of what they are intended for.
\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
68 * @author Tuukka Lehtonen
\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
74 public abstract class StyleBase<Result> implements Style {
\r
76 protected final Map<Tuple, Result> values = new ConcurrentHashMap<Tuple, Result>();
\r
78 // private Map<Resource,ObserverGroupListener> listeners = new ConcurrentHashMap<Resource, ObserverGroupListener>();
\r
80 private Map<Pair<Resource, Group>, ObserverGroupListener> listeners = new HashMap<Pair<Resource, Group>, ObserverGroupListener>();
\r
83 private final List<Resource> removals = new ArrayList<Resource>();
\r
86 * For caching this simple base request that is done in every
\r
87 * {@link StyleBase#calculateStyle(ReadGraph, Resource, Resource, Resource)}.
\r
89 static class RuntimeDiagramVariableRequest extends UnaryRead<Resource, Variable> {
\r
90 public RuntimeDiagramVariableRequest(Resource runtimeDiagram) {
\r
91 super(runtimeDiagram);
\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
100 Variable activeVariable = org.simantics.db.layer0.variable.Variables.getPossibleVariable(graph, variableURI);
\r
101 return activeVariable;
\r
106 * Calculates a typed result to be used for applying the style to a diagram
\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
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
120 * database read access
\r
121 * @param runtimeDiagram
\r
122 * resource describing the runtime data of the styled diagram
\r
124 * profile entry resource, don't care if now aware
\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
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
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
142 * Calculates a typed result to be used for applying the style to a diagram
\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
148 * @param graph database read access
\r
149 * @param runtimeDiagram resource describing the runtime data of the styled
\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
158 * @return the calculated style result object
\r
159 * @throws DatabaseException
\r
160 * @see {@link StyleBase#calculateStyle(ReadGraph, Resource, Resource, Resource, Variable)}
\r
162 public Result calculateStyle(ReadGraph graph, Resource runtimeDiagram, Resource entry, Resource groupItem, Variable activeComposite) throws DatabaseException {
\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
171 * {@link #applyStyleForElement(Observer, IDiagram, Object, IElement, Object)}.
\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
181 values.put(new Tuple2(runtimeDiagram, object), result);
\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
191 * <code>StyleBase</code> ensures that this method is invoked in the AWT
\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
200 public void applyStyleForNode(EvaluationContext evaluationContext, INode node, Result result) {
\r
203 public void applyStyleForItem(EvaluationContext evaluationContext, DataNodeMap map, Object item, Result value) {
\r
205 final INode node = map.getNode(item);
\r
206 if (node == null) {
\r
207 evaluationContext.update();
\r
208 // TODO: continue or return?
\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
215 applyStyleForNode(evaluationContext, node, value);
\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
225 * @param node a previously tracked and styled scene graph node
\r
227 protected void cleanupStyleForNode(INode node) {
\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
238 protected void cleanupStyleForNode(EvaluationContext evaluationContext, INode node) {
\r
239 cleanupStyleForNode(node);
\r
242 protected void cleanupStyleForItem(EvaluationContext evaluationContext, DataNodeMap map, Object item) {
\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
254 static class GroupListener<T> extends ObserverGroupListener {
\r
256 private StyleBase<T> style;
\r
257 private Session session;
\r
258 private Resource runtimeDiagram;
\r
259 private Resource entry;
\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
270 public void add(final Resource item) {
\r
272 if (DebugPolicy.DEBUG_PROFILE_STYLE_GROUP_TRACKING)
\r
273 System.out.println(style + ": added to group " + group + ": " + item);
\r
275 session.asyncRequest(
\r
276 style.getStyleCalculationRequest(runtimeDiagram, entry, item),
\r
277 style.getStyleResultListener(this, item, group, observer, runtimeDiagram)
\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
287 synchronized (style.removals) {
\r
288 style.removals.add(item);
\r
291 // TODO: do something here to dispose of ObserverGroupValueListeners?
\r
292 super.remove(item);
\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
301 public final void activate(RequestProcessor backend, final Resource runtimeDiagram, final Resource entry, final Group group, final EvaluationContext observer) {
\r
303 ObserverGroupListener listener = getListener(runtimeDiagram, group);
\r
305 if (listener == null || listener.isDisposed()) {
\r
307 if (DebugPolicy.DEBUG_PROFILE_STYLE_ACTIVATION)
\r
308 System.out.println("activate(" + runtimeDiagram + ", " + group + ", " + observer);
\r
310 listener = new GroupListener<Result>(backend.getSession(), runtimeDiagram, entry, this, group, observer);
\r
312 listeners.put(Pair.make(runtimeDiagram, group), listener);
\r
314 group.trackItems(backend, runtimeDiagram, listener);
\r
318 // Register this entry in the listener
\r
319 listener.addEntry(entry);
\r
323 * Used to customize the identity given to graph requests made for this
\r
324 * style. Default identity is getClass().
\r
326 * @return identity object used in graph requests made by this style
\r
328 protected Object getIdentity(Resource entry) {
\r
329 return new Pair<Class<?>, Resource>(getClass(), entry);
\r
333 * @param configuration
\r
334 * @param runtimeDiagram
\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
341 public Result perform(ReadGraph graph) throws DatabaseException {
\r
342 boolean oldSynchronous = graph.getSynchronous();
\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
350 graph.setSynchronous(oldSynchronous);
\r
357 * @param groupListener
\r
361 * @param runtimeDiagram
\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
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
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
380 public final void deactivate(Resource runtimeDiagram, Resource entry, Group group,
\r
381 EvaluationContext observer) {
\r
383 ObserverGroupListener listener = getListener(runtimeDiagram, group);
\r
384 if (listener != null) {
\r
386 if (DebugPolicy.DEBUG_PROFILE_STYLE_ACTIVATION)
\r
387 System.out.println("deactivate(" + runtimeDiagram + ", " + group + ", " + observer);
\r
389 IDiagram diagram = observer.getConstant(ProfileKeys.DIAGRAM);
\r
391 listener.removeEntry(entry);
\r
392 if (!listener.hasEntries()) {
\r
393 listener.dispose();
\r
394 listeners.remove(Pair.make(runtimeDiagram, group));
\r
397 // This was too eager when multiple groups were tracked!
\r
399 if (diagram != null) {
\r
400 cleanupItems(observer, diagram, listener.getItems().toArray());
\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
412 public final void apply(Resource entry, Group group, final EvaluationContext evaluationContext) {
\r
414 ICanvasContext context = evaluationContext.getConstant(ProfileKeys.CANVAS);
\r
416 assert context.getThreadAccess().currentThreadAccess();
\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
424 final DataNodeMap map = evaluationContext.getConstant(ProfileKeys.NODE_MAP);
\r
426 if (DebugPolicy.DEBUG_PROFILE_STYLE_APPLICATION)
\r
427 System.out.println(StyleBase.this + ": applying style for items: " + listener.getItems());
\r
429 if (!removals.isEmpty()) {
\r
430 Resource[] removed;
\r
431 synchronized (removals) {
\r
432 removed = removals.toArray(Resource.NONE);
\r
435 for (Resource item : removed) {
\r
436 cleanupStyleForItem(evaluationContext, map, item);
\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
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
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
459 protected final void cleanupItems(final EvaluationContext evaluationContext, final IDiagram diagram, final Object[] items) {
\r
460 AWTThread.getThreadAccess().asyncExec(new Runnable() {
\r
462 public void run() {
\r
464 if (evaluationContext.isDisposed())
\r
467 final DataNodeMap map = evaluationContext.getConstant(ProfileKeys.NODE_MAP);
\r
469 if (DebugPolicy.DEBUG_PROFILE_STYLE_ACTIVATION)
\r
470 System.out.println(this + ".cleanupItems(" + evaluationContext + ", " + diagram + ", " + Arrays.toString(items));
\r
472 for (Object item : items) {
\r
473 cleanupStyleForItem(evaluationContext, map, item);
\r
479 private ObserverGroupListener getListener(Resource runtime, Group group) {
\r
480 return listeners.get(Pair.make(runtime, group));
\r