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