]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.diagram/src/org/simantics/diagram/flag/RouteGraphConnectionSplitter.java
Fixed multiple issues causing dangling references to discarded queries
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / flag / RouteGraphConnectionSplitter.java
index c23fe1d878b1945447642f2ee566e7ad663eb253..5c85a99440a9d7efe09b2eaaff2338d56b78291f 100644 (file)
-package org.simantics.diagram.flag;\r
-\r
-import gnu.trove.map.hash.TObjectIntHashMap;\r
-\r
-import java.awt.geom.AffineTransform;\r
-import java.awt.geom.Point2D;\r
-import java.util.ArrayList;\r
-import java.util.Collection;\r
-\r
-import javax.vecmath.Tuple2d;\r
-import javax.vecmath.Vector2d;\r
-\r
-import org.simantics.databoard.Bindings;\r
-import org.simantics.db.ReadGraph;\r
-import org.simantics.db.Resource;\r
-import org.simantics.db.Statement;\r
-import org.simantics.db.WriteGraph;\r
-import org.simantics.db.common.request.PossibleTypedParent;\r
-import org.simantics.db.common.utils.NameUtils;\r
-import org.simantics.db.common.utils.OrderedSetUtils;\r
-import org.simantics.db.exception.DatabaseException;\r
-import org.simantics.db.service.SerialisationSupport;\r
-import org.simantics.diagram.adapter.RouteGraphUtils;\r
-import org.simantics.diagram.connection.RouteGraph;\r
-import org.simantics.diagram.connection.RouteLine;\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.splitting.SplittedRouteGraph;\r
-import org.simantics.diagram.content.ConnectionUtil;\r
-import org.simantics.diagram.stubs.DiagramResource;\r
-import org.simantics.diagram.synchronization.graph.AddElement;\r
-import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;\r
-import org.simantics.diagram.synchronization.graph.RouteGraphModification;\r
-import org.simantics.g2d.elementclass.FlagClass;\r
-import org.simantics.layer0.Layer0;\r
-import org.simantics.modeling.ModelingResources;\r
-import org.simantics.structural.stubs.StructuralResource2;\r
-\r
-/**\r
- * A class that handles splitting a route graph connection in two with diagram\r
- * local flags.\r
- * \r
- * The connection splitting process consists of the following steps:\r
- * <ol>\r
- * <li>Disconnect the end points of the selected connection edge segment (SEG).\r
- * An end point is either :DIA.BranchPoint or (terminal) DIA:Connector. This\r
- * operation will always split a valid connection into two separate parts.</li>\r
- * <li>Calculate the contents of the two separated connection parts (branch\r
- * points and connectors) and decide which part to leave with the existing\r
- * connection (=P1) and which part to move into a new connection (=P2). The\r
- * current strategy is to move the parts that\r
- * <em>do not contain output terminal connectors</em> into a new connection.\r
- * This works well with diagram to composite mappings where output terminals\r
- * generate modules behind connections.</li>\r
- * <li>Create new connection C' with the same type and STR.HasConnectionType as\r
- * the original connection C.</li>\r
- * <li>Copy connection routing settings from C to C'.</li>\r
- * <li>Move P2 into C'.</li>\r
- * <li>Create two new flag elements into the same diagram, set their type and\r
- * interpolate a proper transformation for both along the existing connection\r
- * line.</li>\r
- * <li>Connect the new flags to begin(SEG) and end(SEG) connectors.</li>\r
- * <li>Join the flags together.</li>\r
- * </ol>\r
- * \r
- * @author Tuukka Lehtonen\r
- * @author Hannu Niemist&ouml;\r
- */\r
-public class RouteGraphConnectionSplitter {\r
-\r
-    private final static boolean DEBUG = false;\r
-\r
-    Layer0              L0;\r
-    DiagramResource     DIA;\r
-    StructuralResource2 STR;\r
-    ModelingResources   MOD;\r
-    SerialisationSupport ss;\r
-\r
-    public RouteGraphConnectionSplitter(ReadGraph graph) {\r
-        this.L0 = Layer0.getInstance(graph);\r
-        this.DIA = DiagramResource.getInstance(graph);\r
-        this.STR = StructuralResource2.getInstance(graph);\r
-        this.MOD = ModelingResources.getInstance(graph);\r
-        this.ss = graph.getService(SerialisationSupport.class);\r
-    }\r
-    \r
-    public void split(WriteGraph graph, Resource connection, RouteGraph rg, Point2D splitCanvasPos) throws DatabaseException {\r
-        // Create modification writer\r
-        RouteGraphModification modis = new RouteGraphModification(ss, rg);\r
-        TObjectIntHashMap<RouteNode> idMap = modis.getIdMap();\r
-\r
-        // Find the edge to disconnect in the graph.\r
-        // Bisect the nearest route line.\r
-        RouteLine line = SplittedRouteGraph.findNearestLine(rg, splitCanvasPos);\r
-        if (DEBUG)\r
-            rg.print();\r
-        if (line == null)\r
-            return;\r
-        if (DEBUG) {\r
-            line.print(System.out);\r
-            for (RoutePoint rp : line.getPoints())\r
-                System.out.println("RP: " + rp.getX() + ", " + rp.getY());\r
-        }\r
-\r
-        // Get exact intersection point on the line\r
-        double isectX = splitCanvasPos.getX();\r
-        double isectY = splitCanvasPos.getY();\r
-        SplittedRouteGraph srg;\r
-        if (line.isHorizontal()) {\r
-            isectY = line.getPosition();\r
-            srg = rg.splitGraph(line, isectX);\r
-        }\r
-        else {\r
-            isectX = line.getPosition();\r
-            srg = rg.splitGraph(line, isectY);\r
-        }\r
-        if (DEBUG)\r
-            System.out.println(srg);\r
-        \r
-        // Disconnect\r
-        if(rg.isSimpleConnection()) {\r
-            RouteNode na = srg.terminals1.iterator().next();\r
-            RouteNode nb = srg.terminals2.iterator().next();\r
-            Resource a = ss.getResource((Long)na.getData());\r
-            Resource b = ss.getResource((Long)nb.getData());\r
-            graph.deny(a, DIA.AreConnected, b);\r
-            modis.addModi(new RouteGraphModification.RemoveLink(\r
-                    idMap.get(na), idMap.get(nb)\r
-                    ));\r
-        }\r
-        else if(srg.splitLine.isTransient()) {\r
-            RouteTerminal terminal = srg.splitLine.getTerminal();\r
-            Resource connector = ss.getResource((Long)terminal.getData());\r
-            graph.deny(connector, DIA.AreConnected);\r
-            modis.addModi(new RouteGraphModification.RemoveLink(\r
-                    idMap.get(terminal), idMap.get(terminal.getLine())\r
-                    ));\r
-        }\r
-        else  {\r
-            graph.deny(ss.getResource((Long)srg.splitLine.getData()));\r
-            // TODO remove links\r
-            modis.addModi(new RouteGraphModification.RemoveLine(\r
-                    idMap.get(srg.splitLine)\r
-                    ));\r
-        }\r
-        \r
-        ArrayList<Resource> interfaceNodes1Resources = new ArrayList<Resource>(srg.interfaceNodes1.size());\r
-        for(RouteNode n : srg.interfaceNodes1)\r
-            interfaceNodes1Resources.add(ss.getResource((Long)n.getData()));\r
-        ArrayList<Resource> interfaceNodes2Resources = new ArrayList<Resource>(srg.interfaceNodes2.size());\r
-        for(RouteNode n : srg.interfaceNodes2)\r
-            interfaceNodes2Resources.add(ss.getResource((Long)n.getData()));\r
-        \r
-        ArrayList<Resource> lines2Resources = new ArrayList<Resource>(srg.lines2.size());\r
-        for(RouteLine n : srg.lines2)\r
-            lines2Resources.add(ss.getResource((Long)n.getData()));\r
-        \r
-        ArrayList<Resource> terminals1Resources = new ArrayList<Resource>(srg.terminals1.size());\r
-        for(RouteTerminal n : srg.terminals1)\r
-            terminals1Resources.add(ss.getResource((Long)n.getData()));\r
-        ArrayList<Resource> terminals2Resources = new ArrayList<Resource>(srg.terminals2.size());\r
-        for(RouteTerminal n : srg.terminals2)\r
-            terminals2Resources.add(ss.getResource((Long)n.getData()));\r
-        doSplit(graph, connection,\r
-                interfaceNodes1Resources,\r
-                interfaceNodes2Resources,\r
-                lines2Resources,\r
-                terminals1Resources,\r
-                terminals2Resources,\r
-                line.isHorizontal(),\r
-                isectX, isectY);\r
-        modis.addModi(new RouteGraphModification.Split(\r
-                modis.toIds(interfaceNodes1Resources),\r
-                modis.toIds(interfaceNodes2Resources),\r
-                modis.toIds(lines2Resources),\r
-                modis.toIds(terminals1Resources),\r
-                modis.toIds(terminals2Resources),\r
-                line.isHorizontal(),\r
-                isectX, isectY\r
-                ));\r
-        \r
-    }\r
-    \r
-    public void doSplit(WriteGraph graph, \r
-            Resource connection,\r
-            ArrayList<Resource> interfaceNodes1Resources,\r
-            ArrayList<Resource> interfaceNodes2Resources,\r
-            ArrayList<Resource> lines2Resources,\r
-            ArrayList<Resource> terminals1Resources,\r
-            ArrayList<Resource> terminals2Resources,\r
-            boolean isHorizontal, \r
-            double isectX, double isectY) throws DatabaseException {\r
-\r
-        if (DEBUG) {\r
-            System.out.println("doSplit:");\r
-            System.out.println(NameUtils.getSafeName(graph, connection, true));\r
-            for (Resource i : interfaceNodes1Resources)\r
-                System.out.println("i1: " + NameUtils.getSafeName(graph, i, true));\r
-            for (Resource i : interfaceNodes2Resources)\r
-                System.out.println("i2: " + NameUtils.getSafeName(graph, i, true));\r
-            for (Resource l : lines2Resources)\r
-                System.out.println("l2r: " + NameUtils.getSafeName(graph, l, true));\r
-            for (Resource t : terminals1Resources)\r
-                System.out.println("t1: " + NameUtils.getSafeName(graph, t, true));\r
-            for (Resource t : terminals2Resources)\r
-                System.out.println("t2: " + NameUtils.getSafeName(graph, t, true));\r
-            System.out.println("is horizontal: " + isHorizontal);\r
-            System.out.println("@(x,y): " + isectX + ", " + isectY);\r
-        }\r
-\r
-        ConnectionUtil cu = new ConnectionUtil(graph);\r
-        Resource diagram = OrderedSetUtils.getSingleOwnerList(graph, connection, DIA.Diagram);\r
-\r
-        Resource connectionType = graph.getSingleType(connection, DIA.Connection);\r
-        Resource hasConnectionType = graph.getPossibleObject(connection, STR.HasConnectionType);\r
-        Resource newConnection = cu.newConnection(diagram, connectionType);\r
-        if (hasConnectionType != null)\r
-            graph.claim(newConnection, STR.HasConnectionType, null, hasConnectionType);\r
-\r
-        // Give running name to connection increment the counter attached to the diagram.\r
-        AddElement.claimFreshElementName(graph, diagram, newConnection);\r
-\r
-        // WORKAROUND for mapping problems:\r
-        // If any terminal of the split connection contains a flag, make sure their STR.Joins relations are all removed\r
-        // to give mapping a chance to fix them properly.\r
-        removeFlagJoins(graph, cu, connection, terminals1Resources);\r
-        removeFlagJoins(graph, cu, connection, terminals2Resources);\r
-\r
-        // Move route nodes to correct connections\r
-        for(Resource rn : lines2Resources) {\r
-            // TODO: use same predicate that was removed\r
-            graph.denyStatement(connection, DIA.HasInteriorRouteNode, rn);\r
-            graph.claim(newConnection, DIA.HasInteriorRouteNode, rn);\r
-        }\r
-        for(Resource rn : terminals2Resources) {\r
-            Statement stat = graph.getSingleStatement(rn, DIA.IsConnectorOf);\r
-            Resource predicate = stat.getPredicate();\r
-            graph.deny(rn, predicate);\r
-            graph.claim(rn, predicate, newConnection);\r
-        }\r
-\r
-        // 1 = output, 2 = input\r
-        FlagClass.Type type1, type2;\r
-\r
-        FlagLabelingScheme scheme = DiagramFlagPreferences.getActiveFlagLabelingScheme(graph);\r
-        String commonLabel = scheme.generateLabel(graph, diagram);\r
-\r
-        // Create flags and connect both disconnected ends to them.\r
-        Vector2d pos1, pos2;\r
-        double theta;\r
-        double flagDist = 3.0;\r
-        if(isHorizontal) {\r
-            theta = 0.0;\r
-            pos1 = new Vector2d(isectX-flagDist, isectY);\r
-            pos2 = new Vector2d(isectX+flagDist, isectY);\r
-        }\r
-        else {\r
-            theta = Math.PI*0.5;\r
-            pos1 = new Vector2d(isectX, isectY-flagDist);\r
-            pos2 = new Vector2d(isectX, isectY+flagDist);\r
-        }\r
-\r
-        // Chooses flag directions\r
-        {\r
-            @SuppressWarnings("unused")\r
-            int inputs1 = 0, outputs1 = 0;\r
-            for(Resource connector : terminals1Resources) {\r
-                if(graph.hasStatement(connector, DIA.IsHeadConnectorOf))\r
-                    ++inputs1;\r
-                else\r
-                    ++outputs1;\r
-            }\r
-            @SuppressWarnings("unused")\r
-            int inputs2 = 0, outputs2 = 0;\r
-            for(Resource connector : terminals2Resources) {\r
-                if(graph.hasStatement(connector, DIA.IsHeadConnectorOf))\r
-                    ++inputs2;\r
-                else\r
-                    ++outputs2;\r
-            }\r
-            \r
-            if(outputs1 == 0) {\r
-                type1 = FlagClass.Type.In;\r
-                type2 = FlagClass.Type.Out;\r
-                theta += Math.PI;\r
-            }\r
-            else {\r
-                type1 = FlagClass.Type.Out;\r
-                type2 = FlagClass.Type.In;\r
-            }\r
-            if (DEBUG) {\r
-                System.out.println("inputs1:  " + inputs1);\r
-                System.out.println("outputs1: " + outputs1);\r
-                System.out.println("=> type1:  " + type1);\r
-                System.out.println("inputs2:  " + inputs2);\r
-                System.out.println("outputs2: " + outputs2);\r
-                System.out.println("=> type2:  " + type2);\r
-            }\r
-        }\r
-        Resource flag1 = createFlag(graph, diagram, getFlagTransform(pos1, theta), type1, commonLabel);\r
-        Resource flag2 = createFlag(graph, diagram, getFlagTransform(pos2, theta), type2, commonLabel);\r
-        if (DEBUG) {\r
-            System.out.println("FLAG1: " + NameUtils.getSafeName(graph, flag1, true));\r
-            System.out.println("FLAG2: " + NameUtils.getSafeName(graph, flag2, true));\r
-        }\r
-\r
-//        System.out.println("conn1: " + NameUtils.getSafeLabel(graph, type1 == FlagClass.Type.In ? DIA.HasPlainConnector : DIA.HasArrowConnector));\r
-//        System.out.println("conn2: " + NameUtils.getSafeLabel(graph, type2 == FlagClass.Type.In ? DIA.HasPlainConnector : DIA.HasArrowConnector));\r
-        Resource flagConnector1 = cu.newConnector(connection, \r
-                type1 == FlagClass.Type.In ? DIA.HasPlainConnector : DIA.HasArrowConnector);\r
-        Resource flagConnector2 = cu.newConnector(newConnection, \r
-                type2 == FlagClass.Type.In ? DIA.HasPlainConnector : DIA.HasArrowConnector);\r
-        graph.claim(flag1, DIA.Flag_ConnectionPoint, flagConnector1);\r
-        graph.claim(flag2, DIA.Flag_ConnectionPoint, flagConnector2);\r
-\r
-        double position = isHorizontal ? isectY : isectX;\r
-        connectFlag(graph, isHorizontal, position, connection, flagConnector1, \r
-                interfaceNodes1Resources);\r
-        connectFlag(graph, isHorizontal, position, newConnection, flagConnector2, \r
-                interfaceNodes2Resources);\r
-\r
-        FlagUtil.join(graph, flag1, flag2);\r
-\r
-        // Move mapping relations to new connection if necessary\r
-        if(type1 == FlagClass.Type.In) {\r
-            moveStatements(graph, connection, newConnection, MOD.ElementToComponent);\r
-            moveStatements(graph, connection, newConnection, MOD.DiagramConnectionToConnection);\r
-            moveStatements(graph, connection, newConnection, MOD.DiagramConnectionToConnectionSpecial);\r
-            FlagUtil.fixBindsStatements(graph, graph.getPossibleObject(newConnection, MOD.DiagramConnectionToConnection));\r
-        }\r
-        else\r
-            FlagUtil.fixBindsStatements(graph, graph.getPossibleObject(connection, MOD.DiagramConnectionToConnection));\r
-    }\r
-\r
-    /**\r
-     * A workaround for problems with mapping not removing the necessary\r
-     * STR.Joins relations from flags after a split.\r
-     * \r
-     * @param graph\r
-     * @param cu\r
-     * @param connection\r
-     * @param connectors\r
-     * @throws DatabaseException\r
-     */\r
-    private void removeFlagJoins(WriteGraph graph, ConnectionUtil cu, Resource connection, ArrayList<Resource> connectors) throws DatabaseException {\r
-        for (Resource connector : connectors) {\r
-            Resource e = cu.getConnectedComponent(connection, connector);\r
-            if (graph.isInstanceOf(e, DIA.Flag)) {\r
-                Resource diagram = graph.syncRequest(new PossibleTypedParent(e, DIA.Diagram));\r
-                if (diagram == null)\r
-                    continue;\r
-                for (Resource join : graph.getObjects(e, DIA.FlagIsJoinedBy)) {\r
-                    Collection<Resource> joinsComposites = graph.getObjects(join, STR.JoinsComposite);\r
-                    if (joinsComposites.size() == 1) {\r
-                        // Only remove joins that are internal to a diagram.\r
-                        graph.deny(join, STR.Joins);\r
-                    } else if (joinsComposites.size() == 2) {\r
-                        // Only remove the joins relations that refer to\r
-                        // connections that are part of the same diagram.\r
-                        for (Resource joins : graph.getObjects(join, STR.Joins)) {\r
-                            Resource diagramConnection = graph.getPossibleObject(joins, MOD.ConnectionToDiagramConnection);\r
-                            if (diagramConnection == null)\r
-                                continue;\r
-                            Resource partOfDiagram = graph.syncRequest(new PossibleTypedParent(diagramConnection, DIA.Diagram));\r
-                            if (diagram.equals(partOfDiagram)) {\r
-                                graph.deny(join, STR.Joins, joins);\r
-                            }\r
-                        }\r
-                    }\r
-                }\r
-            }\r
-        }\r
-    }\r
-\r
-    private static void moveStatements(WriteGraph graph, Resource from, Resource to, Resource relation) throws DatabaseException {\r
-       if(from.equals(to))\r
-               return;\r
-       for(Statement stat : graph.getStatements(from, relation))\r
-               if(stat.getSubject().equals(from))\r
-                       graph.claim(to, stat.getPredicate(), stat.getObject());\r
-       graph.deny(from, relation);\r
-    }\r
-    \r
-    private void connectFlag(WriteGraph graph, boolean isHorizontal, double position, Resource connection, Resource flagConnector, Collection<Resource> interfaceNodes) \r
-            throws DatabaseException {\r
-        if(interfaceNodes.size() > 1) {\r
-            Resource routeLine = graph.newResource();\r
-            graph.claim(routeLine, L0.InstanceOf, DIA.RouteLine);\r
-            graph.claim(connection, DIA.HasInteriorRouteNode, routeLine);\r
-            graph.claimLiteral(routeLine, DIA.IsHorizontal, isHorizontal);\r
-            graph.claimLiteral(routeLine, DIA.HasPosition, position);\r
-            graph.claim(routeLine, DIA.AreConnected, flagConnector);\r
-            flagConnector = routeLine;\r
-        }\r
-        for(Resource rn : interfaceNodes) {\r
-            graph.claim(flagConnector, DIA.AreConnected, rn);\r
-        }\r
-    }\r
-\r
-    private AffineTransform getFlagTransform(Tuple2d pos, double theta) {\r
-        AffineTransform at = AffineTransform.getTranslateInstance(pos.x, pos.y);\r
-        at.rotate(theta);\r
-        return at;\r
-    }\r
-\r
-    private Resource createFlag(WriteGraph graph, Resource diagram, AffineTransform tr, FlagClass.Type type, String label) throws DatabaseException {\r
-        DiagramResource DIA = DiagramResource.getInstance(graph);\r
-\r
-        Resource flag = graph.newResource();\r
-        graph.claim(flag, L0.InstanceOf, null, DIA.Flag);\r
-        AddElement.claimFreshElementName(graph, diagram, flag);\r
-        graph.claim(flag, L0.PartOf, L0.ConsistsOf, diagram);\r
-        \r
-        DiagramGraphUtil.setTransform(graph, flag, tr);\r
-        if (type != null)\r
-            FlagUtil.setFlagType(graph, flag, type);\r
-\r
-        if (label != null)\r
-            graph.claimLiteral(flag, L0.HasLabel, DIA.FlagLabel, label, Bindings.STRING);\r
-\r
-        OrderedSetUtils.add(graph, diagram, flag);\r
-        return flag;\r
-    }\r
-\r
-    public static void splitConnection(WriteGraph graph, Resource connection, double x, double y) throws DatabaseException {\r
-        RouteGraph rg = RouteGraphUtils.load(graph, null, connection);\r
-        new RouteGraphConnectionSplitter(graph).split(graph, connection, rg, new Point2D.Double(x, y));\r
-    }\r
+package org.simantics.diagram.flag;
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.simantics.databoard.Bindings;
+import org.simantics.db.ReadGraph;
+import org.simantics.db.Resource;
+import org.simantics.db.Statement;
+import org.simantics.db.WriteGraph;
+import org.simantics.db.common.request.PossibleTypedParent;
+import org.simantics.db.common.utils.NameUtils;
+import org.simantics.db.common.utils.OrderedSetUtils;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.service.SerialisationSupport;
+import org.simantics.diagram.adapter.RouteGraphUtils;
+import org.simantics.diagram.connection.RouteGraph;
+import org.simantics.diagram.connection.RouteLine;
+import org.simantics.diagram.connection.RouteNode;
+import org.simantics.diagram.connection.RoutePoint;
+import org.simantics.diagram.connection.RouteTerminal;
+import org.simantics.diagram.connection.splitting.SplittedRouteGraph;
+import org.simantics.diagram.connection.splitting.SplittedRouteGraph.PickResult;
+import org.simantics.diagram.content.ConnectionUtil;
+import org.simantics.diagram.stubs.DiagramResource;
+import org.simantics.diagram.synchronization.graph.AddElement;
+import org.simantics.diagram.synchronization.graph.BasicResources;
+import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;
+import org.simantics.diagram.synchronization.graph.RouteGraphModification;
+import org.simantics.g2d.elementclass.FlagClass;
+import org.simantics.layer0.Layer0;
+import org.simantics.modeling.ModelingResources;
+import org.simantics.structural.stubs.StructuralResource2;
+
+import gnu.trove.map.hash.TObjectIntHashMap;
+
+/**
+ * A class that handles splitting a route graph connection in two with diagram
+ * local flags.
+ * 
+ * The connection splitting process consists of the following steps:
+ * <ol>
+ * <li>Disconnect the end points of the selected connection edge segment (SEG).
+ * An end point is either :DIA.BranchPoint or (terminal) DIA:Connector. This
+ * operation will always split a valid connection into two separate parts.</li>
+ * <li>Calculate the contents of the two separated connection parts (branch
+ * points and connectors) and decide which part to leave with the existing
+ * connection (=P1) and which part to move into a new connection (=P2). The
+ * current strategy is to move the parts that
+ * <em>do not contain output terminal connectors</em> into a new connection.
+ * This works well with diagram to composite mappings where output terminals
+ * generate modules behind connections.</li>
+ * <li>Create new connection C' with the same type and STR.HasConnectionType as
+ * the original connection C.</li>
+ * <li>Copy connection routing settings from C to C'.</li>
+ * <li>Move P2 into C'.</li>
+ * <li>Create two new flag elements into the same diagram, set their type and
+ * interpolate a proper transformation for both along the existing connection
+ * line.</li>
+ * <li>Connect the new flags to begin(SEG) and end(SEG) connectors.</li>
+ * <li>Join the flags together.</li>
+ * </ol>
+ * 
+ * @author Tuukka Lehtonen
+ * @author Hannu Niemist&ouml;
+ */
+public class RouteGraphConnectionSplitter {
+
+    private final static boolean DEBUG = false;
+
+    Layer0              L0;
+    DiagramResource     DIA;
+    StructuralResource2 STR;
+    ModelingResources   MOD;
+    SerialisationSupport ss;
+
+    public RouteGraphConnectionSplitter(ReadGraph graph) {
+        this.L0 = Layer0.getInstance(graph);
+        this.DIA = DiagramResource.getInstance(graph);
+        this.STR = StructuralResource2.getInstance(graph);
+        this.MOD = ModelingResources.getInstance(graph);
+        this.ss = graph.getService(SerialisationSupport.class);
+    }
+    
+    public void split(WriteGraph graph, Resource connection, RouteGraph rg, Point2D splitCanvasPos) throws DatabaseException {
+        // Create modification writer
+        RouteGraphModification modis = new RouteGraphModification(ss, rg);
+        TObjectIntHashMap<RouteNode> idMap = modis.getIdMap();
+
+        if (DEBUG) {
+            System.out.println("Split canvas position: " + splitCanvasPos);
+            rg.print();
+        }
+
+        // Find the edge to disconnect in the graph.
+        // Bisect the nearest route line.
+        PickResult picked = SplittedRouteGraph.pickNearestLine(rg, splitCanvasPos.getX(), splitCanvasPos.getY());
+        if (picked == null)
+            return;
+
+        RouteLine line = picked.nearestLine;
+
+        if (DEBUG) {
+            System.out.println("picked nearest line:");
+            line.print(System.out);
+            for (RoutePoint rp : line.getPoints())
+                System.out.println("RP: " + rp.getX() + ", " + rp.getY());
+        }
+
+        // Get exact intersection point on the line
+        double isectX = picked.intersectionPoint.getX();
+        double isectY = picked.intersectionPoint.getY();
+        SplittedRouteGraph srg = rg.splitGraph(line, line.isHorizontal() ? isectX : isectY);
+        if (DEBUG)
+            System.out.println(srg);
+
+        // Disconnect
+        if(rg.isSimpleConnection()) {
+            RouteNode na = srg.terminals1.iterator().next();
+            RouteNode nb = srg.terminals2.iterator().next();
+            Resource a = ss.getResource((Long)na.getData());
+            Resource b = ss.getResource((Long)nb.getData());
+            graph.deny(a, DIA.AreConnected, b);
+            modis.addModi(new RouteGraphModification.RemoveLink(
+                    idMap.get(na), idMap.get(nb)
+                    ));
+        }
+        else if(srg.splitLine.isTransient()) {
+            RouteTerminal terminal = srg.splitLine.getTerminal();
+            Resource connector = ss.getResource((Long)terminal.getData());
+            graph.deny(connector, DIA.AreConnected);
+            modis.addModi(new RouteGraphModification.RemoveLink(
+                    idMap.get(terminal), idMap.get(terminal.getLine())
+                    ));
+        }
+        else  {
+            graph.deny(ss.getResource((Long)srg.splitLine.getData()));
+            // TODO remove links
+            modis.addModi(new RouteGraphModification.RemoveLine(
+                    idMap.get(srg.splitLine)
+                    ));
+        }
+        ArrayList<Resource> terminals1Resources = toResources(srg.terminals1);
+        ArrayList<Resource> terminals2Resources = toResources(srg.terminals2);
+
+        boolean mustFlip = analyzePartInputs(graph, terminals1Resources, terminals2Resources);
+
+        ArrayList<Resource> interfaceNodes1 = toResources(mustFlip ? srg.interfaceNodes2 : srg.interfaceNodes1);
+        ArrayList<Resource> interfaceNodes2 = toResources(mustFlip ? srg.interfaceNodes1 : srg.interfaceNodes2);
+
+        ArrayList<Resource> lines2 = toResources(mustFlip ? srg.lines1 : srg.lines2);
+        ArrayList<Resource> terminals1 = mustFlip ? terminals2Resources : terminals1Resources;
+        ArrayList<Resource> terminals2 = mustFlip ? terminals1Resources : terminals2Resources;
+
+        doSplit(graph, connection,
+                interfaceNodes1,
+                interfaceNodes2,
+                lines2,
+                terminals1,
+                terminals2,
+                line.isHorizontal(),
+                mustFlip,
+                isectX, isectY);
+        modis.addModi(new RouteGraphModification.Split(
+                modis.toIds(interfaceNodes1),
+                modis.toIds(interfaceNodes2),
+                modis.toIds(lines2),
+                modis.toIds(terminals1),
+                modis.toIds(terminals2),
+                line.isHorizontal(),
+                mustFlip,
+                isectX, isectY
+                ));
+    }
+
+    private ArrayList<Resource> toResources(Collection<? extends RouteNode> nodes) throws DatabaseException {
+        ArrayList<Resource> result = new ArrayList<>(nodes.size());
+        for (RouteNode n : nodes)
+            result.add(ss.getResource((Long)n.getData()));
+        return result;
+    }
+
+    /**
+     * @param graph
+     * @param terminals1
+     * @param terminals2
+     * @return <code>true</code> if inputs need to be flipped, i.e. if terminals2
+     *         contains the output terminals and terminals1 doesn't.
+     * @throws DatabaseException
+     */
+    private boolean analyzePartInputs(ReadGraph graph, List<Resource> terminals1, List<Resource> terminals2) throws DatabaseException {
+        @SuppressWarnings("unused")
+        int inputs1 = 0, outputs1 = 0;
+        for(Resource connector : terminals1) {
+            if(graph.hasStatement(connector, DIA.IsHeadConnectorOf))
+                ++inputs1;
+            else
+                ++outputs1;
+        }
+        @SuppressWarnings("unused")
+        int inputs2 = 0, outputs2 = 0;
+        for(Resource connector : terminals2) {
+            if(graph.hasStatement(connector, DIA.IsHeadConnectorOf))
+                ++inputs2;
+            else
+                ++outputs2;
+        }
+
+        boolean mustFlip = outputs1 == 0;
+
+        if (DEBUG) {
+            System.out.println("inputs1:  " + inputs1);
+            System.out.println("outputs1: " + outputs1);
+            System.out.println("inputs2:  " + inputs2);
+            System.out.println("outputs2: " + outputs2);
+            System.out.println("=> type1:  " + (mustFlip ? FlagClass.Type.In : FlagClass.Type.Out));
+            System.out.println("=> type2:  " + (mustFlip ? FlagClass.Type.Out : FlagClass.Type.In));
+            System.out.println("=> must flip route graph parts to split: " + mustFlip);
+        }
+
+        return mustFlip;
+    }
+
+    private static String routeNodeDebugInfo(ReadGraph graph, Resource c) throws DatabaseException {
+        BasicResources BR = BasicResources.getInstance(graph);
+        String ctr = NameUtils.getSafeName(graph, c, true);
+        for (Resource e : graph.getObjects(c, BR.STR.Connects)) {
+            ctr += " --> " + NameUtils.getSafeName(graph, e);
+        }
+        for (Resource e : graph.getObjects(c, BR.DIA.AreConnected)) {
+            ctr += " <-> " + NameUtils.getSafeName(graph, e);
+        }
+        return ctr;
+    }
+
+    /**
+     * Internal routine that is only public because
+     * {@link RouteGraphModification#runUpdates(WriteGraph)} needs to invoke it.
+     * 
+     * Assumes that #1 parameters will stay with the existing connection and #2
+     * parameters will go to the newly created connection.
+     */
+    public void doSplit(WriteGraph graph, 
+            Resource connection,
+            ArrayList<Resource> interfaceNodes1Resources,
+            ArrayList<Resource> interfaceNodes2Resources,
+            ArrayList<Resource> lines2Resources,
+            ArrayList<Resource> terminals1Resources,
+            ArrayList<Resource> terminals2Resources,
+            boolean isHorizontal,
+            boolean invertFlagRotation,
+            double isectX, double isectY) throws DatabaseException {
+
+        // 1 = output, 2 = input
+        FlagClass.Type
+                type1 = FlagClass.Type.Out,
+                type2 = FlagClass.Type.In;
+
+        if (DEBUG) {
+            System.out.println("doSplit:");
+            System.out.println(NameUtils.getSafeName(graph, connection, true));
+            for (Resource i : interfaceNodes1Resources)
+                System.out.println("i1: " + routeNodeDebugInfo(graph, i));
+            for (Resource i : interfaceNodes2Resources)
+                System.out.println("i2: " + routeNodeDebugInfo(graph, i));
+            for (Resource l : lines2Resources)
+                System.out.println("l2r: " + routeNodeDebugInfo(graph, l));
+            for (Resource t : terminals1Resources)
+                System.out.println("t1: " + routeNodeDebugInfo(graph, t));
+            for (Resource t : terminals2Resources)
+                System.out.println("t2: " + routeNodeDebugInfo(graph, t));
+            System.out.println("is horizontal: " + isHorizontal);
+            System.out.println("@(x,y): " + isectX + ", " + isectY);
+        }
+
+        ConnectionUtil cu = new ConnectionUtil(graph);
+        Resource diagram = OrderedSetUtils.getSingleOwnerList(graph, connection, DIA.Diagram);
+
+        Resource diagramConnectionType = graph.getSingleType(connection, DIA.Connection);
+        Resource hasConnectionType = graph.getPossibleObject(connection, STR.HasConnectionType);
+        Resource newConnection = cu.newConnection(diagram, diagramConnectionType);
+        if (hasConnectionType != null)
+            graph.claim(newConnection, STR.HasConnectionType, null, hasConnectionType);
+
+        // Give running name to connection increment the counter attached to the diagram.
+        AddElement.claimFreshElementName(graph, diagram, newConnection);
+
+        String commonLabel = DiagramFlagPreferences
+                .getActiveFlagLabelingScheme(graph)
+                .generateLabel(graph, diagram);
+
+        Point2D pos1, pos2;
+        double theta;
+        double flagDist = 3.0;
+        if(isHorizontal) {
+            theta = 0.0;
+            pos1 = new Point2D.Double(isectX-flagDist, isectY);
+            pos2 = new Point2D.Double(isectX+flagDist, isectY);
+        } else {
+            theta = Math.PI*0.5;
+            pos1 = new Point2D.Double(isectX, isectY-flagDist);
+            pos2 = new Point2D.Double(isectX, isectY+flagDist);
+        }
+
+        if (invertFlagRotation) {
+            theta += Math.PI;
+            Point2D p = pos1;
+            pos1 = pos2;
+            pos2 = p;
+        }
+
+        // WORKAROUND for mapping problems:
+        // If any terminal of the split connection contains a flag, make sure their STR.Joins relations are all removed
+        // to give mapping a chance to fix them properly.
+        removeFlagJoins(graph, cu, connection, terminals1Resources);
+        removeFlagJoins(graph, cu, connection, terminals2Resources);
+
+        // Move route nodes to correct connections
+        for(Resource rn : lines2Resources) {
+            // TODO: use same predicate that was removed
+            graph.denyStatement(connection, DIA.HasInteriorRouteNode, rn);
+            graph.claim(newConnection, DIA.HasInteriorRouteNode, rn);
+        }
+        for(Resource rn : terminals2Resources) {
+            Statement stat = graph.getSingleStatement(rn, DIA.IsConnectorOf);
+            Resource predicate = stat.getPredicate();
+            graph.deny(rn, predicate);
+            graph.claim(rn, predicate, newConnection);
+        }
+
+        // Create flags and connect both disconnected ends to them.
+        Resource flag1 = createFlag(graph, diagram, getFlagTransform(pos1, theta), type1, commonLabel);
+        Resource flag2 = createFlag(graph, diagram, getFlagTransform(pos2, theta), type2, commonLabel);
+
+        if (DEBUG) {
+            System.out.println("LABEL FOR NEW FLAGS: " + commonLabel);
+            System.out.println("FLAG1: " + NameUtils.getSafeName(graph, flag1, true));
+            System.out.println("FLAG2: " + NameUtils.getSafeName(graph, flag2, true));
+        }
+
+        Resource flagConnector1 = cu.newConnector(connection, DIA.HasArrowConnector); 
+        Resource flagConnector2 = cu.newConnector(newConnection, DIA.HasPlainConnector);
+        graph.claim(flag1, DIA.Flag_ConnectionPoint, flagConnector1);
+        graph.claim(flag2, DIA.Flag_ConnectionPoint, flagConnector2);
+
+        double position = isHorizontal ? isectY : isectX;
+        connectFlag(graph, isHorizontal, position, connection, flagConnector1, interfaceNodes1Resources);
+        connectFlag(graph, isHorizontal, position, newConnection, flagConnector2, interfaceNodes2Resources);
+
+        // Join the flags without activatingn diagram mapping at this point
+        FlagUtil.join(graph, flag1, flag2, false);
+        FlagUtil.fixBindsStatements(graph, graph.getPossibleObject(connection, MOD.DiagramConnectionToConnection));
+
+        // Finally ensure that all the diagrams related to the operation are mapped properly in one go
+        FlagUtil.activateMappingForParentDiagramsOf(graph, flag1, flag2);
+    }
+
+    /**
+     * A workaround for problems with mapping not removing the necessary
+     * STR.Joins relations from flags after a split.
+     * 
+     * @param graph
+     * @param cu
+     * @param connection
+     * @param connectors
+     * @throws DatabaseException
+     */
+    private void removeFlagJoins(WriteGraph graph, ConnectionUtil cu, Resource connection, ArrayList<Resource> connectors) throws DatabaseException {
+        for (Resource connector : connectors) {
+            Resource e = cu.getConnectedComponent(connection, connector);
+            if (graph.isInstanceOf(e, DIA.Flag)) {
+                Resource diagram = graph.syncRequest(new PossibleTypedParent(e, DIA.Diagram));
+                if (diagram == null)
+                    continue;
+                for (Resource join : graph.getObjects(e, DIA.FlagIsJoinedBy)) {
+                    Collection<Resource> joinsComposites = graph.getObjects(join, STR.JoinsComposite);
+                    if (joinsComposites.size() == 1) {
+                        // Only remove joins that are internal to a diagram.
+                        graph.deny(join, STR.Joins);
+                    } else if (joinsComposites.size() == 2) {
+                        // Only remove the joins relations that refer to
+                        // connections that are part of the same diagram.
+                        for (Resource joins : graph.getObjects(join, STR.Joins)) {
+                            Resource diagramConnection = graph.getPossibleObject(joins, MOD.ConnectionToDiagramConnection);
+                            if (diagramConnection == null)
+                                continue;
+                            Resource partOfDiagram = graph.syncRequest(new PossibleTypedParent(diagramConnection, DIA.Diagram));
+                            if (diagram.equals(partOfDiagram)) {
+                                graph.deny(join, STR.Joins, joins);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private void connectFlag(WriteGraph graph, boolean isHorizontal, double position, Resource connection, Resource flagConnector, Collection<Resource> interfaceNodes) 
+            throws DatabaseException {
+        if(interfaceNodes.size() > 1) {
+            Resource routeLine = graph.newResource();
+            graph.claim(routeLine, L0.InstanceOf, DIA.RouteLine);
+            graph.claim(connection, DIA.HasInteriorRouteNode, routeLine);
+            graph.claimLiteral(routeLine, DIA.IsHorizontal, isHorizontal);
+            graph.claimLiteral(routeLine, DIA.HasPosition, position);
+            graph.claim(routeLine, DIA.AreConnected, flagConnector);
+            flagConnector = routeLine;
+        }
+        for(Resource rn : interfaceNodes) {
+            graph.claim(flagConnector, DIA.AreConnected, rn);
+        }
+    }
+
+    private AffineTransform getFlagTransform(Point2D pos, double theta) {
+        AffineTransform at = AffineTransform.getTranslateInstance(pos.getX(), pos.getY());
+        at.rotate(theta);
+        return at;
+    }
+
+    private Resource createFlag(WriteGraph graph, Resource diagram, AffineTransform tr, FlagClass.Type type, String label) throws DatabaseException {
+        DiagramResource DIA = DiagramResource.getInstance(graph);
+
+        Resource flag = graph.newResource();
+        graph.claim(flag, L0.InstanceOf, null, DIA.Flag);
+        AddElement.claimFreshElementName(graph, diagram, flag);
+        graph.claim(flag, L0.PartOf, L0.ConsistsOf, diagram);
+        
+        DiagramGraphUtil.setTransform(graph, flag, tr);
+        if (type != null)
+            FlagUtil.setFlagType(graph, flag, type);
+
+        if (label != null)
+            graph.claimLiteral(flag, L0.HasLabel, DIA.FlagLabel, label, Bindings.STRING);
+
+        OrderedSetUtils.add(graph, diagram, flag);
+        return flag;
+    }
+
+    public static void splitConnection(WriteGraph graph, Resource connection, double x, double y) throws DatabaseException {
+        // TODO: provide a proper runtimeDiagram parameter to load to support also connections attached to flags attached to diagram template flag tables
+        RouteGraph rg = RouteGraphUtils.load(graph, null, connection);
+        new RouteGraphConnectionSplitter(graph).split(graph, connection, rg, new Point2D.Double(x, y));
+    }
 }
\ No newline at end of file