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.Arrays;
16 import org.simantics.databoard.Bindings;
17 import org.simantics.db.ReadGraph;
18 import org.simantics.db.RequestProcessor;
19 import org.simantics.db.Resource;
20 import org.simantics.db.Session;
21 import org.simantics.db.common.procedure.adapter.TransientCacheListener;
22 import org.simantics.db.common.request.TernaryRead;
23 import org.simantics.db.common.request.UnaryRead;
24 import org.simantics.db.common.utils.NameUtils;
25 import org.simantics.db.exception.DatabaseException;
26 import org.simantics.db.layer0.variable.Variable;
27 import org.simantics.db.procedure.Listener;
28 import org.simantics.db.request.Read;
29 import org.simantics.diagram.stubs.DiagramResource;
30 import org.simantics.g2d.canvas.Hints;
31 import org.simantics.g2d.canvas.ICanvasContext;
32 import org.simantics.g2d.diagram.IDiagram;
33 import org.simantics.g2d.diagram.handler.DataElementMap;
34 import org.simantics.g2d.element.IElement;
35 import org.simantics.scenegraph.INode;
36 import org.simantics.scenegraph.profile.DataNodeMap;
37 import org.simantics.scenegraph.profile.EvaluationContext;
38 import org.simantics.scenegraph.profile.Group;
39 import org.simantics.scenegraph.profile.Observer;
40 import org.simantics.scenegraph.profile.Style;
41 import org.simantics.scenegraph.profile.common.ObserverGroupListener;
42 import org.simantics.scenegraph.profile.common.ObserverGroupValueListener;
43 import org.simantics.scenegraph.profile.impl.DebugPolicy;
44 import org.simantics.scl.runtime.tuple.Tuple3;
45 import org.simantics.utils.datastructures.Pair;
48 * For most style implementations it should be enough to override the following
51 * <li>{@link #calculateStyle(ReadGraph, Resource, Resource, Variable)}</li>
52 * <li>{@link #applyStyleForElement(Observer, Object, IElement, Object)}</li>
53 * <li>{@link #cleanupStyleForElement(IDiagram, IElement)}</li>
57 * See each method for a specification of what they are intended for.
59 * Optionally you can also override
60 * {@link #styleResultChanged(Observer, Resource, Object)} for optimization
61 * purposes but this is usually not necessary.
63 * @author Tuukka Lehtonen
65 * @param <Result> type of result objects for styled group items tracked by this
66 * style implementation, see
67 * {@link #calculateStyle(ReadGraph, Resource, Resource, Variable)}
69 public abstract class StyleBase<Result> implements Style {
71 private Object identity;
72 private double priority;
74 public StyleBase(Object identity) {
75 this.identity = identity;
79 this.identity = getClass();
82 @SuppressWarnings("unchecked")
83 protected <T> T getIdentity() {
87 public void setPriority(double priority) {
88 this.priority = priority;
91 public double getPriority() {
96 public int hashCode() {
99 result = prime * result + ((identity == null) ? 0 : identity.hashCode());
104 public boolean equals(Object obj) {
109 if (getClass() != obj.getClass())
111 StyleBase<?> other = (StyleBase<?>) obj;
112 if (identity == null) {
113 if (other.identity != null)
115 } else if (!identity.equals(other.identity))
120 protected Resource getResource() {
121 return getIdentity();
125 * For caching this simple base request that is done in every
126 * {@link StyleBase#calculateStyle(ReadGraph, Resource, Resource, Resource)}.
128 static class RuntimeDiagramVariableRequest extends UnaryRead<Resource, Variable> {
129 public RuntimeDiagramVariableRequest(Resource runtimeDiagram) {
130 super(runtimeDiagram);
134 public Variable perform(ReadGraph graph) throws DatabaseException {
135 DiagramResource DIA = DiagramResource.getInstance(graph);
136 String variableURI = graph.getPossibleRelatedValue(parameter, DIA.RuntimeDiagram_HasVariable, Bindings.STRING);
137 if (variableURI == null)
139 Variable activeVariable = org.simantics.db.layer0.variable.Variables.getPossibleVariable(graph, variableURI);
140 return activeVariable;
145 * Calculates a typed result to be used for applying the style to a diagram
147 * {@link #applyStyleForElement(Observer, IDiagram, Object, IElement, Object)}
148 * . The graph request system will take care of only notifying other systems
149 * when the style result actually changes.
152 * This implementation uses {@link RuntimeDiagramVariableRequest} to
153 * discover the active composite variable related to the specified
154 * runtimeDiagram and invokes
155 * {@link StyleBase#calculateStyle(ReadGraph, Resource, Resource, Resource, Variable)}
159 * database read access
160 * @param runtimeDiagram
161 * resource describing the runtime data of the styled diagram
163 * profile entry resource, don't care if now aware
165 * an item belonging to the observed group
166 * @return the calculated style result object
167 * @throws DatabaseException
168 * @see {@link StyleBase#calculateStyle(ReadGraph, Resource, Resource, Resource, Variable)}
170 public Result calculateStyle(ReadGraph graph, Resource runtimeDiagram, Resource entry, Resource groupItem) throws DatabaseException {
171 Variable activeVariable = graph.syncRequest(new RuntimeDiagramVariableRequest(runtimeDiagram), TransientCacheListener.<Variable>instance());
172 if (activeVariable == null)
175 //System.out.println("URI1: " + configuration.getURI(graph));
176 //System.out.println("URI2: " + activeVariable.getURI(graph));
177 return calculateStyle(graph, runtimeDiagram, entry, groupItem, activeVariable);
181 * Calculates a typed result to be used for applying the style to a diagram
183 * {@link #applyStyleForElement(Observer, IDiagram, Object, IElement, Object)}
184 * . The graph request system will take care of only notifying other systems
185 * when the style result actually changes.
187 * @param graph database read access
188 * @param runtimeDiagram resource describing the runtime data of the styled
190 * @param entry profile entry resource, don't care if now aware
191 * @param groupItem an item belonging to the observed group
192 * @param activeComposite variable for accessing the active realization of the
193 * diagram's corresponding composite. This may change when
194 * experiments are activate and deactivated. When there is no
195 * experiment active, this is the base realization, i.e. the
197 * @return the calculated style result object
198 * @throws DatabaseException
199 * @see {@link StyleBase#calculateStyle(ReadGraph, Resource, Resource, Resource, Variable)}
201 public Result calculateStyle(ReadGraph graph, Resource runtimeDiagram, Resource entry, Resource groupItem, Variable activeComposite) throws DatabaseException {
206 * Invoked when the result calculated by
207 * {@link #calculateStyle(ReadGraph, Resource, Resource, Variable)} changes.
208 * Used for keeping track of the latest calculated style values that are
210 * {@link #applyStyleForElement(Observer, IDiagram, Object, IElement, Object)}.
216 public void styleResultChanged(Observer observer, Resource runtimeDiagram, Resource object, Result result) {
218 StyleBaseData.getInstance().removeValue(new Tuple3(this, runtimeDiagram, object));
220 StyleBaseData.getInstance().putValue(new Tuple3(this, runtimeDiagram, object), result);
221 observer.update(this, object);
225 * Apply the latest style result calculated by
226 * {@link #calculateStyle(ReadGraph, Resource, Resource, Variable)} to the
227 * scene graph of the specified diagram item (i.e. element).
230 * <code>StyleBase</code> ensures that this method is invoked in the AWT
233 * @param observer profile system observer
234 * @param item the styled diagram item data
235 * @param element the styled diagram element representing the data item
236 * @param result the latest result calculated by
237 * {@link #calculateStyle(ReadGraph, Resource, Resource, Variable)}
239 public void applyStyleForNode(EvaluationContext evaluationContext, INode node, Result result) {
242 public void applyStyleForItem(EvaluationContext evaluationContext, DataNodeMap map, Object item, Result value) {
244 final INode node = map.getNode(item);
246 evaluationContext.update(this, item);
247 // TODO: continue or return?
251 if (DebugPolicy.DEBUG_PROFILE_STYLE_APPLICATION)
252 System.out.println(StyleBase.this + ": applying style for item " + item + " and element " + node + " with result " + value);
254 applyStyleForNode(evaluationContext, node, value);
258 * This method is invoked by
259 * {@link #cleanupStyleForNode(EvaluationContext, INode)} when the style is
260 * deactivated. It is invoked for each diagram element tracked by the style
261 * before deactivation.
263 * @param node a previously tracked and styled scene graph node
265 protected void cleanupStyleForNode(INode node) {
269 * This method is invoked by
270 * {@link #cleanupStyleForItem(EvaluationContext, DataNodeMap, Object)} when the style is
271 * deactivated. It is invoked for each diagram element tracked by the style
272 * before deactivation.
273 * @param evaluationContext the context of this style evaluation
274 * @param node a previously tracked and styled scene graph node
276 protected void cleanupStyleForNode(EvaluationContext evaluationContext, INode node) {
277 cleanupStyleForNode(node);
280 protected void cleanupStyleForItem(EvaluationContext evaluationContext, DataNodeMap map, Object item) {
282 final INode node = map.getNode(item);
284 if (DebugPolicy.DEBUG_PROFILE_STYLE_ACTIVATION)
285 System.out.println(this + ".cleanupStyleForItem(" + item + " = " + node + ")");
286 cleanupStyleForNode(evaluationContext, node);
292 static class GroupListener<T> extends ObserverGroupListener {
294 private StyleBase<T> style;
295 private Session session;
296 private Resource runtimeDiagram;
297 private Resource entry;
299 GroupListener(Session session, Resource runtimeDiagram, Resource entry, StyleBase<T> style, Group group, Observer observer) {
300 super(style, group, observer);
302 this.session = session;
303 this.runtimeDiagram = runtimeDiagram;
308 public void add(final Resource item) {
310 if (DebugPolicy.DEBUG_PROFILE_STYLE_GROUP_TRACKING)
311 System.out.println(style + ": added to group " + group + ": " + item);
313 session.asyncRequest(
314 style.getStyleCalculationRequest(runtimeDiagram, entry, item),
315 style.getStyleResultListener(this, item, group, observer, runtimeDiagram)
321 public void remove(Resource item) {
322 if (DebugPolicy.DEBUG_PROFILE_STYLE_GROUP_TRACKING)
323 System.out.println(style + ": removed from group " + group + ": " + item);
325 StyleBaseData.getInstance().removeItem(style, item);
327 // TODO: do something here to dispose of ObserverGroupValueListeners?
334 * @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)
337 public final void activate(RequestProcessor backend, final Resource runtimeDiagram, final Resource entry, final Group group, final EvaluationContext observer) throws DatabaseException {
339 ObserverGroupListener listener = getListener(runtimeDiagram, group);
341 if (listener == null || listener.isDisposed()) {
343 if (DebugPolicy.DEBUG_PROFILE_STYLE_ACTIVATION)
344 System.out.println("activate(" + runtimeDiagram + ", " + group + ", " + observer);
346 listener = new GroupListener<Result>(backend.getSession(), runtimeDiagram, entry, this, group, observer);
348 StyleBaseData.getInstance().putListener(new Tuple3(this, runtimeDiagram, group), listener);
350 group.trackItems(backend, runtimeDiagram, listener);
354 // Register this entry in the listener
355 listener.addEntry(entry);
359 * Used to customize the identity given to graph requests made for this
360 * style. Default identity is getClass().
362 * @return identity object used in graph requests made by this style
364 protected Object getIdentity(Resource entry) {
365 return new Pair<Class<?>, Resource>(getClass(), entry);
369 * @param configuration
370 * @param runtimeDiagram
374 protected Read<Result> getStyleCalculationRequest(Resource runtimeDiagram, final Resource entry, Resource item) {
375 return new TernaryRead<Object, Resource, Resource, Result>(getIdentity(entry), runtimeDiagram, item) {
377 public Result perform(ReadGraph graph) throws DatabaseException {
378 boolean oldSynchronous = graph.getSynchronous();
380 graph.setSynchronous(false);
381 Result result = calculateStyle(graph, parameter2, entry, parameter3);
382 if (DebugPolicy.DEBUG_PROFILE_STYLE_GROUP_TRACKING)
383 System.out.println(StyleBase.this + ": calculated style result for " + NameUtils.getSafeName(graph, parameter3, true) + ": " + result);
386 graph.setSynchronous(oldSynchronous);
393 * @param groupListener
397 * @param runtimeDiagram
400 protected Listener<Result> getStyleResultListener(ObserverGroupListener groupListener, final Resource item,
401 Group group, Observer observer, final Resource runtimeDiagram) {
402 return new ObserverGroupValueListener<Result>(groupListener, observer, group, item) {
404 public void execute(Result result) {
405 if (DebugPolicy.DEBUG_PROFILE_STYLE_GROUP_TRACKING)
406 System.out.println(StyleBase.this + ": style result changed for " + item + ": " + result);
407 styleResultChanged(observer, runtimeDiagram, item, result);
413 * @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)
416 public final void deactivate(Resource runtimeDiagram, Resource entry, Group group,
417 EvaluationContext observer) {
419 ObserverGroupListener listener = getListener(runtimeDiagram, group);
420 if (listener != null) {
422 if (DebugPolicy.DEBUG_PROFILE_STYLE_ACTIVATION)
423 System.out.println("deactivate(" + runtimeDiagram + ", " + group + ", " + observer);
425 IDiagram diagram = observer.getConstant(ProfileKeys.DIAGRAM);
427 listener.removeEntry(entry);
428 if (!listener.hasEntries()) {
430 StyleBaseData.getInstance().removeListener(new Tuple3(this, runtimeDiagram, group));
433 // This was too eager when multiple groups were tracked!
435 if (diagram != null) {
436 cleanupItems(observer, diagram, listener.getItems().toArray());
440 //observer.update(); TODO: Check if this is required!
446 * @see org.simantics.diagram.profile.Style#apply(org.simantics.g2d.diagram.IDiagram, org.simantics.diagram.profile.Group, org.simantics.diagram.profile.Observer)
449 public final void apply(Resource entry, Group group, final EvaluationContext evaluationContext) {
450 ICanvasContext context = evaluationContext.getConstant(ProfileKeys.CANVAS);
452 assert context.getThreadAccess().currentThreadAccess();
454 ObserverGroupListener listener = getListener(evaluationContext.getResource(), group);
455 if (listener == null) {
456 System.out.println(this + "(" + getClass().getSimpleName() + ") had no listener for " + evaluationContext.getResource() + " " + group);
460 final DataNodeMap map = evaluationContext.getConstant(ProfileKeys.NODE_MAP);
462 if (DebugPolicy.DEBUG_PROFILE_STYLE_APPLICATION)
463 System.out.println(StyleBase.this + ": applying style for items: " + listener.getItems());
466 StyleBaseData data = StyleBaseData.getInstance();
468 data.applyRemovals(evaluationContext, this);
470 IDiagram diagram = evaluationContext.getConstant(ProfileKeys.DIAGRAM);
471 assert diagram != null;
472 DataElementMap emap = diagram.getDiagramClass().getSingleItem(DataElementMap.class);
474 for (Object item : listener.getItems()) {
475 Result value = data.getValue(new Tuple3(this, evaluationContext.getResource(), item));
476 applyStyleForItem(evaluationContext, map, item, value);
478 IElement element = emap.getElement(diagram, item);
480 element.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
486 public final void apply2(Object item, final EvaluationContext evaluationContext) {
487 final DataNodeMap map = evaluationContext.getConstant(ProfileKeys.NODE_MAP);
489 StyleBaseData data = StyleBaseData.getInstance();
491 data.applyRemovals(evaluationContext, this);
493 Result value = data.getValue(new Tuple3(this, evaluationContext.getResource(), item));
494 applyStyleForItem(evaluationContext, map, item, value);
496 IDiagram diagram = evaluationContext.getConstant(ProfileKeys.DIAGRAM);
497 assert diagram != null;
498 DataElementMap emap = diagram.getDiagramClass().getSingleItem(DataElementMap.class);
499 IElement element = emap.getElement(diagram, item);
501 element.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
505 * This is ran when this profile entry gets deactivated after being first
506 * active. It allows cleaning up scene graph left-overs for the listened set
507 * of items before deactivation. It will invoke
508 * {@link #cleanupStyleForElement(IDiagram, IElement)} for each diagram element observed
509 * before deactivation in the AWT thread. If the profile observer is
510 * disposed in between scheduling to AWT thread, the method will do nothing.
512 * @param observer the diagram profile observer
513 * @param diagram the diagram this profile system is working with
514 * @param items the diagram data items that need to be cleaned up
516 protected final void cleanupItems(final EvaluationContext evaluationContext, final IDiagram diagram, final Object[] items) {
518 ICanvasContext context = evaluationContext.getConstant(ProfileKeys.CANVAS);
520 context.getThreadAccess().asyncExec(new Runnable() {
525 if (evaluationContext.isDisposed())
528 final DataNodeMap map = evaluationContext.getConstant(ProfileKeys.NODE_MAP);
530 if (DebugPolicy.DEBUG_PROFILE_STYLE_ACTIVATION)
531 System.out.println(this + ".cleanupItems(" + evaluationContext + ", " + diagram + ", " + Arrays.toString(items));
533 IDiagram diagram = evaluationContext.getConstant(ProfileKeys.DIAGRAM);
534 assert diagram != null;
535 DataElementMap emap = diagram.getDiagramClass().getSingleItem(DataElementMap.class);
537 for (Object item : items) {
538 cleanupStyleForItem(evaluationContext, map, item);
540 IElement element = emap.getElement(diagram, item);
542 element.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
548 private ObserverGroupListener getListener(Resource runtime, Group group) {
549 return StyleBaseData.getInstance().getListener(new Tuple3(this, runtime, group));