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;
52 * For most style implementations it should be enough to override the following
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>
61 * See each method for a specification of what they are intended for.
63 * Optionally you can also override
64 * {@link #styleResultChanged(Observer, Resource, Object)} for optimization
65 * purposes but this is usually not necessary.
67 * @author Tuukka Lehtonen
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)}
73 public abstract class StyleBase<Result> implements Style {
75 protected final Map<Tuple, Result> values = new ConcurrentHashMap<Tuple, Result>();
77 // private Map<Resource,ObserverGroupListener> listeners = new ConcurrentHashMap<Resource, ObserverGroupListener>();
79 private Map<Pair<Resource, Group>, ObserverGroupListener> listeners = new HashMap<Pair<Resource, Group>, ObserverGroupListener>();
82 private final List<Resource> removals = new ArrayList<Resource>();
85 * For caching this simple base request that is done in every
86 * {@link StyleBase#calculateStyle(ReadGraph, Resource, Resource, Resource)}.
88 static class RuntimeDiagramVariableRequest extends UnaryRead<Resource, Variable> {
89 public RuntimeDiagramVariableRequest(Resource runtimeDiagram) {
90 super(runtimeDiagram);
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)
99 Variable activeVariable = org.simantics.db.layer0.variable.Variables.getPossibleVariable(graph, variableURI);
100 return activeVariable;
105 * Calculates a typed result to be used for applying the style to a diagram
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.
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)}
119 * database read access
120 * @param runtimeDiagram
121 * resource describing the runtime data of the styled diagram
123 * profile entry resource, don't care if now aware
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)}
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)
135 //System.out.println("URI1: " + configuration.getURI(graph));
136 //System.out.println("URI2: " + activeVariable.getURI(graph));
137 return calculateStyle(graph, runtimeDiagram, entry, groupItem, activeVariable);
141 * Calculates a typed result to be used for applying the style to a diagram
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.
147 * @param graph database read access
148 * @param runtimeDiagram resource describing the runtime data of the styled
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
157 * @return the calculated style result object
158 * @throws DatabaseException
159 * @see {@link StyleBase#calculateStyle(ReadGraph, Resource, Resource, Resource, Variable)}
161 public Result calculateStyle(ReadGraph graph, Resource runtimeDiagram, Resource entry, Resource groupItem, Variable activeComposite) throws DatabaseException {
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
170 * {@link #applyStyleForElement(Observer, IDiagram, Object, IElement, Object)}.
176 public void styleResultChanged(Observer observer, Resource runtimeDiagram, Resource object, Result result) {
178 values.remove(new Tuple2(runtimeDiagram, object));
180 values.put(new Tuple2(runtimeDiagram, object), result);
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).
190 * <code>StyleBase</code> ensures that this method is invoked in the AWT
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)}
199 public void applyStyleForNode(EvaluationContext evaluationContext, INode node, Result result) {
202 public void applyStyleForItem(EvaluationContext evaluationContext, DataNodeMap map, Object item, Result value) {
204 final INode node = map.getNode(item);
206 evaluationContext.update();
207 // TODO: continue or return?
211 if (DebugPolicy.DEBUG_PROFILE_STYLE_APPLICATION)
212 System.out.println(StyleBase.this + ": applying style for item " + item + " and element " + node + " with result " + value);
214 applyStyleForNode(evaluationContext, node, value);
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.
224 * @param node a previously tracked and styled scene graph node
226 protected void cleanupStyleForNode(INode node) {
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
237 protected void cleanupStyleForNode(EvaluationContext evaluationContext, INode node) {
238 cleanupStyleForNode(node);
241 protected void cleanupStyleForItem(EvaluationContext evaluationContext, DataNodeMap map, Object item) {
243 final INode node = map.getNode(item);
245 if (DebugPolicy.DEBUG_PROFILE_STYLE_ACTIVATION)
246 System.out.println(this + ".cleanupStyleForItem(" + item + " = " + node + ")");
247 cleanupStyleForNode(evaluationContext, node);
253 static class GroupListener<T> extends ObserverGroupListener {
255 private StyleBase<T> style;
256 private Session session;
257 private Resource runtimeDiagram;
258 private Resource entry;
260 GroupListener(Session session, Resource runtimeDiagram, Resource entry, StyleBase<T> style, Group group, Observer observer) {
261 super(style, group, observer);
263 this.session = session;
264 this.runtimeDiagram = runtimeDiagram;
269 public void add(final Resource item) {
271 if (DebugPolicy.DEBUG_PROFILE_STYLE_GROUP_TRACKING)
272 System.out.println(style + ": added to group " + group + ": " + item);
274 session.asyncRequest(
275 style.getStyleCalculationRequest(runtimeDiagram, entry, item),
276 style.getStyleResultListener(this, item, group, observer, runtimeDiagram)
282 public void remove(Resource item) {
283 if (DebugPolicy.DEBUG_PROFILE_STYLE_GROUP_TRACKING)
284 System.out.println(style + ": removed from group " + group + ": " + item);
286 synchronized (style.removals) {
287 style.removals.add(item);
290 // TODO: do something here to dispose of ObserverGroupValueListeners?
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)
300 public final void activate(RequestProcessor backend, final Resource runtimeDiagram, final Resource entry, final Group group, final EvaluationContext observer) throws DatabaseException {
302 ObserverGroupListener listener = getListener(runtimeDiagram, group);
304 if (listener == null || listener.isDisposed()) {
306 if (DebugPolicy.DEBUG_PROFILE_STYLE_ACTIVATION)
307 System.out.println("activate(" + runtimeDiagram + ", " + group + ", " + observer);
309 listener = new GroupListener<Result>(backend.getSession(), runtimeDiagram, entry, this, group, observer);
311 listeners.put(Pair.make(runtimeDiagram, group), listener);
313 group.trackItems(backend, runtimeDiagram, listener);
317 // Register this entry in the listener
318 listener.addEntry(entry);
322 * Used to customize the identity given to graph requests made for this
323 * style. Default identity is getClass().
325 * @return identity object used in graph requests made by this style
327 protected Object getIdentity(Resource entry) {
328 return new Pair<Class<?>, Resource>(getClass(), entry);
332 * @param configuration
333 * @param runtimeDiagram
337 protected Read<Result> getStyleCalculationRequest(Resource runtimeDiagram, final Resource entry, Resource item) {
338 return new TernaryRead<Object, Resource, Resource, Result>(getIdentity(entry), runtimeDiagram, item) {
340 public Result perform(ReadGraph graph) throws DatabaseException {
341 boolean oldSynchronous = graph.getSynchronous();
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);
349 graph.setSynchronous(oldSynchronous);
356 * @param groupListener
360 * @param runtimeDiagram
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) {
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);
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)
379 public final void deactivate(Resource runtimeDiagram, Resource entry, Group group,
380 EvaluationContext observer) {
382 ObserverGroupListener listener = getListener(runtimeDiagram, group);
383 if (listener != null) {
385 if (DebugPolicy.DEBUG_PROFILE_STYLE_ACTIVATION)
386 System.out.println("deactivate(" + runtimeDiagram + ", " + group + ", " + observer);
388 IDiagram diagram = observer.getConstant(ProfileKeys.DIAGRAM);
390 listener.removeEntry(entry);
391 if (!listener.hasEntries()) {
393 listeners.remove(Pair.make(runtimeDiagram, group));
396 // This was too eager when multiple groups were tracked!
398 if (diagram != null) {
399 cleanupItems(observer, diagram, listener.getItems().toArray());
408 * @see org.simantics.diagram.profile.Style#apply(org.simantics.g2d.diagram.IDiagram, org.simantics.diagram.profile.Group, org.simantics.diagram.profile.Observer)
411 public final void apply(Resource entry, Group group, final EvaluationContext evaluationContext) {
413 ICanvasContext context = evaluationContext.getConstant(ProfileKeys.CANVAS);
415 assert context.getThreadAccess().currentThreadAccess();
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);
423 final DataNodeMap map = evaluationContext.getConstant(ProfileKeys.NODE_MAP);
425 if (DebugPolicy.DEBUG_PROFILE_STYLE_APPLICATION)
426 System.out.println(StyleBase.this + ": applying style for items: " + listener.getItems());
428 if (!removals.isEmpty()) {
430 synchronized (removals) {
431 removed = removals.toArray(Resource.NONE);
434 for (Resource item : removed) {
435 cleanupStyleForItem(evaluationContext, map, item);
439 for (Object item : listener.getItems()) {
440 Result value = values.get(new Tuple2(evaluationContext.getResource(), item));
441 applyStyleForItem(evaluationContext, map, item, value);
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.
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
458 protected final void cleanupItems(final EvaluationContext evaluationContext, final IDiagram diagram, final Object[] items) {
460 ICanvasContext context = evaluationContext.getConstant(ProfileKeys.CANVAS);
462 context.getThreadAccess().asyncExec(new Runnable() {
467 if (evaluationContext.isDisposed())
470 final DataNodeMap map = evaluationContext.getConstant(ProfileKeys.NODE_MAP);
472 if (DebugPolicy.DEBUG_PROFILE_STYLE_ACTIVATION)
473 System.out.println(this + ".cleanupItems(" + evaluationContext + ", " + diagram + ", " + Arrays.toString(items));
475 for (Object item : items) {
476 cleanupStyleForItem(evaluationContext, map, item);
482 private ObserverGroupListener getListener(Resource runtime, Group group) {
483 return listeners.get(Pair.make(runtime, group));