1 package org.simantics.diagram.flag;
3 import java.awt.geom.AffineTransform;
4 import java.awt.geom.Point2D;
5 import java.util.ArrayList;
6 import java.util.Collection;
9 import org.simantics.databoard.Bindings;
10 import org.simantics.db.ReadGraph;
11 import org.simantics.db.Resource;
12 import org.simantics.db.Statement;
13 import org.simantics.db.WriteGraph;
14 import org.simantics.db.common.request.PossibleTypedParent;
15 import org.simantics.db.common.utils.NameUtils;
16 import org.simantics.db.common.utils.OrderedSetUtils;
17 import org.simantics.db.exception.DatabaseException;
18 import org.simantics.db.service.SerialisationSupport;
19 import org.simantics.diagram.adapter.RouteGraphUtils;
20 import org.simantics.diagram.connection.RouteGraph;
21 import org.simantics.diagram.connection.RouteLine;
22 import org.simantics.diagram.connection.RouteNode;
23 import org.simantics.diagram.connection.RoutePoint;
24 import org.simantics.diagram.connection.RouteTerminal;
25 import org.simantics.diagram.connection.splitting.SplittedRouteGraph;
26 import org.simantics.diagram.connection.splitting.SplittedRouteGraph.PickResult;
27 import org.simantics.diagram.content.ConnectionUtil;
28 import org.simantics.diagram.stubs.DiagramResource;
29 import org.simantics.diagram.synchronization.graph.AddElement;
30 import org.simantics.diagram.synchronization.graph.BasicResources;
31 import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;
32 import org.simantics.diagram.synchronization.graph.RouteGraphModification;
33 import org.simantics.g2d.elementclass.FlagClass;
34 import org.simantics.layer0.Layer0;
35 import org.simantics.modeling.ModelingResources;
36 import org.simantics.structural.stubs.StructuralResource2;
38 import gnu.trove.map.hash.TObjectIntHashMap;
41 * A class that handles splitting a route graph connection in two with diagram
44 * The connection splitting process consists of the following steps:
46 * <li>Disconnect the end points of the selected connection edge segment (SEG).
47 * An end point is either :DIA.BranchPoint or (terminal) DIA:Connector. This
48 * operation will always split a valid connection into two separate parts.</li>
49 * <li>Calculate the contents of the two separated connection parts (branch
50 * points and connectors) and decide which part to leave with the existing
51 * connection (=P1) and which part to move into a new connection (=P2). The
52 * current strategy is to move the parts that
53 * <em>do not contain output terminal connectors</em> into a new connection.
54 * This works well with diagram to composite mappings where output terminals
55 * generate modules behind connections.</li>
56 * <li>Create new connection C' with the same type and STR.HasConnectionType as
57 * the original connection C.</li>
58 * <li>Copy connection routing settings from C to C'.</li>
59 * <li>Move P2 into C'.</li>
60 * <li>Create two new flag elements into the same diagram, set their type and
61 * interpolate a proper transformation for both along the existing connection
63 * <li>Connect the new flags to begin(SEG) and end(SEG) connectors.</li>
64 * <li>Join the flags together.</li>
67 * @author Tuukka Lehtonen
68 * @author Hannu Niemistö
70 public class RouteGraphConnectionSplitter {
72 private final static boolean DEBUG = false;
76 StructuralResource2 STR;
77 ModelingResources MOD;
78 SerialisationSupport ss;
80 public RouteGraphConnectionSplitter(ReadGraph graph) {
81 this.L0 = Layer0.getInstance(graph);
82 this.DIA = DiagramResource.getInstance(graph);
83 this.STR = StructuralResource2.getInstance(graph);
84 this.MOD = ModelingResources.getInstance(graph);
85 this.ss = graph.getService(SerialisationSupport.class);
88 public void split(WriteGraph graph, Resource connection, RouteGraph rg, Point2D splitCanvasPos) throws DatabaseException {
89 // Create modification writer
90 RouteGraphModification modis = new RouteGraphModification(ss, rg);
91 TObjectIntHashMap<RouteNode> idMap = modis.getIdMap();
94 System.out.println("Split canvas position: " + splitCanvasPos);
98 // Find the edge to disconnect in the graph.
99 // Bisect the nearest route line.
100 PickResult picked = SplittedRouteGraph.pickNearestLine(rg, splitCanvasPos.getX(), splitCanvasPos.getY());
104 RouteLine line = picked.nearestLine;
107 System.out.println("picked nearest line:");
108 line.print(System.out);
109 for (RoutePoint rp : line.getPoints())
110 System.out.println("RP: " + rp.getX() + ", " + rp.getY());
113 // Get exact intersection point on the line
114 double isectX = picked.intersectionPoint.getX();
115 double isectY = picked.intersectionPoint.getY();
116 SplittedRouteGraph srg = rg.splitGraph(line, line.isHorizontal() ? isectX : isectY);
118 System.out.println(srg);
121 if(rg.isSimpleConnection()) {
122 RouteNode na = srg.terminals1.iterator().next();
123 RouteNode nb = srg.terminals2.iterator().next();
124 Resource a = ss.getResource((Long)na.getData());
125 Resource b = ss.getResource((Long)nb.getData());
126 graph.deny(a, DIA.AreConnected, b);
127 modis.addModi(new RouteGraphModification.RemoveLink(
128 idMap.get(na), idMap.get(nb)
131 else if(srg.splitLine.isTransient()) {
132 RouteTerminal terminal = srg.splitLine.getTerminal();
133 Resource connector = ss.getResource((Long)terminal.getData());
134 graph.deny(connector, DIA.AreConnected);
135 modis.addModi(new RouteGraphModification.RemoveLink(
136 idMap.get(terminal), idMap.get(terminal.getLine())
140 graph.deny(ss.getResource((Long)srg.splitLine.getData()));
142 modis.addModi(new RouteGraphModification.RemoveLine(
143 idMap.get(srg.splitLine)
146 ArrayList<Resource> terminals1Resources = toResources(srg.terminals1);
147 ArrayList<Resource> terminals2Resources = toResources(srg.terminals2);
149 boolean mustFlip = analyzePartInputs(graph, terminals1Resources, terminals2Resources);
151 ArrayList<Resource> interfaceNodes1 = toResources(mustFlip ? srg.interfaceNodes2 : srg.interfaceNodes1);
152 ArrayList<Resource> interfaceNodes2 = toResources(mustFlip ? srg.interfaceNodes1 : srg.interfaceNodes2);
154 ArrayList<Resource> lines2 = toResources(mustFlip ? srg.lines1 : srg.lines2);
155 ArrayList<Resource> terminals1 = mustFlip ? terminals2Resources : terminals1Resources;
156 ArrayList<Resource> terminals2 = mustFlip ? terminals1Resources : terminals2Resources;
158 doSplit(graph, connection,
167 modis.addModi(new RouteGraphModification.Split(
168 modis.toIds(interfaceNodes1),
169 modis.toIds(interfaceNodes2),
171 modis.toIds(terminals1),
172 modis.toIds(terminals2),
179 private ArrayList<Resource> toResources(Collection<? extends RouteNode> nodes) throws DatabaseException {
180 ArrayList<Resource> result = new ArrayList<>(nodes.size());
181 for (RouteNode n : nodes)
182 result.add(ss.getResource((Long)n.getData()));
190 * @return <code>true</code> if inputs need to be flipped, i.e. if terminals2
191 * contains the output terminals and terminals1 doesn't.
192 * @throws DatabaseException
194 private boolean analyzePartInputs(ReadGraph graph, List<Resource> terminals1, List<Resource> terminals2) throws DatabaseException {
195 @SuppressWarnings("unused")
196 int inputs1 = 0, outputs1 = 0;
197 for(Resource connector : terminals1) {
198 if(graph.hasStatement(connector, DIA.IsHeadConnectorOf))
203 @SuppressWarnings("unused")
204 int inputs2 = 0, outputs2 = 0;
205 for(Resource connector : terminals2) {
206 if(graph.hasStatement(connector, DIA.IsHeadConnectorOf))
212 boolean mustFlip = outputs1 == 0;
215 System.out.println("inputs1: " + inputs1);
216 System.out.println("outputs1: " + outputs1);
217 System.out.println("inputs2: " + inputs2);
218 System.out.println("outputs2: " + outputs2);
219 System.out.println("=> type1: " + (mustFlip ? FlagClass.Type.In : FlagClass.Type.Out));
220 System.out.println("=> type2: " + (mustFlip ? FlagClass.Type.Out : FlagClass.Type.In));
221 System.out.println("=> must flip route graph parts to split: " + mustFlip);
227 private static String routeNodeDebugInfo(ReadGraph graph, Resource c) throws DatabaseException {
228 BasicResources BR = BasicResources.getInstance(graph);
229 String ctr = NameUtils.getSafeName(graph, c, true);
230 for (Resource e : graph.getObjects(c, BR.STR.Connects)) {
231 ctr += " --> " + NameUtils.getSafeName(graph, e);
233 for (Resource e : graph.getObjects(c, BR.DIA.AreConnected)) {
234 ctr += " <-> " + NameUtils.getSafeName(graph, e);
240 * Internal routine that is only public because
241 * {@link RouteGraphModification#runUpdates(WriteGraph)} needs to invoke it.
243 * Assumes that #1 parameters will stay with the existing connection and #2
244 * parameters will go to the newly created connection.
246 public void doSplit(WriteGraph graph,
248 ArrayList<Resource> interfaceNodes1Resources,
249 ArrayList<Resource> interfaceNodes2Resources,
250 ArrayList<Resource> lines2Resources,
251 ArrayList<Resource> terminals1Resources,
252 ArrayList<Resource> terminals2Resources,
253 boolean isHorizontal,
254 boolean invertFlagRotation,
255 double isectX, double isectY) throws DatabaseException {
257 // 1 = output, 2 = input
259 type1 = FlagClass.Type.Out,
260 type2 = FlagClass.Type.In;
263 System.out.println("doSplit:");
264 System.out.println(NameUtils.getSafeName(graph, connection, true));
265 for (Resource i : interfaceNodes1Resources)
266 System.out.println("i1: " + routeNodeDebugInfo(graph, i));
267 for (Resource i : interfaceNodes2Resources)
268 System.out.println("i2: " + routeNodeDebugInfo(graph, i));
269 for (Resource l : lines2Resources)
270 System.out.println("l2r: " + routeNodeDebugInfo(graph, l));
271 for (Resource t : terminals1Resources)
272 System.out.println("t1: " + routeNodeDebugInfo(graph, t));
273 for (Resource t : terminals2Resources)
274 System.out.println("t2: " + routeNodeDebugInfo(graph, t));
275 System.out.println("is horizontal: " + isHorizontal);
276 System.out.println("@(x,y): " + isectX + ", " + isectY);
279 ConnectionUtil cu = new ConnectionUtil(graph);
280 Resource diagram = OrderedSetUtils.getSingleOwnerList(graph, connection, DIA.Diagram);
282 Resource diagramConnectionType = graph.getSingleType(connection, DIA.Connection);
283 Resource hasConnectionType = graph.getPossibleObject(connection, STR.HasConnectionType);
284 Resource newConnection = cu.newConnection(diagram, diagramConnectionType);
285 if (hasConnectionType != null)
286 graph.claim(newConnection, STR.HasConnectionType, null, hasConnectionType);
288 // Give running name to connection increment the counter attached to the diagram.
289 AddElement.claimFreshElementName(graph, diagram, newConnection);
291 String commonLabel = DiagramFlagPreferences
292 .getActiveFlagLabelingScheme(graph)
293 .generateLabel(graph, diagram);
297 double flagDist = 3.0;
300 pos1 = new Point2D.Double(isectX-flagDist, isectY);
301 pos2 = new Point2D.Double(isectX+flagDist, isectY);
304 pos1 = new Point2D.Double(isectX, isectY-flagDist);
305 pos2 = new Point2D.Double(isectX, isectY+flagDist);
308 if (invertFlagRotation) {
315 // WORKAROUND for mapping problems:
316 // If any terminal of the split connection contains a flag, make sure their STR.Joins relations are all removed
317 // to give mapping a chance to fix them properly.
318 removeFlagJoins(graph, cu, connection, terminals1Resources);
319 removeFlagJoins(graph, cu, connection, terminals2Resources);
321 // Move route nodes to correct connections
322 for(Resource rn : lines2Resources) {
323 // TODO: use same predicate that was removed
324 graph.denyStatement(connection, DIA.HasInteriorRouteNode, rn);
325 graph.claim(newConnection, DIA.HasInteriorRouteNode, rn);
327 for(Resource rn : terminals2Resources) {
328 Statement stat = graph.getSingleStatement(rn, DIA.IsConnectorOf);
329 Resource predicate = stat.getPredicate();
330 graph.deny(rn, predicate);
331 graph.claim(rn, predicate, newConnection);
334 // Create flags and connect both disconnected ends to them.
335 Resource flag1 = createFlag(graph, diagram, getFlagTransform(pos1, theta), type1, commonLabel);
336 Resource flag2 = createFlag(graph, diagram, getFlagTransform(pos2, theta), type2, commonLabel);
339 System.out.println("LABEL FOR NEW FLAGS: " + commonLabel);
340 System.out.println("FLAG1: " + NameUtils.getSafeName(graph, flag1, true));
341 System.out.println("FLAG2: " + NameUtils.getSafeName(graph, flag2, true));
344 Resource flagConnector1 = cu.newConnector(connection, DIA.HasArrowConnector);
345 Resource flagConnector2 = cu.newConnector(newConnection, DIA.HasPlainConnector);
346 graph.claim(flag1, DIA.Flag_ConnectionPoint, flagConnector1);
347 graph.claim(flag2, DIA.Flag_ConnectionPoint, flagConnector2);
349 double position = isHorizontal ? isectY : isectX;
350 connectFlag(graph, isHorizontal, position, connection, flagConnector1, interfaceNodes1Resources);
351 connectFlag(graph, isHorizontal, position, newConnection, flagConnector2, interfaceNodes2Resources);
353 // Join the flags without activatingn diagram mapping at this point
354 FlagUtil.join(graph, flag1, flag2, false);
355 FlagUtil.fixBindsStatements(graph, graph.getPossibleObject(connection, MOD.DiagramConnectionToConnection));
357 // Finally ensure that all the diagrams related to the operation are mapped properly in one go
358 FlagUtil.activateMappingForParentDiagramsOf(graph, flag1, flag2);
362 * A workaround for problems with mapping not removing the necessary
363 * STR.Joins relations from flags after a split.
369 * @throws DatabaseException
371 private void removeFlagJoins(WriteGraph graph, ConnectionUtil cu, Resource connection, ArrayList<Resource> connectors) throws DatabaseException {
372 for (Resource connector : connectors) {
373 Resource e = cu.getConnectedComponent(connection, connector);
374 if (graph.isInstanceOf(e, DIA.Flag)) {
375 Resource diagram = graph.syncRequest(new PossibleTypedParent(e, DIA.Diagram));
378 for (Resource join : graph.getObjects(e, DIA.FlagIsJoinedBy)) {
379 Collection<Resource> joinsComposites = graph.getObjects(join, STR.JoinsComposite);
380 if (joinsComposites.size() == 1) {
381 // Only remove joins that are internal to a diagram.
382 graph.deny(join, STR.Joins);
383 } else if (joinsComposites.size() == 2) {
384 // Only remove the joins relations that refer to
385 // connections that are part of the same diagram.
386 for (Resource joins : graph.getObjects(join, STR.Joins)) {
387 Resource diagramConnection = graph.getPossibleObject(joins, MOD.ConnectionToDiagramConnection);
388 if (diagramConnection == null)
390 Resource partOfDiagram = graph.syncRequest(new PossibleTypedParent(diagramConnection, DIA.Diagram));
391 if (diagram.equals(partOfDiagram)) {
392 graph.deny(join, STR.Joins, joins);
401 private void connectFlag(WriteGraph graph, boolean isHorizontal, double position, Resource connection, Resource flagConnector, Collection<Resource> interfaceNodes)
402 throws DatabaseException {
403 if(interfaceNodes.size() > 1) {
404 Resource routeLine = graph.newResource();
405 graph.claim(routeLine, L0.InstanceOf, DIA.RouteLine);
406 graph.claim(connection, DIA.HasInteriorRouteNode, routeLine);
407 graph.claimLiteral(routeLine, DIA.IsHorizontal, isHorizontal);
408 graph.claimLiteral(routeLine, DIA.HasPosition, position);
409 graph.claim(routeLine, DIA.AreConnected, flagConnector);
410 flagConnector = routeLine;
412 for(Resource rn : interfaceNodes) {
413 graph.claim(flagConnector, DIA.AreConnected, rn);
417 private AffineTransform getFlagTransform(Point2D pos, double theta) {
418 AffineTransform at = AffineTransform.getTranslateInstance(pos.getX(), pos.getY());
423 private Resource createFlag(WriteGraph graph, Resource diagram, AffineTransform tr, FlagClass.Type type, String label) throws DatabaseException {
424 DiagramResource DIA = DiagramResource.getInstance(graph);
426 Resource flag = graph.newResource();
427 graph.claim(flag, L0.InstanceOf, null, DIA.Flag);
428 AddElement.claimFreshElementName(graph, diagram, flag);
429 graph.claim(flag, L0.PartOf, L0.ConsistsOf, diagram);
431 DiagramGraphUtil.setTransform(graph, flag, tr);
433 FlagUtil.setFlagType(graph, flag, type);
436 graph.claimLiteral(flag, L0.HasLabel, DIA.FlagLabel, label, Bindings.STRING);
438 OrderedSetUtils.add(graph, diagram, flag);
442 public static void splitConnection(WriteGraph graph, Resource connection, double x, double y) throws DatabaseException {
443 // TODO: provide a proper runtimeDiagram parameter to load to support also connections attached to flags attached to diagram template flag tables
444 RouteGraph rg = RouteGraphUtils.load(graph, null, connection);
445 new RouteGraphConnectionSplitter(graph).split(graph, connection, rg, new Point2D.Double(x, y));