/******************************************************************************* * 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.Arrays; 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.Hints; import org.simantics.g2d.canvas.ICanvasContext; import org.simantics.g2d.diagram.IDiagram; import org.simantics.g2d.diagram.handler.DataElementMap; 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.Tuple3; import org.simantics.utils.datastructures.Pair; /** * 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 { private Object identity; private double priority; public StyleBase(Object identity) { this.identity = identity; } public StyleBase() { this.identity = getClass(); } @SuppressWarnings("unchecked") protected T getIdentity() { return (T)identity; } public void setPriority(double priority) { this.priority = priority; } public double getPriority() { return priority; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((identity == null) ? 0 : identity.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; StyleBase other = (StyleBase) obj; if (identity == null) { if (other.identity != null) return false; } else if (!identity.equals(other.identity)) return false; return true; } protected Resource getResource() { return getIdentity(); } /** * 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) StyleBaseData.getInstance().removeValue(new Tuple3(this, runtimeDiagram, object)); else StyleBaseData.getInstance().putValue(new Tuple3(this, runtimeDiagram, object), result); observer.update(this, object); } /** * 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(this, item); // 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); StyleBaseData.getInstance().removeItem(style, 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) throws DatabaseException { 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); StyleBaseData.getInstance().putListener(new Tuple3(this, 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(); StyleBaseData.getInstance().removeListener(new Tuple3(this, 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(); TODO: Check if this is required! } } /* (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()); StyleBaseData data = StyleBaseData.getInstance(); data.applyRemovals(evaluationContext, this); IDiagram diagram = evaluationContext.getConstant(ProfileKeys.DIAGRAM); assert diagram != null; DataElementMap emap = diagram.getDiagramClass().getSingleItem(DataElementMap.class); for (Object item : listener.getItems()) { Result value = data.getValue(new Tuple3(this, evaluationContext.getResource(), item)); applyStyleForItem(evaluationContext, map, item, value); IElement element = emap.getElement(diagram, item); if (element != null) element.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY); } } @Override public final void apply2(Object item, final EvaluationContext evaluationContext) { final DataNodeMap map = evaluationContext.getConstant(ProfileKeys.NODE_MAP); StyleBaseData data = StyleBaseData.getInstance(); data.applyRemovals(evaluationContext, this); Result value = data.getValue(new Tuple3(this, evaluationContext.getResource(), item)); applyStyleForItem(evaluationContext, map, item, value); IDiagram diagram = evaluationContext.getConstant(ProfileKeys.DIAGRAM); assert diagram != null; DataElementMap emap = diagram.getDiagramClass().getSingleItem(DataElementMap.class); IElement element = emap.getElement(diagram, item); if (element != null) element.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY); } /** * 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) { ICanvasContext context = evaluationContext.getConstant(ProfileKeys.CANVAS); context.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)); IDiagram diagram = evaluationContext.getConstant(ProfileKeys.DIAGRAM); assert diagram != null; DataElementMap emap = diagram.getDiagramClass().getSingleItem(DataElementMap.class); for (Object item : items) { cleanupStyleForItem(evaluationContext, map, item); IElement element = emap.getElement(diagram, item); if (element != null) element.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY); } } }); } private ObserverGroupListener getListener(Resource runtime, Group group) { return StyleBaseData.getInstance().getListener(new Tuple3(this, runtime, group)); } }