]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.diagram/src/org/simantics/diagram/profile/StyleBase.java
Fixed multiple issues causing dangling references to discarded queries
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / profile / StyleBase.java
index 888a6ea0ec14a16b450ce8de7b5dc61e7eeaea89..fee1e8546d7e96e07bf13a21f53b98f26afac6c4 100644 (file)
-/*******************************************************************************\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
+/*******************************************************************************
+ * 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:
+ * <ul>
+ * <li>{@link #calculateStyle(ReadGraph, Resource, Resource, Variable)}</li>
+ * <li>{@link #applyStyleForElement(Observer, Object, IElement, Object)}</li>
+ * <li>{@link #cleanupStyleForElement(IDiagram, IElement)}</li>
+ * </ul>
+ * 
+ * <p>
+ * 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 <Result> type of result objects for styled group items tracked by this
+ *        style implementation, see
+ *        {@link #calculateStyle(ReadGraph, Resource, Resource, Variable)}
+ */
+public abstract class StyleBase<Result> implements Style {
+
+    private Object identity;
+    private double priority;
+
+    public StyleBase(Object identity) {
+        this.identity = identity;
+    }
+
+    public StyleBase() {
+        this.identity = getClass();
+    }
+
+    @SuppressWarnings("unchecked")
+    protected <T> 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<Resource, Variable> {
+        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.
+     * 
+     * <p>
+     * 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.<Variable>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).
+     * 
+     * <p>
+     * <code>StyleBase</code> 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<T> extends ObserverGroupListener {
+       
+       private StyleBase<T> style;
+       private Session session;
+       private Resource runtimeDiagram;
+       private Resource entry;
+       
+       GroupListener(Session session, Resource runtimeDiagram, Resource entry, StyleBase<T> 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<Result>(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<Class<?>, Resource>(getClass(), entry);
+    }
+
+    /**
+     * @param configuration
+     * @param runtimeDiagram
+     * @param item
+     * @return
+     */
+    protected Read<Result> getStyleCalculationRequest(Resource runtimeDiagram, final Resource entry, Resource item) {
+        return new TernaryRead<Object, Resource, Resource, Result>(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<Result> getStyleResultListener(ObserverGroupListener groupListener, final Resource item,
+            Group group, Observer observer, final Resource runtimeDiagram) {
+        return new ObserverGroupValueListener<Result>(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));
+    }
+
+}