]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.diagram/src/org/simantics/diagram/profile/StyleBase.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / profile / StyleBase.java
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 (file)
index 0000000..888a6ea
--- /dev/null
@@ -0,0 +1,483 @@
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management in\r
+ * Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ *     VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.diagram.profile;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Arrays;\r
+import java.util.HashMap;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.concurrent.ConcurrentHashMap;\r
+\r
+import org.simantics.databoard.Bindings;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.RequestProcessor;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.Session;\r
+import org.simantics.db.common.procedure.adapter.TransientCacheListener;\r
+import org.simantics.db.common.request.TernaryRead;\r
+import org.simantics.db.common.request.UnaryRead;\r
+import org.simantics.db.common.utils.NameUtils;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.db.layer0.variable.Variable;\r
+import org.simantics.db.procedure.Listener;\r
+import org.simantics.db.request.Read;\r
+import org.simantics.diagram.stubs.DiagramResource;\r
+import org.simantics.g2d.canvas.ICanvasContext;\r
+import org.simantics.g2d.diagram.IDiagram;\r
+import org.simantics.g2d.element.IElement;\r
+import org.simantics.scenegraph.INode;\r
+import org.simantics.scenegraph.profile.DataNodeMap;\r
+import org.simantics.scenegraph.profile.EvaluationContext;\r
+import org.simantics.scenegraph.profile.Group;\r
+import org.simantics.scenegraph.profile.Observer;\r
+import org.simantics.scenegraph.profile.Style;\r
+import org.simantics.scenegraph.profile.common.ObserverGroupListener;\r
+import org.simantics.scenegraph.profile.common.ObserverGroupValueListener;\r
+import org.simantics.scenegraph.profile.impl.DebugPolicy;\r
+import org.simantics.scl.runtime.tuple.Tuple;\r
+import org.simantics.scl.runtime.tuple.Tuple2;\r
+import org.simantics.utils.datastructures.Pair;\r
+import org.simantics.utils.threads.AWTThread;\r
+\r
+/**\r
+ * For most style implementations it should be enough to override the following\r
+ * methods:\r
+ * <ul>\r
+ * <li>{@link #calculateStyle(ReadGraph, Resource, Resource, Variable)}</li>\r
+ * <li>{@link #applyStyleForElement(Observer, Object, IElement, Object)}</li>\r
+ * <li>{@link #cleanupStyleForElement(IDiagram, IElement)}</li>\r
+ * </ul>\r
+ * \r
+ * <p>\r
+ * See each method for a specification of what they are intended for.\r
+ * \r
+ * Optionally you can also override\r
+ * {@link #styleResultChanged(Observer, Resource, Object)} for optimization\r
+ * purposes but this is usually not necessary.\r
+ * \r
+ * @author Tuukka Lehtonen\r
+ * f\r
+ * @param <Result> type of result objects for styled group items tracked by this\r
+ *        style implementation, see\r
+ *        {@link #calculateStyle(ReadGraph, Resource, Resource, Variable)}\r
+ */\r
+public abstract class StyleBase<Result> implements Style {\r
+\r
+    protected final Map<Tuple, Result> values   = new ConcurrentHashMap<Tuple, Result>();\r
+\r
+//    private Map<Resource,ObserverGroupListener>         listeners = new ConcurrentHashMap<Resource, ObserverGroupListener>();\r
+\r
+    private Map<Pair<Resource, Group>, ObserverGroupListener> listeners = new HashMap<Pair<Resource, Group>, ObserverGroupListener>();\r
+    \r
+\r
+    private final List<Resource>                removals = new ArrayList<Resource>();\r
+\r
+    /**\r
+     * For caching this simple base request that is done in every\r
+     * {@link StyleBase#calculateStyle(ReadGraph, Resource, Resource, Resource)}.\r
+     */\r
+    static class RuntimeDiagramVariableRequest extends UnaryRead<Resource, Variable> {\r
+        public RuntimeDiagramVariableRequest(Resource runtimeDiagram) {\r
+            super(runtimeDiagram);\r
+        }\r
+\r
+        @Override\r
+        public Variable perform(ReadGraph graph) throws DatabaseException {\r
+            DiagramResource DIA = DiagramResource.getInstance(graph);\r
+            String variableURI = graph.getPossibleRelatedValue(parameter, DIA.RuntimeDiagram_HasVariable, Bindings.STRING);\r
+            if (variableURI == null)\r
+                return null;\r
+            Variable activeVariable = org.simantics.db.layer0.variable.Variables.getPossibleVariable(graph, variableURI);\r
+            return activeVariable;\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Calculates a typed result to be used for applying the style to a diagram\r
+     * in\r
+     * {@link #applyStyleForElement(Observer, IDiagram, Object, IElement, Object)}\r
+     * . The graph request system will take care of only notifying other systems\r
+     * when the style result actually changes.\r
+     * \r
+     * <p>\r
+     * This implementation uses {@link RuntimeDiagramVariableRequest} to\r
+     * discover the active composite variable related to the specified\r
+     * runtimeDiagram and invokes\r
+     * {@link StyleBase#calculateStyle(ReadGraph, Resource, Resource, Resource, Variable)}\r
+     * with it.\r
+     * \r
+     * @param graph\r
+     *            database read access\r
+     * @param runtimeDiagram\r
+     *            resource describing the runtime data of the styled diagram\r
+     * @param entry\r
+     *            profile entry resource, don't care if now aware\r
+     * @param groupItem\r
+     *            an item belonging to the observed group\r
+     * @return the calculated style result object\r
+     * @throws DatabaseException\r
+     * @see {@link StyleBase#calculateStyle(ReadGraph, Resource, Resource, Resource, Variable)}\r
+     */\r
+    public Result calculateStyle(ReadGraph graph, Resource runtimeDiagram, Resource entry, Resource groupItem) throws DatabaseException {\r
+        Variable activeVariable = graph.syncRequest(new RuntimeDiagramVariableRequest(runtimeDiagram), TransientCacheListener.<Variable>instance());\r
+        if (activeVariable == null)\r
+            return null;\r
+\r
+        //System.out.println("URI1: " + configuration.getURI(graph));\r
+        //System.out.println("URI2: " + activeVariable.getURI(graph));\r
+        return calculateStyle(graph, runtimeDiagram, entry, groupItem, activeVariable);\r
+    }\r
+\r
+    /**\r
+     * Calculates a typed result to be used for applying the style to a diagram\r
+     * in\r
+     * {@link #applyStyleForElement(Observer, IDiagram, Object, IElement, Object)}\r
+     * . The graph request system will take care of only notifying other systems\r
+     * when the style result actually changes.\r
+     * \r
+     * @param graph database read access\r
+     * @param runtimeDiagram resource describing the runtime data of the styled\r
+     *        diagram\r
+     * @param entry profile entry resource, don't care if now aware\r
+     * @param groupItem an item belonging to the observed group\r
+     * @param activeComposite variable for accessing the active realization of the\r
+     *        diagram's corresponding composite. This may change when\r
+     *        experiments are activate and deactivated. When there is no\r
+     *        experiment active, this is the base realization, i.e. the\r
+     *        configuration.\r
+     * @return the calculated style result object\r
+     * @throws DatabaseException\r
+     * @see {@link StyleBase#calculateStyle(ReadGraph, Resource, Resource, Resource, Variable)}\r
+     */\r
+    public Result calculateStyle(ReadGraph graph, Resource runtimeDiagram, Resource entry, Resource groupItem, Variable activeComposite) throws DatabaseException {\r
+        return null;\r
+    }\r
+\r
+    /**\r
+     * Invoked when the result calculated by\r
+     * {@link #calculateStyle(ReadGraph, Resource, Resource, Variable)} changes.\r
+     * Used for keeping track of the latest calculated style values that are\r
+     * applied in\r
+     * {@link #applyStyleForElement(Observer, IDiagram, Object, IElement, Object)}.\r
+     * \r
+     * @param observer\r
+     * @param object\r
+     * @param result\r
+     */\r
+    public void styleResultChanged(Observer observer, Resource runtimeDiagram, Resource object, Result result) {\r
+        if (result == null)\r
+            values.remove(new Tuple2(runtimeDiagram, object));\r
+        else\r
+            values.put(new Tuple2(runtimeDiagram, object), result);\r
+        observer.update();\r
+    }\r
+\r
+    /**\r
+     * Apply the latest style result calculated by\r
+     * {@link #calculateStyle(ReadGraph, Resource, Resource, Variable)} to the\r
+     * scene graph of the specified diagram item (i.e. element).\r
+     * \r
+     * <p>\r
+     * <code>StyleBase</code> ensures that this method is invoked in the AWT\r
+     * thread only.\r
+     * \r
+     * @param observer profile system observer\r
+     * @param item the styled diagram item data\r
+     * @param element the styled diagram element representing the data item\r
+     * @param result the latest result calculated by\r
+     *        {@link #calculateStyle(ReadGraph, Resource, Resource, Variable)}\r
+     */\r
+    public void applyStyleForNode(EvaluationContext evaluationContext, INode node, Result result) {\r
+    }\r
+    \r
+    public void applyStyleForItem(EvaluationContext evaluationContext, DataNodeMap map, Object item, Result value) {\r
+        \r
+        final INode node = map.getNode(item);\r
+        if (node == null) {\r
+            evaluationContext.update();\r
+            // TODO: continue or return?\r
+            return;\r
+        }\r
+\r
+        if (DebugPolicy.DEBUG_PROFILE_STYLE_APPLICATION)\r
+            System.out.println(StyleBase.this + ": applying style for item " + item + " and element " + node + " with result " + value);\r
+\r
+        applyStyleForNode(evaluationContext, node, value);\r
+        \r
+    }\r
+\r
+    /**\r
+     * This method is invoked by\r
+     * {@link #cleanupStyleForNode(EvaluationContext, INode)} when the style is\r
+     * deactivated. It is invoked for each diagram element tracked by the style\r
+     * before deactivation.\r
+     * \r
+     * @param node a previously tracked and styled scene graph node\r
+     */\r
+    protected void cleanupStyleForNode(INode node) {\r
+    }\r
+\r
+    /**\r
+     * This method is invoked by\r
+     * {@link #cleanupStyleForItem(EvaluationContext, DataNodeMap, Object)} when the style is\r
+     * deactivated. It is invoked for each diagram element tracked by the style\r
+     * before deactivation.\r
+     * @param evaluationContext the context of this style evaluation\r
+     * @param node a previously tracked and styled scene graph node\r
+     */\r
+    protected void cleanupStyleForNode(EvaluationContext evaluationContext, INode node) {\r
+        cleanupStyleForNode(node);\r
+    }\r
+\r
+    protected void cleanupStyleForItem(EvaluationContext evaluationContext, DataNodeMap map, Object item) {\r
+\r
+        final INode node = map.getNode(item);\r
+        if (node != null) {\r
+            if (DebugPolicy.DEBUG_PROFILE_STYLE_ACTIVATION)\r
+                System.out.println(this + ".cleanupStyleForItem(" + item + " = " + node + ")");\r
+            cleanupStyleForNode(evaluationContext, node);\r
+        }\r
+\r
+    }\r
+    \r
+    \r
+    static class GroupListener<T> extends ObserverGroupListener {\r
+       \r
+       private StyleBase<T> style;\r
+       private Session session;\r
+       private Resource runtimeDiagram;\r
+       private Resource entry;\r
+       \r
+       GroupListener(Session session, Resource runtimeDiagram, Resource entry, StyleBase<T> style, Group group, Observer observer) {\r
+               super(style, group, observer);\r
+               this.style = style;\r
+               this.session = session;\r
+               this.runtimeDiagram = runtimeDiagram;\r
+               this.entry = entry;\r
+       }\r
+       \r
+         @Override\r
+          public void add(final Resource item) {\r
+\r
+                 if (DebugPolicy.DEBUG_PROFILE_STYLE_GROUP_TRACKING)\r
+                  System.out.println(style + ": added to group " + group + ": " + item);\r
+\r
+              session.asyncRequest(\r
+                      style.getStyleCalculationRequest(runtimeDiagram, entry, item),\r
+                      style.getStyleResultListener(this, item, group, observer, runtimeDiagram)\r
+              );\r
+\r
+              super.add(item);\r
+          }\r
+          @Override\r
+          public void remove(Resource item) {\r
+              if (DebugPolicy.DEBUG_PROFILE_STYLE_GROUP_TRACKING)\r
+                  System.out.println(style + ": removed from group " + group + ": " + item);\r
+\r
+              synchronized (style.removals) {\r
+                 style.removals.add(item);\r
+              }\r
+\r
+              // TODO: do something here to dispose of ObserverGroupValueListeners?\r
+              super.remove(item);\r
+          }  \r
+          \r
+    }\r
+    \r
+    /* (non-Javadoc)\r
+     * @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)\r
+     */\r
+    @Override\r
+    public final void activate(RequestProcessor backend, final Resource runtimeDiagram, final Resource entry, final Group group, final EvaluationContext observer) {\r
+\r
+        ObserverGroupListener listener = getListener(runtimeDiagram, group);\r
+\r
+        if (listener == null || listener.isDisposed()) {\r
+\r
+            if (DebugPolicy.DEBUG_PROFILE_STYLE_ACTIVATION)\r
+                System.out.println("activate(" + runtimeDiagram + ", " + group + ", " + observer);\r
+\r
+            listener = new GroupListener<Result>(backend.getSession(), runtimeDiagram, entry, this, group, observer);\r
+\r
+            listeners.put(Pair.make(runtimeDiagram, group), listener);\r
+\r
+            group.trackItems(backend, runtimeDiagram, listener);\r
+\r
+        }\r
+\r
+        // Register this entry in the listener\r
+        listener.addEntry(entry);\r
+    }\r
+\r
+    /**\r
+     * Used to customize the identity given to graph requests made for this\r
+     * style. Default identity is getClass().\r
+     * \r
+     * @return identity object used in graph requests made by this style\r
+     */\r
+    protected Object getIdentity(Resource entry) {\r
+        return new Pair<Class<?>, Resource>(getClass(), entry);\r
+    }\r
+\r
+    /**\r
+     * @param configuration\r
+     * @param runtimeDiagram\r
+     * @param item\r
+     * @return\r
+     */\r
+    protected Read<Result> getStyleCalculationRequest(Resource runtimeDiagram, final Resource entry, Resource item) {\r
+        return new TernaryRead<Object, Resource, Resource, Result>(getIdentity(entry), runtimeDiagram, item) {\r
+            @Override\r
+            public Result perform(ReadGraph graph) throws DatabaseException {\r
+                boolean oldSynchronous = graph.getSynchronous();\r
+                try {\r
+                    graph.setSynchronous(false);\r
+                    Result result = calculateStyle(graph, parameter2, entry, parameter3);\r
+                    if (DebugPolicy.DEBUG_PROFILE_STYLE_GROUP_TRACKING)\r
+                        System.out.println(StyleBase.this + ": calculated style result for " + NameUtils.getSafeName(graph, parameter3, true) + ": " + result);\r
+                    return result;\r
+                } finally {\r
+                    graph.setSynchronous(oldSynchronous);\r
+                }\r
+            }\r
+        };\r
+    }\r
+\r
+    /**\r
+     * @param groupListener\r
+     * @param item\r
+     * @param group\r
+     * @param observer\r
+     * @param runtimeDiagram \r
+     * @return\r
+     */\r
+    protected Listener<Result> getStyleResultListener(ObserverGroupListener groupListener, final Resource item,\r
+            Group group, Observer observer, final Resource runtimeDiagram) {\r
+        return new ObserverGroupValueListener<Result>(groupListener, observer, group, item) {\r
+            @Override\r
+            public void execute(Result result) {\r
+                if (DebugPolicy.DEBUG_PROFILE_STYLE_GROUP_TRACKING)\r
+                    System.out.println(StyleBase.this + ": style result changed for " + item + ": " + result);\r
+                styleResultChanged(observer, runtimeDiagram, item, result);\r
+            }\r
+        };\r
+    }\r
+\r
+    /* (non-Javadoc)\r
+     * @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)\r
+     */\r
+    @Override\r
+    public final void deactivate(Resource runtimeDiagram, Resource entry, Group group,\r
+            EvaluationContext observer) {\r
+\r
+        ObserverGroupListener listener = getListener(runtimeDiagram, group);\r
+        if (listener != null) {\r
+\r
+            if (DebugPolicy.DEBUG_PROFILE_STYLE_ACTIVATION)\r
+                System.out.println("deactivate(" + runtimeDiagram + ", " + group + ", " + observer);\r
+\r
+            IDiagram diagram = observer.getConstant(ProfileKeys.DIAGRAM);\r
+\r
+            listener.removeEntry(entry);\r
+            if (!listener.hasEntries()) {\r
+                listener.dispose();\r
+                listeners.remove(Pair.make(runtimeDiagram, group));\r
+            }\r
+\r
+            // This was too eager when multiple groups were tracked!\r
+            //values.clear();\r
+            if (diagram != null) {\r
+                cleanupItems(observer, diagram, listener.getItems().toArray());\r
+                diagram = null;\r
+            }\r
+            observer.update();\r
+        }\r
+\r
+    }\r
+\r
+    /* (non-Javadoc)\r
+     * @see org.simantics.diagram.profile.Style#apply(org.simantics.g2d.diagram.IDiagram, org.simantics.diagram.profile.Group, org.simantics.diagram.profile.Observer)\r
+     */\r
+    @Override\r
+    public final void apply(Resource entry, Group group, final EvaluationContext evaluationContext) {\r
+\r
+        ICanvasContext context = evaluationContext.getConstant(ProfileKeys.CANVAS);\r
+        \r
+        assert context.getThreadAccess().currentThreadAccess();\r
+\r
+        ObserverGroupListener listener = getListener(evaluationContext.getResource(), group);\r
+        if (listener == null) {\r
+            System.out.println(this + "(" + getClass().getSimpleName() + ") had no listener for " + evaluationContext.getResource() + " " + group);\r
+            return;\r
+        }\r
+        \r
+        final DataNodeMap map = evaluationContext.getConstant(ProfileKeys.NODE_MAP);\r
+\r
+        if (DebugPolicy.DEBUG_PROFILE_STYLE_APPLICATION)\r
+            System.out.println(StyleBase.this + ": applying style for items: " + listener.getItems());\r
+\r
+        if (!removals.isEmpty()) {\r
+            Resource[] removed;\r
+            synchronized (removals) {\r
+                removed = removals.toArray(Resource.NONE);\r
+                removals.clear();\r
+            }\r
+            for (Resource item : removed) {\r
+                cleanupStyleForItem(evaluationContext, map, item);\r
+            }\r
+        }\r
+        \r
+        for (Object item : listener.getItems()) {\r
+            Result value = values.get(new Tuple2(evaluationContext.getResource(), item));\r
+            applyStyleForItem(evaluationContext, map, item, value);\r
+        }\r
+        \r
+    }\r
+\r
+    /**\r
+     * This is ran when this profile entry gets deactivated after being first\r
+     * active. It allows cleaning up scene graph left-overs for the listened set\r
+     * of items before deactivation. It will invoke\r
+     * {@link #cleanupStyleForElement(IDiagram, IElement)} for each diagram element observed\r
+     * before deactivation in the AWT thread. If the profile observer is\r
+     * disposed in between scheduling to AWT thread, the method will do nothing.\r
+     * \r
+     * @param observer the diagram profile observer\r
+     * @param diagram the diagram this profile system is working with\r
+     * @param items the diagram data items that need to be cleaned up\r
+     */\r
+    protected final void cleanupItems(final EvaluationContext evaluationContext, final IDiagram diagram, final Object[] items) {\r
+        AWTThread.getThreadAccess().asyncExec(new Runnable() {\r
+            @Override\r
+            public void run() {\r
+                \r
+                if (evaluationContext.isDisposed())\r
+                    return;\r
+\r
+                final DataNodeMap map = evaluationContext.getConstant(ProfileKeys.NODE_MAP);\r
+                \r
+                if (DebugPolicy.DEBUG_PROFILE_STYLE_ACTIVATION)\r
+                    System.out.println(this + ".cleanupItems(" + evaluationContext + ", " + diagram + ", " + Arrays.toString(items));\r
+\r
+                for (Object item : items) {\r
+                    cleanupStyleForItem(evaluationContext, map, item);\r
+                }\r
+            }\r
+        });\r
+    }\r
+    \r
+    private ObserverGroupListener getListener(Resource runtime, Group group) {\r
+        return listeners.get(Pair.make(runtime, group));\r
+    }\r
+\r
+}\r