X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.diagram%2Fsrc%2Forg%2Fsimantics%2Fdiagram%2Fprofile%2FStyleBase.java;fp=bundles%2Forg.simantics.diagram%2Fsrc%2Forg%2Fsimantics%2Fdiagram%2Fprofile%2FStyleBase.java;h=888a6ea0ec14a16b450ce8de7b5dc61e7eeaea89;hb=969bd23cab98a79ca9101af33334000879fb60c5;hp=0000000000000000000000000000000000000000;hpb=866dba5cd5a3929bbeae85991796acb212338a08;p=simantics%2Fplatform.git diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/profile/StyleBase.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/profile/StyleBase.java new file mode 100644 index 000000000..888a6ea0e --- /dev/null +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/profile/StyleBase.java @@ -0,0 +1,483 @@ +/******************************************************************************* + * 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(getIdentity(entry), runtimeDiagram, item) { + @Override + public Result perform(ReadGraph graph) throws DatabaseException { + boolean oldSynchronous = graph.getSynchronous(); + try { + graph.setSynchronous(false); + Result result = calculateStyle(graph, parameter2, entry, parameter3); + if (DebugPolicy.DEBUG_PROFILE_STYLE_GROUP_TRACKING) + System.out.println(StyleBase.this + ": calculated style result for " + NameUtils.getSafeName(graph, parameter3, true) + ": " + result); + return result; + } finally { + graph.setSynchronous(oldSynchronous); + } + } + }; + } + + /** + * @param groupListener + * @param item + * @param group + * @param observer + * @param runtimeDiagram + * @return + */ + protected Listener getStyleResultListener(ObserverGroupListener groupListener, final Resource item, + Group group, Observer observer, final Resource runtimeDiagram) { + return new ObserverGroupValueListener(groupListener, observer, group, item) { + @Override + public void execute(Result result) { + if (DebugPolicy.DEBUG_PROFILE_STYLE_GROUP_TRACKING) + System.out.println(StyleBase.this + ": style result changed for " + item + ": " + result); + styleResultChanged(observer, runtimeDiagram, item, result); + } + }; + } + + /* (non-Javadoc) + * @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) + */ + @Override + public final void deactivate(Resource runtimeDiagram, Resource entry, Group group, + EvaluationContext observer) { + + ObserverGroupListener listener = getListener(runtimeDiagram, group); + if (listener != null) { + + if (DebugPolicy.DEBUG_PROFILE_STYLE_ACTIVATION) + System.out.println("deactivate(" + runtimeDiagram + ", " + group + ", " + observer); + + IDiagram diagram = observer.getConstant(ProfileKeys.DIAGRAM); + + listener.removeEntry(entry); + if (!listener.hasEntries()) { + listener.dispose(); + listeners.remove(Pair.make(runtimeDiagram, group)); + } + + // This was too eager when multiple groups were tracked! + //values.clear(); + if (diagram != null) { + cleanupItems(observer, diagram, listener.getItems().toArray()); + diagram = null; + } + observer.update(); + } + + } + + /* (non-Javadoc) + * @see org.simantics.diagram.profile.Style#apply(org.simantics.g2d.diagram.IDiagram, org.simantics.diagram.profile.Group, org.simantics.diagram.profile.Observer) + */ + @Override + public final void apply(Resource entry, Group group, final EvaluationContext evaluationContext) { + + ICanvasContext context = evaluationContext.getConstant(ProfileKeys.CANVAS); + + assert context.getThreadAccess().currentThreadAccess(); + + ObserverGroupListener listener = getListener(evaluationContext.getResource(), group); + if (listener == null) { + System.out.println(this + "(" + getClass().getSimpleName() + ") had no listener for " + evaluationContext.getResource() + " " + group); + return; + } + + final DataNodeMap map = evaluationContext.getConstant(ProfileKeys.NODE_MAP); + + if (DebugPolicy.DEBUG_PROFILE_STYLE_APPLICATION) + System.out.println(StyleBase.this + ": applying style for items: " + listener.getItems()); + + if (!removals.isEmpty()) { + Resource[] removed; + synchronized (removals) { + removed = removals.toArray(Resource.NONE); + removals.clear(); + } + for (Resource item : removed) { + cleanupStyleForItem(evaluationContext, map, item); + } + } + + for (Object item : listener.getItems()) { + Result value = values.get(new Tuple2(evaluationContext.getResource(), item)); + applyStyleForItem(evaluationContext, map, item, value); + } + + } + + /** + * This is ran when this profile entry gets deactivated after being first + * active. It allows cleaning up scene graph left-overs for the listened set + * of items before deactivation. It will invoke + * {@link #cleanupStyleForElement(IDiagram, IElement)} for each diagram element observed + * before deactivation in the AWT thread. If the profile observer is + * disposed in between scheduling to AWT thread, the method will do nothing. + * + * @param observer the diagram profile observer + * @param diagram the diagram this profile system is working with + * @param items the diagram data items that need to be cleaned up + */ + protected final void cleanupItems(final EvaluationContext evaluationContext, final IDiagram diagram, final Object[] items) { + AWTThread.getThreadAccess().asyncExec(new Runnable() { + @Override + public void run() { + + if (evaluationContext.isDisposed()) + return; + + final DataNodeMap map = evaluationContext.getConstant(ProfileKeys.NODE_MAP); + + if (DebugPolicy.DEBUG_PROFILE_STYLE_ACTIVATION) + System.out.println(this + ".cleanupItems(" + evaluationContext + ", " + diagram + ", " + Arrays.toString(items)); + + for (Object item : items) { + cleanupStyleForItem(evaluationContext, map, item); + } + } + }); + } + + private ObserverGroupListener getListener(Resource runtime, Group group) { + return listeners.get(Pair.make(runtime, group)); + } + +}