X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.diagram%2Fsrc%2Forg%2Fsimantics%2Fdiagram%2Fflag%2FRouteGraphConnectionSplitter.java;h=5c85a99440a9d7efe09b2eaaff2338d56b78291f;hp=c23fe1d878b1945447642f2ee566e7ad663eb253;hb=HEAD;hpb=969bd23cab98a79ca9101af33334000879fb60c5 diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/flag/RouteGraphConnectionSplitter.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/flag/RouteGraphConnectionSplitter.java index c23fe1d87..5c85a9944 100644 --- a/bundles/org.simantics.diagram/src/org/simantics/diagram/flag/RouteGraphConnectionSplitter.java +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/flag/RouteGraphConnectionSplitter.java @@ -1,430 +1,447 @@ -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 javax.vecmath.Tuple2d; -import javax.vecmath.Vector2d; - -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.content.ConnectionUtil; -import org.simantics.diagram.stubs.DiagramResource; -import org.simantics.diagram.synchronization.graph.AddElement; -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; - -/** - * A class that handles splitting a route graph connection in two with diagram - * local flags. - * - * The connection splitting process consists of the following steps: - *
    - *
  1. 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.
  2. - *
  3. 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 - * do not contain output terminal connectors into a new connection. - * This works well with diagram to composite mappings where output terminals - * generate modules behind connections.
  4. - *
  5. Create new connection C' with the same type and STR.HasConnectionType as - * the original connection C.
  6. - *
  7. Copy connection routing settings from C to C'.
  8. - *
  9. Move P2 into C'.
  10. - *
  11. Create two new flag elements into the same diagram, set their type and - * interpolate a proper transformation for both along the existing connection - * line.
  12. - *
  13. Connect the new flags to begin(SEG) and end(SEG) connectors.
  14. - *
  15. Join the flags together.
  16. - *
- * - * @author Tuukka Lehtonen - * @author Hannu Niemistö - */ -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 idMap = modis.getIdMap(); - - // 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) - return; - if (DEBUG) { - 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); - } - 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 interfaceNodes1Resources = new ArrayList(srg.interfaceNodes1.size()); - for(RouteNode n : srg.interfaceNodes1) - interfaceNodes1Resources.add(ss.getResource((Long)n.getData())); - ArrayList interfaceNodes2Resources = new ArrayList(srg.interfaceNodes2.size()); - for(RouteNode n : srg.interfaceNodes2) - interfaceNodes2Resources.add(ss.getResource((Long)n.getData())); - - ArrayList lines2Resources = new ArrayList(srg.lines2.size()); - for(RouteLine n : srg.lines2) - lines2Resources.add(ss.getResource((Long)n.getData())); - - ArrayList terminals1Resources = new ArrayList(srg.terminals1.size()); - for(RouteTerminal n : srg.terminals1) - terminals1Resources.add(ss.getResource((Long)n.getData())); - ArrayList terminals2Resources = new ArrayList(srg.terminals2.size()); - for(RouteTerminal n : srg.terminals2) - terminals2Resources.add(ss.getResource((Long)n.getData())); - doSplit(graph, connection, - interfaceNodes1Resources, - interfaceNodes2Resources, - lines2Resources, - terminals1Resources, - terminals2Resources, - line.isHorizontal(), - isectX, isectY); - modis.addModi(new RouteGraphModification.Split( - modis.toIds(interfaceNodes1Resources), - modis.toIds(interfaceNodes2Resources), - modis.toIds(lines2Resources), - modis.toIds(terminals1Resources), - modis.toIds(terminals2Resources), - line.isHorizontal(), - isectX, isectY - )); - - } - - public void doSplit(WriteGraph graph, - Resource connection, - ArrayList interfaceNodes1Resources, - ArrayList interfaceNodes2Resources, - ArrayList lines2Resources, - ArrayList terminals1Resources, - ArrayList terminals2Resources, - boolean isHorizontal, - double isectX, double isectY) throws DatabaseException { - - 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)); - for (Resource i : interfaceNodes2Resources) - System.out.println("i2: " + NameUtils.getSafeName(graph, i, true)); - for (Resource l : lines2Resources) - System.out.println("l2r: " + NameUtils.getSafeName(graph, l, true)); - for (Resource t : terminals1Resources) - System.out.println("t1: " + NameUtils.getSafeName(graph, t, true)); - for (Resource t : terminals2Resources) - System.out.println("t2: " + NameUtils.getSafeName(graph, t, true)); - 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 connectionType = graph.getSingleType(connection, DIA.Connection); - Resource hasConnectionType = graph.getPossibleObject(connection, STR.HasConnectionType); - Resource newConnection = cu.newConnection(diagram, connectionType); - 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); - - // 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); - } - - // 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. - Vector2d pos1, pos2; - double theta; - double flagDist = 3.0; - if(isHorizontal) { - theta = 0.0; - pos1 = new Vector2d(isectX-flagDist, isectY); - pos2 = new Vector2d(isectX+flagDist, isectY); - } - else { - theta = Math.PI*0.5; - pos1 = new Vector2d(isectX, isectY-flagDist); - pos2 = new Vector2d(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("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); - 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)); - } - - /** - * 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 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 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 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 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(Tuple2d pos, double theta) { - AffineTransform at = AffineTransform.getTranslateInstance(pos.x, pos.y); - 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 { - RouteGraph rg = RouteGraphUtils.load(graph, null, connection); - new RouteGraphConnectionSplitter(graph).split(graph, connection, rg, new Point2D.Double(x, y)); - } +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: + *
    + *
  1. 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.
  2. + *
  3. 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 + * do not contain output terminal connectors into a new connection. + * This works well with diagram to composite mappings where output terminals + * generate modules behind connections.
  4. + *
  5. Create new connection C' with the same type and STR.HasConnectionType as + * the original connection C.
  6. + *
  7. Copy connection routing settings from C to C'.
  8. + *
  9. Move P2 into C'.
  10. + *
  11. Create two new flag elements into the same diagram, set their type and + * interpolate a proper transformation for both along the existing connection + * line.
  12. + *
  13. Connect the new flags to begin(SEG) and end(SEG) connectors.
  14. + *
  15. Join the flags together.
  16. + *
+ * + * @author Tuukka Lehtonen + * @author Hannu Niemistö + */ +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 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 terminals1Resources = toResources(srg.terminals1); + ArrayList terminals2Resources = toResources(srg.terminals2); + + boolean mustFlip = analyzePartInputs(graph, terminals1Resources, terminals2Resources); + + ArrayList interfaceNodes1 = toResources(mustFlip ? srg.interfaceNodes2 : srg.interfaceNodes1); + ArrayList interfaceNodes2 = toResources(mustFlip ? srg.interfaceNodes1 : srg.interfaceNodes2); + + ArrayList lines2 = toResources(mustFlip ? srg.lines1 : srg.lines2); + ArrayList terminals1 = mustFlip ? terminals2Resources : terminals1Resources; + ArrayList 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 toResources(Collection nodes) throws DatabaseException { + ArrayList 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 true 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 terminals1, List 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 interfaceNodes1Resources, + ArrayList interfaceNodes2Resources, + ArrayList lines2Resources, + ArrayList terminals1Resources, + ArrayList 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 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 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 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