]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.diagram/src/org/simantics/diagram/flag/RouteGraphConnectionSplitter.java
Fixed route graph splitting and diagram mapping race condition problem
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / flag / RouteGraphConnectionSplitter.java
index 3f1076b5df48e70047cd4dae8d9531bd494c9d0d..5c85a99440a9d7efe09b2eaaff2338d56b78291f 100644 (file)
@@ -1,11 +1,10 @@
 package org.simantics.diagram.flag;
 
-import gnu.trove.map.hash.TObjectIntHashMap;
-
 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;
@@ -24,9 +23,11 @@ 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;
@@ -34,6 +35,8 @@ 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.
@@ -87,34 +90,33 @@ public class RouteGraphConnectionSplitter {
         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.
-        RouteLine line = SplittedRouteGraph.findNearestLine(rg, splitCanvasPos);
-        if (DEBUG)
-            rg.print();
-        if (line == null)
+        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 = splitCanvasPos.getX();
-        double isectY = splitCanvasPos.getY();
-        SplittedRouteGraph srg;
-        if (line.isHorizontal()) {
-            isectY = line.getPosition();
-            srg = rg.splitGraph(line, isectX);
-        }
-        else {
-            isectX = line.getPosition();
-            srg = rg.splitGraph(line, isectY);
-        }
+        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();
@@ -141,44 +143,106 @@ public class RouteGraphConnectionSplitter {
                     idMap.get(srg.splitLine)
                     ));
         }
-        
-        ArrayList<Resource> interfaceNodes1Resources = new ArrayList<Resource>(srg.interfaceNodes1.size());
-        for(RouteNode n : srg.interfaceNodes1)
-            interfaceNodes1Resources.add(ss.getResource((Long)n.getData()));
-        ArrayList<Resource> interfaceNodes2Resources = new ArrayList<Resource>(srg.interfaceNodes2.size());
-        for(RouteNode n : srg.interfaceNodes2)
-            interfaceNodes2Resources.add(ss.getResource((Long)n.getData()));
-        
-        ArrayList<Resource> lines2Resources = new ArrayList<Resource>(srg.lines2.size());
-        for(RouteLine n : srg.lines2)
-            lines2Resources.add(ss.getResource((Long)n.getData()));
-        
-        ArrayList<Resource> terminals1Resources = new ArrayList<Resource>(srg.terminals1.size());
-        for(RouteTerminal n : srg.terminals1)
-            terminals1Resources.add(ss.getResource((Long)n.getData()));
-        ArrayList<Resource> terminals2Resources = new ArrayList<Resource>(srg.terminals2.size());
-        for(RouteTerminal n : srg.terminals2)
-            terminals2Resources.add(ss.getResource((Long)n.getData()));
+        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,
-                interfaceNodes1Resources,
-                interfaceNodes2Resources,
-                lines2Resources,
-                terminals1Resources,
-                terminals2Resources,
+                interfaceNodes1,
+                interfaceNodes2,
+                lines2,
+                terminals1,
+                terminals2,
                 line.isHorizontal(),
