]> gerrit.simantics Code Review - simantics/platform.git/blob - 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
1 package org.simantics.diagram.flag;
2
3 import java.awt.geom.AffineTransform;
4 import java.awt.geom.Point2D;
5 import java.util.ArrayList;
6 import java.util.Collection;
7 import java.util.List;
8
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;
37
38 import gnu.trove.map.hash.TObjectIntHashMap;
39
40 /**
41  * A class that handles splitting a route graph connection in two with diagram
42  * local flags.
43  * 
44  * The connection splitting process consists of the following steps:
45  * <ol>
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
62  * line.</li>
63  * <li>Connect the new flags to begin(SEG) and end(SEG) connectors.</li>
64  * <li>Join the flags together.</li>
65  * </ol>
66  * 
67  * @author Tuukka Lehtonen
68  * @author Hannu Niemist&ouml;
69  */
70 public class RouteGraphConnectionSplitter {
71
72     private final static boolean DEBUG = false;
73
74     Layer0              L0;
75     DiagramResource     DIA;
76     StructuralResource2 STR;
77     ModelingResources   MOD;
78     SerialisationSupport ss;
79
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);
86     }
87     
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();
92
93         if (DEBUG) {
94             System.out.println("Split canvas position: " + splitCanvasPos);
95             rg.print();
96         }
97
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());
101         if (picked == null)
102             return;
103
104         RouteLine line = picked.nearestLine;
105
106         if (DEBUG) {
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());
111         }
112
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);
117         if (DEBUG)
118             System.out.println(srg);
119
120         // Disconnect
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)
129                     ));
130         }
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())
137                     ));
138         }
139         else  {
140             graph.deny(ss.getResource((Long)srg.splitLine.getData()));
141             // TODO remove links
142             modis.addModi(new RouteGraphModification.RemoveLine(
143                     idMap.get(srg.splitLine)
144                     ));
145         }
146         ArrayList<Resource> terminals1Resources = toResources(srg.terminals1);
147         ArrayList<Resource> terminals2Resources = toResources(srg.terminals2);
148
149         boolean mustFlip = analyzePartInputs(graph, terminals1Resources, terminals2Resources);
150
151         ArrayList<Resource> interfaceNodes1 = toResources(mustFlip ? srg.interfaceNodes2 : srg.interfaceNodes1);
152         ArrayList<Resource> interfaceNodes2 = toResources(mustFlip ? srg.interfaceNodes1 : srg.interfaceNodes2);
153
154         ArrayList<Resource> lines2 = toResources(mustFlip ? srg.lines1 : srg.lines2);
155         ArrayList<Resource> terminals1 = mustFlip ? terminals2Resources : terminals1Resources;
156         ArrayList<Resource> terminals2 = mustFlip ? terminals1Resources : terminals2Resources;
157
158         doSplit(graph, connection,
159                 interfaceNodes1,
160                 interfaceNodes2,
161                 lines2,
162                 terminals1,
163                 terminals2,
164                 line.isHorizontal(),
165                 mustFlip,
166                 isectX, isectY);
167         modis.addModi(new RouteGraphModification.Split(
168                 modis.toIds(interfaceNodes1),
169                 modis.toIds(interfaceNodes2),
170                 modis.toIds(lines2),
171                 modis.toIds(terminals1),
172                 modis.toIds(terminals2),
173                 line.isHorizontal(),
174                 mustFlip,
175                 isectX, isectY
176                 ));
177     }
178
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()));
183         return result;
184     }
185
186     /**
187      * @param graph
188      * @param terminals1
189      * @param terminals2
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
193      */
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))
199                 ++inputs1;
200             else
201                 ++outputs1;
202         }
203         @SuppressWarnings("unused")
204         int inputs2 = 0, outputs2 = 0;
205         for(Resource connector : terminals2) {
206             if(graph.hasStatement(connector, DIA.IsHeadConnectorOf))
207                 ++inputs2;
208             else
209                 ++outputs2;
210         }
211
212         boolean mustFlip = outputs1 == 0;
213
214         if (DEBUG) {
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);
222         }
223
224         return mustFlip;
225     }
226
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);
232         }
233         for (Resource e : graph.getObjects(c, BR.DIA.AreConnected)) {
234             ctr += " <-> " + NameUtils.getSafeName(graph, e);
235         }
236         return ctr;
237     }
238
239     /**
240      * Internal routine that is only public because
241      * {@link RouteGraphModification#runUpdates(WriteGraph)} needs to invoke it.
242      * 
243      * Assumes that #1 parameters will stay with the existing connection and #2
244      * parameters will go to the newly created connection.
245      */
246     public void doSplit(WriteGraph graph, 
247             Resource connection,
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 {
256
257         // 1 = output, 2 = input
258         FlagClass.Type
259                 type1 = FlagClass.Type.Out,
260                 type2 = FlagClass.Type.In;
261
262         if (DEBUG) {
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);
277         }
278
279         ConnectionUtil cu = new ConnectionUtil(graph);
280         Resource diagram = OrderedSetUtils.getSingleOwnerList(graph, connection, DIA.Diagram);
281
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);
287
288         // Give running name to connection increment the counter attached to the diagram.
289         AddElement.claimFreshElementName(graph, diagram, newConnection);
290
291         String commonLabel = DiagramFlagPreferences
292                 .getActiveFlagLabelingScheme(graph)
293                 .generateLabel(graph, diagram);
294
295         Point2D pos1, pos2;
296         double theta;
297         double flagDist = 3.0;
298         if(isHorizontal) {
299             theta = 0.0;
300             pos1 = new Point2D.Double(isectX-flagDist, isectY);
301             pos2 = new Point2D.Double(isectX+flagDist, isectY);
302         } else {
303             theta = Math.PI*0.5;
304             pos1 = new Point2D.Double(isectX, isectY-flagDist);
305             pos2 = new Point2D.Double(isectX, isectY+flagDist);
306         }
307
308         if (invertFlagRotation) {
309             theta += Math.PI;
310             Point2D p = pos1;
311             pos1 = pos2;
312             pos2 = p;
313         }
314
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);
320
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);
326         }
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);
332         }
333
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);
337
338         if (DEBUG) {
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));
342         }
343
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);
348
349         double position = isHorizontal ? isectY : isectX;
350         connectFlag(graph, isHorizontal, position, connection, flagConnector1, interfaceNodes1Resources);
351         connectFlag(graph, isHorizontal, position, newConnection, flagConnector2, interfaceNodes2Resources);
352
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));
356
357         // Finally ensure that all the diagrams related to the operation are mapped properly in one go
358         FlagUtil.activateMappingForParentDiagramsOf(graph, flag1, flag2);
359     }
360
361     /**
362      * A workaround for problems with mapping not removing the necessary
363      * STR.Joins relations from flags after a split.
364      * 
365      * @param graph
366      * @param cu
367      * @param connection
368      * @param connectors
369      * @throws DatabaseException
370      */
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));
376                 if (diagram == null)
377                     continue;
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)
389                                 continue;
390                             Resource partOfDiagram = graph.syncRequest(new PossibleTypedParent(diagramConnection, DIA.Diagram));
391                             if (diagram.equals(partOfDiagram)) {
392                                 graph.deny(join, STR.Joins, joins);
393                             }
394                         }
395                     }
396                 }
397             }
398         }
399     }
400
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;
411         }
412         for(Resource rn : interfaceNodes) {
413             graph.claim(flagConnector, DIA.AreConnected, rn);
414         }
415     }
416
417     private AffineTransform getFlagTransform(Point2D pos, double theta) {
418         AffineTransform at = AffineTransform.getTranslateInstance(pos.getX(), pos.getY());
419         at.rotate(theta);
420         return at;
421     }
422
423     private Resource createFlag(WriteGraph graph, Resource diagram, AffineTransform tr, FlagClass.Type type, String label) throws DatabaseException {
424         DiagramResource DIA = DiagramResource.getInstance(graph);
425
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);
430         
431         DiagramGraphUtil.setTransform(graph, flag, tr);
432         if (type != null)
433             FlagUtil.setFlagType(graph, flag, type);
434
435         if (label != null)
436             graph.claimLiteral(flag, L0.HasLabel, DIA.FlagLabel, label, Bindings.STRING);
437
438         OrderedSetUtils.add(graph, diagram, flag);
439         return flag;
440     }
441
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));
446     }
447 }