]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/GraphToDiagramSynchronizer.java
Fix async NPE with GraphToDiagramSynchronizer
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / adapter / GraphToDiagramSynchronizer.java
index a947a1d215633b2f6b73f8bb2ddbedaad35ab651..aa35c63342c28e8d952a7989f2cc5edd26f83f21 100644 (file)
-/*******************************************************************************\r
- * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
- * in 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.adapter;\r
-\r
-import gnu.trove.map.hash.TObjectIntHashMap;\r
-import gnu.trove.set.hash.THashSet;\r
-\r
-import java.awt.geom.AffineTransform;\r
-import java.lang.reflect.InvocationTargetException;\r
-import java.util.ArrayDeque;\r
-import java.util.ArrayList;\r
-import java.util.Arrays;\r
-import java.util.Collection;\r
-import java.util.Collections;\r
-import java.util.Comparator;\r
-import java.util.Deque;\r
-import java.util.EnumSet;\r
-import java.util.HashMap;\r
-import java.util.HashSet;\r
-import java.util.List;\r
-import java.util.Map;\r
-import java.util.Queue;\r
-import java.util.Set;\r
-import java.util.concurrent.ConcurrentHashMap;\r
-import java.util.concurrent.ConcurrentMap;\r
-import java.util.concurrent.atomic.AtomicBoolean;\r
-import java.util.concurrent.locks.Condition;\r
-import java.util.concurrent.locks.ReentrantLock;\r
-\r
-import org.eclipse.core.runtime.IProgressMonitor;\r
-import org.eclipse.core.runtime.SubMonitor;\r
-import org.simantics.db.AsyncReadGraph;\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.ResourceArray;\r
-import org.simantics.db.common.exception.DebugException;\r
-import org.simantics.db.common.procedure.adapter.AsyncProcedureAdapter;\r
-import org.simantics.db.common.procedure.adapter.CacheListener;\r
-import org.simantics.db.common.procedure.adapter.ListenerSupport;\r
-import org.simantics.db.common.procedure.adapter.ProcedureAdapter;\r
-import org.simantics.db.common.request.AsyncReadRequest;\r
-import org.simantics.db.common.request.ReadRequest;\r
-import org.simantics.db.common.session.SessionEventListenerAdapter;\r
-import org.simantics.db.common.utils.NameUtils;\r
-import org.simantics.db.exception.CancelTransactionException;\r
-import org.simantics.db.exception.DatabaseException;\r
-import org.simantics.db.exception.NoSingleResultException;\r
-import org.simantics.db.exception.ServiceException;\r
-import org.simantics.db.procedure.AsyncListener;\r
-import org.simantics.db.procedure.AsyncProcedure;\r
-import org.simantics.db.procedure.Listener;\r
-import org.simantics.db.procedure.Procedure;\r
-import org.simantics.db.request.Read;\r
-import org.simantics.db.service.SessionEventSupport;\r
-import org.simantics.diagram.connection.ConnectionSegmentEnd;\r
-import org.simantics.diagram.content.Change;\r
-import org.simantics.diagram.content.ConnectionUtil;\r
-import org.simantics.diagram.content.DesignatedTerminal;\r
-import org.simantics.diagram.content.DiagramContentChanges;\r
-import org.simantics.diagram.content.DiagramContents;\r
-import org.simantics.diagram.content.EdgeResource;\r
-import org.simantics.diagram.content.ResourceTerminal;\r
-import org.simantics.diagram.internal.DebugPolicy;\r
-import org.simantics.diagram.internal.timing.GTask;\r
-import org.simantics.diagram.internal.timing.Timing;\r
-import org.simantics.diagram.profile.ProfileKeys;\r
-import org.simantics.diagram.synchronization.CollectingModificationQueue;\r
-import org.simantics.diagram.synchronization.CompositeModification;\r
-import org.simantics.diagram.synchronization.CopyAdvisor;\r
-import org.simantics.diagram.synchronization.ErrorHandler;\r
-import org.simantics.diagram.synchronization.IHintSynchronizer;\r
-import org.simantics.diagram.synchronization.IModifiableSynchronizationContext;\r
-import org.simantics.diagram.synchronization.IModification;\r
-import org.simantics.diagram.synchronization.LogErrorHandler;\r
-import org.simantics.diagram.synchronization.ModificationAdapter;\r
-import org.simantics.diagram.synchronization.SynchronizationHints;\r
-import org.simantics.diagram.synchronization.graph.AddElement;\r
-import org.simantics.diagram.synchronization.graph.BasicResources;\r
-import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;\r
-import org.simantics.diagram.synchronization.graph.ElementLoader;\r
-import org.simantics.diagram.synchronization.graph.ElementReorder;\r
-import org.simantics.diagram.synchronization.graph.ElementWriter;\r
-import org.simantics.diagram.synchronization.graph.GraphSynchronizationContext;\r
-import org.simantics.diagram.synchronization.graph.GraphSynchronizationHints;\r
-import org.simantics.diagram.synchronization.graph.ModificationQueue;\r
-import org.simantics.diagram.synchronization.graph.TagChange;\r
-import org.simantics.diagram.synchronization.graph.TransformElement;\r
-import org.simantics.diagram.synchronization.graph.layer.GraphLayer;\r
-import org.simantics.diagram.synchronization.graph.layer.GraphLayerManager;\r
-import org.simantics.diagram.ui.DiagramModelHints;\r
-import org.simantics.g2d.canvas.Hints;\r
-import org.simantics.g2d.canvas.ICanvasContext;\r
-import org.simantics.g2d.connection.ConnectionEntity;\r
-import org.simantics.g2d.connection.EndKeyOf;\r
-import org.simantics.g2d.connection.TerminalKeyOf;\r
-import org.simantics.g2d.diagram.DiagramClass;\r
-import org.simantics.g2d.diagram.DiagramHints;\r
-import org.simantics.g2d.diagram.DiagramMutator;\r
-import org.simantics.g2d.diagram.DiagramUtils;\r
-import org.simantics.g2d.diagram.IDiagram;\r
-import org.simantics.g2d.diagram.IDiagram.CompositionListener;\r
-import org.simantics.g2d.diagram.IDiagram.CompositionVetoListener;\r
-import org.simantics.g2d.diagram.handler.DataElementMap;\r
-import org.simantics.g2d.diagram.handler.ElementFactory;\r
-import org.simantics.g2d.diagram.handler.Relationship;\r
-import org.simantics.g2d.diagram.handler.RelationshipHandler;\r
-import org.simantics.g2d.diagram.handler.SubstituteElementClass;\r
-import org.simantics.g2d.diagram.handler.Topology;\r
-import org.simantics.g2d.diagram.handler.Topology.Connection;\r
-import org.simantics.g2d.diagram.handler.Topology.Terminal;\r
-import org.simantics.g2d.diagram.handler.TransactionContext.TransactionType;\r
-import org.simantics.g2d.diagram.impl.Diagram;\r
-import org.simantics.g2d.diagram.participant.ElementPainter;\r
-import org.simantics.g2d.element.ElementClass;\r
-import org.simantics.g2d.element.ElementHints;\r
-import org.simantics.g2d.element.ElementHints.DiscardableKey;\r
-import org.simantics.g2d.element.ElementUtils;\r
-import org.simantics.g2d.element.IElement;\r
-import org.simantics.g2d.element.IElementClassProvider;\r
-import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;\r
-import org.simantics.g2d.element.handler.ElementHandler;\r
-import org.simantics.g2d.element.handler.ElementLayerListener;\r
-import org.simantics.g2d.element.handler.TerminalTopology;\r
-import org.simantics.g2d.element.impl.Element;\r
-import org.simantics.g2d.layers.ILayer;\r
-import org.simantics.g2d.layers.ILayersEditor;\r
-import org.simantics.g2d.routing.RouterFactory;\r
-import org.simantics.scenegraph.INode;\r
-import org.simantics.scenegraph.profile.DataNodeConstants;\r
-import org.simantics.scenegraph.profile.DataNodeMap;\r
-import org.simantics.scenegraph.profile.common.ProfileObserver;\r
-import org.simantics.structural2.modelingRules.IModelingRules;\r
-import org.simantics.utils.datastructures.ArrayMap;\r
-import org.simantics.utils.datastructures.MapSet;\r
-import org.simantics.utils.datastructures.Pair;\r
-import org.simantics.utils.datastructures.disposable.AbstractDisposable;\r
-import org.simantics.utils.datastructures.hints.HintListenerAdapter;\r
-import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
-import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;\r
-import org.simantics.utils.datastructures.hints.IHintListener;\r
-import org.simantics.utils.datastructures.hints.IHintObservable;\r
-import org.simantics.utils.datastructures.map.AssociativeMap;\r
-import org.simantics.utils.datastructures.map.Associativity;\r
-import org.simantics.utils.datastructures.map.Tuple;\r
-import org.simantics.utils.strings.EString;\r
-import org.simantics.utils.threads.ThreadUtils;\r
-import org.simantics.utils.threads.logger.ITask;\r
-import org.simantics.utils.threads.logger.ThreadLogger;\r
-\r
-/**\r
- * This class loads a diagram contained in the graph database into the runtime\r
- * diagram model and synchronizes changes in the graph into the run-time\r
- * diagram. Any modifications to the graph model will be reflected to the\r
- * run-time diagram model. Hence the name GraphToDiagramSynchronizer.\r
- * \r
- * <p>\r
- * This class does not in itself support modification of the graph diagram\r
- * model. This manipulation is meant to be performed through a\r
- * {@link DiagramMutator} implementation that is installed into the diagram\r
- * using the {@link DiagramHints#KEY_MUTATOR} hint key.\r
- * \r
- * This implementations is built to only support diagrams defined in the graph\r
- * model as indicated in <a\r
- * href="https://www.simantics.org/wiki/index.php/Org.simantics.diagram" >this\r
- * </a> document.\r
- * \r
- * <p>\r
- * The synchronizer in itself is an {@link IDiagramLoader} which means that it\r
- * can be used for loading a diagram from the graph. In order for the\r
- * synchronizer to keep tracking the graph diagram model for changes the diagram\r
- * must be loaded with it. The tracking is implemented using graph database\r
- * queries. If you just want to load the diagram but detach it from the\r
- * synchronizer's tracking mechanisms, all you need to do is to dispose the\r
- * synchronizer after loading the diagram.\r
- * \r
- * <p>\r
- * This class guarantees that a single diagram element (IElement) representing a\r
- * single back-end object ({@link ElementHints#KEY_OBJECT}) will stay the same\r
- * object for the same back-end object.\r
- * \r
- * <p>\r
- * TODO: Currently it just happens that {@link GraphToDiagramSynchronizer}\r
- * contains {@link DefaultDiagramMutator} which depends on some internal details\r
- * of {@link GraphToDiagramSynchronizer} but it should be moved out of here by\r
- * introducing new interfaces.\r
- * \r
- * <h2>Basic usage example</h2>\r
- * <p>\r
- * This example shows how to initialize {@link GraphToDiagramSynchronizer} for a\r
- * specified {@link ICanvasContext} and load a diagram from a specified diagram\r
- * resource in the graph.\r
- * \r
- * <pre>\r
- * IDiagram loadDiagram(final ICanvasContext canvasContext, RequestProcessor processor, Resource diagramResource,\r
- *         ResourceArray structuralPath) throws DatabaseException {\r
- *     GraphToDiagramSynchronizer synchronizer = processor.syncRequest(new Read&lt;GraphToDiagramSynchronizer&gt;() {\r
- *         public GraphToDiagramSynchronizer perform(ReadGraph graph) throws DatabaseException {\r
- *             return new GraphToDiagramSynchronizer(graph, canvasContext, createElementClassProvider(graph));\r
- *         }\r
- *     });\r
- *     IDiagram d = requestProcessor\r
- *             .syncRequest(new DiagramLoadQuery(diagramResource, structuralPath, synchronizer, null));\r
- *     return d;\r
- * }\r
- * \r
- * protected IElementClassProvider createElementClassProvider(ReadGraph graph) {\r
- *     DiagramResource dr = DiagramResource.getInstance(graph);\r
- *     return ElementClassProviders.mappedProvider(ElementClasses.CONNECTION, DefaultConnectionClassFactory.CLASS\r
- *             .newClassWith(new ResourceAdapterImpl(dr.Connection)), ElementClasses.FLAG, FlagClassFactory\r
- *             .createFlagClass(dr.Flag));\r
- * }\r
- * </pre>\r
- * \r
- * <p>\r
- * TODO: make GraphToDiagramSynchronizer a canvas participant to make it more\r
- * uniform with the rest of the canvas system. This does not mean that G2DS must\r
- * be attached to an ICanvasContext in order to be used, rather that it can be\r
- * attached to one.\r
- * \r
- * <p>\r
- * TODO: test that detaching the synchronizer via {@link #dispose()} actually\r
- * works.\r
- * <p>\r
- * TODO: remove {@link DefaultDiagramMutator} and all {@link DiagramMutator}\r
- * stuff altogether\r
- * \r
- * <p>\r
- * TODO: diagram connection loading has no listener\r
- * \r
- * @author Tuukka Lehtonen\r
- * \r
- * @see GraphElementClassFactory\r
- * @see GraphElementFactory\r
- * @see ElementLoader\r
- * @see ElementWriter\r
- * @see IHintSynchronizer\r
- * @see CopyAdvisor\r
- */\r
-public class GraphToDiagramSynchronizer extends AbstractDisposable implements IDiagramLoader, IModifiableSynchronizationContext {\r
-\r
-    /**\r
-     * Controls whether the class adds hint listeners to each diagram element\r
-     * that try to perform basic sanity checks on changes happening in element\r
-     * hints. Having this will immediately inform you of bugs that corrupt the\r
-     * diagram model within the element hints in some way.\r
-     */\r
-    private static final boolean USE_ELEMENT_VALIDATING_LISTENERS = false;\r
-\r
-    /**\r
-     * These keys are used to hang on to Connection instances of edges that will\r
-     * be later installed as EndKeyOf/TerminalKeyOf hints into the loaded\r
-     * element during the "graph to diagram update transaction".\r
-     */\r
-    private static final Key     KEY_CONNECTION_BEGIN_PLACEHOLDER = new KeyOf(PlaceholderConnection.class, "CONNECTION_BEGIN_PLACEHOLDER");\r
-    private static final Key     KEY_CONNECTION_END_PLACEHOLDER   = new KeyOf(PlaceholderConnection.class, "CONNECTION_END_PLACEHOLDER");\r
-\r
-    /**\r
-     * Stored into an edge node during connection edge requests using the\r
-     * KEY_CONNECTION_BEGIN_PLACEHOLDER and KEY_CONNECTION_END_PLACEHOLDER keys.\r
-     */\r
-    static class PlaceholderConnection {\r
-        public final EdgeEnd end;\r
-        public final Object node;\r
-        public final Terminal terminal;\r
-        public PlaceholderConnection(EdgeEnd end, Object node, Terminal terminal) {\r
-            this.end = end;\r
-            this.node = node;\r
-            this.terminal = terminal;\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Indicates to the diagram CompositionListener of this synchronizer that is\r
-     * should deny all relationships for the element this hint is attached to.\r
-     */\r
-    private static final Key        KEY_REMOVE_RELATIONSHIPS = new KeyOf(Boolean.class, "REMOVE_RELATIONSHIPS");\r
-\r
-    static ErrorHandler             errorHandler             = LogErrorHandler.INSTANCE;\r
-\r
-    /**\r
-     * The canvas context which is being synchronized with the graph. Received\r
-     * during construction.\r
-     * \r
-     * <p>\r
-     * Is not nulled during disposal of this class since internal listener's\r
-     * life-cycles depend on canvas.isDisposed.\r
-     */\r
-    ICanvasContext                  canvas;\r
-\r
-    /**\r
-     * The session used by this synchronizer. Received during construction.\r
-     */\r
-    Session                         session;\r
-\r
-    /**\r
-     * Locked while updating diagram contents from the graph.\r
-     */\r
-    ReentrantLock                   diagramUpdateLock        = new ReentrantLock();\r
-\r
-    // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\r
-    // BI-DIRECTIONAL DIAGRAM ELEMENT <-> BACKEND OBJECT MAPPING BEGIN\r
-    // ------------------------------------------------------------------------\r
-\r
-    /**\r
-     * Holds a GraphToDiagramUpdater instance while a diagram content update is\r
-     * currently in progress.\r
-     *\r
-     * <p>\r
-     * Basically this is a hack solution to the problem of properly finding\r
-     * newly added Resource<->IElement mappings while loading the diagram\r
-     * contents. See {@link DataElementMapImpl} for why this is necessary.\r
-     */\r
-    GraphToDiagramUpdater                   currentUpdater                   = null;\r
-\r
-    /**\r
-     * A map from data objects to elements. Elements should already contain the\r
-     * data objects as {@link ElementHints#KEY_OBJECT} hints.\r
-     */\r
-    ConcurrentMap<Object, IElement>         dataElement                      = new ConcurrentHashMap<Object, IElement>();\r
-\r
-    /**\r
-     * Temporary structure for single-threaded use in #{@link DiagramUpdater}.\r
-     */\r
-    Collection<Connection>                  tempConnections = new ArrayList<Connection>();\r
-\r
-    /**\r
-     * A dummy class of which an instance will be given to each new edge element\r
-     * to make {@link TerminalKeyOf} keys unique for each edge.\r
-     */\r
-    static class TransientElementObject {\r
-        @Override\r
-        public String toString() {\r
-            return "MUTATOR GENERATED (hash=" + System.identityHashCode(this) + ")";\r
-        }\r
-    }\r
-\r
-    private static class ConnectionChildren {\r
-        public Set<IElement> branchPoints;\r
-        public Set<IElement> segments;\r
-\r
-        public ConnectionChildren(Set<IElement> branchPoints, Set<IElement> segments) {\r
-            this.branchPoints = branchPoints;\r
-            this.segments = segments;\r
-        }\r
-    }\r
-\r
-    ListenerSupport canvasListenerSupport = new ListenerSupport() {\r
-        @Override\r
-        public void exception(Throwable t) {\r
-            error(t);\r
-        }\r
-\r
-        @Override\r
-        public boolean isDisposed() {\r
-            return !isAlive() || canvas.isDisposed();\r
-        }\r
-    };\r
-\r
-    /**\r
-     * @see ElementHints#KEY_CONNECTION_ENTITY\r
-     */\r
-    class ConnectionEntityImpl implements ConnectionEntity {\r
-\r
-        /**\r
-         * The connection instance resource in the graph backend.\r
-         *\r
-         * May be <code>null</code> if the connection has not been synchronized\r
-         * yet.\r
-         */\r
-        Resource                 connection;\r
-\r
-        /**\r
-         * The connection type resource in the graph backend.\r
-         *\r
-         * May be <code>null</code> if the connection has not been synchronized\r
-         * yet.\r
-         */\r
-        Resource                 connectionType;\r
-\r
-        /**\r
-         * The connection entity element which is a part of the diagram.\r
-         */\r
-        IElement                 connectionElement;\r
-\r
-        /**\r
-         * List of backend-synchronized branch points that are part of this\r
-         * connection.\r
-         */\r
-        Collection<Resource>     branchPoints        = Collections.emptyList();\r
-\r
-        /**\r
-         * List of backend-synchronized edges that are part of this connection.\r
-         */\r
-        Collection<EdgeResource> segments            = Collections.emptyList();\r
-\r
-        Set<Object>              removedBranchPoints = new HashSet<Object>(4);\r
-\r
-        Set<Object>              removedSegments     = new HashSet<Object>(4);\r
-\r
-        /**\r
-         * List of non-backend-synchronized branch point element that are part\r
-         * of this connection.\r
-         */\r
-        List<IElement>           branchPointElements = new ArrayList<IElement>(1);\r
-\r
-        /**\r
-         * List of non-backend-synchronized edge element that are part of this\r
-         * connection.\r
-         */\r
-        List<IElement>           segmentElements     = new ArrayList<IElement>(2);\r
-\r
-        ConnectionListener       listener;\r
-\r
-        ConnectionEntityImpl(Resource connection, Resource connectionType, IElement connectionElement) {\r
-            this.connection = connection;\r
-            this.connectionType = connectionType;\r
-            this.connectionElement = connectionElement;\r
-        }\r
-\r
-        ConnectionEntityImpl(Resource connectionType, IElement connectionElement) {\r
-            this.connectionType = connectionType;\r
-            this.connectionElement = connectionElement;\r
-        }\r
-\r
-        ConnectionEntityImpl(ReadGraph graph, Resource connection, IElement connectionElement)\r
-        throws NoSingleResultException, ServiceException {\r
-            this.connection = connection;\r
-            this.connectionType = graph.getSingleType(connection, br.DIA.Connection);\r
-            this.connectionElement = connectionElement;\r
-        }\r
-\r
-        @Override\r
-        public IElement getConnection() {\r
-            return connectionElement;\r
-        }\r
-\r
-        public Object getConnectionObject() {\r
-            return connection;\r
-        }\r
-\r
-        public IElement getConnectionElement() {\r
-            if (connectionElement == null)\r
-                return getMappedConnectionElement();\r
-            return connectionElement;\r
-        }\r
-\r
-        private IElement getMappedConnectionElement() {\r
-            IElement ce = null;\r
-            if (connection != null)\r
-                ce = getMappedElement(connection);\r
-            return ce == null ? connectionElement : ce;\r
-        }\r
-\r
-        void fix() {\r
-            Collection<IElement> segments = getSegments(null);\r
-\r
-            // Remove all TerminalKeyOf hints from branch points that do not\r
-            // match\r
-            ArrayList<TerminalKeyOf> pruned = null;\r
-            for (IElement bp : getBranchPoints(null)) {\r
-                if (pruned == null)\r
-                    pruned = new ArrayList<TerminalKeyOf>(4);\r
-                pruned.clear();\r
-                for (Map.Entry<TerminalKeyOf, Object> entry : bp.getHintsOfClass(TerminalKeyOf.class).entrySet()) {\r
-                    // First check that the terminal matches.\r
-                    Connection c = (Connection) entry.getValue();\r
-                    if (!segments.contains(c.edge))\r
-                        pruned.add(entry.getKey());\r
-                }\r
-                removeNodeTopologyHints((Element) bp, pruned);\r
-            }\r
-        }\r
-\r
-        public ConnectionChildren getConnectionChildren() {\r
-            Set<IElement> bps = Collections.emptySet();\r
-            Set<IElement> segs = Collections.emptySet();\r
-            if (!branchPoints.isEmpty()) {\r
-                bps = new HashSet<IElement>(branchPoints.size());\r
-                for (Resource bp : branchPoints) {\r
-                    IElement e = getMappedElement(bp);\r
-                    if (e != null)\r
-                        bps.add(e);\r
-                }\r
-            }\r
-            if (!segments.isEmpty()) {\r
-                segs = new HashSet<IElement>(segments.size());\r
-                for (EdgeResource seg : segments) {\r
-                    IElement e = getMappedElement(seg);\r
-                    if (e != null)\r
-                        segs.add(e);\r
-                }\r
-            }\r
-            return new ConnectionChildren(bps, segs);\r
-        }\r
-\r
-        public void setData(Collection<EdgeResource> segments, Collection<Resource> branchPoints) {\r
-            // System.out.println("setData " + segments.size());\r
-            this.branchPoints = branchPoints;\r
-            this.segments = segments;\r
-\r
-            // Reset the added/removed state of segments and branchpoints.\r
-            this.removedBranchPoints = new HashSet<Object>(4);\r
-            this.removedSegments = new HashSet<Object>(4);\r
-            this.branchPointElements = new ArrayList<IElement>(4);\r
-            this.segmentElements = new ArrayList<IElement>(4);\r
-        }\r
-\r
-        public void fireListener(ConnectionChildren old, ConnectionChildren current) {\r
-            if (listener != null) {\r
-                List<IElement> removed = new ArrayList<IElement>();\r
-                List<IElement> added = new ArrayList<IElement>();\r
-\r
-                for (IElement oldBp : old.branchPoints)\r
-                    if (!current.branchPoints.contains(oldBp))\r
-                        removed.add(oldBp);\r
-                for (IElement oldSeg : old.segments)\r
-                    if (!current.segments.contains(oldSeg))\r
-                        removed.add(oldSeg);\r
-\r
-                for (IElement bp : current.branchPoints)\r
-                    if (!old.branchPoints.contains(bp))\r
-                        added.add(bp);\r
-                for (IElement seg : current.segments)\r
-                    if (!old.segments.contains(seg))\r
-                        added.add(seg);\r
-\r
-                if (!removed.isEmpty() || !added.isEmpty()) {\r
-                    listener.connectionChanged(new ConnectionEvent(this.connectionElement, removed, added));\r
-                }\r
-            }\r
-        }\r
-\r
-        @Override\r
-        public Collection<IElement> getBranchPoints(Collection<IElement> result) {\r
-            if (result == null)\r
-                result = new ArrayList<IElement>(branchPoints.size());\r
-            for (Resource bp : branchPoints) {\r
-                if (!removedBranchPoints.contains(bp)) {\r
-                    IElement e = getMappedElement(bp);\r
-                    if (e != null)\r
-                        result.add(e);\r
-                }\r
-            }\r
-            result.addAll(branchPointElements);\r
-            return result;\r
-        }\r
-\r
-        @Override\r
-        public Collection<IElement> getSegments(Collection<IElement> result) {\r
-            if (result == null)\r
-                result = new ArrayList<IElement>(segments.size());\r
-            for (EdgeResource seg : segments) {\r
-                if (!removedSegments.contains(seg)) {\r
-                    IElement e = getMappedElement(seg);\r
-                    if (e != null)\r
-                        result.add(e);\r
-                }\r
-            }\r
-            result.addAll(segmentElements);\r
-            return result;\r
-        }\r
-\r
-        @Override\r
-        public Collection<Connection> getTerminalConnections(Collection<Connection> result) {\r
-            if (result == null)\r
-                result = new ArrayList<Connection>(segments.size() * 2);\r
-            Set<org.simantics.utils.datastructures.Pair<IElement, Terminal>> processed = new HashSet<org.simantics.utils.datastructures.Pair<IElement, Terminal>>();\r
-            for (EdgeResource seg : segments) {\r
-                IElement edge = getMappedElement(seg);\r
-                if (edge != null) {\r
-                    for (EndKeyOf key : EndKeyOf.KEYS) {\r
-                        Connection c = edge.getHint(key);\r
-                        if (c != null && (c.terminal instanceof ResourceTerminal) && processed.add(Pair.make(c.node, c.terminal)))\r
-                            result.add(c);\r
-                    }\r
-                }\r
-            }\r
-            return result;\r
-        }\r
-\r
-        @Override\r
-        public void setListener(ConnectionListener listener) {\r
-            this.listener = listener;\r
-        }\r
-\r
-        @Override\r
-        public String toString() {\r
-            return getClass().getSimpleName() + "[resource=" + connection + ", branch points=" + branchPoints\r
-            + ", segments=" + segments + ", connectionElement=" + connectionElement\r
-            + ", branch point elements=" + branchPointElements + ", segment elements=" + segmentElements\r
-            + ", removed branch points=" + removedBranchPoints + ", removed segments=" + removedSegments + "]";\r
-        }\r
-\r
-    }\r
-\r
-    /**\r
-     * A map from connection data objects to connection entities. The connection\r
-     * part elements should already contain the data objects as\r
-     * {@link ElementHints#KEY_OBJECT} hints.\r
-     */\r
-    ConcurrentMap<Object, ConnectionEntityImpl> dataConnection = new ConcurrentHashMap<Object, ConnectionEntityImpl>();\r
-\r
-    /**\r
-     * @param data\r
-     * @param element\r
-     */\r
-    void mapElement(final Object data, final IElement element) {\r
-        if (!(element instanceof Element)) {\r
-            throw new IllegalArgumentException("mapElement: expected instance of Element, got " + element + " with data " + data);\r
-        }\r
-        assert data != null;\r
-        assert element != null;\r
-        if (DebugPolicy.DEBUG_MAPPING)\r
-            new Exception(Thread.currentThread() + " MAPPING: " + data + " -> " + element).printStackTrace();\r
-        dataElement.put(data, element);\r
-    }\r
-\r
-    /**\r
-     * @param data\r
-     * @return\r
-     */\r
-    IElement getMappedElement(final Object data) {\r
-        assert (data != null);\r
-        IElement element = dataElement.get(data);\r
-        return element;\r
-    }\r
-\r
-    IElement getMappedElementByElementObject(IElement e) {\r
-        if (e == null)\r
-            return null;\r
-        Object o = e.getHint(ElementHints.KEY_OBJECT);\r
-        if (o == null)\r
-            return null;\r
-        return getMappedElement(o);\r
-    }\r
-\r
-    /**\r
-     * @param data\r
-     * @return\r
-     */\r
-    IElement assertMappedElement(final Object data) {\r
-        IElement element = dataElement.get(data);\r
-        assert element != null;\r
-        return element;\r
-    }\r
-\r
-    /**\r
-     * @param data\r
-     * @return\r
-     */\r
-    IElement unmapElement(final Object data) {\r
-        IElement element = dataElement.remove(data);\r
-        if (DebugPolicy.DEBUG_MAPPING)\r
-            new Exception(Thread.currentThread() + " UN-MAPPED: " + data + " -> " + element).printStackTrace();\r
-        return element;\r
-    }\r
-\r
-    /**\r
-     * @param data\r
-     * @param element\r
-     */\r
-    void mapConnection(final Object data, final ConnectionEntityImpl connection) {\r
-        assert data != null;\r
-        assert connection != null;\r
-        if (DebugPolicy.DEBUG_MAPPING)\r
-            System.out.println(Thread.currentThread() + " MAPPING CONNECTION: " + data + " -> " + connection);\r
-        dataConnection.put(data, connection);\r
-    }\r
-\r
-    /**\r
-     * @param data\r
-     * @return\r
-     */\r
-    ConnectionEntityImpl getMappedConnection(final Object data) {\r
-        ConnectionEntityImpl connection = dataConnection.get(data);\r
-        return connection;\r
-    }\r
-\r
-    /**\r
-     * @param data\r
-     * @return\r
-     */\r
-    ConnectionEntityImpl assertMappedConnection(final Object data) {\r
-        ConnectionEntityImpl connection = getMappedConnection(data);\r
-        assert connection != null;\r
-        return connection;\r
-    }\r
-\r
-    /**\r
-     * @param data\r
-     * @return\r
-     */\r
-    ConnectionEntityImpl unmapConnection(final Object data) {\r
-        ConnectionEntityImpl connection = dataConnection.remove(data);\r
-        if (DebugPolicy.DEBUG_MAPPING)\r
-            System.out.println(Thread.currentThread() + " UN-MAPPED CONNECTION: " + data + " -> " + connection);\r
-        return connection;\r
-    }\r
-\r
-    class DataElementMapImpl implements DataElementMap {\r
-        @Override\r
-        public Object getData(IDiagram d, IElement element) {\r
-            if (d == null)\r
-                throw new NullPointerException("null diagram");\r
-            if (element == null)\r
-                throw new NullPointerException("null element");\r
-\r
-            assert ElementUtils.getDiagram(element) == d;\r
-            return element.getHint(ElementHints.KEY_OBJECT);\r
-        }\r
-\r
-        @Override\r
-        public IElement getElement(IDiagram d, Object data) {\r
-            if (d == null)\r
-                throw new NullPointerException("null diagram");\r
-            if (data == null)\r
-                throw new NullPointerException("null data");\r
-\r
-            GraphToDiagramUpdater updater = currentUpdater;\r
-            if (updater != null) {\r
-                // This HACK is for allowing GraphElementFactory implementations\r
-                // to find the IElements they are related to.\r
-                IElement e = updater.addedElementMap.get(data);\r
-                if (e != null)\r
-                    return e;\r
-            }\r
-\r
-            IElement e = getMappedElement(data);\r
-            if (e != null)\r
-                return e;\r
-            return null;\r
-        }\r
-    }\r
-\r
-    class SubstituteElementClassImpl implements SubstituteElementClass {\r
-        @Override\r
-        public ElementClass substitute(IDiagram d, ElementClass ec) {\r
-            if (d != diagram)\r
-                throw new IllegalArgumentException("specified diagram does not have this SubstituteElementClass handler");\r
-\r
-            // If the element class is our own, there's no point in creating\r
-            // a copy of it.\r
-            if (ec.contains(elementLayerListener))\r
-                return ec;\r
-\r
-            List<ElementHandler> all = ec.getAll();\r
-            List<ElementHandler> result = new ArrayList<ElementHandler>(all.size());\r
-            for (ElementHandler eh : all) {\r
-                if (eh instanceof ElementLayerListenerImpl)\r
-                    result.add(elementLayerListener);\r
-                else\r
-                    result.add(eh);\r
-            }\r
-            return ElementClass.compile(result).setId(ec.getId());\r
-        }\r
-    }\r
-\r
-    final DataElementMapImpl         dataElementMap         = new DataElementMapImpl();\r
-\r
-    final SubstituteElementClassImpl substituteElementClass = new SubstituteElementClassImpl();\r
-\r
-    // ------------------------------------------------------------------------\r
-    // BI-DIRECTIONAL DIAGRAM ELEMENT <-> BACKEND OBJECT MAPPING END\r
-    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\r
-\r
-    void warning(String message, Exception e) {\r
-        errorHandler.warning(message, e);\r
-    }\r
-\r
-    void warning(Exception e) {\r
-        errorHandler.warning(e.getMessage(), e);\r
-    }\r
-\r
-    void error(String message, Throwable e) {\r
-        errorHandler.error(message, e);\r
-    }\r
-\r
-    void error(Throwable e) {\r
-        errorHandler.error(e.getMessage(), e);\r
-    }\r
-\r
-    // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\r
-    // GRAPH MODIFICATION QUEUE BEGIN\r
-    // ------------------------------------------------------------------------\r
-\r
-    ModificationQueue                 modificationQueue;\r
-    IModifiableSynchronizationContext synchronizationContext;\r
-\r
-    @Override\r
-    public <T> T set(Key key, Object value) {\r
-        if (synchronizationContext == null)\r
-            return null;\r
-        return synchronizationContext.set(key, value);\r
-    }\r
-\r
-    @Override\r
-    public <T> T get(Key key) {\r
-        if (synchronizationContext == null)\r
-            return null;\r
-        return synchronizationContext.get(key);\r
-    }\r
-\r
-    // ------------------------------------------------------------------------\r
-    // GRAPH MODIFICATION QUEUE END\r
-    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\r
-\r
-    /**\r
-     * The previously loaded version of the diagram content. This is needed to\r
-     * calculate the difference between new and old content on each\r
-     * {@link #diagramGraphUpdater(DiagramContents)} invocation.\r
-     */\r
-    DiagramContents       previousContent;\r
-\r
-    /**\r
-     * The diagram instance that this synchronizer is synchronizing with the\r
-     * graph.\r
-     */\r
-    IDiagram              diagram;\r
-\r
-    /**\r
-     * An observer for diagram profile entries. Has a life-cycle that must be\r
-     * bound to the life-cycle of this GraphToDiagramSynchronizer instance.\r
-     * Disposed if synchronizer is detached in {@link #doDispose()} or finally\r
-     * when the canvas is disposed.\r
-     */\r
-    ProfileObserver       profileObserver;\r
-\r
-    IElementClassProvider elementClassProvider;\r
-\r
-    BasicResources        br;\r
-\r
-    // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\r
-    // Internal state machine handling BEGIN\r
-    // ------------------------------------------------------------------------\r
-\r
-    /**\r
-     * An indicator for the current state of this synchronizer. This is a simple\r
-     * state machine with the following possible state transitions:\r
-     *\r
-     * <ul>\r
-     * <li>INITIAL -> LOADING, DISPOSED</li>\r
-     * <li>LOADING -> IDLE</li>\r
-     * <li>IDLE -> UPDATING_DIAGRAM, DISPOSED</li>\r
-     * <li>UPDATING_DIAGRAM -> IDLE</li>\r
-     * </ul>\r
-     * \r
-     * Start states: INITIAL\r
-     * End states: DISPOSED\r
-     */\r
-    static enum State {\r
-        /**\r
-         * The initial state of the synchronizer.\r
-         */\r
-        INITIAL,\r
-        /**\r
-         * The synchronizer is performing load-time initialization. During this\r
-         * time no canvas refreshes should be forced.\r
-         */\r
-        LOADING,\r
-        /**\r
-         * The synchronizer is performing updates to the diagram model. This\r
-         * process goes on in the canvas context thread.\r
-         */\r
-        UPDATING_DIAGRAM,\r
-        /**\r
-         * The synchronizer is doing nothing.\r
-         */\r
-        IDLE,\r
-        /**\r
-         * The synchronized diagram is being disposed, which means that this\r
-         * synchronizer should not accept any further actions.\r
-         */\r
-        DISPOSED,\r
-    }\r
-\r
-    public static final EnumSet<State> FROM_INITIAL          = EnumSet.of(State.LOADING, State.DISPOSED);\r
-    public static final EnumSet<State> FROM_LOADING          = EnumSet.of(State.IDLE);\r
-    public static final EnumSet<State> FROM_UPDATING_DIAGRAM = EnumSet.of(State.IDLE);\r
-    public static final EnumSet<State> FROM_IDLE             = EnumSet.of(State.UPDATING_DIAGRAM, State.DISPOSED);\r
-    public static final EnumSet<State> NO_STATES             = EnumSet.noneOf(State.class);\r
-\r
-    private EnumSet<State> validTargetStates(State start) {\r
-        switch (start) {\r
-            case INITIAL: return FROM_INITIAL;\r
-            case LOADING: return FROM_LOADING;\r
-            case UPDATING_DIAGRAM: return FROM_UPDATING_DIAGRAM;\r
-            case IDLE: return FROM_IDLE;\r
-            case DISPOSED: return NO_STATES;\r
-        }\r
-        throw new IllegalArgumentException("unrecognized state " + start);\r
-    }\r
-\r
-    private String validateStateChange(State start, State end) {\r
-        EnumSet<State> validTargets = validTargetStates(start);\r
-        if (!validTargets.contains(end))\r
-            return "Cannot transition from " + start + " state to " + end + ".";\r
-        return null;\r
-    }\r
-\r
-    /**\r
-     * The current state of the synchronizer. At start it is\r
-     * {@link State#INITIAL} and after loading it is {@link State#IDLE}.\r
-     */\r
-    State                              synchronizerState     = State.INITIAL;\r
-\r
-    /**\r
-     * A condition variable used to synchronize synchronizer state changes.\r
-     */\r
-    ReentrantLock                      stateLock             = new ReentrantLock();\r
-\r
-    /**\r
-     * A condition that is signaled when the synchronizer state changes to IDLE.\r
-     */\r
-    Condition                          idleCondition         = stateLock.newCondition();\r
-\r
-    State getState() {\r
-        return synchronizerState;\r
-    }\r
-\r
-    /**\r
-     * Activates the desired state after making sure that the synchronizer has\r
-     * been IDLE in between its current state and this invocation.\r
-     *\r
-     * @param newState the new state to activate\r
-     * @throws InterruptedException if waiting for IDLE state gets interrupted\r
-     * @throws IllegalStateException if the requested transition from the\r
-     *         current state to the desired state would be illegal.\r
-     */\r
-    void activateState(State newState, boolean waitForIdle) throws InterruptedException {\r
-        stateLock.lock();\r
-        try {\r
-            // Wait until the state of the synchronizer IDLEs if necessary.\r
-            if (waitForIdle && synchronizerState != State.IDLE) {\r
-                String error = validateStateChange(synchronizerState, State.IDLE);\r
-                if (error != null)\r
-                    throw new IllegalStateException(error);\r
-\r
-                while (synchronizerState != State.IDLE) {\r
-                    if (DebugPolicy.DEBUG_STATE)\r
-                        System.out.println(Thread.currentThread() + " waiting for IDLE state, current="\r
-                                + synchronizerState);\r
-                    idleCondition.await();\r
-                }\r
-            }\r
-\r
-            String error = validateStateChange(synchronizerState, newState);\r
-            if (error != null)\r
-                throw new IllegalStateException(error);\r
-\r
-            if (DebugPolicy.DEBUG_STATE)\r
-                System.out.println(Thread.currentThread() + " activated state " + newState);\r
-            this.synchronizerState = newState;\r
-\r
-            if (newState == State.IDLE)\r
-                idleCondition.signalAll();\r
-        } finally {\r
-            stateLock.unlock();\r
-        }\r
-    }\r
-\r
-    void idle() throws IllegalStateException, InterruptedException {\r
-        activateState(State.IDLE, false);\r
-    }\r
-\r
-    static interface StateRunnable extends Runnable {\r
-        void execute() throws InvocationTargetException;\r
-\r
-        public abstract class Stub implements StateRunnable {\r
-            @Override\r
-            public void run() {\r
-            }\r
-\r
-            @Override\r
-            public final void execute() throws InvocationTargetException {\r
-                try {\r
-                    perform();\r
-                } catch (Exception e) {\r
-                    throw new InvocationTargetException(e);\r
-                } catch (LinkageError e) {\r
-                    throw new InvocationTargetException(e);\r
-                }\r
-            }\r
-\r
-            protected abstract void perform() throws Exception;\r
-        }\r
-    }\r
-\r
-    protected void runInState(State state, StateRunnable runnable) throws InvocationTargetException {\r
-        try {\r
-            activateState(state, true);\r
-            try {\r
-                runnable.execute();\r
-            } finally {\r
-                idle();\r
-            }\r
-        } catch (IllegalStateException e) {\r
-            throw new InvocationTargetException(e);\r
-        } catch (InterruptedException e) {\r
-            throw new InvocationTargetException(e);\r
-        }\r
-    }\r
-\r
-    protected void safeRunInState(State state, StateRunnable runnable) {\r
-        try {\r
-            runInState(state, runnable);\r
-        } catch (InvocationTargetException e) {\r
-            error("Failed to run runnable " + runnable + " in state " + state + ". See exception for details.", e\r
-                    .getCause());\r
-        }\r
-    }\r
-\r
-    // ------------------------------------------------------------------------\r
-    // Internal state machine handling END\r
-    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\r
-\r
-    /**\r
-     * @param processor\r
-     * @param canvas\r
-     * @param elementClassProvider\r
-     * @throws DatabaseException\r
-     */\r
-    public GraphToDiagramSynchronizer(RequestProcessor processor, ICanvasContext canvas, IElementClassProvider elementClassProvider) throws DatabaseException {\r
-        if (processor == null)\r
-            throw new IllegalArgumentException("null processor");\r
-        if (canvas == null)\r
-            throw new IllegalArgumentException("null canvas");\r
-        if (elementClassProvider == null)\r
-            throw new IllegalArgumentException("null element class provider");\r
-\r
-        this.session = processor.getSession();\r
-        this.canvas = canvas;\r
-        this.modificationQueue = new ModificationQueue(session, errorHandler);\r
-\r
-        processor.syncRequest(new ReadRequest() {\r
-            @Override\r
-            public void run(ReadGraph graph) throws DatabaseException {\r
-                initializeResources(graph);\r
-            }\r
-        });\r
-\r
-        this.elementClassProvider = elementClassProvider;\r
-        synchronizationContext.set(SynchronizationHints.ELEMENT_CLASS_PROVIDER, elementClassProvider);\r
-\r
-        attachSessionListener(processor.getSession());\r
-    }\r
-\r
-    /**\r
-     * @return\r
-     */\r
-    public IElementClassProvider getElementClassProvider() {\r
-        return elementClassProvider;\r
-    }\r
-\r
-    public Session getSession() {\r
-        return session;\r
-    }\r
-\r
-    public ICanvasContext getCanvasContext() {\r
-        return canvas;\r
-    }\r
-\r
-    public IDiagram getDiagram() {\r
-        return diagram;\r
-    }\r
-\r
-    void setCanvasDirty() {\r
-        ICanvasContext c = canvas;\r
-        if (synchronizerState != State.LOADING && c != null && !c.isDisposed()) {\r
-            // TODO: Consider adding an invocation limiter here, to prevent\r
-            // calling setDirty too often if enough time hasn't passed yet since\r
-            // the last invocation.\r
-            c.getContentContext().setDirty();\r
-        }\r
-    }\r
-\r
-    /**\r
-     * @param elementType\r
-     * @return\r
-     * @throws DatabaseException if ElementClass cannot be retrieved\r
-     */\r
-    public ElementClass getNodeClass(Resource elementType) throws DatabaseException {\r
-        return getNodeClass(session, elementType);\r
-    }\r
-\r
-    public ElementClass getNodeClass(RequestProcessor processor, Resource elementType) throws DatabaseException {\r
-        ElementClass ec = processor.syncRequest(new NodeClassRequest(canvas, diagram, elementType, true));\r
-        return ec;\r
-    }\r
-\r
-    @Override\r
-    protected void doDispose() {\r
-        try {\r
-            try {\r
-                stateLock.lock();\r
-                boolean isInitial = getState() == State.INITIAL;\r
-                activateState(State.DISPOSED, !isInitial);\r
-            } finally {\r
-                stateLock.unlock();\r
-            }\r
-        } catch (InterruptedException e) {\r
-            // Shouldn't happen.\r
-            e.printStackTrace();\r
-        } finally {\r
-            detachSessionListener();\r
-\r
-            if (profileObserver != null) {\r
-                profileObserver.dispose();\r
-                profileObserver = null;\r
-            }\r
-\r
-            if (diagram != null) {\r
-                diagram.removeCompositionListener(diagramListener);\r
-                diagram.removeCompositionVetoListener(diagramListener);\r
-            }\r
-\r
-            // TODO: we should probably leave the dataElement map as is since DataElementMap needs it even after the synchronizer has been disposed.\r
-            // Currently the diagram's DataElementMap will be broken after disposal.\r
-//            dataElement.clear();\r
-//            dataConnection.clear();\r
-\r
-            if (layerManager != null) {\r
-                layerManager.dispose();\r
-            }\r
-\r
-            // Let GC work.\r
-            modificationQueue.dispose();\r
-        }\r
-    }\r
-\r
-    void initializeResources(ReadGraph graph) {\r
-        this.br = new BasicResources(graph);\r
-\r
-        // Initialize synchronization context\r
-        synchronizationContext = new GraphSynchronizationContext(graph, modificationQueue);\r
-        synchronizationContext.set(SynchronizationHints.ERROR_HANDLER, errorHandler);\r
-    }\r
-\r
-    // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\r
-    // LAYERS BEGIN\r
-    // ------------------------------------------------------------------------\r
-\r
-    GraphLayerManager layerManager;\r
-\r
-    /**\r
-     * A common handler for all elements that is used to listen to changes in\r
-     * element visibility and focusability on diagram layers.\r
-     */\r
-    class ElementLayerListenerImpl implements ElementLayerListener {\r
-        private static final long serialVersionUID = -3410052116598828129L;\r
-\r
-        @Override\r
-        public void visibilityChanged(IElement e, ILayer layer, boolean visible) {\r
-            if (!isAlive())\r
-                return;\r
-            if (DebugPolicy.DEBUG_LAYERS)\r
-                System.out.println("visibility changed: " + e + ", " + layer + ", " + visible);\r
-            GraphLayer gl = layerManager.getGraphLayer(layer.getName());\r
-            if (gl != null) {\r
-                changeTag(e, gl.getVisible(), visible);\r
-            }\r
-        }\r
-\r
-        @Override\r
-        public void focusabilityChanged(IElement e, ILayer layer, boolean focusable) {\r
-            if (!isAlive())\r
-                return;\r
-            if (DebugPolicy.DEBUG_LAYERS)\r
-                System.out.println("focusability changed: " + e + ", " + layer + ", " + focusable);\r
-            GraphLayer gl = layerManager.getGraphLayer(layer.getName());\r
-            if (gl != null) {\r
-                changeTag(e, gl.getFocusable(), focusable);\r
-            }\r
-        }\r
-\r
-        void changeTag(IElement e, Resource tag, boolean set) {\r
-            Object object = e.getHint(ElementHints.KEY_OBJECT);\r
-            Resource tagged = null;\r
-            if (object instanceof Resource) {\r
-                tagged = (Resource) object;\r
-            } else if (object instanceof EdgeResource) {\r
-                ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
-                if (ce instanceof ConnectionEntityImpl) {\r
-                    tagged = ((ConnectionEntityImpl) ce).connection;\r
-                }\r
-            }\r
-            if (tagged == null)\r
-                return;\r
-\r
-            modificationQueue.async(new TagChange(tagged, tag, set), null);\r
-        }\r
-    };\r
-\r
-    ElementLayerListenerImpl elementLayerListener = new ElementLayerListenerImpl();\r
-\r
-    // ------------------------------------------------------------------------\r
-    // LAYERS END\r
-    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\r
-\r
-    @Override\r
-    public IDiagram loadDiagram(IProgressMonitor progressMonitor, ReadGraph g, final String modelURI, final Resource diagram, final Resource runtime, final ResourceArray structuralPath,\r
-            IHintObservable initialHints) throws DatabaseException {\r
-        if (DebugPolicy.DEBUG_LOAD)\r
-            System.out.println(Thread.currentThread() + " loadDiagram: " + NameUtils.getSafeName(g, diagram));\r
-\r
-        SubMonitor monitor = SubMonitor.convert(progressMonitor, "Load Diagram", 100);\r
-\r
-        Object loadTask = Timing.BEGIN("GDS.loadDiagram");\r
-        try {\r
-            try {\r
-                activateState(State.LOADING, false);\r
-            } catch (IllegalStateException e) {\r
-                // Disposed already before loading even began.\r
-                this.diagram = Diagram.spawnNew(DiagramClass.DEFAULT);\r
-                return this.diagram;\r
-            }\r
-            try {\r
-                // Query for diagram class\r
-                Resource diagramClassResource = g.getPossibleType(diagram, br.DIA.Composite);\r
-                if (diagramClassResource != null) {\r
-                    // Spawn new diagram\r
-                    Object task = Timing.BEGIN("GDS.DiagramClassRequest");\r
-                    final DiagramClass diagramClass = g.syncRequest(new DiagramClassRequest(diagram));\r
-                    Timing.END(task);\r
-                    final IDiagram d = Diagram.spawnNew(diagramClass);\r
-                    {\r
-                        d.setHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE, diagram);\r
-                        if (runtime != null)\r
-                            d.setHint(DiagramModelHints.KEY_DIAGRAM_RUNTIME_RESOURCE, runtime);\r
-                        if (modelURI != null)\r
-                            d.setHint(DiagramModelHints.KEY_DIAGRAM_MODEL_URI, modelURI);\r
-                        d.setHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE_ARRAY, structuralPath);\r
-\r
-                        // Set dumb default routing when DiagramClass does not\r
-                        // predefine the default connection routing for the diagram.\r
-                        if (!d.containsHint(DiagramHints.ROUTE_ALGORITHM))\r
-                            d.setHint(DiagramHints.ROUTE_ALGORITHM, RouterFactory.create(true, false));\r
-\r
-                        d.setHint(SynchronizationHints.CONTEXT, this);\r
-\r
-                        // Initialize hints with hints from initialHints if given\r
-                        if (initialHints != null) {\r
-                            d.setHints(initialHints.getHints());\r
-                        }\r
-                    }\r
-\r
-                    // ITask task2 = ThreadLogger.getInstance().begin("loadLayers");\r
-                    monitor.subTask("Layers");\r
-                    {\r
-                        this.layerManager = new GraphLayerManager(g, modificationQueue, diagram);\r
-                        synchronizationContext.set(GraphSynchronizationHints.GRAPH_LAYER_MANAGER, this.layerManager);\r
-                        ILayersEditor layers = layerManager.loadLayers(d, g, diagram);\r
-                        // task2.finish();\r
-\r
-                        d.setHint(DiagramHints.KEY_LAYERS, layers);\r
-                        d.setHint(DiagramHints.KEY_LAYERS_EDITOR, layers);\r
-\r
-                        d.addCompositionVetoListener(diagramListener);\r
-                        d.addCompositionListener(diagramListener);\r
-\r
-                        this.diagram = d;\r
-\r
-                        d.setHint(DiagramHints.KEY_MUTATOR, new DefaultDiagramMutator(d, diagram, synchronizationContext));\r
-\r
-                        // Add default layer if no layers exist.\r
-                        // NOTE: this must be done after this.diagram has been set\r
-                        // as it will trigger a graph modification which needs the\r
-                        // diagram resource.\r
-                        // ITask task3 = ThreadLogger.getInstance().begin("addDefaultLayer");\r
-//                        if (layers.getLayers().isEmpty()) {\r
-//                            if (DebugPolicy.DEBUG_LAYERS)\r
-//                                System.out.println("No layers, creating default layer '"\r
-//                                        + DiagramConstants.DEFAULT_LAYER_NAME + "'");\r
-//                            SimpleLayer defaultLayer = new SimpleLayer(DiagramConstants.DEFAULT_LAYER_NAME);\r
-//                            layers.addLayer(defaultLayer);\r
-//                            layers.activate(defaultLayer);\r
-//                        }\r
-//                        // task3.finish();\r
-                    }\r
-                    monitor.worked(10);\r
-\r
-                    monitor.subTask("Contents");\r
-                    // Discover the plain resources that form the content of the\r
-                    // diagram through a separate query. This allows us to\r
-                    // separately\r
-                    // track changes to the diagram structure itself, not the\r
-                    // substructures contained by the structure elements.\r
-                    ITask task4 = ThreadLogger.getInstance().begin("DiagramContentRequest1");\r
-                    DiagramContentRequest query = new DiagramContentRequest(canvas, diagram, errorHandler);\r
-                    g.syncRequest(query, new DiagramContentListener(diagram));\r
-                    task4.finish();\r
-                    // ITask task5 = ThreadLogger.getInstance().begin("DiagramContentRequest2");\r
-                    ITask task42 = ThreadLogger.getInstance().begin("DiagramContentRequest2");\r
-                    DiagramContents contents = g.syncRequest(query);\r
-                    task42.finish();\r
-                    // task5.finish();\r
-                    monitor.worked(10);\r
-\r
-                    monitor.subTask("Graphical elements");\r
-                    {\r
-                        Object applyDiagramContents = Timing.BEGIN("GDS.applyDiagramContents");\r
-                        ITask task6 = ThreadLogger.getInstance().begin("applyDiagramContents");\r
-                        processGraphUpdates(g, Collections.singleton(diagramGraphUpdater(contents)));\r
-                        task6.finish();\r
-                        Timing.END(applyDiagramContents);\r
-                    }\r
-                    monitor.worked(80);\r
-\r
-                    DataNodeMap dn = new DataNodeMap() {\r
-                        @Override\r
-                        public INode getNode(Object data) {\r
-                            if (DataNodeConstants.CANVAS_ROOT == data)\r
-                                return canvas.getCanvasNode();\r
-                            if (DataNodeConstants.DIAGRAM_ELEMENT_PARENT == data) {\r
-                                ElementPainter ep = canvas.getAtMostOneItemOfClass(ElementPainter.class);\r
-                                return ep != null ? ep.getDiagramElementParentNode() : null;\r
-                            }\r
-\r
-                            DataElementMap emap = GraphToDiagramSynchronizer.this.diagram.getDiagramClass().getSingleItem(DataElementMap.class);\r
-                            IElement element = emap.getElement(GraphToDiagramSynchronizer.this.diagram, data);\r
-                            if(element == null) return null;\r
-                            return element.getHint(ElementHints.KEY_SG_NODE);\r
-                        }\r
-                    };\r
-\r
-                    profileObserver = new ProfileObserver(g.getSession(), runtime,\r
-                            canvas.getThreadAccess(), canvas, canvas.getSceneGraph(), diagram, \r
-                            ArrayMap.keys(ProfileKeys.DIAGRAM, ProfileKeys.CANVAS, ProfileKeys.NODE_MAP).values(GraphToDiagramSynchronizer.this.diagram, canvas, dn),\r
-                            new CanvasNotification(canvas));\r
-\r
-                    profileObserver.listen(g, GraphToDiagramSynchronizer.this);\r
-\r
-                    return d;\r
-\r
-                }\r
-\r
-                this.diagram = Diagram.spawnNew(DiagramClass.DEFAULT);\r
-                return this.diagram;\r
-\r
-            } finally {\r
-                idle();\r
-            }\r
-        } catch (InterruptedException e) {\r
-            throw new RuntimeException(e);\r
-        } catch (IllegalStateException e) {\r
-            // If the synchronizer was disposed ahead of time, it was done\r
-            // for a reason, such as the user having closed the owner editor.\r
-            if (!isAlive())\r
-                throw new CancelTransactionException(e);\r
-            throw new RuntimeException(e);\r
-        } finally {\r
-            Timing.END(loadTask);\r
-        }\r
-    }\r
-\r
-    static class CanvasNotification implements Runnable {\r
-\r
-        final private ICanvasContext canvas;\r
-\r
-        public CanvasNotification(ICanvasContext canvas) {\r
-            this.canvas = canvas;\r
-        }\r
-\r
-        public void run() {\r
-            canvas.getContentContext().setDirty();\r
-        }\r
-\r
-    }\r
-\r
-    ArrayList<IModification>        pendingModifications = new ArrayList<IModification>();\r
-    MapSet<IElement, IModification> modificationIndex    = new MapSet.Hash<IElement, IModification>();\r
-\r
-    void addModification(IElement element, IModification modification) {\r
-        pendingModifications.add(modification);\r
-        if (element != null)\r
-            modificationIndex.add(element, modification);\r
-\r
-    }\r
-    class DefaultDiagramMutator implements DiagramMutator {\r
-\r
-        Map<IElement, Resource> creation = new HashMap<IElement, Resource>();\r
-\r
-        IDiagram d;\r
-        Resource diagram;\r
-\r
-        IModifiableSynchronizationContext synchronizationContext;\r
-\r
-        public DefaultDiagramMutator(IDiagram d, Resource diagram, IModifiableSynchronizationContext synchronizationContext) {\r
-            this.d = d;\r
-            this.diagram = diagram;\r
-            this.synchronizationContext = synchronizationContext;\r
-\r
-            if (synchronizationContext.get(SynchronizationHints.ELEMENT_CLASS_PROVIDER) == null)\r
-                throw new IllegalArgumentException("SynchronizationHints.ELEMENT_CLASS_PROVIDER not available");\r
-        }\r
-\r
-        void assertNotDisposed() {\r
-            if (!isAlive())\r
-                throw new IllegalStateException(getClass().getSimpleName() + " is disposed");\r
-        }\r
-\r
-        @Override\r
-        public IElement newElement(ElementClass clazz) {\r
-            assertNotDisposed();\r
-            ElementFactory ef = d.getDiagramClass().getAtMostOneItemOfClass(ElementFactory.class);\r
-            IElement element = null;\r
-            if (ef != null)\r
-                element = ef.spawnNew(clazz);\r
-            else\r
-                element = Element.spawnNew(clazz);\r
-\r
-            element.setHint(ElementHints.KEY_OBJECT, new TransientElementObject());\r
-\r
-            addModification(element, new AddElement(synchronizationContext, d, element));\r
-\r
-            return element;\r
-        }\r
-\r
-        @Override\r
-        public void commit() {\r
-            assertNotDisposed();\r
-            if (DebugPolicy.DEBUG_MUTATOR_COMMIT) {\r
-                System.out.println("DiagramMutator is about to commit changes:");\r
-                for (IModification mod : pendingModifications)\r
-                    System.out.println("\t- " + mod);\r
-            }\r
-\r
-            Collections.sort(pendingModifications);\r
-\r
-            if (DebugPolicy.DEBUG_MUTATOR_COMMIT) {\r
-                if (pendingModifications.size() > 1) {\r
-                    System.out.println("* changes were re-ordered to:");\r
-                    for (IModification mod : pendingModifications)\r
-                        System.out.println("\t" + mod);\r
-                }\r
-            }\r
-\r
-            Timing.safeTimed(errorHandler, "QUEUE AND WAIT FOR MODIFICATIONS TO FINISH", new GTask() {\r
-                @Override\r
-                public void run() throws DatabaseException {\r
-                    // Performs a separate write request and query result update\r
-                    // for each modification\r
-//                    for (IModification mod : pendingModifications) {\r
-//                        try {\r
-//                            modificationQueue.sync(mod);\r
-//                        } catch (InterruptedException e) {\r
-//                            error("Pending diagram modification " + mod\r
-//                                    + " was interrupted. See exception for details.", e);\r
-//                        }\r
-//                    }\r
-\r
-                    // NOTE: this is still under testing, the author is not\r
-                    // truly certain that it should work in all cases ATM.\r
-\r
-                    // Performs all modifications with in a single write request\r
-                    for (IModification mod : pendingModifications) {\r
-                        modificationQueue.offer(mod, null);\r
-                    }\r
-                    try {\r
-                        // Perform the modifications in a single request.\r
-                        modificationQueue.finish();\r
-                    } catch (InterruptedException e) {\r
-                        errorHandler.error("Diagram modification finishing was interrupted. See exception for details.", e);\r
-                    }\r
-                }\r
-            });\r
-            pendingModifications.clear();\r
-            modificationIndex.clear();\r
-            creation.clear();\r
-            if (DebugPolicy.DEBUG_MUTATOR_COMMIT)\r
-                System.out.println("DiagramMutator has committed");\r
-        }\r
-\r
-        @Override\r
-        public void clear() {\r
-            assertNotDisposed();\r
-            pendingModifications.clear();\r
-            modificationIndex.clear();\r
-            creation.clear();\r
-            if (DebugPolicy.DEBUG_MUTATOR)\r
-                System.out.println("DiagramMutator has been cleared");\r
-        }\r
-\r
-        @Override\r
-        public void modifyTransform(IElement element) {\r
-            assertNotDisposed();\r
-            Resource resource = backendObject(element);\r
-            AffineTransform tr = element.getHint(ElementHints.KEY_TRANSFORM);\r
-            if (resource != null && tr != null) {\r
-                addModification(element, new TransformElement(resource, tr));\r
-            }\r
-        }\r
-\r
-        @Override\r
-        public void synchronizeHintsToBackend(IElement element) {\r
-            assertNotDisposed();\r
-            IHintSynchronizer synchronizer = element.getHint(SynchronizationHints.HINT_SYNCHRONIZER);\r
-            if (synchronizer != null) {\r
-                CollectingModificationQueue queue = new CollectingModificationQueue();\r
-                synchronizer.synchronize(synchronizationContext, element);\r
-                addModification(element, new CompositeModification(ModificationAdapter.LOW_PRIORITY, queue.getQueue()));\r
-            }\r
-        }\r
-\r
-        @Override\r
-        public void synchronizeElementOrder() {\r
-            assertNotDisposed();\r
-            List<IElement> snapshot = d.getSnapshot();\r
-            addModification(null, new ElementReorder(d, snapshot));\r
-        }\r
-\r
-        @Override\r
-        public void register(IElement element, Object object) {\r
-            creation.put(element, (Resource) object);\r
-        }\r
-\r
-        @SuppressWarnings("unchecked")\r
-        @Override\r
-        public <T> T backendObject(IElement element) {\r
-            Object object = ElementUtils.getObject(element);\r
-            if (object instanceof Resource)\r
-                return (T) object;\r
-            else\r
-                return (T) creation.get(element);\r
-        }\r
-\r
-    }\r
-\r
-    // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\r
-    // GRAPH TO DIAGRAM SYCHRONIZATION LOGIC BEGIN\r
-    // ------------------------------------------------------------------------\r
-\r
-    static class ConnectionData {\r
-        ConnectionEntityImpl impl;\r
-        List<Resource>       branchPoints = new ArrayList<Resource>();\r
-        List<EdgeResource>   segments     = new ArrayList<EdgeResource>();\r
-\r
-        ConnectionData(ConnectionEntityImpl ce) {\r
-            this.impl = ce;\r
-        }\r
-\r
-        void addBranchPoint(Resource bp) {\r
-            branchPoints.add(bp);\r
-        }\r
-\r
-        void addSegment(EdgeResource seg) {\r
-            segments.add(seg);\r
-        }\r
-    }\r
-\r
-    class GraphToDiagramUpdater {\r
-        DiagramContents                                 lastContent;\r
-        DiagramContents                                 content;\r
-        DiagramContentChanges                           changes;\r
-\r
-        final List<IElement>                            addedElements;\r
-        final List<IElement>                            removedElements;\r
-\r
-        final List<IElement>                            addedConnectionSegments;\r
-        final List<IElement>                            removedConnectionSegments;\r
-\r
-        final List<IElement>                            addedBranchPoints;\r
-        final List<IElement>                            removedBranchPoints;\r
-\r
-        final Map<Object, IElement>                     addedElementMap;\r
-        final Map<Resource, IElement>                   addedConnectionMap;\r
-        final Map<Resource, ConnectionEntityImpl>       addedConnectionEntities;\r
-        final List<Resource>                            removedConnectionEntities;\r
-        final Map<ConnectionEntityImpl, ConnectionData> changedConnectionEntities;\r
-\r
-        final Map<Resource, IElement>                   addedRouteGraphConnectionMap;\r
-        final List<IElement>                            removedRouteGraphConnections;\r
-\r
-\r
-        GraphToDiagramUpdater(DiagramContents lastContent, DiagramContents content, DiagramContentChanges changes) {\r
-            this.lastContent = lastContent;\r
-            this.content = content;\r
-            this.changes = changes;\r
-\r
-            this.addedElements = new ArrayList<IElement>(changes.elements.size() + changes.branchPoints.size());\r
-            this.removedElements = new ArrayList<IElement>(changes.elements.size() + changes.branchPoints.size());\r
-            this.addedConnectionSegments = new ArrayList<IElement>(content.connectionSegments.size());\r
-            this.removedConnectionSegments = new ArrayList<IElement>(content.connectionSegments.size());\r
-            this.addedBranchPoints = new ArrayList<IElement>(content.branchPoints.size());\r
-            this.removedBranchPoints = new ArrayList<IElement>(content.branchPoints.size());\r
-            this.addedElementMap = new HashMap<Object, IElement>();\r
-            this.addedConnectionMap = new HashMap<Resource, IElement>();\r
-            this.addedConnectionEntities = new HashMap<Resource, ConnectionEntityImpl>();\r
-            this.removedConnectionEntities = new ArrayList<Resource>(changes.connections.size());\r
-            this.changedConnectionEntities = new HashMap<ConnectionEntityImpl, ConnectionData>();\r
-            this.addedRouteGraphConnectionMap = new HashMap<Resource, IElement>();\r
-            this.removedRouteGraphConnections = new ArrayList<IElement>(changes.routeGraphConnections.size());\r
-        }\r
-\r
-        public void clear() {\r
-               // Prevent DiagramContents leakage through DisposableListeners.\r
-               lastContent = null;\r
-               content = null;\r
-               changes = null;\r
-               \r
-            this.addedElements.clear();\r
-            this.removedElements.clear();\r
-            this.addedConnectionSegments.clear();\r
-            this.removedConnectionSegments.clear();\r
-            this.addedBranchPoints.clear();\r
-            this.removedBranchPoints.clear();\r
-            this.addedElementMap.clear();\r
-            this.addedConnectionMap.clear();\r
-            this.addedConnectionEntities.clear();\r
-            this.removedConnectionEntities.clear();\r
-            this.changedConnectionEntities.clear();\r
-            this.addedRouteGraphConnectionMap.clear();\r
-            this.removedRouteGraphConnections.clear();\r
-        }\r
-\r
-        void processNodes(AsyncReadGraph graph) {\r
-\r
-            for (Map.Entry<Resource, Change> entry : changes.elements.entrySet()) {\r
-\r
-                final Resource element = entry.getKey();\r
-                Change change = entry.getValue();\r
-\r
-                switch (change) {\r
-                    case ADDED: {\r
-                        IElement mappedElement = getMappedElement(element);\r
-                        if (mappedElement == null) {\r
-                            if (DebugPolicy.DEBUG_NODE_LOAD)\r
-                                graph.asyncRequest(new ReadRequest() {\r
-                                    @Override\r
-                                    public void run(ReadGraph graph) throws DatabaseException {\r
-                                        System.out.println("    EXTERNALLY ADDED ELEMENT: "\r
-                                                + NameUtils.getSafeName(graph, element) + " ("\r
-                                                + element.getResourceId() + ")");\r
-                                    }\r
-                                });\r
-\r
-                            if (content.connectionSet.contains(element)) {\r
-\r
-                                // TODO: Connection loading has no listening, changes :Connection will not be noticed by this code!\r
-                                Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {\r
-                                       @Override\r
-                                       public String toString() {\r
-                                               return "Connection load listener for " + element;\r
-                                       }\r
-                                    @Override\r
-                                    public void execute(IElement loaded) {\r
-                                        // Invoked when the element has been loaded.\r
-                                        if (DebugPolicy.DEBUG_CONNECTION_LISTENER)\r
-                                            System.out.println("CONNECTION LoadListener for " + loaded);\r
-\r
-                                        if (loaded == null) {\r
-                                            disposeListener();\r
-                                            return;\r
-                                        }\r
-\r
-                                        Object data = loaded.getHint(ElementHints.KEY_OBJECT);\r
-\r
-                                        // Logic for disposing listener\r
-                                        if (!previousContent.connectionSet.contains(data)) {\r
-                                            if (DebugPolicy.DEBUG_CONNECTION_LISTENER)\r
-                                                System.out.println("CONNECTION LoadListener, connection not in current content: " + data + ". Disposing.");\r
-                                            disposeListener();\r
-                                            return;\r
-                                        }\r
-\r
-                                        if (addedElementMap.containsKey(data)) {\r
-                                            // This element was just loaded, in\r
-                                            // which case its hints need to\r
-                                            // uploaded to the real mapped\r
-                                            // element immediately.\r
-                                            IElement mappedElement = getMappedElement(data);\r
-                                            if (DebugPolicy.DEBUG_CONNECTION_LISTENER)\r
-                                                System.out.println("LOADED ADDED CONNECTION, currently mapped connection: " + mappedElement);\r
-                                            if (mappedElement != null && (mappedElement instanceof Element)) {\r
-                                                if (DebugPolicy.DEBUG_CONNECTION_LISTENER) {\r
-                                                    System.out.println("  mapped hints: " + mappedElement.getHints());\r
-                                                    System.out.println("  loaded hints: " + loaded.getHints());\r
-                                                }\r
-                                                updateMappedElement((Element) mappedElement, loaded);\r
-                                            }\r
-                                        } else {\r
-                                            // This element was already loaded.\r
-                                            // Just schedule an update some time\r
-                                            // in the future.\r
-                                            if (DebugPolicy.DEBUG_CONNECTION_LISTENER)\r
-                                                System.out.println("PREVIOUSLY LOADED CONNECTION UPDATED, scheduling update into the future");\r
-                                            offerGraphUpdate( connectionUpdater(element, loaded) );\r
-                                        }\r
-                                    }\r
-                                };\r
-\r
-                                graph.asyncRequest(new ConnectionRequest(canvas, diagram, element, errorHandler, loadListener), new AsyncProcedure<IElement>() {\r
-                                    @Override\r
-                                    public void execute(AsyncReadGraph graph, final IElement e) {\r
-                                        if (e == null)\r
-                                            return;\r
-\r
-                                        //System.out.println("ConnectionRequestProcedure " + e);\r
-                                        mapElement(element, e);\r
-                                        synchronized (GraphToDiagramUpdater.this) {\r
-                                            addedElements.add(e);\r
-                                            addedElementMap.put(element, e);\r
-                                            addedConnectionMap.put(element, e);\r
-                                        }\r
-\r
-                                        // Read connection type\r
-                                        graph.forSingleType(element, br.DIA.Connection, new Procedure<Resource>() {\r
-                                            @Override\r
-                                            public void exception(Throwable t) {\r
-                                                error(t);\r
-                                            }\r
-\r
-                                            @Override\r
-                                            public void execute(Resource connectionType) {\r
-                                                synchronized (GraphToDiagramUpdater.this) {\r
-                                                    //System.out.println("new connection entity " + e);\r
-                                                    ConnectionEntityImpl entity = new ConnectionEntityImpl(element, connectionType, e);\r
-                                                    e.setHint(ElementHints.KEY_CONNECTION_ENTITY, entity);\r
-                                                    addedConnectionEntities.put(element, entity);\r
-                                                }\r
-                                            }\r
-                                        });\r
-                                    }\r
-\r
-                                    @Override\r
-                                    public void exception(AsyncReadGraph graph, Throwable throwable) {\r
-                                        error(throwable);\r
-                                    }\r
-                                });\r
-                            } else if (content.nodeSet.contains(element)) {\r
-\r
-                                Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {\r
-                                       @Override\r
-                                       public String toString() {\r
-                                               return "Node load listener for " + element;\r
-                                       }\r
-                                    @Override\r
-                                    public void execute(IElement loaded) {\r
-                                        // Invoked when the element has been loaded.\r
-                                        if (DebugPolicy.DEBUG_NODE_LISTENER)\r
-                                            System.out.println("NODE LoadListener for " + loaded);\r
-\r
-                                        if (loaded == null) {\r
-                                            disposeListener();\r
-                                            return;\r
-                                        }\r
-\r
-                                        Object data = loaded.getHint(ElementHints.KEY_OBJECT);\r
-\r
-                                        // Logic for disposing listener\r
-                                        if (!previousContent.nodeSet.contains(data)) {\r
-                                            if (DebugPolicy.DEBUG_NODE_LISTENER)\r
-                                                System.out.println("NODE LoadListener, node not in current content: " + data + ". Disposing.");\r
-                                            disposeListener();\r
-                                            return;\r
-                                        }\r
-\r
-                                        if (addedElementMap.containsKey(data)) {\r
-                                            // This element was just loaded, in\r
-                                            // which case its hints need to\r
-                                            // uploaded to the real mapped\r
-                                            // element immediately.\r
-                                            IElement mappedElement = getMappedElement(data);\r
-                                            if (DebugPolicy.DEBUG_NODE_LISTENER)\r
-                                                System.out.println("LOADED ADDED ELEMENT, currently mapped element: " + mappedElement);\r
-                                            if (mappedElement != null && (mappedElement instanceof Element)) {\r
-                                                if (DebugPolicy.DEBUG_NODE_LISTENER) {\r
-                                                    System.out.println("  mapped hints: " + mappedElement.getHints());\r
-                                                    System.out.println("  loaded hints: " + loaded.getHints());\r
-                                                }\r
-                                                updateMappedElement((Element) mappedElement, loaded);\r
-                                            }\r
-                                        } else {\r
-                                            // This element was already loaded.\r
-                                            // Just schedule an update some time\r
-                                            // in the future.\r
-                                            if (DebugPolicy.DEBUG_NODE_LISTENER)\r
-                                                System.out.println("PREVIOUSLY LOADED NODE UPDATED, scheduling update into the future");\r
-                                            offerGraphUpdate( nodeUpdater(element, loaded) );\r
-                                        }\r
-                                    }\r
-                                };\r
-\r
-                                //System.out.println("NODE REQUEST: " + element);\r
-                                graph.asyncRequest(new NodeRequest(canvas, diagram, element, loadListener), new AsyncProcedure<IElement>() {\r
-                                    @Override\r
-                                    public void execute(AsyncReadGraph graph, IElement e) {\r
-                                        if (e == null)\r
-                                            return;\r
-\r
-                                        // This is invoked before the element is actually loaded.\r
-                                        //System.out.println("NodeRequestProcedure " + e);\r
-                                        if (DebugPolicy.DEBUG_NODE_LOAD)\r
-                                            System.out.println("MAPPING ADDED NODE: " + element + " -> " + e);\r
-                                        mapElement(element, e);\r
-                                        synchronized (GraphToDiagramUpdater.this) {\r
-                                            addedElements.add(e);\r
-                                            addedElementMap.put(element, e);\r
-                                        }\r
-                                    }\r
-\r
-                                    @Override\r
-                                    public void exception(AsyncReadGraph graph, Throwable throwable) {\r
-                                        error(throwable);\r
-                                    }\r
-                                });\r
-\r
-                            } else {\r
-//                                warning("Diagram elements must be either elements or connections, "\r
-//                                        + NameUtils.getSafeName(g, element) + " is neither",\r
-//                                        new AssumptionException(""));\r
-                            }\r
-                        }\r
-                        break;\r
-                    }\r
-                    case REMOVED: {\r
-                        IElement e = getMappedElement(element);\r
-                        if (DebugPolicy.DEBUG_NODE_LOAD)\r
-                            graph.asyncRequest(new ReadRequest() {\r
-                                @Override\r
-                                public void run(ReadGraph graph) throws DatabaseException {\r
-                                    System.out.println("    EXTERNALLY REMOVED ELEMENT: "\r
-                                            + NameUtils.getSafeName(graph, element) + " ("\r
-                                            + element.getResourceId() + ")");\r
-                                }\r
-                            });\r
-                        if (e != null) {\r
-                            removedElements.add(e);\r
-                        }\r
-                        break;\r
-                    }\r
-                }\r
-            }\r
-        }\r
-\r
-        void gatherChangedConnectionParts(Map<?, Change> changes) {\r
-            for (Map.Entry<?, Change> entry : changes.entrySet()) {\r
-                Object part = entry.getKey();\r
-                Change change = entry.getValue();\r
-\r
-                switch (change) {\r
-                    case ADDED: {\r
-                        synchronized (GraphToDiagramUpdater.this) {\r
-                            Resource connection = content.partToConnection.get(part);\r
-                            assert connection != null;\r
-\r
-                            IElement ce = getMappedElement(connection);\r
-                            if (ce == null)\r
-                                ce = addedElementMap.get(connection);\r
-\r
-                            if (ce != null)\r
-                                markConnectionChanged(ce);\r
-                            break;\r
-                        }\r
-                    }\r
-                    case REMOVED: {\r
-                        if (lastContent == null)\r
-                            break;\r
-                        Resource connection = lastContent.partToConnection.get(part);\r
-                        if (connection != null && content.connectionSet.contains(connection)) {\r
-                            markConnectionChanged(connection);\r
-                        }\r
-                        break;\r
-                    }\r
-                }\r
-            }\r
-        }\r
-\r
-        void markConnectionChanged(Resource connection) {\r
-//            System.out.println("markConnectionChanged");\r
-            ConnectionEntityImpl ce = getMappedConnection(connection);\r
-            if (ce != null) {\r
-                markConnectionChanged(ce);\r
-                return;\r
-            }\r
-            error("WARNING: marking connection entity " + connection\r
-                    + " changed, but the connection was not previously mapped",\r
-                    new Exception("created exception to get a stack trace"));\r
-        }\r
-\r
-        void markConnectionChanged(IElement connection) {\r
-            ConnectionEntityImpl entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
-            if (entity != null)\r
-                markConnectionChanged(entity);\r
-        }\r
-\r
-        void markConnectionChanged(ConnectionEntityImpl ce) {\r
-            if (!changedConnectionEntities.containsKey(ce)) {\r
-                changedConnectionEntities.put(ce, new ConnectionData(ce));\r
-            }\r
-        }\r
-\r
-        void processConnections() {\r
-            // Find added/removed connection segments/branch points\r
-            // in order to find all changed connection entities.\r
-            gatherChangedConnectionParts(changes.connectionSegments);\r
-            gatherChangedConnectionParts(changes.branchPoints);\r
-\r
-            // Find removed connection entities\r
-            for (Map.Entry<Resource, Change> entry : changes.connections.entrySet()) {\r
-                Resource ce = entry.getKey();\r
-                Change change = entry.getValue();\r
-\r
-                switch (change) {\r
-                    case REMOVED: {\r
-                        removedConnectionEntities.add(ce);\r
-                    }\r
-                }\r
-            }\r
-\r
-            // Generate update data of changed connection entities.\r
-            // This ConnectionData will be applied in the canvas thread\r
-            // diagram updater.\r
-            for (ConnectionData cd : changedConnectionEntities.values()) {\r
-                for (Object part : content.connectionToParts.getValuesUnsafe(cd.impl.connection)) {\r
-                    if (part instanceof Resource) {\r
-                        cd.branchPoints.add((Resource) part);\r
-                    } else if (part instanceof EdgeResource) {\r
-                        cd.segments.add((EdgeResource) part);\r
-                    }\r
-                }\r
-            }\r
-        }\r
-\r
-        void processRouteGraphConnections(AsyncReadGraph graph) {\r
-            for (Map.Entry<Resource, Change> entry : changes.routeGraphConnections.entrySet()) {\r
-                final Resource connection = entry.getKey();\r
-\r
-                Change change = entry.getValue();\r
-                switch (change) {\r
-                    case ADDED: {\r
-                        IElement mappedElement = getMappedElement(connection);\r
-                        if (mappedElement != null)\r
-                            continue;\r
-\r
-                        Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {\r
-                               @Override\r
-                               public String toString() {\r
-                                       return "processRouteGraphConnections " + connection;\r
-                               }\r
-                            @Override\r
-                            public void execute(IElement loaded) {\r
-                                // Invoked when the element has been loaded.\r
-                                if (DebugPolicy.DEBUG_CONNECTION_LISTENER)\r
-                                    System.out.println("ROUTE GRAPH CONNECTION LoadListener for " + loaded);\r
-\r
-                                if (loaded == null) {\r
-                                    disposeListener();\r
-                                    return;\r
-                                }\r
-\r
-                                Object data = loaded.getHint(ElementHints.KEY_OBJECT);\r
-\r
-                                // Logic for disposing listener\r
-                                if (!previousContent.routeGraphConnectionSet.contains(data)) {\r
-                                    if (DebugPolicy.DEBUG_CONNECTION_LISTENER)\r
-                                        System.out.println("ROUTE GRAPH CONNECTION LoadListener, connection not in current content: " + data + ". Disposing.");\r
-                                    disposeListener();\r
-                                    return;\r
-                                }\r
-\r
-                                if (addedElementMap.containsKey(data)) {\r
-                                    // This element was just loaded, in\r
-                                    // which case its hints need to\r
-                                    // uploaded to the real mapped\r
-                                    // element immediately.\r
-                                    IElement mappedElement = getMappedElement(data);\r
-                                    if (DebugPolicy.DEBUG_CONNECTION_LISTENER)\r
-                                        System.out.println("LOADED ADDED ROUTE GRAPH CONNECTION, currently mapped connection: " + mappedElement);\r
-                                    if (mappedElement instanceof Element) {\r
-                                        if (DebugPolicy.DEBUG_CONNECTION_LISTENER) {\r
-                                            System.out.println("  mapped hints: " + mappedElement.getHints());\r
-                                            System.out.println("  loaded hints: " + loaded.getHints());\r
-                                        }\r
-                                        updateMappedElement((Element) mappedElement, loaded);\r
-                                    }\r
-                                } else {\r
-                                    // This element was already loaded.\r
-                                    // Just schedule an update some time\r
-                                    // in the future.\r
-                                    if (DebugPolicy.DEBUG_CONNECTION_LISTENER)\r
-                                        System.out.println("PREVIOUSLY LOADED ROUTE GRAPH CONNECTION UPDATED, scheduling update into the future: " + connection);\r
-\r
-                                    Set<Object> dirtyNodes = new THashSet<Object>(4);\r
-                                    IElement mappedElement = getMappedElement(connection);\r
-                                    ConnectionEntity ce = mappedElement.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
-                                    if (ce != null) {\r
-                                        for (Connection conn : ce.getTerminalConnections(null)) {\r
-                                            Object o = conn.node.getHint(ElementHints.KEY_OBJECT);\r
-                                            if (o != null) {\r
-                                                dirtyNodes.add(o);\r
-                                                if (DebugPolicy.DEBUG_CONNECTION_LISTENER)\r
-                                                    System.out.println("Marked connectivity dirty for node: " + conn.node);\r
-                                            }\r
-                                        }\r
-                                    }\r
-\r
-                                    offerGraphUpdate( routeGraphConnectionUpdater(connection, loaded, dirtyNodes) );\r
-                                }\r
-                            }\r
-                        };\r
-\r
-                        graph.asyncRequest(new ConnectionRequest(canvas, diagram, connection, errorHandler, loadListener), new Procedure<IElement>() {\r
-                            @Override\r
-                            public void execute(final IElement e) {\r
-                                if (e == null)\r
-                                    return;\r
-\r
-                                //System.out.println("ConnectionRequestProcedure " + e);\r
-                                if (DebugPolicy.DEBUG_NODE_LOAD)\r
-                                    System.out.println("MAPPING ADDED ROUTE GRAPH CONNECTION: " + connection + " -> " + e);\r
-                                mapElement(connection, e);\r
-                                synchronized (GraphToDiagramUpdater.this) {\r
-                                    addedElements.add(e);\r
-                                    addedElementMap.put(connection, e);\r
-                                    addedRouteGraphConnectionMap.put(connection, e);\r
-                                }\r
-                            }\r
-                            @Override\r
-                            public void exception(Throwable throwable) {\r
-                                error(throwable);\r
-                            }\r
-                        });\r
-                        break;\r
-                    }\r
-                    case REMOVED: {\r
-                        IElement e = getMappedElement(connection);\r
-                        if (e != null)\r
-                            removedRouteGraphConnections.add(e);\r
-                        break;\r
-                    }\r
-                }\r
-            }\r
-        }\r
-\r
-        ConnectionEntityImpl getConnectionEntity(Object connectionPart) {\r
-            Resource connection = content.partToConnection.get(connectionPart);\r
-            assert connection != null;\r
-            ConnectionEntityImpl ce = addedConnectionEntities.get(connection);\r
-            if (ce != null)\r
-                return ce;\r
-            return assertMappedConnection(connection);\r
-        }\r
-\r
-        void processBranchPoints(AsyncReadGraph graph) {\r
-            for (Map.Entry<Resource, Change> entry : changes.branchPoints.entrySet()) {\r
-\r
-                final Resource element = entry.getKey();\r
-                Change change = entry.getValue();\r
-\r
-                switch (change) {\r
-                    case ADDED: {\r
-                        IElement mappedElement = getMappedElement(element);\r
-                        if (mappedElement == null) {\r
-                            if (DebugPolicy.DEBUG_NODE_LOAD)\r
-                                graph.asyncRequest(new ReadRequest() {\r
-                                    @Override\r
-                                    public void run(ReadGraph graph) throws DatabaseException {\r
-                                        System.out.println("    EXTERNALLY ADDED BRANCH POINT: "\r
-                                                + NameUtils.getSafeName(graph, element) + " ("\r
-                                                + element.getResourceId() + ")");\r
-                                    }\r
-                                });\r
-\r
-                            Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {\r
-                               @Override\r
-                               public String toString() {\r
-                                       return "processBranchPoints for " + element;\r
-                               }\r
-                                @Override\r
-                                public void execute(IElement loaded) {\r
-                                    // Invoked when the element has been loaded.\r
-                                    if (DebugPolicy.DEBUG_NODE_LISTENER)\r
-                                        System.out.println("BRANCH POINT LoadListener for " + loaded);\r
-\r
-                                    if (loaded == null) {\r
-                                        disposeListener();\r
-                                        return;\r
-                                    }\r
-\r
-                                    Object data = loaded.getHint(ElementHints.KEY_OBJECT);\r
-                                    if (addedElementMap.containsKey(data)) {\r
-                                        // This element was just loaded, in\r
-                                        // which case its hints need to\r
-                                        // uploaded to the real mapped\r
-                                        // element immediately.\r
-                                        IElement mappedElement = getMappedElement(data);\r
-                                        if (DebugPolicy.DEBUG_NODE_LISTENER)\r
-                                            System.out.println("LOADED ADDED BRANCH POINT, currently mapped element: " + mappedElement);\r
-                                        if (mappedElement != null && (mappedElement instanceof Element)) {\r
-                                            if (DebugPolicy.DEBUG_NODE_LISTENER) {\r
-                                                System.out.println("  mapped hints: " + mappedElement.getHints());\r
-                                                System.out.println("  loaded hints: " + loaded.getHints());\r
-                                            }\r
-                                            updateMappedElement((Element) mappedElement, loaded);\r
-                                        }\r
-                                    } else {\r
-                                        // This element was already loaded.\r
-                                        // Just schedule an update some time\r
-                                        // in the future.\r
-                                        if (DebugPolicy.DEBUG_NODE_LISTENER)\r
-                                            System.out.println("PREVIOUSLY LOADED BRANCH POINT UPDATED, scheduling update into the future");\r
-                                        offerGraphUpdate( nodeUpdater(element, loaded) );\r
-                                    }\r
-                                }\r
-                            };\r
-\r
-                            graph.asyncRequest(new NodeRequest(canvas, diagram, element, loadListener), new AsyncProcedure<IElement>() {\r
-                                @Override\r
-                                public void execute(AsyncReadGraph graph, IElement e) {\r
-                                    if (e != null) {\r
-                                        mapElement(element, e);\r
-                                        synchronized (GraphToDiagramUpdater.this) {\r
-                                            addedBranchPoints.add(e);\r
-                                            addedElementMap.put(element, e);\r
-                                            ConnectionEntityImpl ce = getConnectionEntity(element);\r
-                                            e.setHint(ElementHints.KEY_CONNECTION_ENTITY, ce);\r
-                                            e.setHint(ElementHints.KEY_PARENT_ELEMENT, ce.getConnectionElement());\r
-                                        }\r
-                                    }\r
-                                }\r
-\r
-                                @Override\r
-                                public void exception(AsyncReadGraph graph, Throwable throwable) {\r
-                                    error(throwable);\r
-                                }\r
-                            });\r
-                        }\r
-                        break;\r
-                    }\r
-                    case REMOVED: {\r
-                        IElement e = getMappedElement(element);\r
-                        if (DebugPolicy.DEBUG_NODE_LOAD)\r
-                            graph.asyncRequest(new ReadRequest() {\r
-                                @Override\r
-                                public void run(ReadGraph graph) throws DatabaseException {\r
-                                    System.out.println("    EXTERNALLY REMOVED BRANCH POINT: "\r
-                                            + NameUtils.getSafeName(graph, element) + " ("\r
-                                            + element.getResourceId() + ")");\r
-                                }\r
-                            });\r
-                        if (e != null) {\r
-                            removedBranchPoints.add(e);\r
-                        }\r
-                        break;\r
-                    }\r
-                }\r
-            }\r
-        }\r
-\r
-        void processConnectionSegments(AsyncReadGraph graph) {\r
-            ConnectionSegmentAdapter adapter = connectionSegmentAdapter;\r
-\r
-            for (Map.Entry<EdgeResource, Change> entry : changes.connectionSegments.entrySet()) {\r
-                final EdgeResource seg = entry.getKey();\r
-                Change change = entry.getValue();\r
-\r
-                switch (change) {\r
-                    case ADDED: {\r
-                        IElement mappedElement = getMappedElement(seg);\r
-                        if (mappedElement == null) {\r
-                            if (DebugPolicy.DEBUG_EDGE_LOAD)\r
-                                graph.asyncRequest(new ReadRequest() {\r
-                                    @Override\r
-                                    public void run(ReadGraph graph) throws DatabaseException {\r
-                                        System.out.println("    EXTERNALLY ADDED CONNECTION SEGMENT: " + seg.toString()\r
-                                                + " - " + seg.toString(graph));\r
-                                    }\r
-                                });\r
-\r
-                            graph.asyncRequest(new EdgeRequest(canvas, errorHandler, canvasListenerSupport, diagram, adapter, seg), new AsyncProcedure<IElement>() {\r
-                                @Override\r
-                                public void execute(AsyncReadGraph graph, IElement e) {\r
-                                    if (DebugPolicy.DEBUG_EDGE_LOAD)\r
-                                        System.out.println("ADDED EDGE LOADED: " + e);\r
-                                    if (e != null) {\r
-                                        mapElement(seg, e);\r
-                                        synchronized (GraphToDiagramUpdater.this) {\r
-                                            addedConnectionSegments.add(e);\r
-                                            addedElementMap.put(seg, e);\r
-                                            ConnectionEntityImpl ce = getConnectionEntity(seg);\r
-                                            e.setHint(ElementHints.KEY_CONNECTION_ENTITY, ce);\r
-                                            e.setHint(ElementHints.KEY_PARENT_ELEMENT, ce.getConnectionElement());\r
-                                        }\r
-                                    }\r
-                                }\r
-\r
-                                @Override\r
-                                public void exception(AsyncReadGraph graph, Throwable throwable) {\r
-                                    error(throwable);\r
-                                }\r
-                            });\r
-                        }\r
-                        break;\r
-                    }\r
-                    case REMOVED: {\r
-                        final IElement e = getMappedElement(seg);\r
-                        if (DebugPolicy.DEBUG_EDGE_LOAD)\r
-                            graph.asyncRequest(new ReadRequest() {\r
-                                @Override\r
-                                public void run(ReadGraph graph) throws DatabaseException {\r
-                                    System.out.println("    EXTERNALLY REMOVED CONNECTION SEGMENT: " + seg.toString() + " - "\r
-                                            + seg.toString(graph) + " -> " + e);\r
-                                }\r
-                            });\r
-                        if (e != null) {\r
-                            removedConnectionSegments.add(e);\r
-                        }\r
-                        break;\r
-                    }\r
-                }\r
-            }\r
-        }\r
-\r
-        void executeDeferredLoaders(ReadGraph graph) throws DatabaseException {\r
-            // The rest of the diagram loading passes\r
-            Deque<IElement> q1 = new ArrayDeque<IElement>();\r
-            Deque<IElement> q2 = new ArrayDeque<IElement>();\r
-            collectElementLoaders(q1, addedElements);\r
-            while (!q1.isEmpty()) {\r
-                //System.out.println("DEFFERED LOADERS: " + q1);\r
-                for (IElement e : q1) {\r
-                    ElementLoader loader = e.removeHint(DiagramModelHints.KEY_ELEMENT_LOADER);\r
-                    //System.out.println("EXECUTING DEFFERED LOADER: " + loader);\r
-                    loader.load(graph, diagram, e);\r
-                }\r
-\r
-                collectElementLoaders(q2, q1);\r
-                Deque<IElement> qt = q1;\r
-                q1 = q2;\r
-                q2 = qt;\r
-                q2.clear();\r
-            }\r
-        }\r
-\r
-        private void collectElementLoaders(Queue<IElement> queue, Collection<IElement> cs) {\r
-            for (IElement e : cs) {\r
-                ElementLoader loader = e.getHint(DiagramModelHints.KEY_ELEMENT_LOADER);\r
-                if (loader != null)\r
-                    queue.add(e);\r
-            }\r
-        }\r
-\r
-        public void process(ReadGraph graph) throws DatabaseException {\r
-            // No changes? Do nothing.\r
-            if (changes.isEmpty())\r
-                return;\r
-\r
-            // NOTE: This order is important.\r
-            Object task = Timing.BEGIN("processNodesConnections");\r
-            //System.out.println("---- PROCESS NODES & CONNECTIONS BEGIN");\r
-            if (!changes.elements.isEmpty()) {\r
-                graph.syncRequest(new AsyncReadRequest() {\r
-                    @Override\r
-                    public void run(AsyncReadGraph graph) {\r
-                        processNodes(graph);\r
-                    }\r
-                    @Override\r
-                    public String toString() {\r
-                        return "processNodes";\r
-                    }\r
-                });\r
-            }\r
-            //System.out.println("---- PROCESS NODES & CONNECTIONS END");\r
-\r
-            processConnections();\r
-\r
-            //System.out.println("---- PROCESS BRANCH POINTS BEGIN");\r
-            if (!changes.branchPoints.isEmpty()) {\r
-                graph.syncRequest(new AsyncReadRequest() {\r
-                    @Override\r
-                    public void run(AsyncReadGraph graph) {\r
-                        processBranchPoints(graph);\r
-                    }\r
-                    @Override\r
-                    public String toString() {\r
-                        return "processBranchPoints";\r
-                    }\r
-                });\r
-            }\r
-            //System.out.println("---- PROCESS BRANCH POINTS END");\r
-\r
-            Timing.END(task);\r
-            task = Timing.BEGIN("processConnectionSegments");\r
-\r
-            //System.out.println("---- PROCESS CONNECTION SEGMENTS BEGIN");\r
-            if (!changes.connectionSegments.isEmpty()) {\r
-                graph.syncRequest(new AsyncReadRequest() {\r
-                    @Override\r
-                    public void run(AsyncReadGraph graph) {\r
-                        processConnectionSegments(graph);\r
-                    }\r
-                    @Override\r
-                    public String toString() {\r
-                        return "processConnectionSegments";\r
-                    }\r
-                });\r
-            }\r
-            //System.out.println("---- PROCESS CONNECTION SEGMENTS END");\r
-\r
-            Timing.END(task);\r
-\r
-            task = Timing.BEGIN("processRouteGraphConnections");\r
-            if (!changes.routeGraphConnections.isEmpty()) {\r
-                graph.syncRequest(new AsyncReadRequest() {\r
-                    @Override\r
-                    public void run(AsyncReadGraph graph) {\r
-                        processRouteGraphConnections(graph);\r
-                    }\r
-                    @Override\r
-                    public String toString() {\r
-                        return "processRouteGraphConnections";\r
-                    }\r
-                });\r
-            }\r
-            Timing.END(task);\r
-\r
-            //System.out.println("---- AFTER LOADING");\r
-            //for (IElement e : addedElements)\r
-            //    System.out.println("    ADDED ELEMENT: " + e);\r
-            //for (IElement e : addedBranchPoints)\r
-            //    System.out.println("    ADDED BRANCH POINTS: " + e);\r
-\r
-            task = Timing.BEGIN("executeDeferredLoaders");\r
-            executeDeferredLoaders(graph);\r
-            Timing.END(task);\r
-        }\r
-\r
-        public boolean isEmpty() {\r
-            return addedElements.isEmpty() && removedElements.isEmpty()\r
-            && addedConnectionSegments.isEmpty() && removedConnectionSegments.isEmpty()\r
-            && addedBranchPoints.isEmpty() && removedBranchPoints.isEmpty()\r
-            && addedConnectionEntities.isEmpty() && removedConnectionEntities.isEmpty()\r
-            && addedRouteGraphConnectionMap.isEmpty() && removedRouteGraphConnections.isEmpty()\r
-            && !changes.elementOrderChanged;\r
-        }\r
-\r
-        class DefaultConnectionSegmentAdapter implements ConnectionSegmentAdapter {\r
-\r
-            @Override\r
-            public void getClass(AsyncReadGraph graph, EdgeResource edge, ConnectionInfo info, ListenerSupport listenerSupport, ICanvasContext canvas, IDiagram diagram, final AsyncProcedure<ElementClass> procedure) {\r
-                if (info.connectionType != null) {\r
-                    NodeClassRequest request = new NodeClassRequest(canvas, diagram, info.connectionType, true);\r
-                    graph.asyncRequest(request, new CacheListener<ElementClass>(listenerSupport));\r
-                    graph.asyncRequest(request, procedure);\r
-                } else {\r
-                    procedure.execute(graph, null);\r
-                }\r
-            }\r
-\r
-            @Override\r
-            public void load(AsyncReadGraph graph, final EdgeResource edge, final ConnectionInfo info, ListenerSupport listenerSupport, ICanvasContext canvas, final IDiagram diagram, final IElement element) {\r
-                graph.asyncRequest(new Read<IElement>() {\r
-                    @Override\r
-                    public IElement perform(ReadGraph graph) throws DatabaseException {\r
-                        //ITask task = ThreadLogger.getInstance().begin("LoadSegment");\r
-                        syncLoad(graph, edge, info, diagram, element);\r
-                        //task.finish();\r
-                        return element;\r
-                    }\r
-                    @Override\r
-                    public String toString() {\r
-                        return "defaultConnectionSegmentAdapter";\r
-                    }\r
-                }, new DisposableListener<IElement>(listenerSupport) {\r
-                       \r
-                       @Override\r
-                       public String toString() {\r
-                               return "DefaultConnectionSegmentAdapter listener for " + edge;\r
-                       }\r
-                       \r
-                    @Override\r
-                    public void execute(IElement loaded) {\r
-                        // Invoked when the element has been loaded.\r
-                        if (DebugPolicy.DEBUG_EDGE_LISTENER)\r
-                            System.out.println("EDGE LoadListener for " + loaded);\r
-\r
-                        if (loaded == null) {\r
-                            disposeListener();\r
-                            return;\r
-                        }\r
-\r
-                        Object data = loaded.getHint(ElementHints.KEY_OBJECT);\r
-                        if (addedElementMap.containsKey(data)) {\r
-                            // This element was just loaded, in\r
-                            // which case its hints need to\r
-                            // uploaded to the real mapped\r
-                            // element immediately.\r
-                            IElement mappedElement = getMappedElement(data);\r
-                            if (DebugPolicy.DEBUG_EDGE_LISTENER)\r
-                                System.out.println("LOADED ADDED EDGE, currently mapped element: " + mappedElement);\r
-                            if (mappedElement != null && (mappedElement instanceof Element)) {\r
-                                if (DebugPolicy.DEBUG_EDGE_LISTENER) {\r
-                                    System.out.println("  mapped hints: " + mappedElement.getHints());\r
-                                    System.out.println("  loaded hints: " + loaded.getHints());\r
-                                }\r
-                                updateMappedElement((Element) mappedElement, loaded);\r
-                            }\r
-                        } else {\r
-                            // This element was already loaded.\r
-                            // Just schedule an update some time\r
-                            // in the future.\r
-                            if (DebugPolicy.DEBUG_EDGE_LISTENER)\r
-                                System.out.println("PREVIOUSLY LOADED EDGE UPDATED, scheduling update into the future");\r
-                            offerGraphUpdate( edgeUpdater(element, loaded) );\r
-                        }\r
-                    }\r
-                });\r
-            }\r
-\r
-            void syncLoad(ReadGraph graph, EdgeResource connectionSegment, ConnectionInfo info, IDiagram diagram, IElement element) throws DatabaseException {\r
-                // Check that at least some data exists before continuing further.\r
-                if (!graph.hasStatement(connectionSegment.first()) && !graph.hasStatement(connectionSegment.second())) {\r
-                    return;\r
-                }\r
-\r
-                // Validate that both ends of the segment are\r
-                // part of the same connection before loading.\r
-                // This will happen for connections that are\r
-                // modified through splitting and joining of\r
-                // connection segments.\r
-                Resource c = ConnectionUtil.tryGetConnection(graph, connectionSegment);\r
-                if (c == null) {\r
-                    // Ok, this segment is somehow invalid. Just don't load it.\r
-                    if (DebugPolicy.DEBUG_CONNECTION_LOAD)\r
-                        System.out.println("Skipping edge " + connectionSegment + ". Both segment ends are not part of the same connection.");\r
-                    return;\r
-                }\r
-\r
-                if (!info.isValid()) {\r
-                    // This edge must be somehow invalid, don't proceed with loading.\r
-                    if (DebugPolicy.DEBUG_CONNECTION_LOAD)\r
-                        warning("Cannot load edge " + connectionSegment + ". ConnectionInfo " + info + " is invalid.", new DebugException("execution trace"));\r
-                    return;\r
-                }\r
-\r
-                Element edge = (Element) element;\r
-                edge.setHint(ElementHints.KEY_OBJECT, connectionSegment);\r
-\r
-                // connectionSegment resources may currently be in a different\r
-                // order than ConnectionInfo.firstEnd/secondEnd. Therefore the\r
-                // segment ends must be resolved here.\r
-                ConnectionSegmentEnd firstEnd = DiagramGraphUtil.resolveConnectionSegmentEnd(graph, connectionSegment.first());\r
-                ConnectionSegmentEnd secondEnd = DiagramGraphUtil.resolveConnectionSegmentEnd(graph, connectionSegment.second());\r
-                if (firstEnd == null || secondEnd == null) {\r
-                    if (DebugPolicy.DEBUG_CONNECTION_LOAD)\r
-                        warning("End attachments for edge " + connectionSegment + " are unresolved: (" + firstEnd + "," + secondEnd + ")", new DebugException("execution trace"));\r
-                    return;\r
-                }\r
-\r
-                if (DebugPolicy.DEBUG_CONNECTION_LOAD)\r
-                    System.out.println("CONNECTION INFO: " + connectionSegment + " - " + info);\r
-                DesignatedTerminal firstTerminal = DiagramGraphUtil.findDesignatedTerminal(\r
-                        graph, diagram, connectionSegment.first(), firstEnd);\r
-                DesignatedTerminal secondTerminal = DiagramGraphUtil.findDesignatedTerminal(\r
-                        graph, diagram, connectionSegment.second(), secondEnd);\r
-\r
-                // Edges must be connected at both ends in order for edge loading to succeed.\r
-                String err = validateConnectivity(graph, connectionSegment, firstTerminal, secondTerminal);\r
-                if (err != null) {\r
-                    // Stop loading edge if the connectivity cannot be completely resolved.\r
-                    if (DebugPolicy.DEBUG_CONNECTION_LOAD)\r
-                        warning(err, null);\r
-                    return;\r
-                }\r
-\r
-                // NOTICE: Layer information is loaded from the connection entity resource\r
-                // that is shared by all segments of the same connection.\r
-                ElementFactoryUtil.loadLayersForElement(graph, layerManager, diagram, edge, info.connection,\r
-                        new AsyncProcedureAdapter<IElement>() {\r
-                    @Override\r
-                    public void exception(AsyncReadGraph graph, Throwable t) {\r
-                        error("failed to load layers for connection segment", t);\r
-                    }\r
-                });\r
-\r
-                edge.setHintWithoutNotification(KEY_CONNECTION_BEGIN_PLACEHOLDER, new PlaceholderConnection(\r
-                        EdgeEnd.Begin,\r
-                        firstTerminal.element.getHint(ElementHints.KEY_OBJECT),\r
-                        firstTerminal.terminal));\r
-                edge.setHintWithoutNotification(KEY_CONNECTION_END_PLACEHOLDER, new PlaceholderConnection(\r
-                        EdgeEnd.End,\r
-                        secondTerminal.element.getHint(ElementHints.KEY_OBJECT),\r
-                        secondTerminal.terminal));\r
-\r
-                IModelingRules modelingRules = diagram.getHint(DiagramModelHints.KEY_MODELING_RULES);\r
-                if (modelingRules != null) {\r
-                    ConnectionVisualsLoader loader = diagram.getHint(DiagramModelHints.KEY_CONNECTION_VISUALS_LOADER);\r
-                    if (loader != null)\r
-                        loader.loadConnectionVisuals(graph, modelingRules, info.connection, diagram, edge, firstTerminal, secondTerminal);\r
-                    else\r
-                        DiagramGraphUtil.loadConnectionVisuals(graph, modelingRules, info.connection, diagram, edge, firstTerminal, secondTerminal);\r
-                }\r
-            }\r
-\r
-            private String validateConnectivity(ReadGraph graph, EdgeResource edge,\r
-                    DesignatedTerminal firstTerminal,\r
-                    DesignatedTerminal secondTerminal)\r
-            throws DatabaseException {\r
-                boolean firstLoose = firstTerminal == null;\r
-                boolean secondLoose = secondTerminal == null;\r
-                boolean stray = firstLoose && secondLoose;\r
-                if (firstTerminal == null || secondTerminal == null) {\r
-                    StringBuilder sb = new StringBuilder();\r
-                    sb.append("encountered ");\r
-                    sb.append(stray ? "stray" : "loose");\r
-                    sb.append(" connection segment, ");\r
-                    if (firstLoose)\r
-                        sb.append("first ");\r
-                    if (stray)\r
-                        sb.append("and ");\r
-                    if (secondLoose)\r
-                        sb.append("second ");\r
-                    sb.append("end disconnected: ");\r
-                    sb.append(edge.toString(graph));\r
-                    sb.append(" - ");\r
-                    sb.append(edge.toString());\r
-                    return sb.toString();\r
-                }\r
-                return null;\r
-            }\r
-\r
-        }\r
-\r
-        ConnectionSegmentAdapter connectionSegmentAdapter = new DefaultConnectionSegmentAdapter();\r
-\r
-    }\r
-\r
-    private static final Double DIAGRAM_UPDATE_DIAGRAM_PRIORITY = 1d;\r
-    private static final Double DIAGRAM_UPDATE_NODE_PRIORITY = 2d;\r
-    private static final Double DIAGRAM_UPDATE_CONNECTION_PRIORITY = 3d;\r
-    private static final Double DIAGRAM_UPDATE_EDGE_PRIORITY = 4d;\r
-\r
-    interface DiagramUpdater extends Runnable {\r
-        Double getPriority();\r
-\r
-        Comparator<DiagramUpdater> DIAGRAM_UPDATER_COMPARATOR = new Comparator<DiagramUpdater>() {\r
-            @Override\r
-            public int compare(DiagramUpdater o1, DiagramUpdater o2) {\r
-                return o1.getPriority().compareTo(o2.getPriority());\r
-            }\r
-        };\r
-    }\r
-\r
-    interface GraphUpdateReactor {\r
-        DiagramUpdater graphUpdate(ReadGraph graph) throws DatabaseException;\r
-    }\r
-\r
-    static abstract class AbstractDiagramUpdater implements DiagramUpdater, GraphUpdateReactor {\r
-        protected final Double priority;\r
-        protected final String runnerName;\r
-\r
-        public AbstractDiagramUpdater(Double priority, String runnerName) {\r
-            if (priority == null)\r
-                throw new NullPointerException("null priority");\r
-            if (runnerName == null)\r
-                throw new NullPointerException("null runner name");\r
-            this.priority = priority;\r
-            this.runnerName = runnerName;\r
-        }\r
-\r
-        @Override\r
-        public Double getPriority() {\r
-            return priority;\r
-        }\r
-\r
-        @Override\r
-        public AbstractDiagramUpdater graphUpdate(ReadGraph graph) {\r
-            return this;\r
-        }\r
-\r
-        @Override\r
-        public void run() {\r
-            Object task = Timing.BEGIN(runnerName);\r
-            forDiagram();\r
-            Timing.END(task);\r
-        }\r
-\r
-        protected void forDiagram() {\r
-        }\r
-\r
-        @Override\r
-        public String toString() {\r
-            return runnerName + "@" + System.identityHashCode(this) + " [" + priority + "]";\r
-        }\r
-    }\r
-\r
-    /**\r
-     * @param content the new contents of the diagram, must not be\r
-     *        <code>null</code>.\r
-     */\r
-    private GraphUpdateReactor diagramGraphUpdater(final DiagramContents content) {\r
-        if (content == null)\r
-            throw new NullPointerException("null diagram content");\r
-\r
-        return new GraphUpdateReactor() {\r
-            @Override\r
-            public String toString() {\r
-                return "DiagramGraphUpdater@" + System.identityHashCode(this);\r
-            }\r
-\r
-            @Override\r
-            public DiagramUpdater graphUpdate(ReadGraph graph) throws DatabaseException {\r
-                // Never do anything here if the canvas has already been disposed.\r
-                if (!GraphToDiagramSynchronizer.this.isAlive())\r
-                    return null;\r
-\r
-                // We must be prepared for the following changes in the diagram graph\r
-                // model:\r
-                // - the diagram has been completely removed\r
-                // - elements have been added\r
-                // - elements have been removed\r
-                //\r
-                // Element-specific changes are handled by the element query listeners:\r
-                // - elements have been modified\r
-                // - element position has changed\r
-                // - element class (e.g. image) has changed\r
-\r
-                diagramUpdateLock.lock();\r
-                try {\r
-                    if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)\r
-                        System.out.println("In diagramGraphUpdater:");\r
-\r
-                    // Find out what has changed since the last query.\r
-                    Object task = Timing.BEGIN("diagramContentDifference");\r
-                    DiagramContents lastContent = previousContent;\r
-                    DiagramContentChanges changes = content.differenceFrom(previousContent);\r
-                    previousContent = content;\r
-                    Timing.END(task);\r
-                    if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)\r
-                        System.out.println("  changes: " + changes);\r
-\r
-                    // Bail out if there are no changes to react to.\r
-                    if (changes.isEmpty())\r
-                        return null;\r
-\r
-                    // Load everything that needs to be loaded from the graph,\r
-                    // but don't update the UI model in this thread yet.\r
-                    task = Timing.BEGIN("updater.process");\r
-                    GraphToDiagramUpdater updater = new GraphToDiagramUpdater(lastContent, content, changes);\r
-                    GraphToDiagramSynchronizer.this.currentUpdater = updater;\r
-                    try {\r
-                       updater.process(graph);\r
-                    } finally {\r
-                       GraphToDiagramSynchronizer.this.currentUpdater = null;\r
-                    }\r
-                    Timing.END(task);\r
-\r
-                    if (updater.isEmpty())\r
-                        return null;\r
-\r
-                    // Return an updater that will update the UI run-time model.\r
-                    return diagramUpdater(updater);\r
-                } finally {\r
-                    diagramUpdateLock.unlock();\r
-                }\r
-            }\r
-        };\r
-    }\r
-\r
-    DiagramUpdater diagramUpdater(final GraphToDiagramUpdater updater) {\r
-        return new AbstractDiagramUpdater(DIAGRAM_UPDATE_DIAGRAM_PRIORITY, "updateDiagram") {\r
-            @Override\r
-            protected void forDiagram() {\r
-                if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)\r
-                    System.out.println("running diagram updater: " + this);\r
-\r
-                // DiagramUtils.testDiagram(diagram);\r
-                Set<IElement> dirty = new HashSet<IElement>();\r
-\r
-                Object task2 = Timing.BEGIN("Preprocess connection changes");\r
-                Map<ConnectionEntity, ConnectionChildren> connectionChangeData = new HashMap<ConnectionEntity, ConnectionChildren>(updater.changedConnectionEntities.size());\r
-                for (ConnectionData cd : updater.changedConnectionEntities.values()) {\r
-                    connectionChangeData.put(cd.impl, cd.impl.getConnectionChildren());\r
-                }\r
-                Timing.END(task2);\r
-\r
-                task2 = Timing.BEGIN("removeRouteGraphConnections");\r
-                for (IElement removedRouteGraphConnection : updater.removedRouteGraphConnections) {\r
-                    if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)\r
-                        System.out.println("removing route graph connection: " + removedRouteGraphConnection);\r
-\r
-                    ConnectionEntity ce = removedRouteGraphConnection.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
-                    if (ce == null)\r
-                        continue;\r
-                    Object connectionData = ElementUtils.getObject(removedRouteGraphConnection);\r
-                    tempConnections.clear();\r
-                    for (Connection conn : ce.getTerminalConnections(tempConnections)) {\r
-                        ((Element) conn.node).removeHintWithoutNotification(new TerminalKeyOf(conn.terminal, connectionData, Connection.class));\r
-                        // To be sure the view will be up-to-date, mark the node\r
-                        // connected to the removed connection dirty.\r
-                        dirty.add(conn.node);\r
-                    }\r
-                }\r
-                Timing.END(task2);\r
-\r
-                task2 = Timing.BEGIN("removeBranchPoints");\r
-                for (IElement removed : updater.removedBranchPoints) {\r
-                    if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)\r
-                        System.out.println("removing branch point: " + removed);\r
-\r
-                    unmapElement(removed.getHint(ElementHints.KEY_OBJECT));\r
-                    removeNodeTopologyHints((Element) removed);\r
-\r
-                    IElement connection = ElementUtils.getParent(removed);\r
-                    if (connection != null) {\r
-                        dirty.add(connection);\r
-                    }\r
-                }\r
-                Timing.END(task2);\r
-\r
-                task2 = Timing.BEGIN("removeConnectionSegments");\r
-                for (IElement removed : updater.removedConnectionSegments) {\r
-                    if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)\r
-                        System.out.println("removing segment: " + removed);\r
-\r
-                    unmapElement(removed.getHint(ElementHints.KEY_OBJECT));\r
-                    removeEdgeTopologyHints((Element) removed);\r
-\r
-                    IElement connection = ElementUtils.getParent(removed);\r
-                    if (connection != null) {\r
-                        dirty.add(connection);\r
-                    }\r
-                }\r
-                Timing.END(task2);\r
-\r
-                task2 = Timing.BEGIN("removeElements");\r
-                for (IElement removed : updater.removedElements) {\r
-                    if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)\r
-                        System.out.println("removing element: " + removed);\r
-\r
-                    removed.setHint(KEY_REMOVE_RELATIONSHIPS, Boolean.TRUE);\r
-                    if (diagram.containsElement(removed)) {\r
-                        diagram.removeElement(removed);\r
-                    }\r
-                    unmapElement(removed.getHint(ElementHints.KEY_OBJECT));\r
-                    removeNodeTopologyHints((Element) removed);\r
-\r
-                    // No use marking removed elements dirty.\r
-                    dirty.remove(removed);\r
-                }\r
-                Timing.END(task2);\r
-\r
-                // TODO: get rid of this\r
-                task2 = Timing.BEGIN("removeConnectionEntities");\r
-                for (Resource ce : updater.removedConnectionEntities) {\r
-                    unmapConnection(ce);\r
-                }\r
-                Timing.END(task2);\r
-\r
-                task2 = Timing.BEGIN("setConnectionData");\r
-                for (ConnectionData cd : updater.changedConnectionEntities.values()) {\r
-                    cd.impl.setData(cd.segments, cd.branchPoints);\r
-                }\r
-                Timing.END(task2);\r
-\r
-                // TODO: get rid of this\r
-                task2 = Timing.BEGIN("addConnectionEntities");\r
-                for (Map.Entry<Resource, ConnectionEntityImpl> entry : updater.addedConnectionEntities\r
-                        .entrySet()) {\r
-                    mapConnection(entry.getKey(), entry.getValue());\r
-                }\r
-                Timing.END(task2);\r
-\r
-                task2 = Timing.BEGIN("addBranchPoints");\r
-                for (IElement added : updater.addedBranchPoints) {\r
-                    if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)\r
-                        System.out.println("adding branch point: " + added);\r
-\r
-                    mapElement(ElementUtils.getObject(added), added);\r
-\r
-                    IElement connection = ElementUtils.getParent(added);\r
-                    if (connection != null) {\r
-                        dirty.add(connection);\r
-                    }\r
-                }\r
-                Timing.END(task2);\r
-\r
-                // Add new elements at end of diagram, element order will be synchronized later.\r
-                task2 = Timing.BEGIN("addElements");\r
-                for(Resource r : updater.content.elements) {\r
-                    IElement added = updater.addedElementMap.get(r);\r
-                    if(added != null) {\r
-                        if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)\r
-                            System.out.println("adding element: " + added);\r
-\r
-                        //Object task3 = BEGIN("mapElement " + added);\r
-                        Object task3 = Timing.BEGIN("mapElement");\r
-                        mapElement(added.getHint(ElementHints.KEY_OBJECT), added);\r
-                        Timing.END(task3);\r
-\r
-                        //task3 = BEGIN("addElement " + added);\r
-                        task3 = Timing.BEGIN("addElement");\r
-                        //System.out.println("diagram.addElement: " + added + " - " + diagram);\r
-                        diagram.addElement(added);\r
-                        dirty.add(added);\r
-                        Timing.END(task3);\r
-                    }\r
-                }\r
-                Timing.END(task2);\r
-\r
-                // We've ensured that all nodes must have been and\r
-                // mapped before reaching this.\r
-                task2 = Timing.BEGIN("addConnectionSegments");\r
-                for (IElement added : updater.addedConnectionSegments) {\r
-                    if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)\r
-                        System.out.println("adding segment: " + added);\r
-\r
-                    PlaceholderConnection cb = added.removeHint(GraphToDiagramSynchronizer.KEY_CONNECTION_BEGIN_PLACEHOLDER);\r
-                    PlaceholderConnection ce = added.removeHint(GraphToDiagramSynchronizer.KEY_CONNECTION_END_PLACEHOLDER);\r
-                    if (cb == null || ce == null) {\r
-                        if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)\r
-                            warning("ignoring connection segment " + added + ", connectivity was not resolved (begin=" + cb + ", end=" + ce +")", null);\r
-                        continue;\r
-                    }\r
-\r
-                    mapElement(ElementUtils.getObject(added), added);\r
-\r
-                    IElement beginNode = assertMappedElement(cb.node);\r
-                    IElement endNode = assertMappedElement(ce.node);\r
-\r
-                    if (cb != null)\r
-                        connect(added, cb.end, beginNode, cb.terminal);\r
-                    if (ce != null)\r
-                        connect(added, ce.end, endNode, ce.terminal);\r
-\r
-                    IElement connection = ElementUtils.getParent(added);\r
-                    if (connection != null) {\r
-                        dirty.add(connection);\r
-                    }\r
-                }\r
-                Timing.END(task2);\r
-\r
-                // We've ensured that all nodes must have been and\r
-                // mapped before reaching this.\r
-\r
-                task2 = Timing.BEGIN("handle dirty RouteGraph connections");\r
-                for (IElement addedRouteGraphConnection : updater.addedRouteGraphConnectionMap.values()) {\r
-                    updateDirtyRouteGraphConnection(addedRouteGraphConnection, dirty);\r
-                }\r
-                Timing.END(task2);\r
-\r
-                // Prevent memory leaks\r
-                tempConnections.clear();\r
-\r
-                // Make sure that the diagram element order matches that of the database.\r
-                final TObjectIntHashMap<IElement> orderMap = new TObjectIntHashMap<IElement>(2 * updater.content.elements.size());\r
-                int i = 1;\r
-                for (Resource r : updater.content.elements) {\r
-                    IElement e = getMappedElement(r);\r
-                    if (e != null)\r
-                        orderMap.put(e, i);\r
-                    ++i;\r
-                }\r
-                diagram.sort(new Comparator<IElement>() {\r
-                    @Override\r
-                    public int compare(IElement e1, IElement e2) {\r
-                        int o1 = orderMap.get(e1);\r
-                        int o2 = orderMap.get(e2);\r
-                        return o1 - o2;\r
-                    }\r
-                });\r
-\r
-                // TODO: consider removing this. The whole thing should work without it and\r
-                // this "fix" will only be hiding the real problems.\r
-                task2 = Timing.BEGIN("fixChangedConnections");\r
-                for (ConnectionData cd : updater.changedConnectionEntities.values()) {\r
-                    cd.impl.fix();\r
-                }\r
-                Timing.END(task2);\r
-\r
-                task2 = Timing.BEGIN("validateAndFix");\r
-                DiagramUtils.validateAndFix(diagram, dirty);\r
-                Timing.END(task2);\r
-\r
-                // This will fire connection entity change listeners\r
-                task2 = Timing.BEGIN("Postprocess connection changes");\r
-                for (ConnectionData cd : updater.changedConnectionEntities.values()) {\r
-                    ConnectionChildren oldChildren = connectionChangeData.get(cd.impl);\r
-                    if (oldChildren != null) {\r
-                        ConnectionChildren currentChildren = cd.impl.getConnectionChildren();\r
-                        cd.impl.fireListener(oldChildren, currentChildren);\r
-                    }\r
-                }\r
-                Timing.END(task2);\r
-\r
-                task2 = Timing.BEGIN("setDirty");\r
-                for (IElement e : dirty) {\r
-                    if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)\r
-                        System.out.println("MARKING ELEMENT DIRTY: " + e);\r
-                    e.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);\r
-                }\r
-                Timing.END(task2);\r
-\r
-                // Signal about possible changes in the z-order of diagram elements.\r
-                if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)\r
-                    System.out.println("MARKING DIAGRAM DIRTY: " + diagram);\r
-                diagram.setHint(Hints.KEY_DIRTY, Hints.VALUE_Z_ORDER_CHANGED);\r
-\r
-                // Mark the updater as "processed".\r
-                updater.clear();\r
-\r
-                // Inform listeners that the diagram has been updated.\r
-                diagram.setHint(DiagramModelHints.KEY_DIAGRAM_CONTENTS_UPDATED, Boolean.TRUE);\r
-            }\r
-        };\r
-    }\r
-\r
-    /**\r
-     * @param connection\r
-     * @param dirtySet\r
-     */\r
-    private void updateDirtyRouteGraphConnection(IElement connection, Set<IElement> dirtySet) {\r
-        if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)\r
-            System.out.println("updating dirty route graph connection: " + connection);\r
-\r
-        ConnectionEntity ce = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
-        if (ce == null)\r
-            return;\r
-\r
-        tempConnections.clear();\r
-        Object connectionData = ElementUtils.getObject(connection);\r
-        for (Connection conn : ce.getTerminalConnections(tempConnections)) {\r
-            ((Element) conn.node).setHintWithoutNotification(\r
-                    new TerminalKeyOf(conn.terminal, connectionData, Connection.class),\r
-                    conn);\r
-            if (dirtySet != null)\r
-                dirtySet.add(conn.node);\r
-        }\r
-\r
-        // Prevent memory leaks.\r
-        tempConnections.clear();\r
-    }\r
-\r
-    abstract class ElementUpdater extends AbstractDiagramUpdater {\r
-        private final IElement newElement;\r
-\r
-        public ElementUpdater(Double priority, String runnerName, IElement newElement) {\r
-            super(priority, runnerName);\r
-            if (newElement == null)\r
-                throw new NullPointerException("null element");\r
-            this.newElement = newElement;\r
-        }\r
-\r
-        @Override\r
-        public String toString() {\r
-            return super.toString() + "[" + newElement + "]";\r
-        }\r
-\r
-        @Override\r
-        public void run() {\r
-//            System.out.println("ElementUpdateRunner new=" + newElement);\r
-            Object elementResource = newElement.getHint(ElementHints.KEY_OBJECT);\r
-//            System.out.println("ElementUpdateRunner res=" + elementResource);\r
-            final Element mappedElement = (Element) getMappedElement(elementResource);\r
-//            System.out.println("ElementUpdateRunner mapped=" + mappedElement);\r
-            if (mappedElement == null) {\r
-                if (DebugPolicy.DEBUG_ELEMENT_LIFECYCLE) {\r
-                    System.out.println("SKIP DIAGRAM UPDATE " + this  + " for element resource " + elementResource + ", no mapped element (newElement=" + newElement + ")");\r
-                }\r
-                // Indicates the element has been removed from the graph.\r
-                return;\r
-            }\r
-\r
-            Object task = Timing.BEGIN(runnerName);\r
-            forMappedElement(mappedElement);\r
-            Timing.END(task);\r
-        }\r
-\r
-        protected abstract void forMappedElement(Element mappedElement);\r
-    }\r
-\r
-    ElementUpdater nodeUpdater(final Resource resource, final IElement newElement) {\r
-\r
-        return new ElementUpdater(DIAGRAM_UPDATE_NODE_PRIORITY, "updateNode", newElement) {\r
-\r
-            Collection<Terminal> getTerminals(IElement e) {\r
-                Collection<Terminal> ts = Collections.emptyList();\r
-                TerminalTopology tt = e.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class);\r
-                if (tt != null) {\r
-                    ts = new ArrayList<Terminal>();\r
-                    tt.getTerminals(newElement, ts);\r
-                }\r
-                return ts;\r
-            }\r
-\r
-            @Override\r
-            protected void forMappedElement(final Element mappedElement) {\r
-                if (DebugPolicy.DEBUG_NODE_UPDATE)\r
-                    System.out.println("running node updater: " + this + " - new element: " + newElement);\r
-\r
-                // Newly loaded node elements NEVER contain topology-related\r
-                // hints, i.e. TerminalKeyOf hints. Instead all connections are\r
-                // actually set into element hints when connection edges are\r
-                // loaded.\r
-\r
-                Collection<Terminal> oldTerminals = getTerminals(mappedElement);\r
-                Collection<Terminal> newTerminals = getTerminals(newElement);\r
-                if (!oldTerminals.equals(newTerminals)) {\r
-                    // Okay, there are differences in the terminals. Need to fix\r
-                    // the TerminalKeyOf hint values to use the new terminal\r
-                    // instances when correspondences exist.\r
-\r
-                    // If there is no correspondence for an old terminal, we\r
-                    // are simply forced to remove the hints related to this\r
-                    // connection.\r
-\r
-                    Map<Terminal, Terminal> newTerminalMap = new HashMap<Terminal, Terminal>(newTerminals.size());\r
-                    for (Terminal t : newTerminals) {\r
-                        newTerminalMap.put(t, t);\r
-                    }\r
-\r
-                    for (Map.Entry<TerminalKeyOf, Object> entry : mappedElement.getHintsOfClass(TerminalKeyOf.class).entrySet()) {\r
-                        TerminalKeyOf key = entry.getKey();\r
-                        Connection c = (Connection) entry.getValue();\r
-                        if (c.node == mappedElement) {\r
-                            Terminal newTerminal = newTerminalMap.get(c.terminal);\r
-                            if (newTerminal != null) {\r
-                                c = new Connection(c.edge, c.end, c.node, newTerminal);\r
-                                ((Element) c.edge).setHintWithoutNotification(EndKeyOf.get(c.end), c);\r
-                            } else {\r
-                                mappedElement.removeHintWithoutNotification(key);\r
-                            }\r
-                        }\r
-                    }\r
-                }\r
-\r
-                updateMappedElement(mappedElement, newElement);\r
-                mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);\r
-            }\r
-        };\r
-    }\r
-\r
-    ElementUpdater connectionUpdater(final Object data, final IElement newElement) {\r
-        return new ElementUpdater(DIAGRAM_UPDATE_CONNECTION_PRIORITY, "updateConnection", newElement) {\r
-            @Override\r
-            public void forMappedElement(Element mappedElement) {\r
-                if (DebugPolicy.DEBUG_CONNECTION_UPDATE)\r
-                    System.out.println("running connection updater: " + this + " - new element: " + newElement\r
-                            + " with data " + data);\r
-\r
-                // This is kept up-to-date by GDS, make sure not to overwrite it\r
-                // from the mapped element.\r
-                newElement.removeHint(ElementHints.KEY_CONNECTION_ENTITY);\r
-\r
-                updateMappedElement(mappedElement, newElement);\r
-                mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);\r
-            }\r
-        };\r
-    }\r
-\r
-    ElementUpdater edgeUpdater(final Object data, final IElement newElement) {\r
-        return new ElementUpdater(DIAGRAM_UPDATE_EDGE_PRIORITY, "updateEdge", newElement) {\r
-            @Override\r
-            public void forMappedElement(Element mappedElement) {\r
-                if (DebugPolicy.DEBUG_EDGE_UPDATE)\r
-                    System.out.println("running edge updater: " + this + " - new element: " + newElement\r
-                            + " with data " + data);\r
-\r
-                updateMappedElement(mappedElement, newElement);\r
-                mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);\r
-            }\r
-        };\r
-    }\r
-\r
-    ElementUpdater routeGraphConnectionUpdater(final Object data, final IElement newElement, final Set<Object> dirtyNodes) {\r
-        return new ElementUpdater(DIAGRAM_UPDATE_CONNECTION_PRIORITY, "updateRouteGraphConnection", newElement) {\r
-            @Override\r
-            public void forMappedElement(Element mappedElement) {\r
-                if (DebugPolicy.DEBUG_CONNECTION_UPDATE)\r
-                    System.out.println("running route graph connection updater: " + this + " - new element: " + newElement\r
-                            + " with data " + data);\r
-\r
-                // Remove all TerminalKeyOf hints from nodes that were\r
-                // previously connected to the connection (mappedElement)\r
-                // before updating mappedElement with new topology information.\r
-\r
-                for (Object dirtyNodeObject : dirtyNodes) {\r
-                    Element dirtyNode = (Element) getMappedElement(dirtyNodeObject);\r
-                    if (dirtyNode == null)\r
-                        continue;\r
-                    if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)\r
-                        System.out.println("preparing node with dirty connectivity: " + dirtyNode);\r
-\r
-                    for (Map.Entry<TerminalKeyOf, Object> entry : dirtyNode.getHintsOfClass(TerminalKeyOf.class).entrySet()) {\r
-                        Connection conn = (Connection) entry.getValue();\r
-                        Object connectionNode = conn.edge.getHint(ElementHints.KEY_OBJECT);\r
-                        if (data.equals(connectionNode)) {\r
-                            dirtyNode.removeHintWithoutNotification(entry.getKey());\r
-                        }\r
-                    }\r
-                }\r
-\r
-                // Update connection information, including topology\r
-                updateMappedElement(mappedElement, newElement);\r
-\r
-                // Reinstall TerminalKeyOf hints into nodes that are now connected\r
-                // to mappedElement to keep diagram run-time model properly in sync\r
-                // with the database.\r
-                updateDirtyRouteGraphConnection(mappedElement, null);\r
-\r
-                // TODO: should mark dirty nodes' scene graph dirty ?\r
-\r
-                mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);\r
-            }\r
-        };\r
-    }\r
-\r
-    /**\r
-     * Copies hints from <code>newElement</code> to <code>mappedElement</code>\r
-     * asserting some validity conditions at the same time.\r
-     * \r
-     * @param mappedElement\r
-     * @param newElement\r
-     */\r
-    static void updateMappedElement(Element mappedElement, IElement newElement) {\r
-        if (mappedElement == newElement)\r
-            // Can't update anything if the two elements are the same.\r
-            return;\r
-\r
-        ElementClass oldClass = mappedElement.getElementClass();\r
-        ElementClass newClass = newElement.getElementClass();\r
-\r
-        Object mappedData = mappedElement.getHint(ElementHints.KEY_OBJECT);\r
-        Object newData = newElement.getHint(ElementHints.KEY_OBJECT);\r
-\r
-        assert mappedData != null;\r
-        assert newData != null;\r
-        assert mappedData.equals(newData);\r
-\r
-        if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {\r
-            System.out.println("Updating mapped element, setting hints\n  from: " + newElement + "\n  into: " + mappedElement);\r
-        }\r
-\r
-        // TODO: consider if this equals check is a waste of time or does it pay\r
-        // off due to having to reinitialize per-class caches for the new\r
-        // ElementClass that are constructed on the fly?\r
-        if (!newClass.equals(oldClass)) {\r
-            if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {\r
-                System.out.println("  old element class: " + oldClass);\r
-                System.out.println("  new element class: " + newClass);\r
-            }\r
-            mappedElement.setElementClass(newClass);\r
-        }\r
-\r
-        // Tuukka@2010-02-19: replaced with notifications for making\r
-        // the graph synchronizer more transparent to the client.\r
-\r
-        // Hint notifications will not work when this is used.\r
-        //mappedElement.setHintsWithoutNotification(newElement.getHints());\r
-\r
-        Map<DiscardableKey, Object> discardableHints = mappedElement.getHintsOfClass(DiscardableKey.class);\r
-\r
-        // Set all hints from newElement to mappedElement.\r
-        // Leave any hints in mappedElement but not in newElement as is.\r
-        Map<Key, Object> hints = newElement.getHints();\r
-        Map<Key, Object> newHints = new HashMap<Key, Object>();\r
-        for (Map.Entry<Key, Object> entry : hints.entrySet()) {\r
-            Key key = entry.getKey();\r
-            Object newValue = entry.getValue();\r
-            Object oldValue = mappedElement.getHint(key);\r
-            if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE_DETAIL) {\r
-                System.out.println("  hint " + key + " compare values: " + oldValue + "  ->  " + newValue);\r
-            }\r
-            if (!newValue.equals(oldValue)) {\r
-                newHints.put(key, newValue);\r
-                if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {\r
-                    System.out.format("    %-42s : %64s -> %-64s\n", key, oldValue, newValue);\r
-                }\r
-            } else {\r
-                // If the hint value has not changed but the hint still exists\r
-                // we don't need to discard it even if it is considered\r
-                // discardable.\r
-                discardableHints.remove(key);\r
-            }\r
-        }\r
-\r
-        // Set all hints at once and send notifications after setting the values.\r
-        if (!discardableHints.isEmpty()) {\r
-            if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE)\r
-                System.out.println("Discarding " + discardableHints.size() + " discardable hints:\n  " + discardableHints);\r
-            mappedElement.removeHints(discardableHints.keySet());\r
-        }\r
-        if (!newHints.isEmpty()) {\r
-            if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {\r
-                System.out.println("Updating mapped element, setting new hints:\n\t"\r
-                        + EString.implode(newHints.entrySet(), "\n\t") + "\nto replace old hints\n\t"\r
-                        + EString.implode(mappedElement.getHints().entrySet(), "\n\t"));\r
-            }\r
-            mappedElement.setHints(newHints);\r
-        }\r
-        if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {\r
-            System.out.println("All hints after update:\n\t"\r
-                    + EString.implode(mappedElement.getHints().entrySet(), "\n\t"));\r
-        }\r
-    }\r
-\r
-    class TransactionListener extends SessionEventListenerAdapter {\r
-        long startTime;\r
-        @Override\r
-        public void writeTransactionStarted() {\r
-            startTime = System.nanoTime();\r
-            if (DebugPolicy.DEBUG_WRITE_TRANSACTIONS)\r
-                System.out.println(GraphToDiagramSynchronizer.class.getSimpleName() + ".sessionEventListener.writeTransactionStarted");\r
-            inWriteTransaction.set(true);\r
-        }\r
-        @Override\r
-        public void writeTransactionFinished() {\r
-            long endTime = System.nanoTime();\r
-            if (DebugPolicy.DEBUG_WRITE_TRANSACTIONS)\r
-                System.out.println(GraphToDiagramSynchronizer.class.getSimpleName() + ".sessionEventListener.writeTransactionFinished: " + (endTime - startTime)*1e-6 + " ms");\r
-            inWriteTransaction.set(false);\r
-            scheduleGraphUpdates();\r
-        }\r
-    };\r
-\r
-    Object                   graphUpdateLock             = new Object();\r
-    TransactionListener      sessionListener             = null;\r
-    AtomicBoolean            inWriteTransaction          = new AtomicBoolean(false);\r
-    AtomicBoolean            graphUpdateRequestScheduled = new AtomicBoolean(false);\r
-    List<GraphUpdateReactor> queuedGraphUpdates          = new ArrayList<GraphUpdateReactor>();\r
-\r
-    private void offerGraphUpdate(GraphUpdateReactor update) {\r
-        if (DebugPolicy.DEBUG_GRAPH_UPDATE)\r
-            System.out.println("offerGraphUpdate: " + update);\r
-        boolean inWrite = inWriteTransaction.get();\r
-        synchronized (graphUpdateLock) {\r
-            if (DebugPolicy.DEBUG_GRAPH_UPDATE)\r
-                System.out.println("queueing graph update: " + update);\r
-            queuedGraphUpdates.add(update);\r
-        }\r
-        if (!inWrite) {\r
-            if (DebugPolicy.DEBUG_GRAPH_UPDATE)\r
-                System.out.println("scheduling queued graph update immediately: " + update);\r
-            scheduleGraphUpdates();\r
-        }\r
-    }\r
-\r
-    private Collection<GraphUpdateReactor> scrubGraphUpdates() {\r
-        synchronized (graphUpdateLock) {\r
-            if (queuedGraphUpdates.isEmpty())\r
-                return Collections.emptyList();\r
-            final List<GraphUpdateReactor> updates = queuedGraphUpdates;\r
-            queuedGraphUpdates = new ArrayList<GraphUpdateReactor>();\r
-            return updates;\r
-        }\r
-    }\r
-\r
-    private void scheduleGraphUpdates() {\r
-        synchronized (graphUpdateLock) {\r
-            if (queuedGraphUpdates.isEmpty())\r
-                return;\r
-            if (!graphUpdateRequestScheduled.compareAndSet(false, true))\r
-                return;\r
-        }\r
-\r
-        if (DebugPolicy.DEBUG_GRAPH_UPDATE)\r
-            System.out.println("scheduling " + queuedGraphUpdates.size() + " queued graph updates with ");\r
-\r
-        session.asyncRequest(new ReadRequest() {\r
-            @Override\r
-            public void run(final ReadGraph graph) throws DatabaseException {\r
-                Collection<GraphUpdateReactor> updates;\r
-                synchronized (graphUpdateLock) {\r
-                    graphUpdateRequestScheduled.set(false);\r
-                    updates = scrubGraphUpdates();\r
-                }\r
-\r
-                if (!GraphToDiagramSynchronizer.this.isAlive())\r
-                    return;\r
-\r
-                processGraphUpdates(graph, updates);\r
-            }\r
-        }, new ProcedureAdapter<Object>() {\r
-            @Override\r
-            public void exception(Throwable t) {\r
-                error(t);\r
-            }\r
-        });\r
-    }\r
-\r
-    private void processGraphUpdates(ReadGraph graph, final Collection<GraphUpdateReactor> graphUpdates)\r
-    throws DatabaseException {\r
-        final List<DiagramUpdater> diagramUpdates = new ArrayList<DiagramUpdater>(graphUpdates.size());\r
-\r
-        // Run GraphUpdaters and gather DiagramUpdaters.\r
-        if (DebugPolicy.DEBUG_GRAPH_UPDATE)\r
-            System.out.println("Running GRAPH updates: " + graphUpdates);\r
-        for (GraphUpdateReactor graphUpdate : graphUpdates) {\r
-            DiagramUpdater diagramUpdate = graphUpdate.graphUpdate(graph);\r
-            if (diagramUpdate != null) {\r
-                if (DebugPolicy.DEBUG_GRAPH_UPDATE)\r
-                    System.out.println(graphUpdate + " => " + diagramUpdate);\r
-                diagramUpdates.add(diagramUpdate);\r
-            }\r
-        }\r
-\r
-        if (diagramUpdates.isEmpty())\r
-            return;\r
-\r
-        if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)\r
-            System.out.println("Diagram updates: " + diagramUpdates);\r
-        Collections.sort(diagramUpdates, DiagramUpdater.DIAGRAM_UPDATER_COMPARATOR);\r
-        if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)\r
-            System.out.println("Sorted diagram updates: " + diagramUpdates);\r
-\r
-        ThreadUtils.asyncExec(canvas.getThreadAccess(), new StateRunnable() {\r
-            @Override\r
-            public void run() {\r
-                if (GraphToDiagramSynchronizer.this.isAlive() && getState() != State.DISPOSED)\r
-                    safeRunInState(State.UPDATING_DIAGRAM, this);\r
-            }\r
-\r
-            @Override\r
-            public void execute() throws InvocationTargetException {\r
-                // Block out diagram write transactions.\r
-                DiagramUtils.inDiagramTransaction(diagram, TransactionType.READ, new Runnable() {\r
-                    @Override\r
-                    public void run() {\r
-                        if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)\r
-                            System.out.println("Running DIAGRAM updates: " + diagramUpdates);\r
-                        for (DiagramUpdater update : diagramUpdates) {\r
-                            if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)\r
-                                System.out.println("Running DIAGRAM update: " + update);\r
-                            update.run();\r
-                        }\r
-                    }\r
-                });\r
-\r
-                setCanvasDirty();\r
-            }\r
-        });\r
-    }\r
-\r
-    private void attachSessionListener(Session session) {\r
-        SessionEventSupport support = session.peekService(SessionEventSupport.class);\r
-        if (support != null) {\r
-            sessionListener = new TransactionListener();\r
-            support.addListener(sessionListener);\r
-        }\r
-    }\r
-\r
-    private void detachSessionListener() {\r
-        if (sessionListener != null) {\r
-            session.getService(SessionEventSupport.class).removeListener(sessionListener);\r
-            sessionListener = null;\r
-        }\r
-    }\r
-\r
-\r
-    // ------------------------------------------------------------------------\r
-    // GRAPH TO DIAGRAM SYNCHRONIZATION LOGIC END\r
-    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\r
-\r
-    // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\r
-    // DIAGRAM CHANGE TRACKING, MAINLY VALIDATION PURPOSES.\r
-    // This does not try to synchronize anything back into the graph.\r
-    // ------------------------------------------------------------------------\r
-\r
-    IHintListener elementHintValidator = new HintListenerAdapter() {\r
-        @Override\r
-        public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {\r
-            if (!(sender instanceof Element))\r
-                throw new IllegalStateException("invalid sender: " + sender);\r
-            Element e = (Element) sender;\r
-            if (newValue != null) {\r
-                if (key instanceof TerminalKeyOf) {\r
-                    Connection c = (Connection) newValue;\r
-                    if (e != c.node)\r
-                        throw new IllegalStateException("TerminalKeyOf hint of node " + e + " refers to a different node " + c.node + ". Should be the same.");\r
-                    Object edgeObject = ElementUtils.getObject(c.edge);\r
-                    if (!(edgeObject instanceof EdgeResource))\r
-                        throw new IllegalStateException("EndKeyOf hint of edge " + c.edge + " refers contains an invalid object: " + edgeObject);\r
-                } else if (key instanceof EndKeyOf) {\r
-                    Connection c = (Connection) newValue;\r
-                    if (e != c.edge)\r
-                        throw new IllegalStateException("EndKeyOf hint of edge " + e + " refers to a different edge " + c.edge + ". Should be the same.");\r
-                    Object edgeObject = ElementUtils.getObject(c.edge);\r
-                    if (!(edgeObject instanceof EdgeResource))\r
-                        throw new IllegalStateException("EndKeyOf hint of edge " + e + " refers contains an invalid object: " + edgeObject);\r
-                }\r
-            }\r
-        }\r
-    };\r
-\r
-    class DiagramListener implements CompositionListener, CompositionVetoListener {\r
-        @Override\r
-        public boolean beforeElementAdded(IDiagram d, IElement e) {\r
-            // Make sure that MutatedElements NEVER get added to the diagram.\r
-            if (d == diagram) {\r
-                if (!(e instanceof Element)) {\r
-                    // THIS IS NOT GOOD!\r
-                    error("Attempting to add another implementation of IElement besides Element (=" + e.getElementClass().getClass().getName() + ") to the synchronized diagram which means that there is a bug somewhere! See stack trace to find out who is doing this!", new Exception("stacktrace"));\r
-                    System.err.println("Attempting to add another implementation of IElement besides Element (=" + e.getElementClass().getClass().getName() + ") to the synchronized diagram which means that there is a bug somewhere! See Error Log.");\r
-                    return false;\r
-                }\r
-\r
-                // Perform sanity checks that might veto the element addition.\r
-                boolean pass = true;\r
-\r
-                // Check that all elements added to the diagram are adaptable to Resource\r
-                ElementClass ec = e.getElementClass();\r
-                Resource resource = ElementUtils.adapt(ec, Resource.class);\r
-                if (resource == null) {\r
-                    pass = false;\r
-                    new Exception("Attempted to add an element to the diagram that is not adaptable to Resource: " + e + ", class: " + ec).printStackTrace();\r
-                }\r
-\r
-                // Sanity check connection hints\r
-                for (Map.Entry<TerminalKeyOf, Object> entry : e.getHintsOfClass(TerminalKeyOf.class).entrySet()) {\r
-                    Connection c = (Connection) entry.getValue();\r
-                    Object edgeObject = ElementUtils.getObject(c.edge);\r
-                    if (e != c.node) {\r
-                        System.err.println("Invalid node in TerminalKeyOf hint: " + entry.getKey() + "=" + entry.getValue());\r
-                        System.err.println("\tconnection.edge=" + c.edge);\r
-                        System.err.println("\tconnection.node=" + c.node);\r
-                        System.err.println("\tconnection.end=" + c.end);\r
-                        System.err.println("\telement=" + e);\r
-                        System.err.println("\telement class=" + e.getElementClass());\r
-                        pass = false;\r
-                    }\r
-                    if (!(edgeObject instanceof EdgeResource)) {\r
-                        System.err.println("Invalid object in TerminalKeyOf hint edge: " + entry.getKey() + "=" + entry.getValue());\r
-                        System.err.println("\tconnection.edge=" + c.edge);\r
-                        System.err.println("\tconnection.node=" + c.node);\r
-                        System.err.println("\tconnection.end=" + c.end);\r
-                        System.err.println("\telement=" + e);\r
-                        System.err.println("\telement class=" + e.getElementClass());\r
-                        pass = false;\r
-                    }\r
-                }\r
-\r
-                return pass;\r
-            }\r
-            return true;\r
-        }\r
-\r
-        @Override\r
-        public boolean beforeElementRemoved(IDiagram d, IElement e) {\r
-            // Never veto diagram changes.\r
-            return true;\r
-        }\r
-\r
-        @Override\r
-        public void onElementAdded(IDiagram d, IElement e) {\r
-            if (DebugPolicy.DEBUG_ELEMENT_LIFECYCLE)\r
-                System.out.println("[" + d + "] element added: " + e);\r
-\r
-            if (USE_ELEMENT_VALIDATING_LISTENERS)\r
-                e.addHintListener(elementHintValidator);\r
-        }\r
-\r
-        @Override\r
-        public void onElementRemoved(IDiagram d, IElement e) {\r
-            if (DebugPolicy.DEBUG_ELEMENT_LIFECYCLE)\r
-                System.out.println("[" + d + "] element removed: " + e);\r
-\r
-            if (USE_ELEMENT_VALIDATING_LISTENERS)\r
-                e.removeHintListener(elementHintValidator);\r
-\r
-            if (e.containsHint(KEY_REMOVE_RELATIONSHIPS))\r
-                relationshipHandler.denyAll(diagram, e);\r
-        }\r
-    }\r
-\r
-    DiagramListener diagramListener = new DiagramListener();\r
-\r
-    static void removeNodeTopologyHints(Element node) {\r
-        Set<TerminalKeyOf> terminalKeys = node.getHintsOfClass(TerminalKeyOf.class).keySet();\r
-        if (!terminalKeys.isEmpty()) {\r
-            removeNodeTopologyHints(node, terminalKeys);\r
-        }\r
-    }\r
-\r
-    static void removeNodeTopologyHints(Element node, Collection<TerminalKeyOf> terminalKeys) {\r
-        for (TerminalKeyOf key : terminalKeys) {\r
-            Connection c = node.removeHintWithoutNotification(key);\r
-            if (c != null) {\r
-                removeEdgeTopologyHints((Element) c.edge);\r
-            }\r
-        }\r
-    }\r
-\r
-    static void removeEdgeTopologyHints(Element edge) {\r
-        Object edgeData = edge.getHint(ElementHints.KEY_OBJECT);\r
-        for (EndKeyOf key : EndKeyOf.KEYS) {\r
-            Connection c = edge.removeHintWithoutNotification(key);\r
-            if (c != null) {\r
-                ((Element) c.node).removeHintWithoutNotification(new TerminalKeyOf(c.terminal, edgeData, Connection.class));\r
-            }\r
-        }\r
-    }\r
-\r
-    // ------------------------------------------------------------------------\r
-    // DIAGRAM CHANGE TRACKING, MAINLY VALIDATION PURPOSES.\r
-    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\r
-\r
-    // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\r
-    // BACKEND TO DIAGRAM LOAD/LISTEN LOGIC BEGIN\r
-    // ------------------------------------------------------------------------\r
-\r
-    void adaptDiagramClass(AsyncReadGraph graph, Resource diagram, final AsyncProcedure<DiagramClass> procedure) {\r
-        graph.forAdapted(diagram, DiagramClass.class, new AsyncProcedure<DiagramClass>() {\r
-            @Override\r
-            public void exception(AsyncReadGraph graph, Throwable throwable) {\r
-                procedure.exception(graph, throwable);\r
-            }\r
-\r
-            @Override\r
-            public void execute(AsyncReadGraph graph, DiagramClass dc) {\r
-                // To move TopologyImpl out of here, we need a separate\r
-                // DiagramClassFactory that takes a canvas context as an argument.\r
-                // DataElementMapImpl, ElementFactoryImpl and diagramLifeCycle can\r
-                // safely stay here.\r
-                procedure.execute(graph, dc.newClassWith(\r
-                        // This handler takes care of the topology of the diagram model.\r
-                        // It sets and fixes element hints related to describing the\r
-                        // connectivity of elements.\r
-                        diagramTopology,\r
-                        // TODO: not quite sure whether this can prove itself useful or not.\r
-                        elementFactory,\r
-                        // This map provides a bidirectional mapping between\r
-                        // IElement and back-end objects.\r
-                        dataElementMap,\r
-                        // This handler provides a facility to adapt an element class\r
-                        // to work properly with a diagram synchronized using this\r
-                        // GraphToDiagramSynchronizer.\r
-                        substituteElementClass,\r
-                        // These handlers provide a way to create simple identified\r
-                        // uni- and bidirectional relationships between any diagram\r
-                        // objects/elements.\r
-                        relationshipHandler));\r
-            }\r
-        });\r
-    }\r
-\r
-    static Connection connect(IElement edge, EdgeEnd end, IElement element, Terminal terminal) {\r
-        Connection c = new Connection(edge, end, element, terminal);\r
-\r
-        Object edgeData = edge.getHint(ElementHints.KEY_OBJECT);\r
-        if (DebugPolicy.DEBUG_CONNECTION) {\r
-            System.out.println("[connect](edge=" + edge + ", edgeData=" + edgeData + ", end=" + end + ", element="\r
-                    + element + ", terminal=" + terminal + ")");\r
-        }\r
-\r
-        TerminalKeyOf key = new TerminalKeyOf(terminal, edgeData, Connection.class);\r
-        element.setHint(key, c);\r
-\r
-        EndKeyOf key2 = EndKeyOf.get(end);\r
-        edge.setHint(key2, c);\r
-\r
-        return c;\r
-    }\r
-\r
-    static class ElementFactoryImpl implements ElementFactory {\r
-        @Override\r
-        public IElement spawnNew(ElementClass clazz) {\r
-            IElement e = Element.spawnNew(clazz);\r
-            return e;\r
-        }\r
-    }\r
-\r
-    ElementFactoryImpl elementFactory = new ElementFactoryImpl();\r
-\r
-    public static final Object FIRST_TIME = new Object() {\r
-        @Override\r
-        public String toString() {\r
-            return "FIRST_TIME";\r
-        }\r
-    };\r
-\r
-\r
-    /**\r
-     * A base for all listeners of graph requests performed internally by\r
-     * GraphToDiagramSynchronizer.\r
-     *\r
-     * @param <T> type of stored data element\r
-     * @param <Result> query result type\r
-     */\r
-    abstract class BaseListener<T, Result> implements AsyncListener<Result> {\r
-\r
-        protected final T    data;\r
-\r
-        private Object       oldResult = FIRST_TIME;\r
-\r
-        protected boolean    disposed  = false;\r
-\r
-        final ICanvasContext canvas;\r
-\r
-        public BaseListener(T data) {\r
-            this.canvas = GraphToDiagramSynchronizer.this.canvas;\r
-            this.data = data;\r
-        }\r
-\r
-        @Override\r
-        public void exception(AsyncReadGraph graph, Throwable throwable) {\r
-            // Exceptions are always expected to mean that the listener should\r
-            // be considered disposed once a query fails.\r
-            disposed = true;\r
-        }\r
-\r
-        abstract void execute(AsyncReadGraph graph, Object oldResult, Object newResult);\r
-\r
-        @Override\r
-        public void execute(AsyncReadGraph graph, Result result) {\r
-            if (DebugPolicy.DEBUG_LISTENER_BASE)\r
-                System.out.println("BaseListener: " + result);\r
-\r
-            if (disposed) {\r
-                if (DebugPolicy.DEBUG_LISTENER_BASE)\r
-                    System.out.println("BaseListener: execute invoked although listener is disposed!");\r
-                return;\r
-            }\r
-\r
-            // A null result will permanently mark this listener disposed!\r
-            if (result == null) {\r
-                disposed = true;\r
-                if (DebugPolicy.DEBUG_LISTENER_BASE)\r
-                    System.out.println(this + " null result, listener marked disposed");\r
-            }\r
-\r
-            if (oldResult == FIRST_TIME) {\r
-                oldResult = result;\r
-                if (DebugPolicy.DEBUG_LISTENER_BASE)\r
-                    System.out.println(this + " first result computed: " + result);\r
-            } else {\r
-                if (DebugPolicy.DEBUG_LISTENER_BASE)\r
-                    System.out.println(this + " result changed from '" + oldResult + "' to '" + result + "'");\r
-                try {\r
-                    execute(graph, oldResult, result);\r
-                } finally {\r
-                    oldResult = result;\r
-                }\r
-            }\r
-        }\r
-\r
-        @Override\r
-        public boolean isDisposed() {\r
-            if (disposed)\r
-                return true;\r
-\r
-            boolean alive = isAlive();\r
-            //System.out.println(getClass().getName() + ": isDisposed(" + resource.getResourceId() + "): canvas=" + canvas + ", isAlive=" + alive);\r
-            if (!alive)\r
-                return true;\r
-            // If a mapping no longer exists for this element, dispose of this\r
-            // listener.\r
-            //IElement e = getMappedElement(resource);\r
-            //System.out.println(getClass().getName() + ": isDisposed(" + resource.getResourceId() + "): canvas=" + canvas + ", element=" + e);\r
-            //return e == null;\r
-            return false;\r
-        }\r
-\r
-//        @Override\r
-//        protected void finalize() throws Throwable {\r
-//            System.out.println("finalize listener: " + this);\r
-//            super.finalize();\r
-//        }\r
-    }\r
-\r
-    class DiagramClassRequest extends BaseRequest2<Resource, DiagramClass> {\r
-        public DiagramClassRequest(Resource resource) {\r
-            super(GraphToDiagramSynchronizer.this.canvas, resource);\r
-        }\r
-\r
-        @Override\r
-        public void perform(AsyncReadGraph graph, AsyncProcedure<DiagramClass> procedure) {\r
-            adaptDiagramClass(graph, data, procedure);\r
-        }\r
-    }\r
-\r
-    public class DiagramContentListener extends BaseListener<Resource, DiagramContents> {\r
-\r
-        public DiagramContentListener(Resource resource) {\r
-            super(resource);\r
-        }\r
-\r
-        @Override\r
-        public void execute(final AsyncReadGraph graph, Object oldResult, Object newResult) {\r
-            final DiagramContents newContent = (newResult == null) ? new DiagramContents()\r
-            : (DiagramContents) newResult;\r
-\r
-            // diagramGraphUpdater is called synchronously during\r
-            // loading. The first result will not get updated through\r
-            // this listener but through loadDiagram.\r
-\r
-            if (DebugPolicy.DISABLE_DIAGRAM_UPDATES) {\r
-                System.out.println("Skipped diagram content update: " + newResult);\r
-                return;\r
-            }\r
-\r
-            if (DebugPolicy.DEBUG_DIAGRAM_LISTENER)\r
-                System.out.println("diagram contents changed: " + oldResult + " => " + newResult);\r
-\r
-            offerGraphUpdate( diagramGraphUpdater(newContent) );\r
-        }\r
-\r
-        @Override\r
-        public boolean isDisposed() {\r
-            return !isAlive();\r
-        }\r
-\r
-        @Override\r
-        public void exception(AsyncReadGraph graph, Throwable t) {\r
-            super.exception(graph, t);\r
-            error("DiagramContentRequest failed", t);\r
-        }\r
-    }\r
-\r
-    // ------------------------------------------------------------------------\r
-    // BACKEND TO DIAGRAM LOAD/LISTEN LOGIC END\r
-    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\r
-\r
-    // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\r
-    // GRAPH-CUSTOMIZED DIAGRAM TOPOLOGY HANDLER BEGIN\r
-    // ------------------------------------------------------------------------\r
-\r
-    static class TopologyImpl implements Topology {\r
-\r
-        @Override\r
-        public Connection getConnection(IElement edge, EdgeEnd end) {\r
-            Key key = EndKeyOf.get(end);\r
-            Connection c = edge.getHint(key);\r
-            if (c == null)\r
-                return null;\r
-            return c;\r
-        }\r
-\r
-        @Override\r
-        public void getConnections(IElement node, Terminal terminal, Collection<Connection> connections) {\r
-//            IDiagram d = ElementUtils.getDiagram(node);\r
-            for (Map.Entry<TerminalKeyOf, Object> entry : node.getHintsOfClass(TerminalKeyOf.class).entrySet()) {\r
-                // First check that the terminal matches.\r
-                TerminalKeyOf key = entry.getKey();\r
-                if (!key.getTerminal().equals(terminal))\r
-                    continue;\r
-\r
-                Connection c = (Connection) entry.getValue();\r
-                if (c != null) {\r
-                    connections.add(c);\r
-                }\r
-            }\r
-        }\r
-\r
-        @Override\r
-        public void connect(IElement edge, EdgeEnd end, IElement node, Terminal terminal) {\r
-            if (node != null && terminal != null)\r
-                GraphToDiagramSynchronizer.connect(edge, end, node, terminal);\r
-\r
-            if (DebugPolicy.DEBUG_CONNECTION) {\r
-                if (end == EdgeEnd.Begin)\r
-                    System.out.println("Connection started from: " + edge + ", " + end + ", " + node + ", " + terminal);\r
-                else\r
-                    System.out.println("Creating connection to: " + edge + ", " + end + ", " + node + ", " + terminal);\r
-            }\r
-        }\r
-\r
-        @Override\r
-        public void disconnect(IElement edge, EdgeEnd end, IElement node, Terminal terminal) {\r
-            EndKeyOf edgeKey = EndKeyOf.get(end);\r
-            Connection c = edge.getHint(edgeKey);\r
-            if (c == null)\r
-                throw new UnsupportedOperationException("cannot disconnect, no Connection in edge " + edge);\r
-\r
-            for (Map.Entry<TerminalKeyOf, Object> entry : node.getHintsOfClass(TerminalKeyOf.class).entrySet()) {\r
-                Connection cc = (Connection) entry.getValue();\r
-                if (c == cc) {\r
-                    node.removeHint(entry.getKey());\r
-                    edge.removeHint(edgeKey);\r
-                    return;\r
-                }\r
-            }\r
-\r
-            throw new UnsupportedOperationException("cannot disconnect, no connection between found between edge "\r
-                    + edge + " and node " + node);\r
-        }\r
-    }\r
-\r
-    Topology            diagramTopology     = new TopologyImpl();\r
-\r
-    // ------------------------------------------------------------------------\r
-    // GRAPH-CUSTOMIZED DIAGRAM TOPOLOGY HANDLER END\r
-    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\r
-\r
-    // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\r
-    // DIAGRAM OBJECT RELATIONSHIP HANDLER BEGIN\r
-    // ------------------------------------------------------------------------\r
-\r
-    RelationshipHandler relationshipHandler = new RelationshipHandler() {\r
-\r
-        AssociativeMap map = new AssociativeMap(Associativity.of(true, false, false));\r
-\r
-        Object getPossibleObjectOrElement(Object o) {\r
-            if (o instanceof IElement) {\r
-                IElement e = (IElement) o;\r
-                Object oo = e.getHint(ElementHints.KEY_OBJECT);\r
-                return oo != null ? oo : e;\r
-            }\r
-            return o;\r
-        }\r
-\r
-        IElement getElement(Object o) {\r
-            if (o instanceof IElement)\r
-                return (IElement) o;\r
-            return getMappedElement(o);\r
-        }\r
-\r
-        @Override\r
-        public void claim(IDiagram diagram, Object subject,\r
-                Relationship predicate, Object object) {\r
-            Object sd = getPossibleObjectOrElement(subject);\r
-            Object od = getPossibleObjectOrElement(object);\r
-\r
-            Collection<Tuple> ts = null;\r
-            Relationship inverse = predicate.getInverse();\r
-            if (inverse != null)\r
-                ts = Arrays.asList(new Tuple(sd, predicate, od), new Tuple(od, inverse, sd));\r
-            else\r
-                ts = Collections.singletonList(new Tuple(sd, predicate, od));\r
-\r
-            synchronized (this) {\r
-                map.add(ts);\r
-            }\r
-\r
-            if (DebugPolicy.DEBUG_RELATIONSHIP) {\r
-                new Exception().printStackTrace();\r
-                System.out.println("Claimed relationships:");\r
-                for (Tuple t : ts)\r
-                    System.out.println("\t" + t);\r
-            }\r
-        }\r
-\r
-        private void doDeny(IDiagram diagram, Object subject,\r
-                Relationship predicate, Object object) {\r
-            Object sd = getPossibleObjectOrElement(subject);\r
-            Object od = getPossibleObjectOrElement(object);\r
-            if (sd == subject || od == object) {\r
-                System.out\r
-                .println("WARNING: denying relationship '"\r
-                        + predicate\r
-                        + "' between diagram element(s), not back-end object(s): "\r
-                        + sd + " -> " + od);\r
-            }\r
-\r
-            Collection<Tuple> ts = null;\r
-            Relationship inverse = predicate.getInverse();\r
-            if (inverse != null)\r
-                ts = Arrays.asList(new Tuple(sd, predicate, od), new Tuple(od,\r
-                        inverse, sd));\r
-            else\r
-                ts = Collections.singleton(new Tuple(sd, predicate, od));\r
-\r
-            synchronized (this) {\r
-                map.remove(ts);\r
-            }\r
-\r
-            if (DebugPolicy.DEBUG_RELATIONSHIP) {\r
-                new Exception().printStackTrace();\r
-                System.out.println("Denied relationships:");\r
-                for (Tuple t : ts)\r
-                    System.out.println("\t" + t);\r
-            }\r
-        }\r
-\r
-        @Override\r
-        public void deny(IDiagram diagram, Object subject,\r
-                Relationship predicate, Object object) {\r
-            synchronized (this) {\r
-                doDeny(diagram, subject, predicate, object);\r
-            }\r
-        }\r
-\r
-        @Override\r
-        public void deny(IDiagram diagram, Relation relation) {\r
-            synchronized (this) {\r
-                doDeny(diagram, relation.getSubject(), relation\r
-                        .getRelationship(), relation.getObject());\r
-            }\r
-        }\r
-\r
-        @Override\r
-        public void denyAll(IDiagram diagram, Object element) {\r
-            synchronized (this) {\r
-                for (Relation relation : getRelations(diagram, element, null)) {\r
-                    doDeny(diagram, relation.getSubject(), relation\r
-                            .getRelationship(), relation.getObject());\r
-                }\r
-            }\r
-        }\r
-\r
-        @Override\r
-        public Collection<Relation> getRelations(IDiagram diagram,\r
-                Object element, Collection<Relation> result) {\r
-            if (DebugPolicy.DEBUG_GET_RELATIONSHIP)\r
-                System.out.println("getRelations(" + element + ")");\r
-            Object e = getPossibleObjectOrElement(element);\r
-\r
-            Collection<Tuple> tuples = null;\r
-            synchronized (this) {\r
-                tuples = map.get(new Tuple(e, null, null), null);\r
-            }\r
-\r
-            if (DebugPolicy.DEBUG_GET_RELATIONSHIP) {\r
-                System.out.println("Result size: " + tuples.size());\r
-                for (Tuple t : tuples)\r
-                    System.out.println("\t" + t);\r
-            }\r
-\r
-            if (tuples.isEmpty())\r
-                return Collections.emptyList();\r
-            if (result == null)\r
-                result = new ArrayList<Relation>(tuples.size());\r
-            for (Tuple t : tuples) {\r
-                Object obj = t.getField(2);\r
-                IElement el = getElement(obj);\r
-                Relationship r = (Relationship) t.getField(1);\r
-                result.add(new Relation(element, r, el != null ? el : obj));\r
-            }\r
-            return result;\r
-        }\r
-\r
-    };\r
-\r
-    // ------------------------------------------------------------------------\r
-    // DIAGRAM ELEMENT RELATIONSHIP HANDLER END\r
-    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\r
-\r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2018 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.adapter;
+
+import java.awt.geom.AffineTransform;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Deque;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.SubMonitor;
+import org.simantics.db.AsyncReadGraph;
+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.ResourceArray;
+import org.simantics.db.common.exception.DebugException;
+import org.simantics.db.common.procedure.adapter.AsyncProcedureAdapter;
+import org.simantics.db.common.procedure.adapter.CacheListener;
+import org.simantics.db.common.procedure.adapter.ListenerSupport;
+import org.simantics.db.common.procedure.adapter.ProcedureAdapter;
+import org.simantics.db.common.procedure.adapter.TransientCacheAsyncListener;
+import org.simantics.db.common.request.AsyncReadRequest;
+import org.simantics.db.common.request.ReadRequest;
+import org.simantics.db.common.session.SessionEventListenerAdapter;
+import org.simantics.db.common.utils.NameUtils;
+import org.simantics.db.exception.CancelTransactionException;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.exception.NoSingleResultException;
+import org.simantics.db.exception.ServiceException;
+import org.simantics.db.procedure.AsyncListener;
+import org.simantics.db.procedure.AsyncProcedure;
+import org.simantics.db.procedure.Listener;
+import org.simantics.db.procedure.Procedure;
+import org.simantics.db.request.Read;
+import org.simantics.db.service.SessionEventSupport;
+import org.simantics.diagram.connection.ConnectionSegmentEnd;
+import org.simantics.diagram.content.Change;
+import org.simantics.diagram.content.ConnectionUtil;
+import org.simantics.diagram.content.DesignatedTerminal;
+import org.simantics.diagram.content.DiagramContentChanges;
+import org.simantics.diagram.content.DiagramContents;
+import org.simantics.diagram.content.EdgeResource;
+import org.simantics.diagram.content.ResourceTerminal;
+import org.simantics.diagram.internal.DebugPolicy;
+import org.simantics.diagram.internal.timing.GTask;
+import org.simantics.diagram.internal.timing.Timing;
+import org.simantics.diagram.profile.ProfileKeys;
+import org.simantics.diagram.synchronization.CollectingModificationQueue;
+import org.simantics.diagram.synchronization.CompositeModification;
+import org.simantics.diagram.synchronization.CopyAdvisor;
+import org.simantics.diagram.synchronization.ErrorHandler;
+import org.simantics.diagram.synchronization.IHintSynchronizer;
+import org.simantics.diagram.synchronization.IModifiableSynchronizationContext;
+import org.simantics.diagram.synchronization.IModification;
+import org.simantics.diagram.synchronization.LogErrorHandler;
+import org.simantics.diagram.synchronization.ModificationAdapter;
+import org.simantics.diagram.synchronization.SynchronizationHints;
+import org.simantics.diagram.synchronization.graph.AddElement;
+import org.simantics.diagram.synchronization.graph.BasicResources;
+import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;
+import org.simantics.diagram.synchronization.graph.ElementLoader;
+import org.simantics.diagram.synchronization.graph.ElementReorder;
+import org.simantics.diagram.synchronization.graph.ElementWriter;
+import org.simantics.diagram.synchronization.graph.GraphSynchronizationContext;
+import org.simantics.diagram.synchronization.graph.GraphSynchronizationHints;
+import org.simantics.diagram.synchronization.graph.ModificationQueue;
+import org.simantics.diagram.synchronization.graph.TagChange;
+import org.simantics.diagram.synchronization.graph.TransformElement;
+import org.simantics.diagram.synchronization.graph.layer.GraphLayer;
+import org.simantics.diagram.synchronization.graph.layer.GraphLayerManager;
+import org.simantics.diagram.ui.DiagramModelHints;
+import org.simantics.g2d.canvas.Hints;
+import org.simantics.g2d.canvas.ICanvasContext;
+import org.simantics.g2d.connection.ConnectionEntity;
+import org.simantics.g2d.connection.EndKeyOf;
+import org.simantics.g2d.connection.TerminalKeyOf;
+import org.simantics.g2d.diagram.DiagramClass;
+import org.simantics.g2d.diagram.DiagramHints;
+import org.simantics.g2d.diagram.DiagramMutator;
+import org.simantics.g2d.diagram.DiagramUtils;
+import org.simantics.g2d.diagram.IDiagram;
+import org.simantics.g2d.diagram.IDiagram.CompositionListener;
+import org.simantics.g2d.diagram.IDiagram.CompositionVetoListener;
+import org.simantics.g2d.diagram.handler.DataElementMap;
+import org.simantics.g2d.diagram.handler.ElementFactory;
+import org.simantics.g2d.diagram.handler.Relationship;
+import org.simantics.g2d.diagram.handler.RelationshipHandler;
+import org.simantics.g2d.diagram.handler.SubstituteElementClass;
+import org.simantics.g2d.diagram.handler.Topology;
+import org.simantics.g2d.diagram.handler.Topology.Connection;
+import org.simantics.g2d.diagram.handler.Topology.Terminal;
+import org.simantics.g2d.diagram.handler.TransactionContext.TransactionType;
+import org.simantics.g2d.diagram.impl.Diagram;
+import org.simantics.g2d.diagram.participant.ElementPainter;
+import org.simantics.g2d.element.ElementClass;
+import org.simantics.g2d.element.ElementHints;
+import org.simantics.g2d.element.ElementHints.DiscardableKey;
+import org.simantics.g2d.element.ElementUtils;
+import org.simantics.g2d.element.IElement;
+import org.simantics.g2d.element.IElementClassProvider;
+import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
+import org.simantics.g2d.element.handler.ElementHandler;
+import org.simantics.g2d.element.handler.ElementLayerListener;
+import org.simantics.g2d.element.handler.TerminalTopology;
+import org.simantics.g2d.element.impl.Element;
+import org.simantics.g2d.layers.ILayer;
+import org.simantics.g2d.layers.ILayersEditor;
+import org.simantics.g2d.routing.RouterFactory;
+import org.simantics.scenegraph.INode;
+import org.simantics.scenegraph.profile.DataNodeConstants;
+import org.simantics.scenegraph.profile.DataNodeMap;
+import org.simantics.scenegraph.profile.common.ProfileObserver;
+import org.simantics.structural2.modelingRules.IModelingRules;
+import org.simantics.utils.datastructures.ArrayMap;
+import org.simantics.utils.datastructures.MapSet;
+import org.simantics.utils.datastructures.Pair;
+import org.simantics.utils.datastructures.disposable.AbstractDisposable;
+import org.simantics.utils.datastructures.hints.HintListenerAdapter;
+import org.simantics.utils.datastructures.hints.IHintContext.Key;
+import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
+import org.simantics.utils.datastructures.hints.IHintListener;
+import org.simantics.utils.datastructures.hints.IHintObservable;
+import org.simantics.utils.datastructures.map.AssociativeMap;
+import org.simantics.utils.datastructures.map.Associativity;
+import org.simantics.utils.datastructures.map.Tuple;
+import org.simantics.utils.strings.EString;
+import org.simantics.utils.threads.ThreadUtils;
+import org.simantics.utils.threads.logger.ITask;
+import org.simantics.utils.threads.logger.ThreadLogger;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import gnu.trove.map.hash.TObjectIntHashMap;
+import gnu.trove.set.hash.THashSet;
+
+/**
+ * This class loads a diagram contained in the graph database into the runtime
+ * diagram model and synchronizes changes in the graph into the run-time
+ * diagram. Any modifications to the graph model will be reflected to the
+ * run-time diagram model. Hence the name GraphToDiagramSynchronizer.
+ * 
+ * <p>
+ * This class does not in itself support modification of the graph diagram
+ * model. This manipulation is meant to be performed through a
+ * {@link DiagramMutator} implementation that is installed into the diagram
+ * using the {@link DiagramHints#KEY_MUTATOR} hint key.
+ * 
+ * This implementations is built to only support diagrams defined in the graph
+ * model as indicated in <a
+ * href="https://www.simantics.org/wiki/index.php/Org.simantics.diagram" >this
+ * </a> document.
+ * 
+ * <p>
+ * The synchronizer in itself is an {@link IDiagramLoader} which means that it
+ * can be used for loading a diagram from the graph. In order for the
+ * synchronizer to keep tracking the graph diagram model for changes the diagram
+ * must be loaded with it. The tracking is implemented using graph database
+ * queries. If you just want to load the diagram but detach it from the
+ * synchronizer's tracking mechanisms, all you need to do is to dispose the
+ * synchronizer after loading the diagram.
+ * 
+ * <p>
+ * This class guarantees that a single diagram element (IElement) representing a
+ * single back-end object ({@link ElementHints#KEY_OBJECT}) will stay the same
+ * object for the same back-end object.
+ * 
+ * <p>
+ * TODO: Currently it just happens that {@link GraphToDiagramSynchronizer}
+ * contains {@link DefaultDiagramMutator} which depends on some internal details
+ * of {@link GraphToDiagramSynchronizer} but it should be moved out of here by
+ * introducing new interfaces.
+ * 
+ * <h2>Basic usage example</h2>
+ * <p>
+ * This example shows how to initialize {@link GraphToDiagramSynchronizer} for a
+ * specified {@link ICanvasContext} and load a diagram from a specified diagram
+ * resource in the graph.
+ * 
+ * <pre>
+ * IDiagram loadDiagram(final ICanvasContext canvasContext, RequestProcessor processor, Resource diagramResource,
+ *         ResourceArray structuralPath) throws DatabaseException {
+ *     GraphToDiagramSynchronizer synchronizer = processor.syncRequest(new Read&lt;GraphToDiagramSynchronizer&gt;() {
+ *         public GraphToDiagramSynchronizer perform(ReadGraph graph) throws DatabaseException {
+ *             return new GraphToDiagramSynchronizer(graph, canvasContext, createElementClassProvider(graph));
+ *         }
+ *     });
+ *     IDiagram d = requestProcessor
+ *             .syncRequest(new DiagramLoadQuery(diagramResource, structuralPath, synchronizer, null));
+ *     return d;
+ * }
+ * 
+ * protected IElementClassProvider createElementClassProvider(ReadGraph graph) {
+ *     DiagramResource dr = DiagramResource.getInstance(graph);
+ *     return ElementClassProviders.mappedProvider(ElementClasses.CONNECTION, DefaultConnectionClassFactory.CLASS
+ *             .newClassWith(new ResourceAdapterImpl(dr.Connection)), ElementClasses.FLAG, FlagClassFactory
+ *             .createFlagClass(dr.Flag));
+ * }
+ * </pre>
+ * 
+ * <p>
+ * TODO: make GraphToDiagramSynchronizer a canvas participant to make it more
+ * uniform with the rest of the canvas system. This does not mean that G2DS must
+ * be attached to an ICanvasContext in order to be used, rather that it can be
+ * attached to one.
+ * 
+ * <p>
+ * TODO: test that detaching the synchronizer via {@link #dispose()} actually
+ * works.
+ * <p>
+ * TODO: remove {@link DefaultDiagramMutator} and all {@link DiagramMutator}
+ * stuff altogether
+ * 
+ * <p>
+ * TODO: diagram connection loading has no listener
+ * 
+ * @author Tuukka Lehtonen
+ * 
+ * @see GraphElementClassFactory
+ * @see GraphElementFactory
+ * @see ElementLoader
+ * @see ElementWriter
+ * @see IHintSynchronizer
+ * @see CopyAdvisor
+ */
+public class GraphToDiagramSynchronizer extends AbstractDisposable implements IDiagramLoader, IModifiableSynchronizationContext {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(GraphToDiagramSynchronizer.class);
+
+    /**
+     * Controls whether the class adds hint listeners to each diagram element
+     * that try to perform basic sanity checks on changes happening in element
+     * hints. Having this will immediately inform you of bugs that corrupt the
+     * diagram model within the element hints in some way.
+     */
+    private static final boolean USE_ELEMENT_VALIDATING_LISTENERS = false;
+
+    /**
+     * These keys are used to hang on to Connection instances of edges that will
+     * be later installed as EndKeyOf/TerminalKeyOf hints into the loaded
+     * element during the "graph to diagram update transaction".
+     */
+    private static final Key     KEY_CONNECTION_BEGIN_PLACEHOLDER = new KeyOf(PlaceholderConnection.class, "CONNECTION_BEGIN_PLACEHOLDER");
+    private static final Key     KEY_CONNECTION_END_PLACEHOLDER   = new KeyOf(PlaceholderConnection.class, "CONNECTION_END_PLACEHOLDER");
+
+    /**
+     * Stored into an edge node during connection edge requests using the
+     * KEY_CONNECTION_BEGIN_PLACEHOLDER and KEY_CONNECTION_END_PLACEHOLDER keys.
+     */
+    static class PlaceholderConnection {
+        public final EdgeEnd end;
+        public final Object node;
+        public final Terminal terminal;
+        public PlaceholderConnection(EdgeEnd end, Object node, Terminal terminal) {
+            this.end = end;
+            this.node = node;
+            this.terminal = terminal;
+        }
+    }
+
+    /**
+     * Indicates to the diagram CompositionListener of this synchronizer that is
+     * should deny all relationships for the element this hint is attached to.
+     */
+    private static final Key        KEY_REMOVE_RELATIONSHIPS = new KeyOf(Boolean.class, "REMOVE_RELATIONSHIPS");
+
+    static ErrorHandler             errorHandler             = LogErrorHandler.INSTANCE;
+
+    /**
+     * The canvas context which is being synchronized with the graph. Received
+     * during construction.
+     * 
+     * <p>
+     * Is not nulled during disposal of this class since internal listener's
+     * life-cycles depend on canvas.isDisposed.
+     */
+    ICanvasContext                  canvas;
+
+    /**
+     * The session used by this synchronizer. Received during construction.
+     */
+    Session                         session;
+
+    /**
+     * Locked while updating diagram contents from the graph.
+     */
+    ReentrantLock                   diagramUpdateLock        = new ReentrantLock();
+
+    // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+    // BI-DIRECTIONAL DIAGRAM ELEMENT <-> BACKEND OBJECT MAPPING BEGIN
+    // ------------------------------------------------------------------------
+
+    /**
+     * Holds a GraphToDiagramUpdater instance while a diagram content update is
+     * currently in progress.
+     *
+     * <p>
+     * Basically this is a hack solution to the problem of properly finding
+     * newly added Resource<->IElement mappings while loading the diagram
+     * contents. See {@link DataElementMapImpl} for why this is necessary.
+     */
+    GraphToDiagramUpdater                   currentUpdater                   = null;
+
+    /**
+     * A map from data objects to elements. Elements should already contain the
+     * data objects as {@link ElementHints#KEY_OBJECT} hints.
+     */
+    ConcurrentMap<Object, IElement>         dataElement                      = new ConcurrentHashMap<Object, IElement>();
+
+    /**
+     * Temporary structure for single-threaded use in #{@link DiagramUpdater}.
+     */
+    Collection<Connection>                  tempConnections = new ArrayList<Connection>();
+
+    /**
+     * A dummy class of which an instance will be given to each new edge element
+     * to make {@link TerminalKeyOf} keys unique for each edge.
+     */
+    static class TransientElementObject {
+        @Override
+        public String toString() {
+            return "MUTATOR GENERATED (hash=" + System.identityHashCode(this) + ")";
+        }
+    }
+
+    private static class ConnectionChildren {
+        public Set<IElement> branchPoints;
+        public Set<IElement> segments;
+
+        public ConnectionChildren(Set<IElement> branchPoints, Set<IElement> segments) {
+            this.branchPoints = branchPoints;
+            this.segments = segments;
+        }
+    }
+
+    ListenerSupport canvasListenerSupport = new ListenerSupport() {
+        @Override
+        public void exception(Throwable t) {
+            error(t);
+        }
+
+        @Override
+        public boolean isDisposed() {
+            return !isAlive() || canvas.isDisposed();
+        }
+    };
+
+    /**
+     * @see ElementHints#KEY_CONNECTION_ENTITY
+     */
+    class ConnectionEntityImpl implements ConnectionEntity {
+
+        /**
+         * The connection instance resource in the graph backend.
+         *
+         * May be <code>null</code> if the connection has not been synchronized
+         * yet.
+         */
+        Resource                 connection;
+
+        /**
+         * The connection type resource in the graph backend.
+         *
+         * May be <code>null</code> if the connection has not been synchronized
+         * yet.
+         */
+        Resource                 connectionType;
+
+        /**
+         * The connection entity element which is a part of the diagram.
+         */
+        IElement                 connectionElement;
+
+        /**
+         * List of backend-synchronized branch points that are part of this
+         * connection.
+         */
+        Collection<Resource>     branchPoints        = Collections.emptyList();
+
+        /**
+         * List of backend-synchronized edges that are part of this connection.
+         */
+        Collection<EdgeResource> segments            = Collections.emptyList();
+
+        Set<Object>              removedBranchPoints = new HashSet<Object>(4);
+
+        Set<Object>              removedSegments     = new HashSet<Object>(4);
+
+        /**
+         * List of non-backend-synchronized branch point element that are part
+         * of this connection.
+         */
+        List<IElement>           branchPointElements = new ArrayList<IElement>(1);
+
+        /**
+         * List of non-backend-synchronized edge element that are part of this
+         * connection.
+         */
+        List<IElement>           segmentElements     = new ArrayList<IElement>(2);
+
+        ConnectionListener       listener;
+
+        ConnectionEntityImpl(Resource connection, Resource connectionType, IElement connectionElement) {
+            this.connection = connection;
+            this.connectionType = connectionType;
+            this.connectionElement = connectionElement;
+        }
+
+        ConnectionEntityImpl(Resource connectionType, IElement connectionElement) {
+            this.connectionType = connectionType;
+            this.connectionElement = connectionElement;
+        }
+
+        ConnectionEntityImpl(ReadGraph graph, Resource connection, IElement connectionElement)
+        throws NoSingleResultException, ServiceException {
+            this.connection = connection;
+            this.connectionType = graph.getSingleType(connection, br.DIA.Connection);
+            this.connectionElement = connectionElement;
+        }
+
+        @Override
+        public IElement getConnection() {
+            return connectionElement;
+        }
+
+        public Object getConnectionObject() {
+            return connection;
+        }
+
+        public IElement getConnectionElement() {
+            if (connectionElement == null)
+                return getMappedConnectionElement();
+            return connectionElement;
+        }
+
+        private IElement getMappedConnectionElement() {
+            IElement ce = null;
+            if (connection != null)
+                ce = getMappedElement(connection);
+            return ce == null ? connectionElement : ce;
+        }
+
+        void fix() {
+            Collection<IElement> segments = getSegments(null);
+
+            // Remove all TerminalKeyOf hints from branch points that do not
+            // match
+            ArrayList<TerminalKeyOf> pruned = null;
+            for (IElement bp : getBranchPoints(null)) {
+                if (pruned == null)
+                    pruned = new ArrayList<TerminalKeyOf>(4);
+                pruned.clear();
+                for (Map.Entry<TerminalKeyOf, Object> entry : bp.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
+                    // First check that the terminal matches.
+                    Connection c = (Connection) entry.getValue();
+                    if (!segments.contains(c.edge))
+                        pruned.add(entry.getKey());
+                }
+                removeNodeTopologyHints((Element) bp, pruned);
+            }
+        }
+
+        public ConnectionChildren getConnectionChildren() {
+            Set<IElement> bps = Collections.emptySet();
+            Set<IElement> segs = Collections.emptySet();
+            if (!branchPoints.isEmpty()) {
+                bps = new HashSet<IElement>(branchPoints.size());
+                for (Resource bp : branchPoints) {
+                    IElement e = getMappedElement(bp);
+                    if (e != null)
+                        bps.add(e);
+                }
+            }
+            if (!segments.isEmpty()) {
+                segs = new HashSet<IElement>(segments.size());
+                for (EdgeResource seg : segments) {
+                    IElement e = getMappedElement(seg);
+                    if (e != null)
+                        segs.add(e);
+                }
+            }
+            return new ConnectionChildren(bps, segs);
+        }
+
+        public void setData(Collection<EdgeResource> segments, Collection<Resource> branchPoints) {
+            // System.out.println("setData " + segments.size());
+            this.branchPoints = branchPoints;
+            this.segments = segments;
+
+            // Reset the added/removed state of segments and branchpoints.
+            this.removedBranchPoints = new HashSet<Object>(4);
+            this.removedSegments = new HashSet<Object>(4);
+            this.branchPointElements = new ArrayList<IElement>(4);
+            this.segmentElements = new ArrayList<IElement>(4);
+        }
+
+        public void fireListener(ConnectionChildren old, ConnectionChildren current) {
+            if (listener != null) {
+                List<IElement> removed = new ArrayList<IElement>();
+                List<IElement> added = new ArrayList<IElement>();
+
+                for (IElement oldBp : old.branchPoints)
+                    if (!current.branchPoints.contains(oldBp))
+                        removed.add(oldBp);
+                for (IElement oldSeg : old.segments)
+                    if (!current.segments.contains(oldSeg))
+                        removed.add(oldSeg);
+
+                for (IElement bp : current.branchPoints)
+                    if (!old.branchPoints.contains(bp))
+                        added.add(bp);
+                for (IElement seg : current.segments)
+                    if (!old.segments.contains(seg))
+                        added.add(seg);
+
+                if (!removed.isEmpty() || !added.isEmpty()) {
+                    listener.connectionChanged(new ConnectionEvent(this.connectionElement, removed, added));
+                }
+            }
+        }
+
+        @Override
+        public Collection<IElement> getBranchPoints(Collection<IElement> result) {
+            if (result == null)
+                result = new ArrayList<IElement>(branchPoints.size());
+            for (Resource bp : branchPoints) {
+                if (!removedBranchPoints.contains(bp)) {
+                    IElement e = getMappedElement(bp);
+                    if (e != null)
+                        result.add(e);
+                }
+            }
+            result.addAll(branchPointElements);
+            return result;
+        }
+
+        @Override
+        public Collection<IElement> getSegments(Collection<IElement> result) {
+            if (result == null)
+                result = new ArrayList<IElement>(segments.size());
+            for (EdgeResource seg : segments) {
+                if (!removedSegments.contains(seg)) {
+                    IElement e = getMappedElement(seg);
+                    if (e != null)
+                        result.add(e);
+                }
+            }
+            result.addAll(segmentElements);
+            return result;
+        }
+
+        @Override
+        public Collection<Connection> getTerminalConnections(Collection<Connection> result) {
+            if (result == null)
+                result = new ArrayList<Connection>(segments.size() * 2);
+            Set<org.simantics.utils.datastructures.Pair<IElement, Terminal>> processed = new HashSet<org.simantics.utils.datastructures.Pair<IElement, Terminal>>();
+            for (EdgeResource seg : segments) {
+                IElement edge = getMappedElement(seg);
+                if (edge != null) {
+                    for (EndKeyOf key : EndKeyOf.KEYS) {
+                        Connection c = edge.getHint(key);
+                        if (c != null && (c.terminal instanceof ResourceTerminal) && processed.add(Pair.make(c.node, c.terminal)))
+                            result.add(c);
+                    }
+                }
+            }
+            return result;
+        }
+
+        @Override
+        public void setListener(ConnectionListener listener) {
+            this.listener = listener;
+        }
+
+        @Override
+        public String toString() {
+            return getClass().getSimpleName() + "[resource=" + connection + ", branch points=" + branchPoints
+            + ", segments=" + segments + ", connectionElement=" + connectionElement
+            + ", branch point elements=" + branchPointElements + ", segment elements=" + segmentElements
+            + ", removed branch points=" + removedBranchPoints + ", removed segments=" + removedSegments + "]";
+        }
+
+    }
+
+    /**
+     * A map from connection data objects to connection entities. The connection
+     * part elements should already contain the data objects as
+     * {@link ElementHints#KEY_OBJECT} hints.
+     */
+    ConcurrentMap<Object, ConnectionEntityImpl> dataConnection = new ConcurrentHashMap<Object, ConnectionEntityImpl>();
+
+    /**
+     * @param data
+     * @param element
+     */
+    void mapElement(final Object data, final IElement element) {
+        if (!(element instanceof Element)) {
+            throw new IllegalArgumentException("mapElement: expected instance of Element, got " + element + " with data " + data);
+        }
+        assert data != null;
+        assert element != null;
+        if (DebugPolicy.DEBUG_MAPPING)
+            new Exception(Thread.currentThread() + " MAPPING: " + data + " -> " + element).printStackTrace();
+        dataElement.put(data, element);
+    }
+
+    /**
+     * @param data
+     * @return
+     */
+    IElement getMappedElement(final Object data) {
+        assert (data != null);
+        IElement element = dataElement.get(data);
+        return element;
+    }
+
+    IElement getMappedElementByElementObject(IElement e) {
+        if (e == null)
+            return null;
+        Object o = e.getHint(ElementHints.KEY_OBJECT);
+        if (o == null)
+            return null;
+        return getMappedElement(o);
+    }
+
+    /**
+     * @param data
+     * @return
+     */
+    IElement assertMappedElement(final Object data) {
+        IElement element = dataElement.get(data);
+        assert element != null;
+        return element;
+    }
+
+    /**
+     * @param data
+     * @return
+     */
+    IElement unmapElement(final Object data) {
+        IElement element = dataElement.remove(data);
+        if (DebugPolicy.DEBUG_MAPPING)
+            new Exception(Thread.currentThread() + " UN-MAPPED: " + data + " -> " + element).printStackTrace();
+        return element;
+    }
+
+    /**
+     * @param data
+     * @param element
+     */
+    void mapConnection(final Object data, final ConnectionEntityImpl connection) {
+        assert data != null;
+        assert connection != null;
+        if (DebugPolicy.DEBUG_MAPPING)
+            System.out.println(Thread.currentThread() + " MAPPING CONNECTION: " + data + " -> " + connection);
+        dataConnection.put(data, connection);
+    }
+
+    /**
+     * @param data
+     * @return
+     */
+    ConnectionEntityImpl getMappedConnection(final Object data) {
+        ConnectionEntityImpl connection = dataConnection.get(data);
+        return connection;
+    }
+
+    /**
+     * @param data
+     * @return
+     */
+    ConnectionEntityImpl assertMappedConnection(final Object data) {
+        ConnectionEntityImpl connection = getMappedConnection(data);
+        assert connection != null;
+        return connection;
+    }
+
+    /**
+     * @param data
+     * @return
+     */
+    ConnectionEntityImpl unmapConnection(final Object data) {
+        ConnectionEntityImpl connection = dataConnection.remove(data);
+        if (DebugPolicy.DEBUG_MAPPING)
+            System.out.println(Thread.currentThread() + " UN-MAPPED CONNECTION: " + data + " -> " + connection);
+        return connection;
+    }
+
+    class DataElementMapImpl implements DataElementMap {
+        @Override
+        public Object getData(IDiagram d, IElement element) {
+            if (d == null)
+                throw new NullPointerException("null diagram");
+            if (element == null)
+                throw new NullPointerException("null element");
+
+            assert ElementUtils.getDiagram(element) == d;
+            return element.getHint(ElementHints.KEY_OBJECT);
+        }
+
+        @Override
+        public IElement getElement(IDiagram d, Object data) {
+            if (d == null)
+                throw new NullPointerException("null diagram");
+            if (data == null)
+                throw new NullPointerException("null data");
+
+            GraphToDiagramUpdater updater = currentUpdater;
+            if (updater != null) {
+                // This HACK is for allowing GraphElementFactory implementations
+                // to find the IElements they are related to.
+                IElement e = updater.addedElementMap.get(data);
+                if (e != null)
+                    return e;
+            }
+
+            IElement e = getMappedElement(data);
+            if (e != null)
+                return e;
+            return null;
+        }
+    }
+
+    class SubstituteElementClassImpl implements SubstituteElementClass {
+        @Override
+        public ElementClass substitute(IDiagram d, ElementClass ec) {
+            if (d != diagram)
+                throw new IllegalArgumentException("specified diagram does not have this SubstituteElementClass handler");
+
+            // If the element class is our own, there's no point in creating
+            // a copy of it.
+            if (ec.contains(elementLayerListener))
+                return ec;
+
+            List<ElementHandler> all = ec.getAll();
+            List<ElementHandler> result = new ArrayList<ElementHandler>(all.size());
+            for (ElementHandler eh : all) {
+                if (eh instanceof ElementLayerListenerImpl)
+                    result.add(elementLayerListener);
+                else
+                    result.add(eh);
+            }
+            return ElementClass.compile(result, false).setId(ec.getId());
+        }
+    }
+
+    final DataElementMapImpl         dataElementMap         = new DataElementMapImpl();
+
+    final SubstituteElementClassImpl substituteElementClass = new SubstituteElementClassImpl();
+
+    // ------------------------------------------------------------------------
+    // BI-DIRECTIONAL DIAGRAM ELEMENT <-> BACKEND OBJECT MAPPING END
+    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+
+    void warning(String message, Exception e) {
+        errorHandler.warning(message, e);
+    }
+
+    void warning(Exception e) {
+        errorHandler.warning(e.getMessage(), e);
+    }
+
+    void error(String message, Throwable e) {
+        errorHandler.error(message, e);
+    }
+
+    void error(Throwable e) {
+        errorHandler.error(e.getMessage(), e);
+    }
+
+    // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+    // GRAPH MODIFICATION QUEUE BEGIN
+    // ------------------------------------------------------------------------
+
+    ModificationQueue                 modificationQueue;
+    IModifiableSynchronizationContext synchronizationContext;
+
+    @Override
+    public <T> T set(Key key, Object value) {
+        if (synchronizationContext == null)
+            return null;
+        return synchronizationContext.set(key, value);
+    }
+
+    @Override
+    public <T> T get(Key key) {
+        if (synchronizationContext == null)
+            return null;
+        return synchronizationContext.get(key);
+    }
+
+    // ------------------------------------------------------------------------
+    // GRAPH MODIFICATION QUEUE END
+    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+
+    /**
+     * The previously loaded version of the diagram content. This is needed to
+     * calculate the difference between new and old content on each
+     * {@link #diagramGraphUpdater(DiagramContents)} invocation.
+     */
+    DiagramContents       previousContent;
+
+    /**
+     * The diagram instance that this synchronizer is synchronizing with the
+     * graph.
+     */
+    IDiagram              diagram;
+
+    /**
+     * An observer for diagram profile entries. Has a life-cycle that must be
+     * bound to the life-cycle of this GraphToDiagramSynchronizer instance.
+     * Disposed if synchronizer is detached in {@link #doDispose()} or finally
+     * when the canvas is disposed.
+     */
+    ProfileObserver       profileObserver;
+
+    IElementClassProvider elementClassProvider;
+
+    BasicResources        br;
+
+    // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+    // Internal state machine handling BEGIN
+    // ------------------------------------------------------------------------
+
+    /**
+     * An indicator for the current state of this synchronizer. This is a simple
+     * state machine with the following possible state transitions:
+     *
+     * <ul>
+     * <li>INITIAL -> LOADING, DISPOSED</li>
+     * <li>LOADING -> IDLE</li>
+     * <li>IDLE -> UPDATING_DIAGRAM, DISPOSED</li>
+     * <li>UPDATING_DIAGRAM -> IDLE</li>
+     * </ul>
+     * 
+     * Start states: INITIAL
+     * End states: DISPOSED
+     */
+    static enum State {
+        /**
+         * The initial state of the synchronizer.
+         */
+        INITIAL,
+        /**
+         * The synchronizer is performing load-time initialization. During this
+         * time no canvas refreshes should be forced.
+         */
+        LOADING,
+        /**
+         * The synchronizer is performing updates to the diagram model. This
+         * process goes on in the canvas context thread.
+         */
+        UPDATING_DIAGRAM,
+        /**
+         * The synchronizer is doing nothing.
+         */
+        IDLE,
+        /**
+         * The synchronized diagram is being disposed, which means that this
+         * synchronizer should not accept any further actions.
+         */
+        DISPOSED,
+    }
+
+    public static final EnumSet<State> FROM_INITIAL          = EnumSet.of(State.LOADING, State.DISPOSED);
+    public static final EnumSet<State> FROM_LOADING          = EnumSet.of(State.IDLE);
+    public static final EnumSet<State> FROM_UPDATING_DIAGRAM = EnumSet.of(State.IDLE);
+    public static final EnumSet<State> FROM_IDLE             = EnumSet.of(State.UPDATING_DIAGRAM, State.DISPOSED);
+    public static final EnumSet<State> NO_STATES             = EnumSet.noneOf(State.class);
+
+    private EnumSet<State> validTargetStates(State start) {
+        switch (start) {
+            case INITIAL: return FROM_INITIAL;
+            case LOADING: return FROM_LOADING;
+            case UPDATING_DIAGRAM: return FROM_UPDATING_DIAGRAM;
+            case IDLE: return FROM_IDLE;
+            case DISPOSED: return NO_STATES;
+        }
+        throw new IllegalArgumentException("unrecognized state " + start);
+    }
+
+    private String validateStateChange(State start, State end) {
+        EnumSet<State> validTargets = validTargetStates(start);
+        if (!validTargets.contains(end))
+            return "Cannot transition from " + start + " state to " + end + ".";
+        return null;
+    }
+
+    /**
+     * The current state of the synchronizer. At start it is
+     * {@link State#INITIAL} and after loading it is {@link State#IDLE}.
+     */
+    State                              synchronizerState     = State.INITIAL;
+
+    /**
+     * A condition variable used to synchronize synchronizer state changes.
+     */
+    ReentrantLock                      stateLock             = new ReentrantLock();
+
+    /**
+     * A condition that is signaled when the synchronizer state changes to IDLE.
+     */
+    Condition                          idleCondition         = stateLock.newCondition();
+
+    State getState() {
+        return synchronizerState;
+    }
+
+    /**
+     * Activates the desired state after making sure that the synchronizer has
+     * been IDLE in between its current state and this invocation.
+     *
+     * @param newState the new state to activate
+     * @throws InterruptedException if waiting for IDLE state gets interrupted
+     * @throws IllegalStateException if the requested transition from the
+     *         current state to the desired state would be illegal.
+     */
+    void activateState(State newState, boolean waitForIdle) throws InterruptedException {
+        stateLock.lock();
+        try {
+            // Wait until the state of the synchronizer IDLEs if necessary.
+            if (waitForIdle && synchronizerState != State.IDLE) {
+                String error = validateStateChange(synchronizerState, State.IDLE);
+                if (error != null)
+                    throw new IllegalStateException(error);
+
+                while (synchronizerState != State.IDLE) {
+                    if (DebugPolicy.DEBUG_STATE)
+                        System.out.println(Thread.currentThread() + " waiting for IDLE state, current="
+                                + synchronizerState);
+                    idleCondition.await();
+                }
+            }
+
+            String error = validateStateChange(synchronizerState, newState);
+            if (error != null)
+                throw new IllegalStateException(error);
+
+            if (DebugPolicy.DEBUG_STATE)
+                System.out.println(Thread.currentThread() + " activated state " + newState);
+            this.synchronizerState = newState;
+
+            if (newState == State.IDLE)
+                idleCondition.signalAll();
+        } finally {
+            stateLock.unlock();
+        }
+    }
+
+    void idle() throws IllegalStateException, InterruptedException {
+        activateState(State.IDLE, false);
+    }
+
+    static interface StateRunnable extends Runnable {
+        void execute() throws InvocationTargetException;
+
+        public abstract class Stub implements StateRunnable {
+            @Override
+            public void run() {
+            }
+
+            @Override
+            public final void execute() throws InvocationTargetException {
+                try {
+                    perform();
+                } catch (Exception e) {
+                    throw new InvocationTargetException(e);
+                } catch (LinkageError e) {
+                    throw new InvocationTargetException(e);
+                }
+            }
+
+            protected abstract void perform() throws Exception;
+        }
+    }
+
+    protected void runInState(State state, StateRunnable runnable) throws InvocationTargetException {
+        try {
+            activateState(state, true);
+            try {
+                runnable.execute();
+            } finally {
+                idle();
+            }
+        } catch (IllegalStateException e) {
+            throw new InvocationTargetException(e);
+        } catch (InterruptedException e) {
+            throw new InvocationTargetException(e);
+        }
+    }
+
+    protected void safeRunInState(State state, StateRunnable runnable) {
+        try {
+            runInState(state, runnable);
+        } catch (InvocationTargetException e) {
+            error("Failed to run runnable " + runnable + " in state " + state + ". See exception for details.", e
+                    .getCause());
+        }
+    }
+
+    // ------------------------------------------------------------------------
+    // Internal state machine handling END
+    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+
+    /**
+     * @param processor
+     * @param canvas
+     * @param elementClassProvider
+     * @throws DatabaseException
+     */
+    public GraphToDiagramSynchronizer(RequestProcessor processor, ICanvasContext canvas, IElementClassProvider elementClassProvider) throws DatabaseException {
+        if (processor == null)
+            throw new IllegalArgumentException("null processor");
+        if (canvas == null)
+            throw new IllegalArgumentException("null canvas");
+        if (elementClassProvider == null)
+            throw new IllegalArgumentException("null element class provider");
+
+        this.session = processor.getSession();
+        this.canvas = canvas;
+        this.modificationQueue = new ModificationQueue(session, errorHandler);
+
+        processor.syncRequest(new ReadRequest() {
+            @Override
+            public void run(ReadGraph graph) throws DatabaseException {
+                initializeResources(graph);
+            }
+        });
+
+        this.elementClassProvider = elementClassProvider;
+        synchronizationContext.set(SynchronizationHints.ELEMENT_CLASS_PROVIDER, elementClassProvider);
+
+        attachSessionListener(processor.getSession());
+    }
+
+    /**
+     * @return
+     */
+    public IElementClassProvider getElementClassProvider() {
+        return elementClassProvider;
+    }
+
+    public Session getSession() {
+        return session;
+    }
+
+    public ICanvasContext getCanvasContext() {
+        return canvas;
+    }
+
+    public IDiagram getDiagram() {
+        return diagram;
+    }
+
+    void setCanvasDirty() {
+        ICanvasContext c = canvas;
+        if (synchronizerState != State.LOADING && c != null && !c.isDisposed()) {
+            // TODO: Consider adding an invocation limiter here, to prevent
+            // calling setDirty too often if enough time hasn't passed yet since
+            // the last invocation.
+            c.getContentContext().setDirty();
+        }
+    }
+
+    /**
+     * @param elementType
+     * @return
+     * @throws DatabaseException if ElementClass cannot be retrieved
+     */
+    public ElementClass getNodeClass(Resource elementType) throws DatabaseException {
+        return getNodeClass(session, elementType);
+    }
+
+    public ElementClass getNodeClass(RequestProcessor processor, Resource elementType) throws DatabaseException {
+        ElementClass ec = processor.syncRequest(new NodeClassRequest(canvas, diagram, elementType, true));
+        return ec;
+    }
+
+    @Override
+    protected void doDispose() {
+        try {
+            try {
+                stateLock.lock();
+                boolean isInitial = getState() == State.INITIAL;
+                activateState(State.DISPOSED, !isInitial);
+            } finally {
+                stateLock.unlock();
+            }
+        } catch (InterruptedException e) {
+            // Shouldn't happen.
+            LOGGER.error("Dispose interrupted!", e);
+        } finally {
+            detachSessionListener();
+
+            if (profileObserver != null) {
+                profileObserver.dispose();
+                profileObserver = null;
+            }
+
+            if (diagram != null) {
+                diagram.removeCompositionListener(diagramListener);
+                diagram.removeCompositionVetoListener(diagramListener);
+            }
+
+            // TODO: we should probably leave the dataElement map as is since DataElementMap needs it even after the synchronizer has been disposed.
+            // Currently the diagram's DataElementMap will be broken after disposal.
+//            dataElement.clear();
+//            dataConnection.clear();
+
+            if (layerManager != null) {
+                layerManager.dispose();
+            }
+
+            // Let GC work.
+            modificationQueue.dispose();
+        }
+    }
+
+    void initializeResources(ReadGraph graph) {
+        this.br = new BasicResources(graph);
+
+        // Initialize synchronization context
+        synchronizationContext = new GraphSynchronizationContext(graph, modificationQueue);
+        synchronizationContext.set(SynchronizationHints.ERROR_HANDLER, errorHandler);
+    }
+
+    // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+    // LAYERS BEGIN
+    // ------------------------------------------------------------------------
+
+    GraphLayerManager layerManager;
+
+    /**
+     * A common handler for all elements that is used to listen to changes in
+     * element visibility and focusability on diagram layers.
+     */
+    class ElementLayerListenerImpl implements ElementLayerListener {
+        private static final long serialVersionUID = -3410052116598828129L;
+
+        @Override
+        public void visibilityChanged(IElement e, ILayer layer, boolean visible) {
+            if (!isAlive())
+                return;
+            if (DebugPolicy.DEBUG_LAYERS)
+                System.out.println("visibility changed: " + e + ", " + layer + ", " + visible);
+            GraphLayer gl = layerManager.getGraphLayer(layer.getName());
+            if (gl != null) {
+                changeTag(e, gl.getVisible(), visible);
+            }
+        }
+
+        @Override
+        public void focusabilityChanged(IElement e, ILayer layer, boolean focusable) {
+            if (!isAlive())
+                return;
+            if (DebugPolicy.DEBUG_LAYERS)
+                System.out.println("focusability changed: " + e + ", " + layer + ", " + focusable);
+            GraphLayer gl = layerManager.getGraphLayer(layer.getName());
+            if (gl != null) {
+                changeTag(e, gl.getFocusable(), focusable);
+            }
+        }
+
+        void changeTag(IElement e, Resource tag, boolean set) {
+            Object object = e.getHint(ElementHints.KEY_OBJECT);
+            Resource tagged = null;
+            if (object instanceof Resource) {
+                tagged = (Resource) object;
+            } else if (object instanceof EdgeResource) {
+                ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);
+                if (ce instanceof ConnectionEntityImpl) {
+                    tagged = ((ConnectionEntityImpl) ce).connection;
+                }
+            }
+            if (tagged == null)
+                return;
+
+            modificationQueue.async(new TagChange(tagged, tag, set), null);
+        }
+    };
+
+    ElementLayerListenerImpl elementLayerListener = new ElementLayerListenerImpl();
+
+    // ------------------------------------------------------------------------
+    // LAYERS END
+    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+
+    @Override
+    public IDiagram loadDiagram(IProgressMonitor progressMonitor, ReadGraph g, final String modelURI, final Resource diagram, final Resource runtime, final ResourceArray structuralPath,
+            IHintObservable initialHints) throws DatabaseException {
+        if (DebugPolicy.DEBUG_LOAD)
+            System.out.println(Thread.currentThread() + " loadDiagram: " + NameUtils.getSafeName(g, diagram));
+
+        SubMonitor monitor = SubMonitor.convert(progressMonitor, "Load Diagram", 100);
+
+        Object loadTask = Timing.BEGIN("GDS.loadDiagram");
+        try {
+            try {
+                activateState(State.LOADING, false);
+            } catch (IllegalStateException e) {
+                // Disposed already before loading even began.
+                this.diagram = Diagram.spawnNew(DiagramClass.DEFAULT);
+                return this.diagram;
+            }
+            try {
+                // Query for diagram class
+                Resource diagramClassResource = g.getPossibleType(diagram, br.DIA.Composite);
+                if (diagramClassResource != null) {
+                    // Spawn new diagram
+                    Object task = Timing.BEGIN("GDS.DiagramClassRequest");
+                    final DiagramClass diagramClass = g.syncRequest(new DiagramClassRequest(diagram));
+                    Timing.END(task);
+                    final IDiagram d = Diagram.spawnNew(diagramClass);
+                    {
+                        d.setHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE, diagram);
+                        if (runtime != null)
+                            d.setHint(DiagramModelHints.KEY_DIAGRAM_RUNTIME_RESOURCE, runtime);
+                        if (modelURI != null)
+                            d.setHint(DiagramModelHints.KEY_DIAGRAM_MODEL_URI, modelURI);
+                        d.setHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE_ARRAY, structuralPath);
+
+                        // Set dumb default routing when DiagramClass does not
+                        // predefine the default connection routing for the diagram.
+                        if (!d.containsHint(DiagramHints.ROUTE_ALGORITHM))
+                            d.setHint(DiagramHints.ROUTE_ALGORITHM, RouterFactory.create(true, false));
+
+                        d.setHint(SynchronizationHints.CONTEXT, this);
+
+                        // Initialize hints with hints from initialHints if given
+                        if (initialHints != null) {
+                            d.setHints(initialHints.getHints());
+                        }
+                    }
+
+                    // ITask task2 = ThreadLogger.getInstance().begin("loadLayers");
+                    monitor.subTask("Layers");
+                    {
+                        this.layerManager = new GraphLayerManager(g, modificationQueue, diagram);
+                        synchronizationContext.set(GraphSynchronizationHints.GRAPH_LAYER_MANAGER, this.layerManager);
+                        ILayersEditor layers = layerManager.loadLayers(d, g, diagram);
+                        // task2.finish();
+
+                        d.setHint(DiagramHints.KEY_LAYERS, layers);
+                        d.setHint(DiagramHints.KEY_LAYERS_EDITOR, layers);
+
+                        d.addCompositionVetoListener(diagramListener);
+                        d.addCompositionListener(diagramListener);
+
+                        this.diagram = d;
+
+                        d.setHint(DiagramHints.KEY_MUTATOR, new DefaultDiagramMutator(d, diagram, synchronizationContext));
+
+                        // Add default layer if no layers exist.
+                        // NOTE: this must be done after this.diagram has been set
+                        // as it will trigger a graph modification which needs the
+                        // diagram resource.
+                        // ITask task3 = ThreadLogger.getInstance().begin("addDefaultLayer");
+//                        if (layers.getLayers().isEmpty()) {
+//                            if (DebugPolicy.DEBUG_LAYERS)
+//                                System.out.println("No layers, creating default layer '"
+//                                        + DiagramConstants.DEFAULT_LAYER_NAME + "'");
+//                            SimpleLayer defaultLayer = new SimpleLayer(DiagramConstants.DEFAULT_LAYER_NAME);
+//                            layers.addLayer(defaultLayer);
+//                            layers.activate(defaultLayer);
+//                        }
+//                        // task3.finish();
+                    }
+                    monitor.worked(10);
+
+                    monitor.subTask("Contents");
+                    // Discover the plain resources that form the content of the
+                    // diagram through a separate query. This allows us to
+                    // separately
+                    // track changes to the diagram structure itself, not the
+                    // substructures contained by the structure elements.
+                    ITask task4 = ThreadLogger.getInstance().begin("DiagramContentRequest1");
+                    DiagramContentRequest query = new DiagramContentRequest(canvas, diagram, errorHandler);
+                    g.syncRequest(query, new DiagramContentListener(diagram));
+                    task4.finish();
+                    // ITask task5 = ThreadLogger.getInstance().begin("DiagramContentRequest2");
+                    ITask task42 = ThreadLogger.getInstance().begin("DiagramContentRequest2");
+                    DiagramContents contents = g.syncRequest(query, TransientCacheAsyncListener.instance());
+                    //System.err.println("contents: " + contents);
+                    task42.finish();
+                    // task5.finish();
+                    monitor.worked(10);
+
+                    monitor.subTask("Graphical elements");
+                    {
+                        Object applyDiagramContents = Timing.BEGIN("GDS.applyDiagramContents");
+                        ITask task6 = ThreadLogger.getInstance().begin("applyDiagramContents");
+                        processGraphUpdates(g, Collections.singleton(diagramGraphUpdater(contents)));
+                        task6.finish();
+                        Timing.END(applyDiagramContents);
+                    }
+                    monitor.worked(80);
+
+                    DataNodeMap dn = new DataNodeMap() {
+                        @Override
+                        public INode getNode(Object data) {
+                            if (DataNodeConstants.CANVAS_ROOT == data)
+                                return canvas.getCanvasNode();
+                            if (DataNodeConstants.DIAGRAM_ELEMENT_PARENT == data) {
+                                ElementPainter ep = canvas.getAtMostOneItemOfClass(ElementPainter.class);
+                                return ep != null ? ep.getDiagramElementParentNode() : null;
+                            }
+
+                            DataElementMap emap = GraphToDiagramSynchronizer.this.diagram.getDiagramClass().getSingleItem(DataElementMap.class);
+                            IElement element = emap.getElement(GraphToDiagramSynchronizer.this.diagram, data);
+                            if(element == null) return null;
+                            return element.getHint(ElementHints.KEY_SG_NODE);
+                        }
+                    };
+
+                    profileObserver = new ProfileObserver(g.getSession(), runtime,
+                            canvas.getThreadAccess(), canvas, canvas.getSceneGraph(), diagram, 
+                            ArrayMap.keys(ProfileKeys.DIAGRAM, ProfileKeys.CANVAS, ProfileKeys.NODE_MAP).values(GraphToDiagramSynchronizer.this.diagram, canvas, dn),
+                            new CanvasNotification(canvas));
+
+                    g.getSession().asyncRequest(new AsyncReadRequest() {
+                        @Override
+                        public void run(AsyncReadGraph graph) {
+                          ProfileObserver po = profileObserver;
+                            if (po != null)
+                                po.listen(graph, GraphToDiagramSynchronizer.this);
+                            else
+                                LOGGER.info("profileObserver has been disposed already!");
+                        }
+                    });
+
+                    return d;
+
+                }
+
+                this.diagram = Diagram.spawnNew(DiagramClass.DEFAULT);
+                return this.diagram;
+
+            } finally {
+                idle();
+            }
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        } catch (IllegalStateException e) {
+            // If the synchronizer was disposed ahead of time, it was done
+            // for a reason, such as the user having closed the owner editor.
+            if (!isAlive())
+                throw new CancelTransactionException(e);
+            throw new RuntimeException(e);
+        } finally {
+            Timing.END(loadTask);
+        }
+    }
+
+    static class CanvasNotification implements Runnable {
+
+        final private ICanvasContext canvas;
+
+        public CanvasNotification(ICanvasContext canvas) {
+            this.canvas = canvas;
+        }
+
+        public void run() {
+            canvas.getContentContext().setDirty();
+        }
+
+    }
+
+    ArrayList<IModification>        pendingModifications = new ArrayList<IModification>();
+    MapSet<IElement, IModification> modificationIndex    = new MapSet.Hash<IElement, IModification>();
+
+    void addModification(IElement element, IModification modification) {
+        pendingModifications.add(modification);
+        if (element != null)
+            modificationIndex.add(element, modification);
+
+    }
+    class DefaultDiagramMutator implements DiagramMutator {
+
+        Map<IElement, Resource> creation = new HashMap<IElement, Resource>();
+
+        IDiagram d;
+        Resource diagram;
+
+        IModifiableSynchronizationContext synchronizationContext;
+
+        public DefaultDiagramMutator(IDiagram d, Resource diagram, IModifiableSynchronizationContext synchronizationContext) {
+            this.d = d;
+            this.diagram = diagram;
+            this.synchronizationContext = synchronizationContext;
+
+            if (synchronizationContext.get(SynchronizationHints.ELEMENT_CLASS_PROVIDER) == null)
+                throw new IllegalArgumentException("SynchronizationHints.ELEMENT_CLASS_PROVIDER not available");
+        }
+
+        void assertNotDisposed() {
+            if (!isAlive())
+                throw new IllegalStateException(getClass().getSimpleName() + " is disposed");
+        }
+
+        @Override
+        public IElement newElement(ElementClass clazz) {
+            assertNotDisposed();
+            ElementFactory ef = d.getDiagramClass().getAtMostOneItemOfClass(ElementFactory.class);
+            IElement element = null;
+            if (ef != null)
+                element = ef.spawnNew(clazz);
+            else
+                element = Element.spawnNew(clazz);
+
+            element.setHint(ElementHints.KEY_OBJECT, new TransientElementObject());
+
+            addModification(element, new AddElement(synchronizationContext, d, element));
+
+            return element;
+        }
+
+        @Override
+        public void commit() {
+            assertNotDisposed();
+            if (DebugPolicy.DEBUG_MUTATOR_COMMIT) {
+                System.out.println("DiagramMutator is about to commit changes:");
+                for (IModification mod : pendingModifications)
+                    System.out.println("\t- " + mod);
+            }
+
+            Collections.sort(pendingModifications);
+
+            if (DebugPolicy.DEBUG_MUTATOR_COMMIT) {
+                if (pendingModifications.size() > 1) {
+                    System.out.println("* changes were re-ordered to:");
+                    for (IModification mod : pendingModifications)
+                        System.out.println("\t" + mod);
+                }
+            }
+
+            Timing.safeTimed(errorHandler, "QUEUE AND WAIT FOR MODIFICATIONS TO FINISH", new GTask() {
+                @Override
+                public void run() throws DatabaseException {
+                    // Performs a separate write request and query result update
+                    // for each modification
+//                    for (IModification mod : pendingModifications) {
+//                        try {
+//                            modificationQueue.sync(mod);
+//                        } catch (InterruptedException e) {
+//                            error("Pending diagram modification " + mod
+//                                    + " was interrupted. See exception for details.", e);
+//                        }
+//                    }
+
+                    // NOTE: this is still under testing, the author is not
+                    // truly certain that it should work in all cases ATM.
+
+                    // Performs all modifications with in a single write request
+                    for (IModification mod : pendingModifications) {
+                        modificationQueue.offer(mod, null);
+                    }
+                    try {
+                        // Perform the modifications in a single request.
+                        modificationQueue.finish();
+                    } catch (InterruptedException e) {
+                        errorHandler.error("Diagram modification finishing was interrupted. See exception for details.", e);
+                    }
+                }
+            });
+            pendingModifications.clear();
+            modificationIndex.clear();
+            creation.clear();
+            if (DebugPolicy.DEBUG_MUTATOR_COMMIT)
+                System.out.println("DiagramMutator has committed");
+        }
+
+        @Override
+        public void clear() {
+            assertNotDisposed();
+            pendingModifications.clear();
+            modificationIndex.clear();
+            creation.clear();
+            if (DebugPolicy.DEBUG_MUTATOR)
+                System.out.println("DiagramMutator has been cleared");
+        }
+
+        @Override
+        public void modifyTransform(IElement element) {
+            assertNotDisposed();
+            Resource resource = backendObject(element);
+            AffineTransform tr = element.getHint(ElementHints.KEY_TRANSFORM);
+            if (resource != null && tr != null) {
+                addModification(element, new TransformElement(resource, tr));
+            }
+        }
+
+        @Override
+        public void synchronizeHintsToBackend(IElement element) {
+            assertNotDisposed();
+            IHintSynchronizer synchronizer = element.getHint(SynchronizationHints.HINT_SYNCHRONIZER);
+            if (synchronizer != null) {
+                CollectingModificationQueue queue = new CollectingModificationQueue();
+                synchronizer.synchronize(synchronizationContext, element);
+                addModification(element, new CompositeModification(ModificationAdapter.LOW_PRIORITY, queue.getQueue()));
+            }
+        }
+
+        @Override
+        public void synchronizeElementOrder() {
+            assertNotDisposed();
+            List<IElement> snapshot = d.getSnapshot();
+            addModification(null, new ElementReorder(d, snapshot));
+        }
+
+        @Override
+        public void register(IElement element, Object object) {
+            creation.put(element, (Resource) object);
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public <T> T backendObject(IElement element) {
+            Object object = ElementUtils.getObject(element);
+            if (object instanceof Resource)
+                return (T) object;
+            else
+                return (T) creation.get(element);
+        }
+
+    }
+
+    // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+    // GRAPH TO DIAGRAM SYCHRONIZATION LOGIC BEGIN
+    // ------------------------------------------------------------------------
+
+    static class ConnectionData {
+        ConnectionEntityImpl impl;
+        List<Resource>       branchPoints = new ArrayList<Resource>();
+        List<EdgeResource>   segments     = new ArrayList<EdgeResource>();
+
+        ConnectionData(ConnectionEntityImpl ce) {
+            this.impl = ce;
+        }
+
+        void addBranchPoint(Resource bp) {
+            branchPoints.add(bp);
+        }
+
+        void addSegment(EdgeResource seg) {
+            segments.add(seg);
+        }
+    }
+
+    class GraphToDiagramUpdater {
+        DiagramContents                                 lastContent;
+        DiagramContents                                 content;
+        DiagramContentChanges                           changes;
+
+        final List<IElement>                            addedElements;
+        final List<IElement>                            removedElements;
+
+        final List<IElement>                            addedConnectionSegments;
+        final List<IElement>                            removedConnectionSegments;
+
+        final List<IElement>                            addedBranchPoints;
+        final List<IElement>                            removedBranchPoints;
+
+        final Map<Object, IElement>                     addedElementMap;
+        final Map<Resource, IElement>                   addedConnectionMap;
+        final Map<Resource, ConnectionEntityImpl>       addedConnectionEntities;
+        final List<Resource>                            removedConnectionEntities;
+        final Map<ConnectionEntityImpl, ConnectionData> changedConnectionEntities;
+
+        final Map<Resource, IElement>                   addedRouteGraphConnectionMap;
+        final List<IElement>                            removedRouteGraphConnections;
+
+
+        GraphToDiagramUpdater(DiagramContents lastContent, DiagramContents content, DiagramContentChanges changes) {
+            this.lastContent = lastContent;
+            this.content = content;
+            this.changes = changes;
+
+            this.addedElements = new ArrayList<IElement>(changes.elements.size() + changes.branchPoints.size());
+            this.removedElements = new ArrayList<IElement>(changes.elements.size() + changes.branchPoints.size());
+            this.addedConnectionSegments = new ArrayList<IElement>(content.connectionSegments.size());
+            this.removedConnectionSegments = new ArrayList<IElement>(content.connectionSegments.size());
+            this.addedBranchPoints = new ArrayList<IElement>(content.branchPoints.size());
+            this.removedBranchPoints = new ArrayList<IElement>(content.branchPoints.size());
+            this.addedElementMap = new HashMap<Object, IElement>();
+            this.addedConnectionMap = new HashMap<Resource, IElement>();
+            this.addedConnectionEntities = new HashMap<Resource, ConnectionEntityImpl>();
+            this.removedConnectionEntities = new ArrayList<Resource>(changes.connections.size());
+            this.changedConnectionEntities = new HashMap<ConnectionEntityImpl, ConnectionData>();
+            this.addedRouteGraphConnectionMap = new HashMap<Resource, IElement>();
+            this.removedRouteGraphConnections = new ArrayList<IElement>(changes.routeGraphConnections.size());
+        }
+
+        public void clear() {
+            // Prevent DiagramContents leakage through DisposableListeners.
+            lastContent = null;
+            content = null;
+            changes = null;
+
+            this.addedElements.clear();
+            this.removedElements.clear();
+            this.addedConnectionSegments.clear();
+            this.removedConnectionSegments.clear();
+            this.addedBranchPoints.clear();
+            this.removedBranchPoints.clear();
+            this.addedElementMap.clear();
+            this.addedConnectionMap.clear();
+            this.addedConnectionEntities.clear();
+            this.removedConnectionEntities.clear();
+            this.changedConnectionEntities.clear();
+            this.addedRouteGraphConnectionMap.clear();
+            this.removedRouteGraphConnections.clear();
+        }
+
+        void processNodes(ReadGraph graph) throws DatabaseException {
+
+            for (Map.Entry<Resource, Change> entry : changes.elements.entrySet()) {
+
+                final Resource element = entry.getKey();
+                Change change = entry.getValue();
+
+                switch (change) {
+                    case ADDED: {
+                        IElement mappedElement = getMappedElement(element);
+                        if (mappedElement == null) {
+                            if (DebugPolicy.DEBUG_NODE_LOAD)
+                                graph.syncRequest(new ReadRequest() {
+                                    @Override
+                                    public void run(ReadGraph graph) throws DatabaseException {
+                                        System.out.println("    EXTERNALLY ADDED ELEMENT: "
+                                                + NameUtils.getSafeName(graph, element) + " ("
+                                                + element.getResourceId() + ")");
+                                    }
+                                });
+
+                            if (content.connectionSet.contains(element)) {
+
+                                // TODO: Connection loading has no listening, changes :Connection will not be noticed by this code!
+                                Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {
+
+                                    boolean firstTime = true;
+
+                                    @Override
+                                    public String toString() {
+                                        return "Connection load listener for " + element;
+                                    }
+                                    @Override
+                                    public void execute(IElement loaded) {
+                                        // Invoked when the element has been loaded.
+                                        if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
+                                            System.out.println("CONNECTION LoadListener for " + loaded);
+
+                                        if (loaded == null) {
+                                            disposeListener();
+                                            return;
+                                        }
+
+                                        if (firstTime) {
+
+                                            mapElement(element, loaded);
+                                            synchronized (GraphToDiagramUpdater.this) {
+                                                addedElements.add(loaded);
+                                                addedElementMap.put(element, loaded);
+                                                addedConnectionMap.put(element, loaded);
+                                            }
+
+                                            firstTime = false;
+
+                                        }
+
+                                        Object data = loaded.getHint(ElementHints.KEY_OBJECT);
+
+                                        // Logic for disposing listener
+                                        if (!previousContent.connectionSet.contains(data)) {
+                                            if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
+                                                System.out.println("CONNECTION LoadListener, connection not in current content: " + data + ". Disposing.");
+                                            disposeListener();
+                                            return;
+                                        }
+
+                                        if (addedElementMap.containsKey(data)) {
+                                            // This element was just loaded, in
+                                            // which case its hints need to
+                                            // uploaded to the real mapped
+                                            // element immediately.
+                                            IElement mappedElement = getMappedElement(data);
+                                            if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
+                                                System.out.println("LOADED ADDED CONNECTION, currently mapped connection: " + mappedElement);
+                                            if (mappedElement != null && (mappedElement instanceof Element)) {
+                                                if (DebugPolicy.DEBUG_CONNECTION_LISTENER) {
+                                                    System.out.println("  mapped hints: " + mappedElement.getHints());
+                                                    System.out.println("  loaded hints: " + loaded.getHints());
+                                                }
+                                                updateMappedElement((Element) mappedElement, loaded);
+                                            }
+                                        } else {
+                                            // This element was already loaded.
+                                            // Just schedule an update some time
+                                            // in the future.
+                                            if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
+                                                System.out.println("PREVIOUSLY LOADED CONNECTION UPDATED, scheduling update into the future");
+                                            offerGraphUpdate( connectionUpdater(element, loaded) );
+                                        }
+                                    }
+                                };
+
+                                graph.syncRequest(new ConnectionRequest(canvas, diagram, element, errorHandler, loadListener), new AsyncProcedure<IElement>() {
+                                    @Override
+                                    public void execute(AsyncReadGraph graph, final IElement e) {
+
+                                        // Read connection type
+                                        graph.forSingleType(element, br.DIA.Connection, new Procedure<Resource>() {
+                                            @Override
+                                            public void exception(Throwable t) {
+                                                error(t);
+                                            }
+
+                                            @Override
+                                            public void execute(Resource connectionType) {
+                                                synchronized (GraphToDiagramUpdater.this) {
+                                                    //System.out.println("new connection entity " + e);
+                                                    ConnectionEntityImpl entity = new ConnectionEntityImpl(element, connectionType, e);
+                                                    e.setHint(ElementHints.KEY_CONNECTION_ENTITY, entity);
+                                                    addedConnectionEntities.put(element, entity);
+                                                }
+                                            }
+                                        });
+
+                                    }
+
+                                    @Override
+                                    public void exception(AsyncReadGraph graph, Throwable throwable) {
+                                        error(throwable);
+                                    }
+                                });
+                            } else if (content.nodeSet.contains(element)) {
+
+                                Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {
+
+                                    boolean firstTime = true;
+
+                                    @Override
+                                    public String toString() {
+                                        return "Node load listener for " + element;
+                                    }
+                                    @Override
+                                    public void execute(IElement loaded) {
+                                        // Invoked when the element has been loaded.
+                                        if (DebugPolicy.DEBUG_NODE_LISTENER)
+                                            System.out.println("NODE LoadListener for " + loaded);
+
+                                        if (loaded == null) {
+                                            disposeListener();
+                                            return;
+                                        }
+
+                                        if (firstTime) {
+
+                                            // This is invoked before the element is actually loaded.
+                                            //System.out.println("NodeRequestProcedure " + e);
+                                            if (DebugPolicy.DEBUG_NODE_LOAD)
+                                                System.out.println("MAPPING ADDED NODE: " + element + " -> " + loaded);
+                                            mapElement(element, loaded);
+                                            synchronized (GraphToDiagramUpdater.this) {
+                                                addedElements.add(loaded);
+                                                addedElementMap.put(element, loaded);
+                                            }
+
+                                            firstTime = false;
+
+                                        }
+
+                                        Object data = loaded.getHint(ElementHints.KEY_OBJECT);
+
+                                        // Logic for disposing listener
+                                        if (!previousContent.nodeSet.contains(data)) {
+                                            if (DebugPolicy.DEBUG_NODE_LISTENER)
+                                                System.out.println("NODE LoadListener, node not in current content: " + data + ". Disposing.");
+                                            disposeListener();
+                                            return;
+                                        }
+
+                                        if (addedElementMap.containsKey(data)) {
+                                            // This element was just loaded, in
+                                            // which case its hints need to
+                                            // uploaded to the real mapped
+                                            // element immediately.
+                                            IElement mappedElement = getMappedElement(data);
+                                            if (DebugPolicy.DEBUG_NODE_LISTENER)
+                                                System.out.println("LOADED ADDED ELEMENT, currently mapped element: " + mappedElement);
+                                            if (mappedElement != null && (mappedElement instanceof Element)) {
+                                                if (DebugPolicy.DEBUG_NODE_LISTENER) {
+                                                    System.out.println("  mapped hints: " + mappedElement.getHints());
+                                                    System.out.println("  loaded hints: " + loaded.getHints());
+                                                }
+                                                updateMappedElement((Element) mappedElement, loaded);
+                                            }
+                                        } else {
+                                            // This element was already loaded.
+                                            // Just schedule an update some time
+                                            // in the future.
+                                            if (DebugPolicy.DEBUG_NODE_LISTENER)
+                                                System.out.println("PREVIOUSLY LOADED NODE UPDATED, scheduling update into the future");
+                                            offerGraphUpdate( nodeUpdater(element, loaded) );
+                                        }
+                                    }
+                                };
+
+                                //System.out.println("NODE REQUEST: " + element);
+                                graph.syncRequest(new NodeRequest(canvas, diagram, element, loadListener), new AsyncProcedure<IElement>() {
+                                    @Override
+                                    public void execute(AsyncReadGraph graph, IElement e) {
+                                    }
+
+                                    @Override
+                                    public void exception(AsyncReadGraph graph, Throwable throwable) {
+                                        error(throwable);
+                                    }
+                                });
+
+                            } else {
+//                                warning("Diagram elements must be either elements or connections, "
+//                                        + NameUtils.getSafeName(g, element) + " is neither",
+//                                        new AssumptionException(""));
+                            }
+                        }
+                        break;
+                    }
+                    case REMOVED: {
+                        IElement e = getMappedElement(element);
+                        if (DebugPolicy.DEBUG_NODE_LOAD)
+                            graph.syncRequest(new ReadRequest() {
+                                @Override
+                                public void run(ReadGraph graph) throws DatabaseException {
+                                    System.out.println("    EXTERNALLY REMOVED ELEMENT: "
+                                            + NameUtils.getSafeName(graph, element) + " ("
+                                            + element.getResourceId() + ")");
+                                }
+                            });
+                        if (e != null) {
+                            removedElements.add(e);
+                        }
+                        break;
+                    }
+                    default:
+                }
+            }
+        }
+
+        void gatherChangedConnectionParts(Map<?, Change> changes) {
+            for (Map.Entry<?, Change> entry : changes.entrySet()) {
+                Object part = entry.getKey();
+                Change change = entry.getValue();
+
+                switch (change) {
+                    case ADDED: {
+                        synchronized (GraphToDiagramUpdater.this) {
+                            Resource connection = content.partToConnection.get(part);
+                            assert connection != null;
+
+                            IElement ce = getMappedElement(connection);
+                            if (ce == null)
+                                ce = addedElementMap.get(connection);
+
+                            if (ce != null)
+                                markConnectionChanged(ce);
+                            break;
+                        }
+                    }
+                    case REMOVED: {
+                        if (lastContent == null)
+                            break;
+                        Resource connection = lastContent.partToConnection.get(part);
+                        if (connection != null && content.connectionSet.contains(connection)) {
+                            markConnectionChanged(connection);
+                        }
+                        break;
+                    }
+                    default:
+                }
+            }
+        }
+
+        void markConnectionChanged(Resource connection) {
+//            System.out.println("markConnectionChanged");
+            ConnectionEntityImpl ce = getMappedConnection(connection);
+            if (ce != null) {
+                markConnectionChanged(ce);
+                return;
+            }
+            error("WARNING: marking connection entity " + connection
+                    + " changed, but the connection was not previously mapped",
+                    new Exception("created exception to get a stack trace"));
+        }
+
+        void markConnectionChanged(IElement connection) {
+            ConnectionEntityImpl entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
+            if (entity != null)
+                markConnectionChanged(entity);
+        }
+
+        void markConnectionChanged(ConnectionEntityImpl ce) {
+            if (!changedConnectionEntities.containsKey(ce)) {
+                changedConnectionEntities.put(ce, new ConnectionData(ce));
+            }
+        }
+
+        void processConnections() {
+            // Find added/removed connection segments/branch points
+            // in order to find all changed connection entities.
+            gatherChangedConnectionParts(changes.connectionSegments);
+            gatherChangedConnectionParts(changes.branchPoints);
+
+            // Find removed connection entities
+            for (Map.Entry<Resource, Change> entry : changes.connections.entrySet()) {
+                Resource ce = entry.getKey();
+                Change change = entry.getValue();
+
+                switch (change) {
+                    case REMOVED: {
+                        removedConnectionEntities.add(ce);
+                    }
+                    default:
+                }
+            }
+
+            // Generate update data of changed connection entities.
+            // This ConnectionData will be applied in the canvas thread
+            // diagram updater.
+            for (ConnectionData cd : changedConnectionEntities.values()) {
+                for (Object part : content.connectionToParts.getValuesUnsafe(cd.impl.connection)) {
+                    if (part instanceof Resource) {
+                        cd.branchPoints.add((Resource) part);
+                    } else if (part instanceof EdgeResource) {
+                        cd.segments.add((EdgeResource) part);
+                    }
+                }
+            }
+        }
+
+        void processRouteGraphConnections(ReadGraph graph) throws DatabaseException {
+            for (Map.Entry<Resource, Change> entry : changes.routeGraphConnections.entrySet()) {
+                final Resource connection = entry.getKey();
+
+                Change change = entry.getValue();
+                switch (change) {
+                    case ADDED: {
+                        IElement mappedElement = getMappedElement(connection);
+                        if (mappedElement != null)
+                            continue;
+
+                        Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {
+
+                            boolean firstTime = true;
+
+                            @Override
+                            public String toString() {
+                                return "processRouteGraphConnections " + connection;
+                            }
+                            @Override
+                            public void execute(IElement loaded) {
+                                // Invoked when the element has been loaded.
+                                if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
+                                    System.out.println("ROUTE GRAPH CONNECTION LoadListener for " + loaded);
+
+                                if (loaded == null) {
+                                    disposeListener();
+                                    return;
+                                }
+
+                                if(firstTime) {
+                                    if (DebugPolicy.DEBUG_NODE_LOAD)
+                                        System.out.println("MAPPING ADDED ROUTE GRAPH CONNECTION: " + connection + " -> " + loaded);
+                                    mapElement(connection, loaded);
+                                    synchronized (GraphToDiagramUpdater.this) {
+                                        addedElements.add(loaded);
+                                        addedElementMap.put(connection, loaded);
+                                        addedRouteGraphConnectionMap.put(connection, loaded);
+                                    }
+                                    firstTime = false;
+                                }
+
+                                Object data = loaded.getHint(ElementHints.KEY_OBJECT);
+
+                                // Logic for disposing listener
+                                if (!previousContent.routeGraphConnectionSet.contains(data)) {
+                                    if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
+                                        System.out.println("ROUTE GRAPH CONNECTION LoadListener, connection not in current content: " + data + ". Disposing.");
+                                    disposeListener();
+                                    return;
+                                }
+
+                                if (addedElementMap.containsKey(data)) {
+                                    // This element was just loaded, in
+                                    // which case its hints need to
+                                    // uploaded to the real mapped
+                                    // element immediately.
+                                    IElement mappedElement = getMappedElement(data);
+                                    if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
+                                        System.out.println("LOADED ADDED ROUTE GRAPH CONNECTION, currently mapped connection: " + mappedElement);
+                                    if (mappedElement instanceof Element) {
+                                        if (DebugPolicy.DEBUG_CONNECTION_LISTENER) {
+                                            System.out.println("  mapped hints: " + mappedElement.getHints());
+                                            System.out.println("  loaded hints: " + loaded.getHints());
+                                        }
+                                        updateMappedElement((Element) mappedElement, loaded);
+                                    }
+                                } else {
+                                    // This element was already loaded.
+                                    // Just schedule an update some time
+                                    // in the future.
+                                    if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
+                                        System.out.println("PREVIOUSLY LOADED ROUTE GRAPH CONNECTION UPDATED, scheduling update into the future: " + connection);
+
+                                    Set<Object> dirtyNodes = new THashSet<Object>(4);
+                                    IElement mappedElement = getMappedElement(connection);
+                                    ConnectionEntity ce = mappedElement.getHint(ElementHints.KEY_CONNECTION_ENTITY);
+                                    if (ce != null) {
+                                        for (Connection conn : ce.getTerminalConnections(null)) {
+                                            Object o = conn.node.getHint(ElementHints.KEY_OBJECT);
+                                            if (o != null) {
+                                                dirtyNodes.add(o);
+                                                if (DebugPolicy.DEBUG_CONNECTION_LISTENER)
+                                                    System.out.println("Marked connectivity dirty for node: " + conn.node);
+                                            }
+                                        }
+                                    }
+
+                                    offerGraphUpdate( routeGraphConnectionUpdater(connection, loaded, dirtyNodes) );
+                                }
+                            }
+                        };
+
+                        graph.syncRequest(new ConnectionRequest(canvas, diagram, connection, errorHandler, loadListener), new Procedure<IElement>() {
+                            @Override
+                            public void execute(final IElement e) {
+                            }
+                            @Override
+                            public void exception(Throwable throwable) {
+                                error(throwable);
+                            }
+                        });
+                        break;
+                    }
+                    case REMOVED: {
+                        IElement e = getMappedElement(connection);
+                        if (e != null)
+                            removedRouteGraphConnections.add(e);
+                        break;
+                    }
+                    default:
+                }
+            }
+        }
+
+        ConnectionEntityImpl getConnectionEntity(Object connectionPart) {
+            Resource connection = content.partToConnection.get(connectionPart);
+            assert connection != null;
+            ConnectionEntityImpl ce = addedConnectionEntities.get(connection);
+            if (ce != null)
+                return ce;
+            return assertMappedConnection(connection);
+        }
+
+        void processBranchPoints(ReadGraph graph) throws DatabaseException {
+            for (Map.Entry<Resource, Change> entry : changes.branchPoints.entrySet()) {
+
+                final Resource element = entry.getKey();
+                Change change = entry.getValue();
+
+                switch (change) {
+                    case ADDED: {
+                        IElement mappedElement = getMappedElement(element);
+                        if (mappedElement == null) {
+                            if (DebugPolicy.DEBUG_NODE_LOAD)
+                                graph.syncRequest(new ReadRequest() {
+                                    @Override
+                                    public void run(ReadGraph graph) throws DatabaseException {
+                                        System.out.println("    EXTERNALLY ADDED BRANCH POINT: "
+                                                + NameUtils.getSafeName(graph, element) + " ("
+                                                + element.getResourceId() + ")");
+                                    }
+                                });
+
+                            Listener<IElement> loadListener = new DisposableListener<IElement>(canvasListenerSupport) {
+
+                                boolean firstTime = true;
+
+                                @Override
+                                public String toString() {
+                                    return "processBranchPoints for " + element;
+                                }
+                                @Override
+                                public void execute(IElement loaded) {
+                                    // Invoked when the element has been loaded.
+                                    if (DebugPolicy.DEBUG_NODE_LISTENER)
+                                        System.out.println("BRANCH POINT LoadListener for " + loaded);
+
+                                    if (loaded == null) {
+                                        disposeListener();
+                                        return;
+                                    }
+
+                                    if (firstTime) {
+
+                                        mapElement(element, loaded);
+                                        synchronized (GraphToDiagramUpdater.this) {
+                                            addedBranchPoints.add(loaded);
+                                            addedElementMap.put(element, loaded);
+                                            ConnectionEntityImpl ce = getConnectionEntity(element);
+                                            loaded.setHint(ElementHints.KEY_CONNECTION_ENTITY, ce);
+                                            loaded.setHint(ElementHints.KEY_PARENT_ELEMENT, ce.getConnectionElement());
+                                        }
+
+                                        firstTime = false;
+
+                                    }
+
+                                    Object data = loaded.getHint(ElementHints.KEY_OBJECT);
+                                    if (addedElementMap.containsKey(data)) {
+                                        // This element was just loaded, in
+                                        // which case its hints need to
+                                        // uploaded to the real mapped
+                                        // element immediately.
+                                        IElement mappedElement = getMappedElement(data);
+                                        if (DebugPolicy.DEBUG_NODE_LISTENER)
+                                            System.out.println("LOADED ADDED BRANCH POINT, currently mapped element: " + mappedElement);
+                                        if (mappedElement != null && (mappedElement instanceof Element)) {
+                                            if (DebugPolicy.DEBUG_NODE_LISTENER) {
+                                                System.out.println("  mapped hints: " + mappedElement.getHints());
+                                                System.out.println("  loaded hints: " + loaded.getHints());
+                                            }
+                                            updateMappedElement((Element) mappedElement, loaded);
+                                        }
+                                    } else {
+                                        // This element was already loaded.
+                                        // Just schedule an update some time
+                                        // in the future.
+                                        if (DebugPolicy.DEBUG_NODE_LISTENER)
+                                            System.out.println("PREVIOUSLY LOADED BRANCH POINT UPDATED, scheduling update into the future");
+                                        offerGraphUpdate( nodeUpdater(element, loaded) );
+                                    }
+                                }
+                            };
+
+                            graph.syncRequest(new NodeRequest(canvas, diagram, element, loadListener), new AsyncProcedure<IElement>() {
+                                @Override
+                                public void execute(AsyncReadGraph graph, IElement e) {
+                                }
+
+                                @Override
+                                public void exception(AsyncReadGraph graph, Throwable throwable) {
+                                    error(throwable);
+                                }
+                            });
+                        }
+                        break;
+                    }
+                    case REMOVED: {
+                        IElement e = getMappedElement(element);
+                        if (DebugPolicy.DEBUG_NODE_LOAD)
+                            graph.syncRequest(new ReadRequest() {
+                                @Override
+                                public void run(ReadGraph graph) throws DatabaseException {
+                                    System.out.println("    EXTERNALLY REMOVED BRANCH POINT: "
+                                            + NameUtils.getSafeName(graph, element) + " ("
+                                            + element.getResourceId() + ")");
+                                }
+                            });
+                        if (e != null) {
+                            removedBranchPoints.add(e);
+                        }
+                        break;
+                    }
+                    default:
+                }
+            }
+        }
+
+        void processConnectionSegments(ReadGraph graph) throws DatabaseException {
+            ConnectionSegmentAdapter adapter = connectionSegmentAdapter;
+
+            for (Map.Entry<EdgeResource, Change> entry : changes.connectionSegments.entrySet()) {
+                final EdgeResource seg = entry.getKey();
+                Change change = entry.getValue();
+
+                switch (change) {
+                    case ADDED: {
+                        IElement mappedElement = getMappedElement(seg);
+                        if (mappedElement == null) {
+                            if (DebugPolicy.DEBUG_EDGE_LOAD)
+                                graph.syncRequest(new ReadRequest() {
+                                    @Override
+                                    public void run(ReadGraph graph) throws DatabaseException {
+                                        System.out.println("    EXTERNALLY ADDED CONNECTION SEGMENT: " + seg.toString()
+                                                + " - " + seg.toString(graph));
+                                    }
+                                });
+
+                            graph.syncRequest(new EdgeRequest(canvas, errorHandler, canvasListenerSupport, diagram, adapter, seg), new AsyncProcedure<IElement>() {
+                                @Override
+                                public void execute(AsyncReadGraph graph, IElement e) {
+                                    if (DebugPolicy.DEBUG_EDGE_LOAD)
+                                        System.out.println("ADDED EDGE LOADED: " + e);
+                                    if (e != null) {
+                                        mapElement(seg, e);
+                                        synchronized (GraphToDiagramUpdater.this) {
+                                            addedConnectionSegments.add(e);
+                                            addedElementMap.put(seg, e);
+                                            ConnectionEntityImpl ce = getConnectionEntity(seg);
+                                            e.setHint(ElementHints.KEY_CONNECTION_ENTITY, ce);
+                                            e.setHint(ElementHints.KEY_PARENT_ELEMENT, ce.getConnectionElement());
+                                        }
+                                    }
+                                }
+
+                                @Override
+                                public void exception(AsyncReadGraph graph, Throwable throwable) {
+                                    error(throwable);
+                                }
+                            });
+                        }
+                        break;
+                    }
+                    case REMOVED: {
+                        final IElement e = getMappedElement(seg);
+                        if (DebugPolicy.DEBUG_EDGE_LOAD)
+                            graph.syncRequest(new ReadRequest() {
+                                @Override
+                                public void run(ReadGraph graph) throws DatabaseException {
+                                    System.out.println("    EXTERNALLY REMOVED CONNECTION SEGMENT: " + seg.toString() + " - "
+                                            + seg.toString(graph) + " -> " + e);
+                                }
+                            });
+                        if (e != null) {
+                            removedConnectionSegments.add(e);
+                        }
+                        break;
+                    }
+                    default:
+                }
+            }
+        }
+
+        void executeDeferredLoaders(ReadGraph graph) throws DatabaseException {
+            // The rest of the diagram loading passes
+            Deque<IElement> q1 = new ArrayDeque<IElement>();
+            Deque<IElement> q2 = new ArrayDeque<IElement>();
+            collectElementLoaders(q1, addedElements);
+            while (!q1.isEmpty()) {
+                //System.out.println("DEFFERED LOADERS: " + q1);
+                for (IElement e : q1) {
+                    ElementLoader loader = e.removeHint(DiagramModelHints.KEY_ELEMENT_LOADER);
+                    //System.out.println("EXECUTING DEFFERED LOADER: " + loader);
+                    loader.load(graph, diagram, e);
+                }
+
+                collectElementLoaders(q2, q1);
+                Deque<IElement> qt = q1;
+                q1 = q2;
+                q2 = qt;
+                q2.clear();
+            }
+        }
+
+        private void collectElementLoaders(Queue<IElement> queue, Collection<IElement> cs) {
+            for (IElement e : cs) {
+                ElementLoader loader = e.getHint(DiagramModelHints.KEY_ELEMENT_LOADER);
+                if (loader != null)
+                    queue.add(e);
+            }
+        }
+
+        public void process(ReadGraph graph) throws DatabaseException {
+            // No changes? Do nothing.
+            if (changes.isEmpty())
+                return;
+
+            // NOTE: This order is important.
+            Object task = Timing.BEGIN("processNodesConnections");
+            //System.out.println("---- PROCESS NODES & CONNECTIONS BEGIN");
+            if (!changes.elements.isEmpty()) {
+                graph.syncRequest(new ReadRequest() {
+                    @Override
+                    public void run(ReadGraph graph) throws DatabaseException {
+                        processNodes(graph);
+                    }
+                    @Override
+                    public String toString() {
+                        return "processNodes";
+                    }
+                });
+            }
+            //System.out.println("---- PROCESS NODES & CONNECTIONS END");
+
+            processConnections();
+
+            //System.out.println("---- PROCESS BRANCH POINTS BEGIN");
+            if (!changes.branchPoints.isEmpty()) {
+                graph.syncRequest(new ReadRequest() {
+                    @Override
+                    public void run(ReadGraph graph) throws DatabaseException {
+                        processBranchPoints(graph);
+                    }
+                    @Override
+                    public String toString() {
+                        return "processBranchPoints";
+                    }
+                });
+            }
+            //System.out.println("---- PROCESS BRANCH POINTS END");
+
+            Timing.END(task);
+            task = Timing.BEGIN("processConnectionSegments");
+
+            //System.out.println("---- PROCESS CONNECTION SEGMENTS BEGIN");
+            if (!changes.connectionSegments.isEmpty()) {
+                graph.syncRequest(new ReadRequest() {
+                    @Override
+                    public void run(ReadGraph graph) throws DatabaseException {
+                        processConnectionSegments(graph);
+                    }
+                    @Override
+                    public String toString() {
+                        return "processConnectionSegments";
+                    }
+                });
+            }
+            //System.out.println("---- PROCESS CONNECTION SEGMENTS END");
+
+            Timing.END(task);
+
+            task = Timing.BEGIN("processRouteGraphConnections");
+            if (!changes.routeGraphConnections.isEmpty()) {
+                graph.syncRequest(new ReadRequest() {
+                    @Override
+                    public void run(ReadGraph graph) throws DatabaseException {
+                        processRouteGraphConnections(graph);
+                    }
+                    @Override
+                    public String toString() {
+                        return "processRouteGraphConnections";
+                    }
+                });
+            }
+            Timing.END(task);
+
+            //System.out.println("---- AFTER LOADING");
+            //for (IElement e : addedElements)
+            //    System.out.println("    ADDED ELEMENT: " + e);
+            //for (IElement e : addedBranchPoints)
+            //    System.out.println("    ADDED BRANCH POINTS: " + e);
+
+            task = Timing.BEGIN("executeDeferredLoaders");
+            executeDeferredLoaders(graph);
+            Timing.END(task);
+        }
+
+        public boolean isEmpty() {
+            return addedElements.isEmpty() && removedElements.isEmpty()
+            && addedConnectionSegments.isEmpty() && removedConnectionSegments.isEmpty()
+            && addedBranchPoints.isEmpty() && removedBranchPoints.isEmpty()
+            && addedConnectionEntities.isEmpty() && removedConnectionEntities.isEmpty()
+            && addedRouteGraphConnectionMap.isEmpty() && removedRouteGraphConnections.isEmpty()
+            && !changes.elementOrderChanged;
+        }
+
+        class DefaultConnectionSegmentAdapter implements ConnectionSegmentAdapter {
+
+            @Override
+            public void getClass(AsyncReadGraph graph, EdgeResource edge, ConnectionInfo info, ListenerSupport listenerSupport, ICanvasContext canvas, IDiagram diagram, final AsyncProcedure<ElementClass> procedure) {
+                if (info.connectionType != null) {
+                    NodeClassRequest request = new NodeClassRequest(canvas, diagram, info.connectionType, true);
+                    graph.asyncRequest(request, new CacheListener<ElementClass>(listenerSupport));
+                    graph.asyncRequest(request, procedure);
+                } else {
+                    procedure.execute(graph, null);
+                }
+            }
+
+            @Override
+            public void load(AsyncReadGraph graph, final EdgeResource edge, final ConnectionInfo info, ListenerSupport listenerSupport, ICanvasContext canvas, final IDiagram diagram, final IElement element) {
+                graph.asyncRequest(new Read<IElement>() {
+                    @Override
+                    public IElement perform(ReadGraph graph) throws DatabaseException {
+                        //ITask task = ThreadLogger.getInstance().begin("LoadSegment");
+                        syncLoad(graph, edge, info, diagram, element);
+                        //task.finish();
+                        return element;
+                    }
+                    @Override
+                    public String toString() {
+                        return "defaultConnectionSegmentAdapter";
+                    }
+                }, new DisposableListener<IElement>(listenerSupport) {
+                    
+                    @Override
+                    public String toString() {
+                        return "DefaultConnectionSegmentAdapter listener for " + edge;
+                    }
+                    
+                    @Override
+                    public void execute(IElement loaded) {
+                        // Invoked when the element has been loaded.
+                        if (DebugPolicy.DEBUG_EDGE_LISTENER)
+                            System.out.println("EDGE LoadListener for " + loaded);
+
+                        if (loaded == null) {
+                            disposeListener();
+                            return;
+                        }
+
+                        Object data = loaded.getHint(ElementHints.KEY_OBJECT);
+                        if (addedElementMap.containsKey(data)) {
+                            // This element was just loaded, in
+                            // which case its hints need to
+                            // uploaded to the real mapped
+                            // element immediately.
+                            IElement mappedElement = getMappedElement(data);
+                            if (DebugPolicy.DEBUG_EDGE_LISTENER)
+                                System.out.println("LOADED ADDED EDGE, currently mapped element: " + mappedElement);
+                            if (mappedElement != null && (mappedElement instanceof Element)) {
+                                if (DebugPolicy.DEBUG_EDGE_LISTENER) {
+                                    System.out.println("  mapped hints: " + mappedElement.getHints());
+                                    System.out.println("  loaded hints: " + loaded.getHints());
+                                }
+                                updateMappedElement((Element) mappedElement, loaded);
+                            }
+                        } else {
+                            // This element was already loaded.
+                            // Just schedule an update some time
+                            // in the future.
+                            if (DebugPolicy.DEBUG_EDGE_LISTENER)
+                                System.out.println("PREVIOUSLY LOADED EDGE UPDATED, scheduling update into the future");
+                            offerGraphUpdate( edgeUpdater(element, loaded) );
+                        }
+                    }
+                });
+            }
+
+            void syncLoad(ReadGraph graph, EdgeResource connectionSegment, ConnectionInfo info, IDiagram diagram, IElement element) throws DatabaseException {
+                // Check that at least some data exists before continuing further.
+                if (!graph.hasStatement(connectionSegment.first()) && !graph.hasStatement(connectionSegment.second())) {
+                    return;
+                }
+
+                // Validate that both ends of the segment are
+                // part of the same connection before loading.
+                // This will happen for connections that are
+                // modified through splitting and joining of
+                // connection segments.
+                Resource c = ConnectionUtil.tryGetConnection(graph, connectionSegment);
+                if (c == null) {
+                    // Ok, this segment is somehow invalid. Just don't load it.
+                    if (DebugPolicy.DEBUG_CONNECTION_LOAD)
+                        System.out.println("Skipping edge " + connectionSegment + ". Both segment ends are not part of the same connection.");
+                    return;
+                }
+
+                if (!info.isValid()) {
+                    // This edge must be somehow invalid, don't proceed with loading.
+                    if (DebugPolicy.DEBUG_CONNECTION_LOAD)
+                        warning("Cannot load edge " + connectionSegment + ". ConnectionInfo " + info + " is invalid.", new DebugException("execution trace"));
+                    return;
+                }
+
+                Element edge = (Element) element;
+                edge.setHint(ElementHints.KEY_OBJECT, connectionSegment);
+
+                // connectionSegment resources may currently be in a different
+                // order than ConnectionInfo.firstEnd/secondEnd. Therefore the
+                // segment ends must be resolved here.
+                ConnectionSegmentEnd firstEnd = DiagramGraphUtil.resolveConnectionSegmentEnd(graph, connectionSegment.first());
+                ConnectionSegmentEnd secondEnd = DiagramGraphUtil.resolveConnectionSegmentEnd(graph, connectionSegment.second());
+                if (firstEnd == null || secondEnd == null) {
+                    if (DebugPolicy.DEBUG_CONNECTION_LOAD)
+                        warning("End attachments for edge " + connectionSegment + " are unresolved: (" + firstEnd + "," + secondEnd + ")", new DebugException("execution trace"));
+                    return;
+                }
+
+                if (DebugPolicy.DEBUG_CONNECTION_LOAD)
+                    System.out.println("CONNECTION INFO: " + connectionSegment + " - " + info);
+                DesignatedTerminal firstTerminal = DiagramGraphUtil.findDesignatedTerminal(
+                        graph, diagram, connectionSegment.first(), firstEnd);
+                DesignatedTerminal secondTerminal = DiagramGraphUtil.findDesignatedTerminal(
+                        graph, diagram, connectionSegment.second(), secondEnd);
+
+                // Edges must be connected at both ends in order for edge loading to succeed.
+                String err = validateConnectivity(graph, connectionSegment, firstTerminal, secondTerminal);
+                if (err != null) {
+                    // Stop loading edge if the connectivity cannot be completely resolved.
+                    if (DebugPolicy.DEBUG_CONNECTION_LOAD)
+                        warning(err, null);
+                    return;
+                }
+
+                graph.syncRequest(new AsyncReadRequest() {
+                    @Override
+                    public void run(AsyncReadGraph graph) {
+                        // NOTICE: Layer information is loaded from the connection entity resource
+                        // that is shared by all segments of the same connection.
+                        ElementFactoryUtil.loadLayersForElement(graph, layerManager, diagram, edge, info.connection,
+                                new AsyncProcedureAdapter<IElement>() {
+                            @Override
+                            public void exception(AsyncReadGraph graph, Throwable t) {
+                                error("failed to load layers for connection segment", t);
+                            }
+                        });
+                    }
+                });
+
+                edge.setHintWithoutNotification(KEY_CONNECTION_BEGIN_PLACEHOLDER, new PlaceholderConnection(
+                        EdgeEnd.Begin,
+                        firstTerminal.element.getHint(ElementHints.KEY_OBJECT),
+                        firstTerminal.terminal));
+                edge.setHintWithoutNotification(KEY_CONNECTION_END_PLACEHOLDER, new PlaceholderConnection(
+                        EdgeEnd.End,
+                        secondTerminal.element.getHint(ElementHints.KEY_OBJECT),
+                        secondTerminal.terminal));
+
+                IModelingRules modelingRules = diagram.getHint(DiagramModelHints.KEY_MODELING_RULES);
+                if (modelingRules != null) {
+                    ConnectionVisualsLoader loader = diagram.getHint(DiagramModelHints.KEY_CONNECTION_VISUALS_LOADER);
+                    if (loader != null)
+                        loader.loadConnectionVisuals(graph, modelingRules, info.connection, diagram, edge, firstTerminal, secondTerminal);
+                    else
+                        DiagramGraphUtil.loadConnectionVisuals(graph, modelingRules, info.connection, diagram, edge, firstTerminal, secondTerminal);
+                }
+            }
+
+            private String validateConnectivity(ReadGraph graph, EdgeResource edge,
+                    DesignatedTerminal firstTerminal,
+                    DesignatedTerminal secondTerminal)
+            throws DatabaseException {
+                boolean firstLoose = firstTerminal == null;
+                boolean secondLoose = secondTerminal == null;
+                boolean stray = firstLoose && secondLoose;
+                if (firstTerminal == null || secondTerminal == null) {
+                    StringBuilder sb = new StringBuilder();
+                    sb.append("encountered ");
+                    sb.append(stray ? "stray" : "loose");
+                    sb.append(" connection segment, ");
+                    if (firstLoose)
+                        sb.append("first ");
+                    if (stray)
+                        sb.append("and ");
+                    if (secondLoose)
+                        sb.append("second ");
+                    sb.append("end disconnected: ");
+                    sb.append(edge.toString(graph));
+                    sb.append(" - ");
+                    sb.append(edge.toString());
+                    return sb.toString();
+                }
+                return null;
+            }
+
+        }
+
+        ConnectionSegmentAdapter connectionSegmentAdapter = new DefaultConnectionSegmentAdapter();
+
+    }
+
+    private static final Double DIAGRAM_UPDATE_DIAGRAM_PRIORITY = 1d;
+    private static final Double DIAGRAM_UPDATE_NODE_PRIORITY = 2d;
+    private static final Double DIAGRAM_UPDATE_CONNECTION_PRIORITY = 3d;
+    private static final Double DIAGRAM_UPDATE_EDGE_PRIORITY = 4d;
+
+    interface DiagramUpdater extends Runnable {
+        Double getPriority();
+
+        Comparator<DiagramUpdater> DIAGRAM_UPDATER_COMPARATOR = new Comparator<DiagramUpdater>() {
+            @Override
+            public int compare(DiagramUpdater o1, DiagramUpdater o2) {
+                return o1.getPriority().compareTo(o2.getPriority());
+            }
+        };
+    }
+
+    interface GraphUpdateReactor {
+        DiagramUpdater graphUpdate(ReadGraph graph) throws DatabaseException;
+    }
+
+    static abstract class AbstractDiagramUpdater implements DiagramUpdater, GraphUpdateReactor {
+        protected final Double priority;
+        protected final String runnerName;
+
+        public AbstractDiagramUpdater(Double priority, String runnerName) {
+            if (priority == null)
+                throw new NullPointerException("null priority");
+            if (runnerName == null)
+                throw new NullPointerException("null runner name");
+            this.priority = priority;
+            this.runnerName = runnerName;
+        }
+
+        @Override
+        public Double getPriority() {
+            return priority;
+        }
+
+        @Override
+        public AbstractDiagramUpdater graphUpdate(ReadGraph graph) {
+            return this;
+        }
+
+        @Override
+        public void run() {
+            Object task = Timing.BEGIN(runnerName);
+            forDiagram();
+            Timing.END(task);
+        }
+
+        protected void forDiagram() {
+        }
+
+        @Override
+        public String toString() {
+            return runnerName + "@" + System.identityHashCode(this) + " [" + priority + "]";
+        }
+    }
+
+    /**
+     * @param content the new contents of the diagram, must not be
+     *        <code>null</code>.
+     */
+    private GraphUpdateReactor diagramGraphUpdater(final DiagramContents content) {
+        if (content == null)
+            throw new NullPointerException("null diagram content");
+
+        return new GraphUpdateReactor() {
+            @Override
+            public String toString() {
+                return "DiagramGraphUpdater@" + System.identityHashCode(this);
+            }
+
+            @Override
+            public DiagramUpdater graphUpdate(ReadGraph graph) throws DatabaseException {
+                // Never do anything here if the canvas has already been disposed.
+                if (!GraphToDiagramSynchronizer.this.isAlive())
+                    return null;
+
+                // We must be prepared for the following changes in the diagram graph
+                // model:
+                // - the diagram has been completely removed
+                // - elements have been added
+                // - elements have been removed
+                //
+                // Element-specific changes are handled by the element query listeners:
+                // - elements have been modified
+                // - element position has changed
+                // - element class (e.g. image) has changed
+
+                diagramUpdateLock.lock();
+                try {
+                    if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
+                        System.out.println("In diagramGraphUpdater:");
+
+                    // Find out what has changed since the last query.
+                    Object task = Timing.BEGIN("diagramContentDifference");
+                    DiagramContents lastContent = previousContent;
+                    DiagramContentChanges changes = content.differenceFrom(previousContent);
+                    previousContent = content;
+                    Timing.END(task);
+                    if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
+                        System.out.println("  changes: " + changes);
+
+                    // Bail out if there are no changes to react to.
+                    if (changes.isEmpty())
+                        return null;
+
+                    // Load everything that needs to be loaded from the graph,
+                    // but don't update the UI model in this thread yet.
+                    task = Timing.BEGIN("updater.process");
+                    GraphToDiagramUpdater updater = new GraphToDiagramUpdater(lastContent, content, changes);
+                    GraphToDiagramSynchronizer.this.currentUpdater = updater;
+                    try {
+                        updater.process(graph);
+                    } finally {
+                        GraphToDiagramSynchronizer.this.currentUpdater = null;
+                    }
+                    Timing.END(task);
+
+                    if (updater.isEmpty())
+                        return null;
+
+                    // Return an updater that will update the UI run-time model.
+                    return diagramUpdater(updater);
+                } finally {
+                    diagramUpdateLock.unlock();
+                }
+            }
+        };
+    }
+
+    DiagramUpdater diagramUpdater(final GraphToDiagramUpdater updater) {
+        return new AbstractDiagramUpdater(DIAGRAM_UPDATE_DIAGRAM_PRIORITY, "updateDiagram") {
+            @Override
+            protected void forDiagram() {
+                if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
+                    System.out.println("running diagram updater: " + this);
+
+                // DiagramUtils.testDiagram(diagram);
+                Set<IElement> dirty = new HashSet<IElement>();
+
+                Object task2 = Timing.BEGIN("Preprocess connection changes");
+                Map<ConnectionEntity, ConnectionChildren> connectionChangeData = new HashMap<ConnectionEntity, ConnectionChildren>(updater.changedConnectionEntities.size());
+                for (ConnectionData cd : updater.changedConnectionEntities.values()) {
+                    connectionChangeData.put(cd.impl, cd.impl.getConnectionChildren());
+                }
+                Timing.END(task2);
+
+                task2 = Timing.BEGIN("removeRouteGraphConnections");
+                for (IElement removedRouteGraphConnection : updater.removedRouteGraphConnections) {
+                    if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
+                        System.out.println("removing route graph connection: " + removedRouteGraphConnection);
+
+                    ConnectionEntity ce = removedRouteGraphConnection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
+                    if (ce == null)
+                        continue;
+                    Object connectionData = ElementUtils.getObject(removedRouteGraphConnection);
+                    tempConnections.clear();
+                    for (Connection conn : ce.getTerminalConnections(tempConnections)) {
+                        ((Element) conn.node).removeHintWithoutNotification(new TerminalKeyOf(conn.terminal, connectionData, Connection.class));
+                        // To be sure the view will be up-to-date, mark the node
+                        // connected to the removed connection dirty.
+                        dirty.add(conn.node);
+                    }
+                }
+                Timing.END(task2);
+
+                task2 = Timing.BEGIN("removeBranchPoints");
+                for (IElement removed : updater.removedBranchPoints) {
+                    if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
+                        System.out.println("removing branch point: " + removed);
+
+                    unmapElement(removed.getHint(ElementHints.KEY_OBJECT));
+                    removeNodeTopologyHints((Element) removed);
+
+                    IElement connection = ElementUtils.getParent(removed);
+                    if (connection != null) {
+                        dirty.add(connection);
+                    }
+                }
+                Timing.END(task2);
+
+                task2 = Timing.BEGIN("removeConnectionSegments");
+                for (IElement removed : updater.removedConnectionSegments) {
+                    if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
+                        System.out.println("removing segment: " + removed);
+
+                    unmapElement(removed.getHint(ElementHints.KEY_OBJECT));
+                    removeEdgeTopologyHints((Element) removed);
+
+                    IElement connection = ElementUtils.getParent(removed);
+                    if (connection != null) {
+                        dirty.add(connection);
+                    }
+                }
+                Timing.END(task2);
+
+                task2 = Timing.BEGIN("removeElements");
+                for (IElement removed : updater.removedElements) {
+                    if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
+                        System.out.println("removing element: " + removed);
+
+                    removed.setHint(KEY_REMOVE_RELATIONSHIPS, Boolean.TRUE);
+                    if (diagram.containsElement(removed)) {
+                        diagram.removeElement(removed);
+                    }
+                    unmapElement(removed.getHint(ElementHints.KEY_OBJECT));
+                    removeNodeTopologyHints((Element) removed);
+
+                    // No use marking removed elements dirty.
+                    dirty.remove(removed);
+                }
+                Timing.END(task2);
+
+                // TODO: get rid of this
+                task2 = Timing.BEGIN("removeConnectionEntities");
+                for (Resource ce : updater.removedConnectionEntities) {
+                    unmapConnection(ce);
+                }
+                Timing.END(task2);
+
+                task2 = Timing.BEGIN("setConnectionData");
+                for (ConnectionData cd : updater.changedConnectionEntities.values()) {
+                    cd.impl.setData(cd.segments, cd.branchPoints);
+                }
+                Timing.END(task2);
+
+                // TODO: get rid of this
+                task2 = Timing.BEGIN("addConnectionEntities");
+                for (Map.Entry<Resource, ConnectionEntityImpl> entry : updater.addedConnectionEntities
+                        .entrySet()) {
+                    mapConnection(entry.getKey(), entry.getValue());
+                }
+                Timing.END(task2);
+
+                task2 = Timing.BEGIN("addBranchPoints");
+                for (IElement added : updater.addedBranchPoints) {
+                    if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
+                        System.out.println("adding branch point: " + added);
+
+                    mapElement(ElementUtils.getObject(added), added);
+
+                    IElement connection = ElementUtils.getParent(added);
+                    if (connection != null) {
+                        dirty.add(connection);
+                    }
+                }
+                Timing.END(task2);
+
+                // Add new elements at end of diagram, element order will be synchronized later.
+                task2 = Timing.BEGIN("addElements");
+                for(Resource r : updater.content.elements) {
+                    IElement added = updater.addedElementMap.get(r);
+                    if(added != null) {
+                        if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
+                            System.out.println("adding element: " + added);
+
+                        //Object task3 = BEGIN("mapElement " + added);
+                        Object task3 = Timing.BEGIN("mapElement");
+                        mapElement(added.getHint(ElementHints.KEY_OBJECT), added);
+                        Timing.END(task3);
+
+                        //task3 = BEGIN("addElement " + added);
+                        task3 = Timing.BEGIN("addElement");
+                        //System.out.println("diagram.addElement: " + added + " - " + diagram);
+                        diagram.addElement(added);
+                        dirty.add(added);
+                        Timing.END(task3);
+                    }
+                }
+                Timing.END(task2);
+
+                // We've ensured that all nodes must have been and
+                // mapped before reaching this.
+                task2 = Timing.BEGIN("addConnectionSegments");
+                for (IElement added : updater.addedConnectionSegments) {
+                    if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
+                        System.out.println("adding segment: " + added);
+
+                    PlaceholderConnection cb = added.removeHint(GraphToDiagramSynchronizer.KEY_CONNECTION_BEGIN_PLACEHOLDER);
+                    PlaceholderConnection ce = added.removeHint(GraphToDiagramSynchronizer.KEY_CONNECTION_END_PLACEHOLDER);
+                    if (cb == null || ce == null) {
+                        if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
+                            warning("ignoring connection segment " + added + ", connectivity was not resolved (begin=" + cb + ", end=" + ce +")", null);
+                        continue;
+                    }
+
+                    mapElement(ElementUtils.getObject(added), added);
+
+                    IElement beginNode = assertMappedElement(cb.node);
+                    IElement endNode = assertMappedElement(ce.node);
+
+                    if (cb != null)
+                        connect(added, cb.end, beginNode, cb.terminal);
+                    if (ce != null)
+                        connect(added, ce.end, endNode, ce.terminal);
+
+                    IElement connection = ElementUtils.getParent(added);
+                    if (connection != null) {
+                        dirty.add(connection);
+                    }
+                }
+                Timing.END(task2);
+
+                // We've ensured that all nodes must have been and
+                // mapped before reaching this.
+
+                task2 = Timing.BEGIN("handle dirty RouteGraph connections");
+                for (IElement addedRouteGraphConnection : updater.addedRouteGraphConnectionMap.values()) {
+                    updateDirtyRouteGraphConnection(addedRouteGraphConnection, dirty);
+                }
+                Timing.END(task2);
+
+                // Prevent memory leaks
+                tempConnections.clear();
+
+                // Make sure that the diagram element order matches that of the database.
+                final TObjectIntHashMap<IElement> orderMap = new TObjectIntHashMap<IElement>(2 * updater.content.elements.size());
+                int i = 1;
+                for (Resource r : updater.content.elements) {
+                    IElement e = getMappedElement(r);
+                    if (e != null)
+                        orderMap.put(e, i);
+                    ++i;
+                }
+                diagram.sort(new Comparator<IElement>() {
+                    @Override
+                    public int compare(IElement e1, IElement e2) {
+                        int o1 = orderMap.get(e1);
+                        int o2 = orderMap.get(e2);
+                        return o1 - o2;
+                    }
+                });
+
+                // TODO: consider removing this. The whole thing should work without it and
+                // this "fix" will only be hiding the real problems.
+                task2 = Timing.BEGIN("fixChangedConnections");
+                for (ConnectionData cd : updater.changedConnectionEntities.values()) {
+                    cd.impl.fix();
+                }
+                Timing.END(task2);
+
+                task2 = Timing.BEGIN("validateAndFix");
+                DiagramUtils.validateAndFix(diagram, dirty);
+                Timing.END(task2);
+
+                // This will fire connection entity change listeners
+                task2 = Timing.BEGIN("Postprocess connection changes");
+                for (ConnectionData cd : updater.changedConnectionEntities.values()) {
+                    ConnectionChildren oldChildren = connectionChangeData.get(cd.impl);
+                    if (oldChildren != null) {
+                        ConnectionChildren currentChildren = cd.impl.getConnectionChildren();
+                        cd.impl.fireListener(oldChildren, currentChildren);
+                    }
+                }
+                Timing.END(task2);
+
+                task2 = Timing.BEGIN("setDirty");
+                for (IElement e : dirty) {
+                    if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
+                        System.out.println("MARKING ELEMENT DIRTY: " + e);
+                    e.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
+                }
+                Timing.END(task2);
+
+                // Signal about possible changes in the z-order of diagram elements.
+                if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
+                    System.out.println("MARKING DIAGRAM DIRTY: " + diagram);
+                diagram.setHint(Hints.KEY_DIRTY, Hints.VALUE_Z_ORDER_CHANGED);
+
+                // Mark the updater as "processed".
+                updater.clear();
+
+                // Inform listeners that the diagram has been updated.
+                diagram.setHint(DiagramModelHints.KEY_DIAGRAM_CONTENTS_UPDATED, Boolean.TRUE);
+            }
+        };
+    }
+
+    /**
+     * @param connection
+     * @param dirtySet
+     */
+    private void updateDirtyRouteGraphConnection(IElement connection, Set<IElement> dirtySet) {
+        if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
+            System.out.println("updating dirty route graph connection: " + connection);
+
+        ConnectionEntity ce = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
+        if (ce == null)
+            return;
+
+        tempConnections.clear();
+        Object connectionData = ElementUtils.getObject(connection);
+        for (Connection conn : ce.getTerminalConnections(tempConnections)) {
+            ((Element) conn.node).setHintWithoutNotification(
+                    new TerminalKeyOf(conn.terminal, connectionData, Connection.class),
+                    conn);
+            if (dirtySet != null)
+                dirtySet.add(conn.node);
+        }
+
+        // Prevent memory leaks.
+        tempConnections.clear();
+    }
+
+    abstract class ElementUpdater extends AbstractDiagramUpdater {
+        private final IElement newElement;
+
+        public ElementUpdater(Double priority, String runnerName, IElement newElement) {
+            super(priority, runnerName);
+            if (newElement == null)
+                throw new NullPointerException("null element");
+            this.newElement = newElement;
+        }
+
+        @Override
+        public String toString() {
+            return super.toString() + "[" + newElement + "]";
+        }
+
+        @Override
+        public void run() {
+//            System.out.println("ElementUpdateRunner new=" + newElement);
+            Object elementResource = newElement.getHint(ElementHints.KEY_OBJECT);
+//            System.out.println("ElementUpdateRunner res=" + elementResource);
+            final Element mappedElement = (Element) getMappedElement(elementResource);
+//            System.out.println("ElementUpdateRunner mapped=" + mappedElement);
+            if (mappedElement == null) {
+                if (DebugPolicy.DEBUG_ELEMENT_LIFECYCLE) {
+                    System.out.println("SKIP DIAGRAM UPDATE " + this  + " for element resource " + elementResource + ", no mapped element (newElement=" + newElement + ")");
+                }
+                // Indicates the element has been removed from the graph.
+                return;
+            }
+
+            Object task = Timing.BEGIN(runnerName);
+            forMappedElement(mappedElement);
+            Timing.END(task);
+        }
+
+        protected abstract void forMappedElement(Element mappedElement);
+    }
+
+    ElementUpdater nodeUpdater(final Resource resource, final IElement newElement) {
+
+        return new ElementUpdater(DIAGRAM_UPDATE_NODE_PRIORITY, "updateNode", newElement) {
+
+            Collection<Terminal> getTerminals(IElement e) {
+                Collection<Terminal> ts = Collections.emptyList();
+                TerminalTopology tt = e.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class);
+                if (tt != null) {
+                    ts = new ArrayList<Terminal>();
+                    tt.getTerminals(newElement, ts);
+                }
+                return ts;
+            }
+
+            @Override
+            protected void forMappedElement(final Element mappedElement) {
+                if (DebugPolicy.DEBUG_NODE_UPDATE)
+                    System.out.println("running node updater: " + this + " - new element: " + newElement);
+
+                // Newly loaded node elements NEVER contain topology-related
+                // hints, i.e. TerminalKeyOf hints. Instead all connections are
+                // actually set into element hints when connection edges are
+                // loaded.
+
+                Collection<Terminal> oldTerminals = getTerminals(mappedElement);
+                Collection<Terminal> newTerminals = getTerminals(newElement);
+                if (!oldTerminals.equals(newTerminals)) {
+                    // Okay, there are differences in the terminals. Need to fix
+                    // the TerminalKeyOf hint values to use the new terminal
+                    // instances when correspondences exist.
+
+                    // If there is no correspondence for an old terminal, we
+                    // are simply forced to remove the hints related to this
+                    // connection.
+
+                    Map<Terminal, Terminal> newTerminalMap = new HashMap<Terminal, Terminal>(newTerminals.size());
+                    for (Terminal t : newTerminals) {
+                        newTerminalMap.put(t, t);
+                    }
+
+                    for (Map.Entry<TerminalKeyOf, Object> entry : mappedElement.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
+                        TerminalKeyOf key = entry.getKey();
+                        Connection c = (Connection) entry.getValue();
+                        if (c.node == mappedElement) {
+                            Terminal newTerminal = newTerminalMap.get(c.terminal);
+                            if (newTerminal != null) {
+                                c = new Connection(c.edge, c.end, c.node, newTerminal);
+                                ((Element) c.edge).setHintWithoutNotification(EndKeyOf.get(c.end), c);
+                            } else {
+                                mappedElement.removeHintWithoutNotification(key);
+                            }
+                        }
+                    }
+                }
+
+                updateMappedElement(mappedElement, newElement);
+                mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
+            }
+        };
+    }
+
+    ElementUpdater connectionUpdater(final Object data, final IElement newElement) {
+        return new ElementUpdater(DIAGRAM_UPDATE_CONNECTION_PRIORITY, "updateConnection", newElement) {
+            @Override
+            public void forMappedElement(Element mappedElement) {
+                if (DebugPolicy.DEBUG_CONNECTION_UPDATE)
+                    System.out.println("running connection updater: " + this + " - new element: " + newElement
+                            + " with data " + data);
+
+                // This is kept up-to-date by GDS, make sure not to overwrite it
+                // from the mapped element.
+                newElement.removeHint(ElementHints.KEY_CONNECTION_ENTITY);
+
+                updateMappedElement(mappedElement, newElement);
+                mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
+            }
+        };
+    }
+
+    ElementUpdater edgeUpdater(final Object data, final IElement newElement) {
+        return new ElementUpdater(DIAGRAM_UPDATE_EDGE_PRIORITY, "updateEdge", newElement) {
+            @Override
+            public void forMappedElement(Element mappedElement) {
+                if (DebugPolicy.DEBUG_EDGE_UPDATE)
+                    System.out.println("running edge updater: " + this + " - new element: " + newElement
+                            + " with data " + data);
+
+                updateMappedElement(mappedElement, newElement);
+                mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
+            }
+        };
+    }
+
+    ElementUpdater routeGraphConnectionUpdater(final Object data, final IElement newElement, final Set<Object> dirtyNodes) {
+        return new ElementUpdater(DIAGRAM_UPDATE_CONNECTION_PRIORITY, "updateRouteGraphConnection", newElement) {
+            @Override
+            public void forMappedElement(Element mappedElement) {
+                if (DebugPolicy.DEBUG_CONNECTION_UPDATE)
+                    System.out.println("running route graph connection updater: " + this + " - new element: " + newElement
+                            + " with data " + data);
+
+                // Remove all TerminalKeyOf hints from nodes that were
+                // previously connected to the connection (mappedElement)
+                // before updating mappedElement with new topology information.
+
+                for (Object dirtyNodeObject : dirtyNodes) {
+                    Element dirtyNode = (Element) getMappedElement(dirtyNodeObject);
+                    if (dirtyNode == null)
+                        continue;
+                    if (DebugPolicy.DEBUG_DIAGRAM_UPDATE_DETAIL)
+                        System.out.println("preparing node with dirty connectivity: " + dirtyNode);
+
+                    for (Map.Entry<TerminalKeyOf, Object> entry : dirtyNode.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
+                        Connection conn = (Connection) entry.getValue();
+                        Object connectionNode = conn.edge.getHint(ElementHints.KEY_OBJECT);
+                        if (data.equals(connectionNode)) {
+                            dirtyNode.removeHintWithoutNotification(entry.getKey());
+                        }
+                    }
+                }
+
+                // Update connection information, including topology
+                updateMappedElement(mappedElement, newElement);
+
+                // Reinstall TerminalKeyOf hints into nodes that are now connected
+                // to mappedElement to keep diagram run-time model properly in sync
+                // with the database.
+                updateDirtyRouteGraphConnection(mappedElement, null);
+
+                // TODO: should mark dirty nodes' scene graph dirty ?
+
+                mappedElement.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
+            }
+        };
+    }
+
+    /**
+     * Copies hints from <code>newElement</code> to <code>mappedElement</code>
+     * asserting some validity conditions at the same time.
+     * 
+     * @param mappedElement
+     * @param newElement
+     */
+    static void updateMappedElement(Element mappedElement, IElement newElement) {
+        if (mappedElement == newElement)
+            // Can't update anything if the two elements are the same.
+            return;
+
+        ElementClass oldClass = mappedElement.getElementClass();
+        ElementClass newClass = newElement.getElementClass();
+
+        Object mappedData = mappedElement.getHint(ElementHints.KEY_OBJECT);
+        Object newData = newElement.getHint(ElementHints.KEY_OBJECT);
+
+        assert mappedData != null;
+        assert newData != null;
+        assert mappedData.equals(newData);
+
+        if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
+            System.out.println("Updating mapped element, setting hints\n  from: " + newElement + "\n  into: " + mappedElement);
+        }
+
+        // TODO: consider if this equals check is a waste of time or does it pay
+        // off due to having to reinitialize per-class caches for the new
+        // ElementClass that are constructed on the fly?
+        if (!newClass.equals(oldClass)) {
+            if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
+                System.out.println("  old element class: " + oldClass);
+                System.out.println("  new element class: " + newClass);
+            }
+            mappedElement.setElementClass(newClass);
+        }
+
+        // Tuukka@2010-02-19: replaced with notifications for making
+        // the graph synchronizer more transparent to the client.
+
+        // Hint notifications will not work when this is used.
+        //mappedElement.setHintsWithoutNotification(newElement.getHints());
+
+        Map<DiscardableKey, Object> discardableHints = mappedElement.getHintsOfClass(DiscardableKey.class);
+
+        // Set all hints from newElement to mappedElement.
+        // Leave any hints in mappedElement but not in newElement as is.
+        Map<Key, Object> hints = newElement.getHints();
+        Map<Key, Object> newHints = new HashMap<Key, Object>();
+        for (Map.Entry<Key, Object> entry : hints.entrySet()) {
+            Key key = entry.getKey();
+            Object newValue = entry.getValue();
+            Object oldValue = mappedElement.getHint(key);
+            if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE_DETAIL) {
+                System.out.println("  hint " + key + " compare values: " + oldValue + "  ->  " + newValue);
+            }
+            if (!newValue.equals(oldValue)) {
+                newHints.put(key, newValue);
+                if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
+                    System.out.format("    %-42s : %64s -> %-64s\n", key, oldValue, newValue);
+                }
+            } else {
+                // If the hint value has not changed but the hint still exists
+                // we don't need to discard it even if it is considered
+                // discardable.
+                discardableHints.remove(key);
+            }
+        }
+
+        // Set all hints at once and send notifications after setting the values.
+        if (!discardableHints.isEmpty()) {
+            if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE)
+                System.out.println("Discarding " + discardableHints.size() + " discardable hints:\n  " + discardableHints);
+            mappedElement.removeHints(discardableHints.keySet());
+        }
+        if (!newHints.isEmpty()) {
+            if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
+                System.out.println("Updating mapped element, setting new hints:\n\t"
+                        + EString.implode(newHints.entrySet(), "\n\t") + "\nto replace old hints\n\t"
+                        + EString.implode(mappedElement.getHints().entrySet(), "\n\t"));
+            }
+            mappedElement.setHints(newHints);
+        }
+        if (DebugPolicy.DEBUG_GENERAL_ELEMENT_UPDATE) {
+            System.out.println("All hints after update:\n\t"
+                    + EString.implode(mappedElement.getHints().entrySet(), "\n\t"));
+        }
+    }
+
+    class TransactionListener extends SessionEventListenerAdapter {
+        long startTime;
+        @Override
+        public void writeTransactionStarted() {
+            startTime = System.nanoTime();
+            if (DebugPolicy.DEBUG_WRITE_TRANSACTIONS)
+                System.out.println(GraphToDiagramSynchronizer.class.getSimpleName() + ".sessionEventListener.writeTransactionStarted");
+            inWriteTransaction.set(true);
+        }
+        @Override
+        public void writeTransactionFinished() {
+            long endTime = System.nanoTime();
+            if (DebugPolicy.DEBUG_WRITE_TRANSACTIONS)
+                System.out.println(GraphToDiagramSynchronizer.class.getSimpleName() + ".sessionEventListener.writeTransactionFinished: " + (endTime - startTime)*1e-6 + " ms");
+            inWriteTransaction.set(false);
+            scheduleGraphUpdates();
+        }
+    };
+
+    Object                   graphUpdateLock             = new Object();
+    TransactionListener      sessionListener             = null;
+    AtomicBoolean            inWriteTransaction          = new AtomicBoolean(false);
+    AtomicBoolean            graphUpdateRequestScheduled = new AtomicBoolean(false);
+    List<GraphUpdateReactor> queuedGraphUpdates          = new ArrayList<GraphUpdateReactor>();
+
+    private void offerGraphUpdate(GraphUpdateReactor update) {
+        if (DebugPolicy.DEBUG_GRAPH_UPDATE)
+            System.out.println("offerGraphUpdate: " + update);
+        boolean inWrite = inWriteTransaction.get();
+        synchronized (graphUpdateLock) {
+            if (DebugPolicy.DEBUG_GRAPH_UPDATE)
+                System.out.println("queueing graph update: " + update);
+            queuedGraphUpdates.add(update);
+        }
+        if (!inWrite) {
+            if (DebugPolicy.DEBUG_GRAPH_UPDATE)
+                System.out.println("scheduling queued graph update immediately: " + update);
+            scheduleGraphUpdates();
+        }
+    }
+
+    private Collection<GraphUpdateReactor> scrubGraphUpdates() {
+        synchronized (graphUpdateLock) {
+            if (queuedGraphUpdates.isEmpty())
+                return Collections.emptyList();
+            final List<GraphUpdateReactor> updates = queuedGraphUpdates;
+            queuedGraphUpdates = new ArrayList<GraphUpdateReactor>();
+            return updates;
+        }
+    }
+
+    private void scheduleGraphUpdates() {
+        synchronized (graphUpdateLock) {
+            if (queuedGraphUpdates.isEmpty())
+                return;
+            if (!graphUpdateRequestScheduled.compareAndSet(false, true))
+                return;
+        }
+
+        if (DebugPolicy.DEBUG_GRAPH_UPDATE)
+            System.out.println("scheduling " + queuedGraphUpdates.size() + " queued graph updates with ");
+
+        session.asyncRequest(new ReadRequest() {
+            @Override
+            public void run(final ReadGraph graph) throws DatabaseException {
+                Collection<GraphUpdateReactor> updates;
+                synchronized (graphUpdateLock) {
+                    graphUpdateRequestScheduled.set(false);
+                    updates = scrubGraphUpdates();
+                }
+
+                if (!GraphToDiagramSynchronizer.this.isAlive())
+                    return;
+
+                processGraphUpdates(graph, updates);
+            }
+        }, new ProcedureAdapter<Object>() {
+            @Override
+            public void exception(Throwable t) {
+                error(t);
+            }
+        });
+    }
+
+    private void processGraphUpdates(ReadGraph graph, final Collection<GraphUpdateReactor> graphUpdates)
+    throws DatabaseException {
+        final List<DiagramUpdater> diagramUpdates = new ArrayList<DiagramUpdater>(graphUpdates.size());
+
+        // Run GraphUpdaters and gather DiagramUpdaters.
+        if (DebugPolicy.DEBUG_GRAPH_UPDATE)
+            System.out.println("Running GRAPH updates: " + graphUpdates);
+        for (GraphUpdateReactor graphUpdate : graphUpdates) {
+            DiagramUpdater diagramUpdate = graphUpdate.graphUpdate(graph);
+            if (diagramUpdate != null) {
+                if (DebugPolicy.DEBUG_GRAPH_UPDATE)
+                    System.out.println(graphUpdate + " => " + diagramUpdate);
+                diagramUpdates.add(diagramUpdate);
+            }
+        }
+
+        if (diagramUpdates.isEmpty())
+            return;
+
+        if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
+            System.out.println("Diagram updates: " + diagramUpdates);
+        Collections.sort(diagramUpdates, DiagramUpdater.DIAGRAM_UPDATER_COMPARATOR);
+        if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
+            System.out.println("Sorted diagram updates: " + diagramUpdates);
+
+        ThreadUtils.asyncExec(canvas.getThreadAccess(), new StateRunnable() {
+            @Override
+            public void run() {
+                if (GraphToDiagramSynchronizer.this.isAlive() && getState() != State.DISPOSED)
+                    safeRunInState(State.UPDATING_DIAGRAM, this);
+            }
+
+            @Override
+            public void execute() throws InvocationTargetException {
+                // Block out diagram write transactions.
+                DiagramUtils.inDiagramTransaction(diagram, TransactionType.READ, new Runnable() {
+                    @Override
+                    public void run() {
+                        if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
+                            System.out.println("Running DIAGRAM updates: " + diagramUpdates);
+                        for (DiagramUpdater update : diagramUpdates) {
+                            if (DebugPolicy.DEBUG_DIAGRAM_UPDATE)
+                                System.out.println("Running DIAGRAM update: " + update);
+                            update.run();
+                        }
+                    }
+                });
+
+                setCanvasDirty();
+            }
+        });
+    }
+
+    private void attachSessionListener(Session session) {
+        SessionEventSupport support = session.peekService(SessionEventSupport.class);
+        if (support != null) {
+            sessionListener = new TransactionListener();
+            support.addListener(sessionListener);
+        }
+    }
+
+    private void detachSessionListener() {
+        if (sessionListener != null) {
+            session.getService(SessionEventSupport.class).removeListener(sessionListener);
+            sessionListener = null;
+        }
+    }
+
+
+    // ------------------------------------------------------------------------
+    // GRAPH TO DIAGRAM SYNCHRONIZATION LOGIC END
+    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+
+    // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+    // DIAGRAM CHANGE TRACKING, MAINLY VALIDATION PURPOSES.
+    // This does not try to synchronize anything back into the graph.
+    // ------------------------------------------------------------------------
+
+    IHintListener elementHintValidator = new HintListenerAdapter() {
+        @Override
+        public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
+            if (!(sender instanceof Element))
+                throw new IllegalStateException("invalid sender: " + sender);
+            Element e = (Element) sender;
+            if (newValue != null) {
+                if (key instanceof TerminalKeyOf) {
+                    Connection c = (Connection) newValue;
+                    if (e != c.node)
+                        throw new IllegalStateException("TerminalKeyOf hint of node " + e + " refers to a different node " + c.node + ". Should be the same.");
+                    Object edgeObject = ElementUtils.getObject(c.edge);
+                    if (!(edgeObject instanceof EdgeResource))
+                        throw new IllegalStateException("EndKeyOf hint of edge " + c.edge + " refers contains an invalid object: " + edgeObject);
+                } else if (key instanceof EndKeyOf) {
+                    Connection c = (Connection) newValue;
+                    if (e != c.edge)
+                        throw new IllegalStateException("EndKeyOf hint of edge " + e + " refers to a different edge " + c.edge + ". Should be the same.");
+                    Object edgeObject = ElementUtils.getObject(c.edge);
+                    if (!(edgeObject instanceof EdgeResource))
+                        throw new IllegalStateException("EndKeyOf hint of edge " + e + " refers contains an invalid object: " + edgeObject);
+                }
+            }
+        }
+    };
+
+    class DiagramListener implements CompositionListener, CompositionVetoListener {
+        @Override
+        public boolean beforeElementAdded(IDiagram d, IElement e) {
+            // Make sure that MutatedElements NEVER get added to the diagram.
+            if (d == diagram) {
+                if (!(e instanceof Element)) {
+                    // THIS IS NOT GOOD!
+                    error("Attempting to add another implementation of IElement besides Element (=" + e.getElementClass().getClass().getName() + ") to the synchronized diagram which means that there is a bug somewhere! See stack trace to find out who is doing this!", new Exception("stacktrace"));
+                    System.err.println("Attempting to add another implementation of IElement besides Element (=" + e.getElementClass().getClass().getName() + ") to the synchronized diagram which means that there is a bug somewhere! See Error Log.");
+                    return false;
+                }
+
+                // Perform sanity checks that might veto the element addition.
+                boolean pass = true;
+
+                // Check that all elements added to the diagram are adaptable to Resource
+                ElementClass ec = e.getElementClass();
+                Resource resource = ElementUtils.adapt(ec, Resource.class);
+                if (resource == null) {
+                    pass = false;
+                    LOGGER.error("", new Exception("Attempted to add an element to the diagram that is not adaptable to Resource: " + e + ", class: " + ec));
+                }
+
+                // Sanity check connection hints
+                for (Map.Entry<TerminalKeyOf, Object> entry : e.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
+                    Connection c = (Connection) entry.getValue();
+                    Object edgeObject = ElementUtils.getObject(c.edge);
+                    if (e != c.node) {
+                        System.err.println("Invalid node in TerminalKeyOf hint: " + entry.getKey() + "=" + entry.getValue());
+                        System.err.println("\tconnection.edge=" + c.edge);
+                        System.err.println("\tconnection.node=" + c.node);
+                        System.err.println("\tconnection.end=" + c.end);
+                        System.err.println("\telement=" + e);
+                        System.err.println("\telement class=" + e.getElementClass());
+                        pass = false;
+                    }
+                    if (!(edgeObject instanceof EdgeResource)) {
+                        System.err.println("Invalid object in TerminalKeyOf hint edge: " + entry.getKey() + "=" + entry.getValue());
+                        System.err.println("\tconnection.edge=" + c.edge);
+                        System.err.println("\tconnection.node=" + c.node);
+                        System.err.println("\tconnection.end=" + c.end);
+                        System.err.println("\telement=" + e);
+                        System.err.println("\telement class=" + e.getElementClass());
+                        pass = false;
+                    }
+                }
+
+                return pass;
+            }
+            return true;
+        }
+
+        @Override
+        public boolean beforeElementRemoved(IDiagram d, IElement e) {
+            // Never veto diagram changes.
+            return true;
+        }
+
+        @Override
+        public void onElementAdded(IDiagram d, IElement e) {
+            if (DebugPolicy.DEBUG_ELEMENT_LIFECYCLE)
+                System.out.println("[" + d + "] element added: " + e);
+
+            if (USE_ELEMENT_VALIDATING_LISTENERS)
+                e.addHintListener(elementHintValidator);
+        }
+
+        @Override
+        public void onElementRemoved(IDiagram d, IElement e) {
+            if (DebugPolicy.DEBUG_ELEMENT_LIFECYCLE)
+                System.out.println("[" + d + "] element removed: " + e);
+
+            if (USE_ELEMENT_VALIDATING_LISTENERS)
+                e.removeHintListener(elementHintValidator);
+
+            if (e.containsHint(KEY_REMOVE_RELATIONSHIPS))
+                relationshipHandler.denyAll(diagram, e);
+        }
+    }
+
+    DiagramListener diagramListener = new DiagramListener();
+
+    static void removeNodeTopologyHints(Element node) {
+        Set<TerminalKeyOf> terminalKeys = node.getHintsOfClass(TerminalKeyOf.class).keySet();
+        if (!terminalKeys.isEmpty()) {
+            removeNodeTopologyHints(node, terminalKeys);
+        }
+    }
+
+    static void removeNodeTopologyHints(Element node, Collection<TerminalKeyOf> terminalKeys) {
+        for (TerminalKeyOf key : terminalKeys) {
+            Connection c = node.removeHintWithoutNotification(key);
+            if (c != null) {
+                removeEdgeTopologyHints((Element) c.edge);
+            }
+        }
+    }
+
+    static void removeEdgeTopologyHints(Element edge) {
+        Object edgeData = edge.getHint(ElementHints.KEY_OBJECT);
+        for (EndKeyOf key : EndKeyOf.KEYS) {
+            Connection c = edge.removeHintWithoutNotification(key);
+            if (c != null) {
+                ((Element) c.node).removeHintWithoutNotification(new TerminalKeyOf(c.terminal, edgeData, Connection.class));
+            }
+        }
+    }
+
+    // ------------------------------------------------------------------------
+    // DIAGRAM CHANGE TRACKING, MAINLY VALIDATION PURPOSES.
+    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+
+    // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+    // BACKEND TO DIAGRAM LOAD/LISTEN LOGIC BEGIN
+    // ------------------------------------------------------------------------
+
+    void adaptDiagramClass(AsyncReadGraph graph, Resource diagram, final AsyncProcedure<DiagramClass> procedure) {
+        graph.forAdapted(diagram, DiagramClass.class, new AsyncProcedure<DiagramClass>() {
+            @Override
+            public void exception(AsyncReadGraph graph, Throwable throwable) {
+                procedure.exception(graph, throwable);
+            }
+
+            @Override
+            public void execute(AsyncReadGraph graph, DiagramClass dc) {
+                // To move TopologyImpl out of here, we need a separate
+                // DiagramClassFactory that takes a canvas context as an argument.
+                // DataElementMapImpl, ElementFactoryImpl and diagramLifeCycle can
+                // safely stay here.
+                procedure.execute(graph, dc.newClassWith(
+                        // This handler takes care of the topology of the diagram model.
+                        // It sets and fixes element hints related to describing the
+                        // connectivity of elements.
+                        diagramTopology,
+                        // TODO: not quite sure whether this can prove itself useful or not.
+                        elementFactory,
+                        // This map provides a bidirectional mapping between
+                        // IElement and back-end objects.
+                        dataElementMap,
+                        // This handler provides a facility to adapt an element class
+                        // to work properly with a diagram synchronized using this
+                        // GraphToDiagramSynchronizer.
+                        substituteElementClass,
+                        // These handlers provide a way to create simple identified
+                        // uni- and bidirectional relationships between any diagram
+                        // objects/elements.
+                        relationshipHandler));
+            }
+        });
+    }
+
+    static Connection connect(IElement edge, EdgeEnd end, IElement element, Terminal terminal) {
+        Connection c = new Connection(edge, end, element, terminal);
+
+        Object edgeData = edge.getHint(ElementHints.KEY_OBJECT);
+        if (DebugPolicy.DEBUG_CONNECTION) {
+            System.out.println("[connect](edge=" + edge + ", edgeData=" + edgeData + ", end=" + end + ", element="
+                    + element + ", terminal=" + terminal + ")");
+        }
+
+        TerminalKeyOf key = new TerminalKeyOf(terminal, edgeData, Connection.class);
+        element.setHint(key, c);
+
+        EndKeyOf key2 = EndKeyOf.get(end);
+        edge.setHint(key2, c);
+
+        return c;
+    }
+
+    static class ElementFactoryImpl implements ElementFactory {
+        @Override
+        public IElement spawnNew(ElementClass clazz) {
+            IElement e = Element.spawnNew(clazz);
+            return e;
+        }
+    }
+
+    ElementFactoryImpl elementFactory = new ElementFactoryImpl();
+
+    public static final Object FIRST_TIME = new Object() {
+        @Override
+        public String toString() {
+            return "FIRST_TIME";
+        }
+    };
+
+
+    /**
+     * A base for all listeners of graph requests performed internally by
+     * GraphToDiagramSynchronizer.
+     *
+     * @param <T> type of stored data element
+     * @param <Result> query result type
+     */
+    abstract class BaseListener<T, Result> implements AsyncListener<Result> {
+
+        protected final T    data;
+
+        private Object       oldResult = FIRST_TIME;
+
+        protected boolean    disposed  = false;
+
+        final ICanvasContext canvas;
+
+        public BaseListener(T data) {
+            this.canvas = GraphToDiagramSynchronizer.this.canvas;
+            this.data = data;
+        }
+
+        @Override
+        public void exception(AsyncReadGraph graph, Throwable throwable) {
+            // Exceptions are always expected to mean that the listener should
+            // be considered disposed once a query fails.
+            disposed = true;
+        }
+
+        abstract void execute(AsyncReadGraph graph, Object oldResult, Object newResult);
+
+        @Override
+        public void execute(AsyncReadGraph graph, Result result) {
+            if (DebugPolicy.DEBUG_LISTENER_BASE)
+                System.out.println("BaseListener: " + result);
+
+            if (disposed) {
+                if (DebugPolicy.DEBUG_LISTENER_BASE)
+                    System.out.println("BaseListener: execute invoked although listener is disposed!");
+                return;
+            }
+
+            // A null result will permanently mark this listener disposed!
+            if (result == null) {
+                disposed = true;
+                if (DebugPolicy.DEBUG_LISTENER_BASE)
+                    System.out.println(this + " null result, listener marked disposed");
+            }
+
+            if (oldResult == FIRST_TIME) {
+                oldResult = result;
+                if (DebugPolicy.DEBUG_LISTENER_BASE)
+                    System.out.println(this + " first result computed: " + result);
+            } else {
+                if (DebugPolicy.DEBUG_LISTENER_BASE)
+                    System.out.println(this + " result changed from '" + oldResult + "' to '" + result + "'");
+                try {
+                    execute(graph, oldResult, result);
+                } finally {
+                    oldResult = result;
+                }
+            }
+        }
+
+        @Override
+        public boolean isDisposed() {
+            if (disposed)
+                return true;
+
+            boolean alive = isAlive();
+            //System.out.println(getClass().getName() + ": isDisposed(" + resource.getResourceId() + "): canvas=" + canvas + ", isAlive=" + alive);
+            if (!alive)
+                return true;
+            // If a mapping no longer exists for this element, dispose of this
+            // listener.
+            //IElement e = getMappedElement(resource);
+            //System.out.println(getClass().getName() + ": isDisposed(" + resource.getResourceId() + "): canvas=" + canvas + ", element=" + e);
+            //return e == null;
+            return false;
+        }
+
+//        @Override
+//        protected void finalize() throws Throwable {
+//            System.out.println("finalize listener: " + this);
+//            super.finalize();
+//        }
+    }
+
+    class DiagramClassRequest extends BaseRequest2<Resource, DiagramClass> {
+        public DiagramClassRequest(Resource resource) {
+            super(GraphToDiagramSynchronizer.this.canvas, resource);
+        }
+
+        @Override
+        public void perform(AsyncReadGraph graph, AsyncProcedure<DiagramClass> procedure) {
+            adaptDiagramClass(graph, data, procedure);
+        }
+    }
+
+    public class DiagramContentListener extends BaseListener<Resource, DiagramContents> {
+
+        public DiagramContentListener(Resource resource) {
+            super(resource);
+        }
+
+        @Override
+        public void execute(final AsyncReadGraph graph, Object oldResult, Object newResult) {
+            final DiagramContents newContent = (newResult == null) ? new DiagramContents()
+            : (DiagramContents) newResult;
+
+            // diagramGraphUpdater is called synchronously during
+            // loading. The first result will not get updated through
+            // this listener but through loadDiagram.
+
+            if (DebugPolicy.DISABLE_DIAGRAM_UPDATES) {
+                System.out.println("Skipped diagram content update: " + newResult);
+                return;
+            }
+
+            if (DebugPolicy.DEBUG_DIAGRAM_LISTENER)
+                System.out.println("diagram contents changed: " + oldResult + " => " + newResult);
+
+            offerGraphUpdate( diagramGraphUpdater(newContent) );
+        }
+
+        @Override
+        public boolean isDisposed() {
+            return !isAlive();
+        }
+
+        @Override
+        public void exception(AsyncReadGraph graph, Throwable t) {
+            super.exception(graph, t);
+            error("DiagramContentRequest failed", t);
+        }
+    }
+
+    // ------------------------------------------------------------------------
+    // BACKEND TO DIAGRAM LOAD/LISTEN LOGIC END
+    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+
+    // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+    // GRAPH-CUSTOMIZED DIAGRAM TOPOLOGY HANDLER BEGIN
+    // ------------------------------------------------------------------------
+
+    static class TopologyImpl implements Topology {
+
+        @Override
+        public Connection getConnection(IElement edge, EdgeEnd end) {
+            Key key = EndKeyOf.get(end);
+            Connection c = edge.getHint(key);
+            if (c == null)
+                return null;
+            return c;
+        }
+
+        @Override
+        public void getConnections(IElement node, Terminal terminal, Collection<Connection> connections) {
+//            IDiagram d = ElementUtils.getDiagram(node);
+            for (Map.Entry<TerminalKeyOf, Object> entry : node.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
+                // First check that the terminal matches.
+                TerminalKeyOf key = entry.getKey();
+                if (!key.getTerminal().equals(terminal))
+                    continue;
+
+                Connection c = (Connection) entry.getValue();
+                if (c != null) {
+                    connections.add(c);
+                }
+            }
+        }
+
+        @Override
+        public void connect(IElement edge, EdgeEnd end, IElement node, Terminal terminal) {
+            if (node != null && terminal != null)
+                GraphToDiagramSynchronizer.connect(edge, end, node, terminal);
+
+            if (DebugPolicy.DEBUG_CONNECTION) {
+                if (end == EdgeEnd.Begin)
+                    System.out.println("Connection started from: " + edge + ", " + end + ", " + node + ", " + terminal);
+                else
+                    System.out.println("Creating connection to: " + edge + ", " + end + ", " + node + ", " + terminal);
+            }
+        }
+
+        @Override
+        public void disconnect(IElement edge, EdgeEnd end, IElement node, Terminal terminal) {
+            EndKeyOf edgeKey = EndKeyOf.get(end);
+            Connection c = edge.getHint(edgeKey);
+            if (c == null)
+                throw new UnsupportedOperationException("cannot disconnect, no Connection in edge " + edge);
+
+            for (Map.Entry<TerminalKeyOf, Object> entry : node.getHintsOfClass(TerminalKeyOf.class).entrySet()) {
+                Connection cc = (Connection) entry.getValue();
+                if (c == cc) {
+                    node.removeHint(entry.getKey());
+                    edge.removeHint(edgeKey);
+                    return;
+                }
+            }
+
+            throw new UnsupportedOperationException("cannot disconnect, no connection between found between edge "
+                    + edge + " and node " + node);
+        }
+    }
+
+    Topology            diagramTopology     = new TopologyImpl();
+
+    // ------------------------------------------------------------------------
+    // GRAPH-CUSTOMIZED DIAGRAM TOPOLOGY HANDLER END
+    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+
+    // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+    // DIAGRAM OBJECT RELATIONSHIP HANDLER BEGIN
+    // ------------------------------------------------------------------------
+
+    RelationshipHandler relationshipHandler = new RelationshipHandler() {
+
+        AssociativeMap map = new AssociativeMap(Associativity.of(true, false, false));
+
+        Object getPossibleObjectOrElement(Object o) {
+            if (o instanceof IElement) {
+                IElement e = (IElement) o;
+                Object oo = e.getHint(ElementHints.KEY_OBJECT);
+                return oo != null ? oo : e;
+            }
+            return o;
+        }
+
+        IElement getElement(Object o) {
+            if (o instanceof IElement)
+                return (IElement) o;
+            return getMappedElement(o);
+        }
+
+        @Override
+        public void claim(IDiagram diagram, Object subject,
+                Relationship predicate, Object object) {
+            Object sd = getPossibleObjectOrElement(subject);
+            Object od = getPossibleObjectOrElement(object);
+
+            Collection<Tuple> ts = null;
+            Relationship inverse = predicate.getInverse();
+            if (inverse != null)
+                ts = Arrays.asList(new Tuple(sd, predicate, od), new Tuple(od, inverse, sd));
+            else
+                ts = Collections.singletonList(new Tuple(sd, predicate, od));
+
+            synchronized (this) {
+                map.add(ts);
+            }
+
+            if (DebugPolicy.DEBUG_RELATIONSHIP) {
+                new Exception().printStackTrace();
+                System.out.println("Claimed relationships:");
+                for (Tuple t : ts)
+                    System.out.println("\t" + t);
+            }
+        }
+
+        private void doDeny(IDiagram diagram, Object subject,
+                Relationship predicate, Object object) {
+            Object sd = getPossibleObjectOrElement(subject);
+            Object od = getPossibleObjectOrElement(object);
+            if (sd == subject || od == object) {
+                System.out
+                .println("WARNING: denying relationship '"
+                        + predicate
+                        + "' between diagram element(s), not back-end object(s): "
+                        + sd + " -> " + od);
+            }
+
+            Collection<Tuple> ts = null;
+            Relationship inverse = predicate.getInverse();
+            if (inverse != null)
+                ts = Arrays.asList(new Tuple(sd, predicate, od), new Tuple(od,
+                        inverse, sd));
+            else
+                ts = Collections.singleton(new Tuple(sd, predicate, od));
+
+            synchronized (this) {
+                map.remove(ts);
+            }
+
+            if (DebugPolicy.DEBUG_RELATIONSHIP) {
+                new Exception().printStackTrace();
+                System.out.println("Denied relationships:");
+                for (Tuple t : ts)
+                    System.out.println("\t" + t);
+            }
+        }
+
+        @Override
+        public void deny(IDiagram diagram, Object subject,
+                Relationship predicate, Object object) {
+            synchronized (this) {
+                doDeny(diagram, subject, predicate, object);
+            }
+        }
+
+        @Override
+        public void deny(IDiagram diagram, Relation relation) {
+            synchronized (this) {
+                doDeny(diagram, relation.getSubject(), relation
+                        .getRelationship(), relation.getObject());
+            }
+        }
+
+        @Override
+        public void denyAll(IDiagram diagram, Object element) {
+            synchronized (this) {
+                for (Relation relation : getRelations(diagram, element, null)) {
+                    doDeny(diagram, relation.getSubject(), relation
+                            .getRelationship(), relation.getObject());
+                }
+            }
+        }
+
+        @Override
+        public Collection<Relation> getRelations(IDiagram diagram,
+                Object element, Collection<Relation> result) {
+            if (DebugPolicy.DEBUG_GET_RELATIONSHIP)
+                System.out.println("getRelations(" + element + ")");
+            Object e = getPossibleObjectOrElement(element);
+
+            Collection<Tuple> tuples = null;
+            synchronized (this) {
+                tuples = map.get(new Tuple(e, null, null), null);
+            }
+
+            if (DebugPolicy.DEBUG_GET_RELATIONSHIP) {
+                System.out.println("Result size: " + tuples.size());
+                for (Tuple t : tuples)
+                    System.out.println("\t" + t);
+            }
+
+            if (tuples.isEmpty())
+                return Collections.emptyList();
+            if (result == null)
+                result = new ArrayList<Relation>(tuples.size());
+            for (Tuple t : tuples) {
+                Object obj = t.getField(2);
+                IElement el = getElement(obj);
+                Relationship r = (Relationship) t.getField(1);
+                result.add(new Relation(element, r, el != null ? el : obj));
+            }
+            return result;
+        }
+
+    };
+
+    // ------------------------------------------------------------------------
+    // DIAGRAM ELEMENT RELATIONSHIP HANDLER END
+    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+
+}