/*******************************************************************************
* Copyright (c) 2007, 2010 Association for Decentralized Information Management in
* Industry THTH ry.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* VTT Technical Research Centre of Finland - initial API and implementation
*******************************************************************************/
package org.simantics.diagram.profile;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.simantics.databoard.Bindings;
import org.simantics.db.ReadGraph;
import org.simantics.db.RequestProcessor;
import org.simantics.db.Resource;
import org.simantics.db.Session;
import org.simantics.db.common.procedure.adapter.TransientCacheListener;
import org.simantics.db.common.request.TernaryRead;
import org.simantics.db.common.request.UnaryRead;
import org.simantics.db.common.utils.NameUtils;
import org.simantics.db.exception.DatabaseException;
import org.simantics.db.layer0.variable.Variable;
import org.simantics.db.procedure.Listener;
import org.simantics.db.request.Read;
import org.simantics.diagram.stubs.DiagramResource;
import org.simantics.g2d.canvas.ICanvasContext;
import org.simantics.g2d.diagram.IDiagram;
import org.simantics.g2d.element.IElement;
import org.simantics.scenegraph.INode;
import org.simantics.scenegraph.profile.DataNodeMap;
import org.simantics.scenegraph.profile.EvaluationContext;
import org.simantics.scenegraph.profile.Group;
import org.simantics.scenegraph.profile.Observer;
import org.simantics.scenegraph.profile.Style;
import org.simantics.scenegraph.profile.common.ObserverGroupListener;
import org.simantics.scenegraph.profile.common.ObserverGroupValueListener;
import org.simantics.scenegraph.profile.impl.DebugPolicy;
import org.simantics.scl.runtime.tuple.Tuple;
import org.simantics.scl.runtime.tuple.Tuple2;
import org.simantics.utils.datastructures.Pair;
import org.simantics.utils.threads.AWTThread;
/**
* For most style implementations it should be enough to override the following
* methods:
*
* See each method for a specification of what they are intended for.
*
* Optionally you can also override
* {@link #styleResultChanged(Observer, Resource, Object)} for optimization
* purposes but this is usually not necessary.
*
* @author Tuukka Lehtonen
* f
* @param type of result objects for styled group items tracked by this
* style implementation, see
* {@link #calculateStyle(ReadGraph, Resource, Resource, Variable)}
*/
public abstract class StyleBase implements Style {
protected final Map values = new ConcurrentHashMap();
// private Map listeners = new ConcurrentHashMap();
private Map, ObserverGroupListener> listeners = new HashMap, ObserverGroupListener>();
private final List removals = new ArrayList();
/**
* For caching this simple base request that is done in every
* {@link StyleBase#calculateStyle(ReadGraph, Resource, Resource, Resource)}.
*/
static class RuntimeDiagramVariableRequest extends UnaryRead {
public RuntimeDiagramVariableRequest(Resource runtimeDiagram) {
super(runtimeDiagram);
}
@Override
public Variable perform(ReadGraph graph) throws DatabaseException {
DiagramResource DIA = DiagramResource.getInstance(graph);
String variableURI = graph.getPossibleRelatedValue(parameter, DIA.RuntimeDiagram_HasVariable, Bindings.STRING);
if (variableURI == null)
return null;
Variable activeVariable = org.simantics.db.layer0.variable.Variables.getPossibleVariable(graph, variableURI);
return activeVariable;
}
}
/**
* Calculates a typed result to be used for applying the style to a diagram
* in
* {@link #applyStyleForElement(Observer, IDiagram, Object, IElement, Object)}
* . The graph request system will take care of only notifying other systems
* when the style result actually changes.
*
*
* This implementation uses {@link RuntimeDiagramVariableRequest} to
* discover the active composite variable related to the specified
* runtimeDiagram and invokes
* {@link StyleBase#calculateStyle(ReadGraph, Resource, Resource, Resource, Variable)}
* with it.
*
* @param graph
* database read access
* @param runtimeDiagram
* resource describing the runtime data of the styled diagram
* @param entry
* profile entry resource, don't care if now aware
* @param groupItem
* an item belonging to the observed group
* @return the calculated style result object
* @throws DatabaseException
* @see {@link StyleBase#calculateStyle(ReadGraph, Resource, Resource, Resource, Variable)}
*/
public Result calculateStyle(ReadGraph graph, Resource runtimeDiagram, Resource entry, Resource groupItem) throws DatabaseException {
Variable activeVariable = graph.syncRequest(new RuntimeDiagramVariableRequest(runtimeDiagram), TransientCacheListener.instance());
if (activeVariable == null)
return null;
//System.out.println("URI1: " + configuration.getURI(graph));
//System.out.println("URI2: " + activeVariable.getURI(graph));
return calculateStyle(graph, runtimeDiagram, entry, groupItem, activeVariable);
}
/**
* Calculates a typed result to be used for applying the style to a diagram
* in
* {@link #applyStyleForElement(Observer, IDiagram, Object, IElement, Object)}
* . The graph request system will take care of only notifying other systems
* when the style result actually changes.
*
* @param graph database read access
* @param runtimeDiagram resource describing the runtime data of the styled
* diagram
* @param entry profile entry resource, don't care if now aware
* @param groupItem an item belonging to the observed group
* @param activeComposite variable for accessing the active realization of the
* diagram's corresponding composite. This may change when
* experiments are activate and deactivated. When there is no
* experiment active, this is the base realization, i.e. the
* configuration.
* @return the calculated style result object
* @throws DatabaseException
* @see {@link StyleBase#calculateStyle(ReadGraph, Resource, Resource, Resource, Variable)}
*/
public Result calculateStyle(ReadGraph graph, Resource runtimeDiagram, Resource entry, Resource groupItem, Variable activeComposite) throws DatabaseException {
return null;
}
/**
* Invoked when the result calculated by
* {@link #calculateStyle(ReadGraph, Resource, Resource, Variable)} changes.
* Used for keeping track of the latest calculated style values that are
* applied in
* {@link #applyStyleForElement(Observer, IDiagram, Object, IElement, Object)}.
*
* @param observer
* @param object
* @param result
*/
public void styleResultChanged(Observer observer, Resource runtimeDiagram, Resource object, Result result) {
if (result == null)
values.remove(new Tuple2(runtimeDiagram, object));
else
values.put(new Tuple2(runtimeDiagram, object), result);
observer.update();
}
/**
* Apply the latest style result calculated by
* {@link #calculateStyle(ReadGraph, Resource, Resource, Variable)} to the
* scene graph of the specified diagram item (i.e. element).
*
*
* StyleBase ensures that this method is invoked in the AWT
* thread only.
*
* @param observer profile system observer
* @param item the styled diagram item data
* @param element the styled diagram element representing the data item
* @param result the latest result calculated by
* {@link #calculateStyle(ReadGraph, Resource, Resource, Variable)}
*/
public void applyStyleForNode(EvaluationContext evaluationContext, INode node, Result result) {
}
public void applyStyleForItem(EvaluationContext evaluationContext, DataNodeMap map, Object item, Result value) {
final INode node = map.getNode(item);
if (node == null) {
evaluationContext.update();
// TODO: continue or return?
return;
}
if (DebugPolicy.DEBUG_PROFILE_STYLE_APPLICATION)
System.out.println(StyleBase.this + ": applying style for item " + item + " and element " + node + " with result " + value);
applyStyleForNode(evaluationContext, node, value);
}
/**
* This method is invoked by
* {@link #cleanupStyleForNode(EvaluationContext, INode)} when the style is
* deactivated. It is invoked for each diagram element tracked by the style
* before deactivation.
*
* @param node a previously tracked and styled scene graph node
*/
protected void cleanupStyleForNode(INode node) {
}
/**
* This method is invoked by
* {@link #cleanupStyleForItem(EvaluationContext, DataNodeMap, Object)} when the style is
* deactivated. It is invoked for each diagram element tracked by the style
* before deactivation.
* @param evaluationContext the context of this style evaluation
* @param node a previously tracked and styled scene graph node
*/
protected void cleanupStyleForNode(EvaluationContext evaluationContext, INode node) {
cleanupStyleForNode(node);
}
protected void cleanupStyleForItem(EvaluationContext evaluationContext, DataNodeMap map, Object item) {
final INode node = map.getNode(item);
if (node != null) {
if (DebugPolicy.DEBUG_PROFILE_STYLE_ACTIVATION)
System.out.println(this + ".cleanupStyleForItem(" + item + " = " + node + ")");
cleanupStyleForNode(evaluationContext, node);
}
}
static class GroupListener extends ObserverGroupListener {
private StyleBase style;
private Session session;
private Resource runtimeDiagram;
private Resource entry;
GroupListener(Session session, Resource runtimeDiagram, Resource entry, StyleBase style, Group group, Observer observer) {
super(style, group, observer);
this.style = style;
this.session = session;
this.runtimeDiagram = runtimeDiagram;
this.entry = entry;
}
@Override
public void add(final Resource item) {
if (DebugPolicy.DEBUG_PROFILE_STYLE_GROUP_TRACKING)
System.out.println(style + ": added to group " + group + ": " + item);
session.asyncRequest(
style.getStyleCalculationRequest(runtimeDiagram, entry, item),
style.getStyleResultListener(this, item, group, observer, runtimeDiagram)
);
super.add(item);
}
@Override
public void remove(Resource item) {
if (DebugPolicy.DEBUG_PROFILE_STYLE_GROUP_TRACKING)
System.out.println(style + ": removed from group " + group + ": " + item);
synchronized (style.removals) {
style.removals.add(item);
}
// TODO: do something here to dispose of ObserverGroupValueListeners?
super.remove(item);
}
}
/* (non-Javadoc)
* @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)
*/
@Override
public final void activate(RequestProcessor backend, final Resource runtimeDiagram, final Resource entry, final Group group, final EvaluationContext observer) {
ObserverGroupListener listener = getListener(runtimeDiagram, group);
if (listener == null || listener.isDisposed()) {
if (DebugPolicy.DEBUG_PROFILE_STYLE_ACTIVATION)
System.out.println("activate(" + runtimeDiagram + ", " + group + ", " + observer);
listener = new GroupListener(backend.getSession(), runtimeDiagram, entry, this, group, observer);
listeners.put(Pair.make(runtimeDiagram, group), listener);
group.trackItems(backend, runtimeDiagram, listener);
}
// Register this entry in the listener
listener.addEntry(entry);
}
/**
* Used to customize the identity given to graph requests made for this
* style. Default identity is getClass().
*
* @return identity object used in graph requests made by this style
*/
protected Object getIdentity(Resource entry) {
return new Pair, Resource>(getClass(), entry);
}
/**
* @param configuration
* @param runtimeDiagram
* @param item
* @return
*/
protected Read getStyleCalculationRequest(Resource runtimeDiagram, final Resource entry, Resource item) {
return new TernaryRead