+                mustFlip,
                 isectX, isectY);
         modis.addModi(new RouteGraphModification.Split(
-                modis.toIds(interfaceNodes1Resources),
-                modis.toIds(interfaceNodes2Resources),
-                modis.toIds(lines2Resources),
-                modis.toIds(terminals1Resources),
-                modis.toIds(terminals2Resources),
+                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,
@@ -186,22 +250,28 @@ public class RouteGraphConnectionSplitter {
             ArrayList<Resource> lines2Resources,
             ArrayList<Resource> terminals1Resources,
             ArrayList<Resource> terminals2Resources,
-            boolean isHorizontal, 
+            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: " + NameUtils.getSafeName(graph, i, true));
+                System.out.println("i1: " + routeNodeDebugInfo(graph, i));
             for (Resource i : interfaceNodes2Resources)
-                System.out.println("i2: " + NameUtils.getSafeName(graph, i, true));
+                System.out.println("i2: " + routeNodeDebugInfo(graph, i));
             for (Resource l : lines2Resources)
-                System.out.println("l2r: " + NameUtils.getSafeName(graph, l, true));
+                System.out.println("l2r: " + routeNodeDebugInfo(graph, l));
             for (Resource t : terminals1Resources)
-                System.out.println("t1: " + NameUtils.getSafeName(graph, t, true));
+                System.out.println("t1: " + routeNodeDebugInfo(graph, t));
             for (Resource t : terminals2Resources)
-                System.out.println("t2: " + NameUtils.getSafeName(graph, t, true));
+                System.out.println("t2: " + routeNodeDebugInfo(graph, t));
             System.out.println("is horizontal: " + isHorizontal);
             System.out.println("@(x,y): " + isectX + ", " + isectY);
         }
@@ -209,15 +279,39 @@ public class RouteGraphConnectionSplitter {
         ConnectionUtil cu = new ConnectionUtil(graph);
         Resource diagram = OrderedSetUtils.getSingleOwnerList(graph, connection, DIA.Diagram);
 
-        Resource connectionType = graph.getSingleType(connection, DIA.Connection);
+        Resource diagramConnectionType = graph.getSingleType(connection, DIA.Connection);
         Resource hasConnectionType = graph.getPossibleObject(connection, STR.HasConnectionType);
-        Resource newConnection = cu.newConnection(diagram, connectionType);
+        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.
@@ -237,97 +331,31 @@ public class RouteGraphConnectionSplitter {
             graph.claim(rn, predicate, newConnection);
         }
 
-        // 1 = output, 2 = input
-        FlagClass.Type type1, type2;
-
-        FlagLabelingScheme scheme = DiagramFlagPreferences.getActiveFlagLabelingScheme(graph);
-        String commonLabel = scheme.generateLabel(graph, diagram);
-
         // Create flags and connect both disconnected ends to them.
-        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);
-        }
-
-        // Chooses flag directions
-        {
-            @SuppressWarnings("unused")
-            int inputs1 = 0, outputs1 = 0;
-            for(Resource connector : terminals1Resources) {
-                if(graph.hasStatement(connector, DIA.IsHeadConnectorOf))
-                    ++inputs1;
-                else
-                    ++outputs1;
-            }
-            @SuppressWarnings("unused")
-            int inputs2 = 0, outputs2 = 0;
-            for(Resource connector : terminals2Resources) {
-                if(graph.hasStatement(connector, DIA.IsHeadConnectorOf))
-                    ++inputs2;
-                else
-                    ++outputs2;
-            }
-            
-            if(outputs1 == 0) {
-                type1 = FlagClass.Type.In;
-                type2 = FlagClass.Type.Out;
-                theta += Math.PI;
-            }
-            else {
-                type1 = FlagClass.Type.Out;
-                type2 = FlagClass.Type.In;
-            }
-            if (DEBUG) {
-                System.out.println("inputs1:  " + inputs1);
-                System.out.println("outputs1: " + outputs1);
-                System.out.println("=> type1:  " + type1);
-                System.out.println("inputs2:  " + inputs2);
-                System.out.println("outputs2: " + outputs2);
-                System.out.println("=> type2:  " + type2);
-            }
-        }
         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));
         }
 
-//        System.out.println("conn1: " + NameUtils.getSafeLabel(graph, type1 == FlagClass.Type.In ? DIA.HasPlainConnector : DIA.HasArrowConnector));
-//        System.out.println("conn2: " + NameUtils.getSafeLabel(graph, type2 == FlagClass.Type.In ? DIA.HasPlainConnector : DIA.HasArrowConnector));
-        Resource flagConnector1 = cu.newConnector(connection, 
-                type1 == FlagClass.Type.In ? DIA.HasPlainConnector : DIA.HasArrowConnector);
-        Resource flagConnector2 = cu.newConnector(newConnection, 
-                type2 == FlagClass.Type.In ? DIA.HasPlainConnector : DIA.HasArrowConnector);
+        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);
-
-        FlagUtil.join(graph, flag1, flag2);
-
-        // Move mapping relations to new connection if necessary
-        if(type1 == FlagClass.Type.In) {
-            moveStatements(graph, connection, newConnection, MOD.ElementToComponent);
-            moveStatements(graph, connection, newConnection, MOD.DiagramConnectionToConnection);
-            moveStatements(graph, connection, newConnection, MOD.DiagramConnectionToConnectionSpecial);
-            FlagUtil.fixBindsStatements(graph, graph.getPossibleObject(newConnection, MOD.DiagramConnectionToConnection));
-        }
-        else
-            FlagUtil.fixBindsStatements(graph, graph.getPossibleObject(connection, MOD.DiagramConnectionToConnection));
+        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);
     }
 
     /**
@@ -370,15 +398,6 @@ public class RouteGraphConnectionSplitter {
         }
     }
 
-    private static void moveStatements(WriteGraph graph, Resource from, Resource to, Resource relation) throws DatabaseException {
-       if(from.equals(to))
-               return;
-       for(Statement stat : graph.getStatements(from, relation))
-               if(stat.getSubject().equals(from))
-                       graph.claim(to, stat.getPredicate(), stat.getObject());
-       graph.deny(from, relation);
-    }
-    
     private void connectFlag(WriteGraph graph, boolean isHorizontal, double position, Resource connection, Resource flagConnector, Collection<Resource> interfaceNodes) 
             throws DatabaseException {
         if(interfaceNodes.size() > 1) {
@@ -421,6 +440,7 @@ public class RouteGraphConnectionSplitter {
     }
 
     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));
     }