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