]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.diagram/src/org/simantics/diagram/synchronization/graph/RouteGraphConnection.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / synchronization / graph / RouteGraphConnection.java
diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/synchronization/graph/RouteGraphConnection.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/synchronization/graph/RouteGraphConnection.java
new file mode 100644 (file)
index 0000000..b7ed42d
--- /dev/null
@@ -0,0 +1,627 @@
+/*******************************************************************************\r
+ * Copyright (c) 2011 Association for Decentralized Information Management in\r
+ * Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ *     VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.diagram.synchronization.graph;\r
+\r
+import gnu.trove.map.hash.TObjectIntHashMap;\r
+import gnu.trove.procedure.TObjectIntProcedure;\r
+import gnu.trove.set.hash.THashSet;\r
+\r
+import java.util.ArrayList;\r
+import java.util.HashMap;\r
+import java.util.HashSet;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Set;\r
+\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.ServiceLocator;\r
+import org.simantics.db.Statement;\r
+import org.simantics.db.WriteGraph;\r
+import org.simantics.db.common.CommandMetadata;\r
+import org.simantics.db.common.CommentMetadata;\r
+import org.simantics.db.common.request.IndexRoot;\r
+import org.simantics.db.common.request.WriteRequest;\r
+import org.simantics.db.common.utils.NameUtils;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.db.request.Write;\r
+import org.simantics.db.service.SerialisationSupport;\r
+import org.simantics.diagram.connection.RouteGraph;\r
+import org.simantics.diagram.connection.RouteLine;\r
+import org.simantics.diagram.connection.RouteLink;\r
+import org.simantics.diagram.connection.RouteNode;\r
+import org.simantics.diagram.connection.RoutePoint;\r
+import org.simantics.diagram.connection.RouteTerminal;\r
+import org.simantics.diagram.connection.delta.RouteGraphDelta;\r
+import org.simantics.diagram.connection.delta.RouteGraphDelta.RouteLinePair;\r
+import org.simantics.diagram.connection.delta.RouteGraphDelta.RouteTerminalPair;\r
+import org.simantics.diagram.content.ConnectionUtil;\r
+import org.simantics.diagram.stubs.DiagramResource;\r
+import org.simantics.layer0.Layer0;\r
+import org.simantics.modeling.ModelingResources;\r
+import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphChangeEvent;\r
+import org.simantics.scl.commands.Commands;\r
+import org.simantics.scl.compiler.top.ValueNotFound;\r
+import org.simantics.scl.osgi.SCLOsgi;\r
+import org.simantics.scl.runtime.SCLContext;\r
+import org.simantics.scl.runtime.function.Function;\r
+import org.simantics.structural.stubs.StructuralResource2;\r
+import org.simantics.utils.datastructures.Pair;\r
+\r
+/**\r
+ * FIXME: Adding new route lines does not reconnect the new route lines to existing terminals/other route lines\r
+ * \r
+ * @author Tuukka Lehtonen\r
+ */\r
+@SuppressWarnings("rawtypes")\r
+public class RouteGraphConnection {\r
+\r
+    private static final boolean DEBUG_SYNC = false;\r
+    private static final boolean USE_COMMAND_BASED_SYNCHRONIZATION = true;\r
+\r
+    public static Write synchronizer(final Resource connection, final RouteGraphChangeEvent event) {\r
+        return new WriteRequest() {\r
+            @Override\r
+            public void perform(WriteGraph graph) throws DatabaseException {\r
+                RouteGraphConnection rgc = new RouteGraphConnection(graph, connection);\r
+                rgc.synchronize(graph, event.before, event.after, event.delta);\r
+                CommentMetadata cm = graph.getMetadata(CommentMetadata.class);\r
+                graph.addMetadata(cm.add("Modified connection route"));\r
+                graph.markUndoPoint();\r
+            }\r
+        };\r
+    }\r
+\r
+    private Layer0 L0;\r
+    private DiagramResource DIA;\r
+    private ConnectionUtil cu;\r
+    private Resource connection;\r
+\r
+    public RouteGraphConnection(WriteGraph graph, Resource connection) {\r
+        this.L0 = Layer0.getInstance(graph);\r
+        this.DIA = DiagramResource.getInstance(graph);\r
+        this.cu = new ConnectionUtil(graph);\r
+        this.connection = connection;\r
+    }\r
+    \r
+    private static Function ConnectionPoint;\r
+    private static Function Element;\r
+    private static Function RouteGraphStructure;\r
+    private static Function UpdateLine;\r
+    private static Function RemoveNode;\r
+    private static Function RemoveLink;\r
+    private static Function CreateLine;\r
+    private static Function CreateLink;\r
+\r
+    private static boolean initialized = false;\r
+    private static void initialize(ReadGraph graph) {\r
+        if(!initialized) {\r
+            Object oldGraph = SCLContext.getCurrent().put("graph", graph);\r
+            try {\r
+                ConnectionPoint = (Function)SCLOsgi.MODULE_REPOSITORY.getValue("Simantics/RouteGraph/ConnectionPoint");\r
+                Element = (Function)SCLOsgi.MODULE_REPOSITORY.getValue("Simantics/RouteGraph/Element");\r
+                RouteGraphStructure = (Function)SCLOsgi.MODULE_REPOSITORY.getValue("Simantics/RouteGraph/RouteGraphStructure");\r
+                UpdateLine = (Function)SCLOsgi.MODULE_REPOSITORY.getValue("Simantics/RouteGraph/UpdateLine");\r
+                RemoveNode = (Function)SCLOsgi.MODULE_REPOSITORY.getValue("Simantics/RouteGraph/RemoveNode");\r
+                RemoveLink = (Function)SCLOsgi.MODULE_REPOSITORY.getValue("Simantics/RouteGraph/RemoveLink");\r
+                CreateLine = (Function)SCLOsgi.MODULE_REPOSITORY.getValue("Simantics/RouteGraph/CreateLine");\r
+                CreateLink = (Function)SCLOsgi.MODULE_REPOSITORY.getValue("Simantics/RouteGraph/CreateLink");\r
+                initialized = true;\r
+            } catch(ValueNotFound e) {\r
+                e.printStackTrace();\r
+            } finally {\r
+                SCLContext.getCurrent().put("graph", oldGraph);\r
+            }\r
+        }\r
+    }\r
+    \r
+    @SuppressWarnings("unchecked")\r
+    private static Object getConnectorIdentifier(ReadGraph graph, Resource connector) throws DatabaseException {\r
+        Layer0 L0 = Layer0.getInstance(graph);\r
+        DiagramResource DIA = DiagramResource.getInstance(graph);\r
+        StructuralResource2 STR = StructuralResource2.getInstance(graph);\r
+        ModelingResources MOD = ModelingResources.getInstance(graph);\r
+        for(Statement stat : graph.getStatements(connector, STR.Connects)) {\r
+            Resource pred = stat.getPredicate();\r
+            Resource element = stat.getObject();\r
+            if(!graph.isSubrelationOf(pred, DIA.IsConnectorOf)) {\r
+                Resource component = graph.getPossibleObject(element, MOD.ElementToComponent);\r
+                if(component != null) {\r
+                    String componentName = graph.getRelatedValue(component, L0.HasName);\r
+                    String relationName = graph.getRelatedValue(graph.getInverse(pred), L0.HasName);\r
+                    return ConnectionPoint.apply(componentName, relationName);\r
+                }\r
+                else /* element is flag or reference element */ {\r
+                    String elementName = graph.getRelatedValue(element, L0.HasName);\r
+                    return Element.apply(elementName);\r
+                }\r
+            }\r
+        }\r
+        throw new DatabaseException("Didn't find indentification for " + connector + ".");\r
+    }\r
+    \r
+    @SuppressWarnings("unchecked")\r
+    public void synchronize(\r
+            WriteGraph graph,\r
+            RouteGraph before,\r
+            RouteGraph after,\r
+            RouteGraphDelta delta) throws DatabaseException {\r
+        \r
+        if (DEBUG_SYNC)\r
+            delta.print();\r
+        \r
+        if(USE_COMMAND_BASED_SYNCHRONIZATION) {\r
+            initialize(graph);        \r
+        \r
+                Layer0 L0 = Layer0.getInstance(graph);\r
+            ModelingResources MOD = ModelingResources.getInstance(graph);\r
+            \r
+            Resource diagram = graph.getSingleObject(connection, L0.PartOf);\r
+            Resource composite = graph.getSingleObject(diagram, MOD.DiagramToComposite);\r
+            \r
+            // Structure\r
+            Object structure;\r
+            TObjectIntHashMap<RouteNode> routeNodeMap = new TObjectIntHashMap<RouteNode>() {\r
+                @Override\r
+                protected int hash(Object notnull) {\r
+                    RouteNode node = (RouteNode)notnull;\r
+                    Object data = node.getData();\r
+                    if(data != null)\r
+                        return data.hashCode();\r
+                    else\r
+                        return node.hashCode();\r
+                }\r
+                \r
+                @Override\r
+                protected boolean equals(Object notnull, Object two) {\r
+                    RouteNode node1 = (RouteNode)notnull;\r
+                    RouteNode node2 = (RouteNode)two;\r
+                    Object data1 = node1.getData();\r
+                    if(data1 == null)\r
+                        return node1 == node2;\r
+                    Object data2 = node2.getData();\r
+                    return data1.equals(data2);\r
+                }\r
+            };\r
+            \r
+            {   \r
+                ArrayList<Object> connectorIdentifiers = new ArrayList<Object>();\r
+                \r
+                for(RouteTerminal terminal : before.getTerminals()) {\r
+                    Resource connector = deserialize(graph, terminal.getData());\r
+                    connectorIdentifiers.add(getConnectorIdentifier(graph, connector));\r
+                    routeNodeMap.put(terminal, routeNodeMap.size());\r
+                }\r
+                \r
+                ArrayList<Integer> routeLinks;\r
+                if(before.isSimpleConnection()) {\r
+                    routeLinks = new ArrayList<Integer>(2);\r
+                    routeLinks.add(0);\r
+                    routeLinks.add(1);\r
+                }\r
+                else {\r
+                    THashSet<RouteLink> linkSet = new THashSet<RouteLink>();\r
+                    for(RouteLine line : before.getLines()) {\r
+                        int id = routeNodeMap.size();\r
+                        routeNodeMap.put(line, id);\r
+                        for(RoutePoint rp : line.getPoints())\r
+                            if(rp instanceof RouteLink) {\r
+                                RouteLink link = (RouteLink)rp;\r
+                                if(!link.getA().isTransient() &&\r
+                                        !link.getB().isTransient())\r
+                                    linkSet.add(link);\r
+                            }\r
+                    }\r
+                    \r
+                    routeLinks = new ArrayList<Integer>(linkSet.size()*2+before.getTerminals().size());\r
+                    for(RouteTerminal terminal : before.getTerminals()) {\r
+                        routeLinks.add(routeNodeMap.get(terminal));\r
+                        routeLinks.add(routeNodeMap.get(terminal.getLine()));\r
+                    }\r
+                    for(RouteLink link : linkSet) {\r
+                        routeLinks.add(routeNodeMap.get(link.getA()));\r
+                        routeLinks.add(routeNodeMap.get(link.getB()));\r
+                    }\r
+                }\r
+                \r
+                structure = RouteGraphStructure.apply(composite, \r
+                         connectorIdentifiers, routeNodeMap.size()-connectorIdentifiers.size(),\r
+                         routeLinks\r
+                         );\r
+            }\r
+            \r
+            // Process modifications\r
+            ArrayList<Object> modifications = new ArrayList<Object>();\r
+            ArrayList<Pair<RouteLine,Integer>> newRouteLines = new ArrayList<Pair<RouteLine,Integer>>(); \r
+            {\r
+                // Make direct changes to connection components\r
+                for (RouteLinePair pair : delta.getLinesThatDiffer())\r
+                    modifications.add(UpdateLine.apply(routeNodeMap.get(pair.left), \r
+                            pair.right.getPosition(), pair.right.isHorizontal()));\r
+                \r
+                // Write new components into connection\r
+                for (RouteLine added : delta.getLinesOnlyInRight()) {\r
+                    modifications.add(CreateLine.apply(added.getPosition(), added.isHorizontal()));\r
+                    newRouteLines.add(Pair.make(added, routeNodeMap.size()));\r
+                    routeNodeMap.put(added, routeNodeMap.size());\r
+                }\r
+                \r
+                // Remove all removed links from connection\r
+                for (RouteLink removed : delta.getLinksOnlyInLeft())\r
+                    modifications.add(RemoveLink.apply(\r
+                            routeNodeMap.get(removed.getA()), \r
+                            routeNodeMap.get(removed.getB())));\r
+                \r
+                // Write modifications to connectivity between terminals and interior\r
+                // route nodes.\r
+                for (RouteLink added : delta.getLinksOnlyInRight())\r
+                    modifications.add(CreateLink.apply(\r
+                            routeNodeMap.get(added.getA()), \r
+                            routeNodeMap.get(added.getB())));\r
+    \r
+                // Update modifications to terminals\r
+                if(before.isSimpleConnection()) {\r
+                    if(!after.isSimpleConnection()) {\r
+                        modifications.add(RemoveLink.apply(0, 1));\r
+                        for(RouteTerminal terminal : after.getTerminals())\r
+                            modifications.add(CreateLink.apply(\r
+                                    routeNodeMap.get(terminal),\r
+                                    routeNodeMap.get(terminal.getLine())\r
+                                    ));\r
+                    }\r
+                }\r
+                else if(after.isSimpleConnection()) {\r
+                    for(RouteTerminal terminal : before.getTerminals())\r
+                        modifications.add(RemoveLink.apply(\r
+                                routeNodeMap.get(terminal),\r
+                                routeNodeMap.get(terminal.getLine())\r
+                                ));\r
+                    modifications.add(CreateLink.apply(0, 1));\r
+                } else {\r
+                    for (RouteTerminalPair pair : delta.getTerminalsThatDiffer()) {\r
+                        // If terminals are connected to different route lines,\r
+                        // disconnect left terminal connector from its connected counterpart\r
+                        // and connect the same terminal connector to the connected counterpart\r
+                        // listed in the right terminal.\r
+    \r
+                        int terminalId = routeNodeMap.get(pair.left);\r
+                        int leftLine = routeNodeMap.get(pair.left.getLine());\r
+                        int rightLine = routeNodeMap.get(pair.right.getLine());\r
+                        \r
+                        if(leftLine != rightLine) {\r
+                            modifications.add(RemoveLink.apply(terminalId, leftLine));\r
+                            modifications.add(CreateLink.apply(terminalId, rightLine));\r
+                        }\r
+                    }\r
+                }\r
+                \r
+                // Disconnect and remove removed terminal connectors\r
+                for(RouteTerminal removedTerminal : delta.getTerminalsOnlyInLeft())\r
+                    modifications.add(RemoveNode.apply(routeNodeMap.get(removedTerminal)));\r
+                \r
+                // Finally delete removed route lines\r
+                for(RouteLine removed : delta.getLinesOnlyInLeft())\r
+                    modifications.add(RemoveNode.apply(routeNodeMap.get(removed)));\r
+            }\r
+            \r
+            // Execute command\r
+            if(!modifications.isEmpty()) {\r
+                /*System.out.println("--");\r
+                System.out.println(structure);\r
+                System.out.println(modifications);*/\r
+                final List<Resource> allRouteNodes = (List<Resource>)\r
+                        Commands.get(graph, "Simantics/RouteGraph/modifyRouteGraph")\r
+                        .execute(graph, graph.syncRequest(new IndexRoot(connection)), \r
+                                structure, modifications);\r
+                for(Pair<RouteLine,Integer> pair : newRouteLines)\r
+                    pair.first.setData(allRouteNodes.get(pair.second).getResourceId());\r
+            }\r
+        }\r
+        else {\r
+\r
+            Map<RouteNode, Resource> newComponents = new HashMap<RouteNode, Resource>();\r
+\r
+            CommentMetadata comments = graph.getMetadata(CommentMetadata.class);\r
+            comments.add("Modified connection " + NameUtils.getSafeLabel(graph, connection));\r
+\r
+            writeCommandMetadata(graph, before, delta, after);\r
+\r
+            // Make direct changes to connection components\r
+            for (RouteLinePair pair : delta.getLinesThatDiffer()) {\r
+                Resource line = deserialize(graph, pair.right.getData());\r
+                if (DEBUG_SYNC) {\r
+                    System.out.print("synchronizing existing route line: " + NameUtils.getSafeLabel(graph, line) + " - ");\r
+                    pair.right.print(System.out);\r
+                }\r
+                writeLine(graph, line, pair.right);\r
+                comments.add("Synchronized existing route line: " + NameUtils.getSafeLabel(graph, line));\r
+            }\r
+\r
+            // Write new components into connection\r
+            for (RouteLine added : delta.getLinesOnlyInRight()) {\r
+                Resource line = tryDeserialize(graph, added.getData());\r
+                if (DEBUG_SYNC) {\r
+                    System.out.print("adding new route line (" + line + "): ");\r
+                    added.print(System.out);\r
+                }\r
+                newComponents.put( added, line = writeLine(graph, line, added) );\r
+\r
+                comments.add("Added new route line " + NameUtils.getSafeLabel(graph, line));\r
+            }\r
+\r
+            // Remove all removed links from connection\r
+            for (RouteLink removed : delta.getLinksOnlyInLeft()) {\r
+                Resource a = deserialize(graph, removed.getA().getData());\r
+                Resource b = deserialize(graph, removed.getB().getData());\r
+                if (DEBUG_SYNC)\r
+                    System.out.println("removing link: " + NameUtils.getSafeLabel(graph, a) + " <> " + NameUtils.getSafeLabel(graph, b));\r
+                cu.disconnect(a, b);\r
+                comments.add("Removed link: " + NameUtils.getSafeLabel(graph, a) + " <> " + NameUtils.getSafeLabel(graph, b));\r
+            }\r
+\r
+            // Write modifications to connectivity between terminals and interior\r
+            // route nodes.\r
+            for (RouteLink added : delta.getLinksOnlyInRight()) {\r
+                if (DEBUG_SYNC) {\r
+                    System.out.println("processing added link:");\r
+                    added.getA().print(System.out);\r
+                    added.getB().print(System.out);\r
+                }\r
+                Resource a = deserialize(graph, added.getA().getData());\r
+                Resource b = deserialize(graph, added.getB().getData());\r
+                if (DEBUG_SYNC)\r
+                    System.out.println("adding link between "\r
+                            + NameUtils.getSafeLabel(graph, a) + " <> "\r
+                            + NameUtils.getSafeLabel(graph, b));\r
+                cu.connect(a, b);\r
+                comments.add("Added link between "\r
+                        + NameUtils.getSafeLabel(graph, a) + " <> "\r
+                        + NameUtils.getSafeLabel(graph, b));\r
+            }\r
+\r
+            Set<Resource> looseConnectors = new HashSet<Resource>();\r
+\r
+            // Handle terminals that differ\r
+            for (RouteTerminalPair pair : delta.getTerminalsThatDiffer()) {\r
+                if (DEBUG_SYNC) {\r
+                    System.out.println("processing differing terminal:");\r
+                    pair.left.print(System.out);\r
+                    pair.right.print(System.out);\r
+                }\r
+\r
+                // If terminals are connected to different route lines,\r
+                // disconnect left terminal connector from its connected counterpart\r
+                // and connect the same terminal connector to the connected counterpart\r
+                // listed in the right terminal.\r
+\r
+                if (pair.left.getLine() != pair.right.getLine()) {\r
+                    Resource connector = deserialize( graph, pair.left.getData() );\r
+\r
+                    // Case 1:\r
+                    // - two terminals connected directly to each other.\r
+                    // - terminals moved so that a transient route line is added but\r
+                    //   nothing should be written into the graph.\r
+                    // Case recognition logic:\r
+                    //      left.line == null\r
+                    //   && right.line != null\r
+                    //   && right.line.data == null\r
+                    if (pair.left.getLine() == null\r
+                            && pair.right.getLine() != null\r
+                            && pair.right.getLine().getData() == null)\r
+                        continue;\r
+\r
+                    // Case 2a:\r
+                    // - terminal previously connected to either another terminal or a persistent route line\r
+                    // Case 2b:\r
+                    // - terminal previously connected to another terminal through a transient route line\r
+                    // Case 2c:\r
+                    // - terminal previously connected to another terminal\r
+                    if (pair.left.getLine() != null && pair.left.getLine().getData() != null) {\r
+                        Resource line = deserialize(graph, pair.left.getLine().getData());\r
+                        // Case 2a\r
+                        if (DEBUG_SYNC)\r
+                            System.out.println("removing link between terminal "\r
+                                    + NameUtils.getSafeLabel(graph, connector) + " and route line "\r
+                                    + NameUtils.getSafeLabel(graph, line));\r
+                        cu.disconnect(connector, line);\r
+                        comments.add("Removed link between terminal "\r
+                                + NameUtils.getSafeLabel(graph, connector) + " and route line "\r
+                                + NameUtils.getSafeLabel(graph, line));\r
+                    } else {\r
+                        // Case 2b & 2c\r
+                        if (DEBUG_SYNC)\r
+                            System.out.println("removing link between terminal "\r
+                                    + NameUtils.getSafeLabel(graph, connector) + " and other terminals ");\r
+                        cu.disconnectFromAllRouteNodes(connector);\r
+                        comments.add("Removed link between terminal "\r
+                                + NameUtils.getSafeLabel(graph, connector) + " and other terminals ");\r
+                    }\r
+\r
+                    // Case 3a:\r
+                    // - terminal is now connected to a persistent route line\r
+                    // Case 3b:\r
+                    // - terminal is now connected to another terminal\r
+                    if (pair.right.getLine() != null && pair.right.getLine().getData() != null) {\r
+                        // Case 3a\r
+                        Resource line = deserialize(graph, pair.right.getLine().getData());\r
+                        if (DEBUG_SYNC)\r
+                            System.out.println("adding link between terminal "\r
+                                    + NameUtils.getSafeLabel(graph, connector) + " and route line "\r
+                                    + NameUtils.getSafeLabel(graph, line));\r
+                        cu.connect(connector, line);\r
+                        comments.add("Added link between terminal "\r
+                                + NameUtils.getSafeLabel(graph, connector) + " and route line "\r
+                                + NameUtils.getSafeLabel(graph, line));\r
+                    } else {\r
+                        // Case 3b\r
+                        // Connector was disconnected from route line.\r
+                        // This can currently only mean one thing:\r
+                        // there are no more route lines in the connection.\r
+                        // If there are still connectors in the connection, the\r
+                        // only possible assumption is that there are two\r
+                        // connectors and they need to be connected together\r
+                        looseConnectors.add(connector);\r
+                    }\r
+                }\r
+            }\r
+            if (looseConnectors.size() == 2) {\r
+                Resource[] cns = looseConnectors.toArray(Resource.NONE);\r
+                if (DEBUG_SYNC)\r
+                    System.out.println("linking two loose terminal connectors "\r
+                            + NameUtils.getSafeLabel(graph, cns[0]) + " and "\r
+                            + NameUtils.getSafeLabel(graph, cns[1]));\r
+                cu.connect(cns[0], cns[1]);\r
+                comments.add("Linking two loose terminal connectors "\r
+                        + NameUtils.getSafeLabel(graph, cns[0]) + " and "\r
+                        + NameUtils.getSafeLabel(graph, cns[1]));\r
+            } else if (!looseConnectors.isEmpty()) {\r
+                System.err.println("BUG: there can only be 0 or 2 loose terminal connectors in any connection. Found " + looseConnectors.size() + ":");\r
+                for (Resource cn : looseConnectors)\r
+                    System.err.println(NameUtils.getSafeLabel(graph, cn));\r
+                System.err.flush();\r
+            }\r
+\r
+            // Disconnect and remove removed terminal connectors\r
+            for (RouteTerminal removedTerminal : delta.getTerminalsOnlyInLeft()) {\r
+                Resource connector = deserialize(graph, removedTerminal.getData());\r
+                if (DEBUG_SYNC)\r
+                    System.out.println("removing route terminal: " + NameUtils.getSafeLabel(graph, connector));\r
+                cu.removeConnectionPart(connector);\r
+                comments.add("Removed route terminal " + NameUtils.getSafeLabel(graph, connector));\r
+            }\r
+\r
+            // Finally delete removed route lines\r
+            for (RouteLine removed : delta.getLinesOnlyInLeft()) {\r
+                Resource line = deserialize(graph, removed.getData());\r
+                if (DEBUG_SYNC)\r
+                    System.out.println("removing route line: " + NameUtils.getSafeLabel(graph, line));\r
+                cu.removeConnectionPart(line);\r
+                comments.add("Removed route line " + NameUtils.getSafeLabel(graph, line));\r
+            }\r
+\r
+            graph.addMetadata(comments);\r
+        }\r
+    }\r
+\r
+    private void writeCommandMetadata(WriteGraph graph, RouteGraph left, RouteGraphDelta delta, RouteGraph right) {\r
+        try {\r
+            if(!delta.getTerminalsOnlyInLeft().isEmpty() || !delta.getTerminalsOnlyInRight().isEmpty())\r
+                return; // These are not changes layout organizer may do.\r
+            \r
+            RouteGraphModification proxy = new RouteGraphModification(graph.getService(SerialisationSupport.class), left);\r
+            TObjectIntHashMap<RouteNode> idMap = proxy.getIdMap();\r
+            TObjectIntHashMap<RouteNode> rightIdMap = new TObjectIntHashMap<RouteNode>();\r
+            {\r
+               final TObjectIntHashMap<Object> keyToId = new TObjectIntHashMap<Object>();\r
+               idMap.forEachEntry(new TObjectIntProcedure<RouteNode>() {                               \r
+                               @Override\r
+                               public boolean execute(RouteNode a, int b) {\r
+                                       Object data = a.getData();\r
+                                       if(data != null)\r
+                                               keyToId.put(data, b);\r
+                                       return true;\r
+                               }\r
+                       });\r
+               for(RouteLine line : right.getLines()) {\r
+                       Object data = line.getData();\r
+                       if(keyToId.containsKey(data))\r
+                               rightIdMap.put(line, keyToId.get(data));\r
+               }\r
+               for(RouteTerminal terminal : right.getTerminals()) {\r
+                       Object data = terminal.getData();\r
+                       if(keyToId.containsKey(data))\r
+                               rightIdMap.put(terminal, keyToId.get(data));\r
+               }\r
+            }\r
+            int id = idMap.size();\r
+            for(RouteLink link : delta.getLinksOnlyInLeft()) {\r
+               proxy.addModi(new RouteGraphModification.RemoveLink(idMap.get(link.getA()), idMap.get(link.getB())));\r
+            }\r
+            for(RouteLine line : delta.getLinesOnlyInLeft()) {\r
+               proxy.addModi(new RouteGraphModification.RemoveLine(idMap.get(line)));\r
+            }        \r
+            for(RouteLinePair pair : delta.getLinesThatDiffer()) {\r
+               proxy.addModi(new RouteGraphModification.UpdateLine(idMap.get(pair.left), pair.right.getPosition(), pair.right.isHorizontal()));\r
+            }\r
+            for(RouteLine line : delta.getLinesOnlyInRight()) {\r
+               rightIdMap.put(line, id++);\r
+               proxy.addModi(new RouteGraphModification.CreateLine(line.getPosition(), line.isHorizontal()));\r
+            }\r
+            if(left.isSimpleConnection() && !right.isSimpleConnection())\r
+                proxy.addModi(new RouteGraphModification.RemoveLink(0, 1));\r
+            for(RouteTerminalPair pair : delta.getTerminalsThatDiffer())\r
+                if(pair.left.getLine() != pair.right.getLine()) {\r
+                    if(pair.left.getLine() != null)\r
+                        proxy.addModi(new RouteGraphModification.RemoveLink(idMap.get(pair.left), idMap.get(pair.left.getLine())));\r
+                    if(pair.right.getLine() != null)\r
+                        proxy.addModi(new RouteGraphModification.CreateLink(idMap.get(pair.left), rightIdMap.get(pair.right.getLine())));\r
+                }\r
+            if(!left.isSimpleConnection() && right.isSimpleConnection() && right.getTerminals().size() == 2) {\r
+                Iterator<RouteTerminal> it = right.getTerminals().iterator();\r
+                proxy.addModi(new RouteGraphModification.CreateLink(\r
+                        rightIdMap.get(it.next()), rightIdMap.get(it.next())));\r
+            }\r
+            for(RouteLink link : delta.getLinksOnlyInRight()) {\r
+               proxy.addModi(new RouteGraphModification.CreateLink(rightIdMap.get(link.getA()), rightIdMap.get(link.getB())));\r
+            }\r
+            Resource model = proxy.findTerminalIdentifiers(graph);\r
+            StringBuilder b = new StringBuilder();\r
+            b.append("modifyConnection \"");\r
+            proxy.toString(b);\r
+            b.append("\"");\r
+            if (model != null)\r
+                CommandMetadata.add(graph, model.getResourceId(), b.toString());\r
+        } catch(DatabaseException e) {\r
+            // Failure in command writing must not cancel the write transaction\r
+            e.printStackTrace();\r
+        } catch(NullPointerException e) {\r
+            // Failure in command writing must not cancel the write transaction            \r
+            e.printStackTrace();\r
+        }\r
+    }\r
+\r
+    public Resource writeLine(WriteGraph graph, Resource line, RouteLine routeLine) throws DatabaseException {\r
+        if (line == null) {\r
+            line = graph.newResource();\r
+            graph.claim(line, L0.InstanceOf, null, DIA.RouteLine);\r
+            graph.claim(connection, DIA.HasInteriorRouteNode, line);\r
+        }\r
+        graph.claimLiteral(line, DIA.IsHorizontal, routeLine.isHorizontal());\r
+        graph.claimLiteral(line, DIA.HasPosition, routeLine.getPosition());\r
+        routeLine.setData( serialize(graph, line) );\r
+        if (DEBUG_SYNC) {\r
+            System.out.print("wrote route line: ");\r
+            routeLine.print(System.out);\r
+        }\r
+        return line;\r
+    }\r
+\r
+    public static Object serialize(ServiceLocator locator, Resource r) throws DatabaseException {\r
+        SerialisationSupport ss = locator.getService(SerialisationSupport.class);\r
+        return ss.getRandomAccessId(r); \r
+    }\r
+\r
+    public static Resource deserialize(ServiceLocator locator, Object o) throws DatabaseException {\r
+        Resource r = tryDeserialize(locator, o);\r
+        if (r != null)\r
+            return r;\r
+        throw new IllegalArgumentException("unrecognized object: " + o);\r
+    }\r
+\r
+    public static Resource tryDeserialize(ServiceLocator locator, Object o) throws DatabaseException {\r
+        if (o instanceof Long)\r
+            return locator.getService(SerialisationSupport.class).getResource((Long) o);\r
+        return null;\r
+    }\r
+\r
+}\r