]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram/src/org/simantics/diagram/flag/RouteGraphConnectionSplitter.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / flag / RouteGraphConnectionSplitter.java
1 package org.simantics.diagram.flag;\r
2 \r
3 import gnu.trove.map.hash.TObjectIntHashMap;\r
4 \r
5 import java.awt.geom.AffineTransform;\r
6 import java.awt.geom.Point2D;\r
7 import java.util.ArrayList;\r
8 import java.util.Collection;\r
9 \r
10 import javax.vecmath.Tuple2d;\r
11 import javax.vecmath.Vector2d;\r
12 \r
13 import org.simantics.databoard.Bindings;\r
14 import org.simantics.db.ReadGraph;\r
15 import org.simantics.db.Resource;\r
16 import org.simantics.db.Statement;\r
17 import org.simantics.db.WriteGraph;\r
18 import org.simantics.db.common.request.PossibleTypedParent;\r
19 import org.simantics.db.common.utils.NameUtils;\r
20 import org.simantics.db.common.utils.OrderedSetUtils;\r
21 import org.simantics.db.exception.DatabaseException;\r
22 import org.simantics.db.service.SerialisationSupport;\r
23 import org.simantics.diagram.adapter.RouteGraphUtils;\r
24 import org.simantics.diagram.connection.RouteGraph;\r
25 import org.simantics.diagram.connection.RouteLine;\r
26 import org.simantics.diagram.connection.RouteNode;\r
27 import org.simantics.diagram.connection.RoutePoint;\r
28 import org.simantics.diagram.connection.RouteTerminal;\r
29 import org.simantics.diagram.connection.splitting.SplittedRouteGraph;\r
30 import org.simantics.diagram.content.ConnectionUtil;\r
31 import org.simantics.diagram.stubs.DiagramResource;\r
32 import org.simantics.diagram.synchronization.graph.AddElement;\r
33 import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;\r
34 import org.simantics.diagram.synchronization.graph.RouteGraphModification;\r
35 import org.simantics.g2d.elementclass.FlagClass;\r
36 import org.simantics.layer0.Layer0;\r
37 import org.simantics.modeling.ModelingResources;\r
38 import org.simantics.structural.stubs.StructuralResource2;\r
39 \r
40 /**\r
41  * A class that handles splitting a route graph connection in two with diagram\r
42  * local flags.\r
43  * \r
44  * The connection splitting process consists of the following steps:\r
45  * <ol>\r
46  * <li>Disconnect the end points of the selected connection edge segment (SEG).\r
47  * An end point is either :DIA.BranchPoint or (terminal) DIA:Connector. This\r
48  * operation will always split a valid connection into two separate parts.</li>\r
49  * <li>Calculate the contents of the two separated connection parts (branch\r
50  * points and connectors) and decide which part to leave with the existing\r
51  * connection (=P1) and which part to move into a new connection (=P2). The\r
52  * current strategy is to move the parts that\r
53  * <em>do not contain output terminal connectors</em> into a new connection.\r
54  * This works well with diagram to composite mappings where output terminals\r
55  * generate modules behind connections.</li>\r
56  * <li>Create new connection C' with the same type and STR.HasConnectionType as\r
57  * the original connection C.</li>\r
58  * <li>Copy connection routing settings from C to C'.</li>\r
59  * <li>Move P2 into C'.</li>\r
60  * <li>Create two new flag elements into the same diagram, set their type and\r
61  * interpolate a proper transformation for both along the existing connection\r
62  * line.</li>\r
63  * <li>Connect the new flags to begin(SEG) and end(SEG) connectors.</li>\r
64  * <li>Join the flags together.</li>\r
65  * </ol>\r
66  * \r
67  * @author Tuukka Lehtonen\r
68  * @author Hannu Niemist&ouml;\r
69  */\r
70 public class RouteGraphConnectionSplitter {\r
71 \r
72     private final static boolean DEBUG = false;\r
73 \r
74     Layer0              L0;\r
75     DiagramResource     DIA;\r
76     StructuralResource2 STR;\r
77     ModelingResources   MOD;\r
78     SerialisationSupport ss;\r
79 \r
80     public RouteGraphConnectionSplitter(ReadGraph graph) {\r
81         this.L0 = Layer0.getInstance(graph);\r
82         this.DIA = DiagramResource.getInstance(graph);\r
83         this.STR = StructuralResource2.getInstance(graph);\r
84         this.MOD = ModelingResources.getInstance(graph);\r
85         this.ss = graph.getService(SerialisationSupport.class);\r
86     }\r
87     \r
88     public void split(WriteGraph graph, Resource connection, RouteGraph rg, Point2D splitCanvasPos) throws DatabaseException {\r
89         // Create modification writer\r
90         RouteGraphModification modis = new RouteGraphModification(ss, rg);\r
91         TObjectIntHashMap<RouteNode> idMap = modis.getIdMap();\r
92 \r
93         // Find the edge to disconnect in the graph.\r
94         // Bisect the nearest route line.\r
95         RouteLine line = SplittedRouteGraph.findNearestLine(rg, splitCanvasPos);\r
96         if (DEBUG)\r
97             rg.print();\r
98         if (line == null)\r
99             return;\r
100         if (DEBUG) {\r
101             line.print(System.out);\r
102             for (RoutePoint rp : line.getPoints())\r
103                 System.out.println("RP: " + rp.getX() + ", " + rp.getY());\r
104         }\r
105 \r
106         // Get exact intersection point on the line\r
107         double isectX = splitCanvasPos.getX();\r
108         double isectY = splitCanvasPos.getY();\r
109         SplittedRouteGraph srg;\r
110         if (line.isHorizontal()) {\r
111             isectY = line.getPosition();\r
112             srg = rg.splitGraph(line, isectX);\r
113         }\r
114         else {\r
115             isectX = line.getPosition();\r
116             srg = rg.splitGraph(line, isectY);\r
117         }\r
118         if (DEBUG)\r
119             System.out.println(srg);\r
120         \r
121         // Disconnect\r
122         if(rg.isSimpleConnection()) {\r
123             RouteNode na = srg.terminals1.iterator().next();\r
124             RouteNode nb = srg.terminals2.iterator().next();\r
125             Resource a = ss.getResource((Long)na.getData());\r
126             Resource b = ss.getResource((Long)nb.getData());\r
127             graph.deny(a, DIA.AreConnected, b);\r
128             modis.addModi(new RouteGraphModification.RemoveLink(\r
129                     idMap.get(na), idMap.get(nb)\r
130                     ));\r
131         }\r
132         else if(srg.splitLine.isTransient()) {\r
133             RouteTerminal terminal = srg.splitLine.getTerminal();\r
134             Resource connector = ss.getResource((Long)terminal.getData());\r
135             graph.deny(connector, DIA.AreConnected);\r
136             modis.addModi(new RouteGraphModification.RemoveLink(\r
137                     idMap.get(terminal), idMap.get(terminal.getLine())\r
138                     ));\r
139         }\r
140         else  {\r
141             graph.deny(ss.getResource((Long)srg.splitLine.getData()));\r
142             // TODO remove links\r
143             modis.addModi(new RouteGraphModification.RemoveLine(\r
144                     idMap.get(srg.splitLine)\r
145                     ));\r
146         }\r
147         \r
148         ArrayList<Resource> interfaceNodes1Resources = new ArrayList<Resource>(srg.interfaceNodes1.size());\r
149         for(RouteNode n : srg.interfaceNodes1)\r
150             interfaceNodes1Resources.add(ss.getResource((Long)n.getData()));\r
151         ArrayList<Resource> interfaceNodes2Resources = new ArrayList<Resource>(srg.interfaceNodes2.size());\r
152         for(RouteNode n : srg.interfaceNodes2)\r
153             interfaceNodes2Resources.add(ss.getResource((Long)n.getData()));\r
154         \r
155         ArrayList<Resource> lines2Resources = new ArrayList<Resource>(srg.lines2.size());\r
156         for(RouteLine n : srg.lines2)\r
157             lines2Resources.add(ss.getResource((Long)n.getData()));\r
158         \r
159         ArrayList<Resource> terminals1Resources = new ArrayList<Resource>(srg.terminals1.size());\r
160         for(RouteTerminal n : srg.terminals1)\r
161             terminals1Resources.add(ss.getResource((Long)n.getData()));\r
162         ArrayList<Resource> terminals2Resources = new ArrayList<Resource>(srg.terminals2.size());\r
163         for(RouteTerminal n : srg.terminals2)\r
164             terminals2Resources.add(ss.getResource((Long)n.getData()));\r
165         doSplit(graph, connection,\r
166                 interfaceNodes1Resources,\r
167                 interfaceNodes2Resources,\r
168                 lines2Resources,\r
169                 terminals1Resources,\r
170                 terminals2Resources,\r
171                 line.isHorizontal(),\r
172                 isectX, isectY);\r
173         modis.addModi(new RouteGraphModification.Split(\r
174                 modis.toIds(interfaceNodes1Resources),\r
175                 modis.toIds(interfaceNodes2Resources),\r
176                 modis.toIds(lines2Resources),\r
177                 modis.toIds(terminals1Resources),\r
178                 modis.toIds(terminals2Resources),\r
179                 line.isHorizontal(),\r
180                 isectX, isectY\r
181                 ));\r
182         \r
183     }\r
184     \r
185     public void doSplit(WriteGraph graph, \r
186             Resource connection,\r
187             ArrayList<Resource> interfaceNodes1Resources,\r
188             ArrayList<Resource> interfaceNodes2Resources,\r
189             ArrayList<Resource> lines2Resources,\r
190             ArrayList<Resource> terminals1Resources,\r
191             ArrayList<Resource> terminals2Resources,\r
192             boolean isHorizontal, \r
193             double isectX, double isectY) throws DatabaseException {\r
194 \r
195         if (DEBUG) {\r
196             System.out.println("doSplit:");\r
197             System.out.println(NameUtils.getSafeName(graph, connection, true));\r
198             for (Resource i : interfaceNodes1Resources)\r
199                 System.out.println("i1: " + NameUtils.getSafeName(graph, i, true));\r
200             for (Resource i : interfaceNodes2Resources)\r
201                 System.out.println("i2: " + NameUtils.getSafeName(graph, i, true));\r
202             for (Resource l : lines2Resources)\r
203                 System.out.println("l2r: " + NameUtils.getSafeName(graph, l, true));\r
204             for (Resource t : terminals1Resources)\r
205                 System.out.println("t1: " + NameUtils.getSafeName(graph, t, true));\r
206             for (Resource t : terminals2Resources)\r
207                 System.out.println("t2: " + NameUtils.getSafeName(graph, t, true));\r
208             System.out.println("is horizontal: " + isHorizontal);\r
209             System.out.println("@(x,y): " + isectX + ", " + isectY);\r
210         }\r
211 \r
212         ConnectionUtil cu = new ConnectionUtil(graph);\r
213         Resource diagram = OrderedSetUtils.getSingleOwnerList(graph, connection, DIA.Diagram);\r
214 \r
215         Resource connectionType = graph.getSingleType(connection, DIA.Connection);\r
216         Resource hasConnectionType = graph.getPossibleObject(connection, STR.HasConnectionType);\r
217         Resource newConnection = cu.newConnection(diagram, connectionType);\r
218         if (hasConnectionType != null)\r
219             graph.claim(newConnection, STR.HasConnectionType, null, hasConnectionType);\r
220 \r
221         // Give running name to connection increment the counter attached to the diagram.\r
222         AddElement.claimFreshElementName(graph, diagram, newConnection);\r
223 \r
224         // WORKAROUND for mapping problems:\r
225         // If any terminal of the split connection contains a flag, make sure their STR.Joins relations are all removed\r
226         // to give mapping a chance to fix them properly.\r
227         removeFlagJoins(graph, cu, connection, terminals1Resources);\r
228         removeFlagJoins(graph, cu, connection, terminals2Resources);\r
229 \r
230         // Move route nodes to correct connections\r
231         for(Resource rn : lines2Resources) {\r
232             // TODO: use same predicate that was removed\r
233             graph.denyStatement(connection, DIA.HasInteriorRouteNode, rn);\r
234             graph.claim(newConnection, DIA.HasInteriorRouteNode, rn);\r
235         }\r
236         for(Resource rn : terminals2Resources) {\r
237             Statement stat = graph.getSingleStatement(rn, DIA.IsConnectorOf);\r
238             Resource predicate = stat.getPredicate();\r
239             graph.deny(rn, predicate);\r
240             graph.claim(rn, predicate, newConnection);\r
241         }\r
242 \r
243         // 1 = output, 2 = input\r
244         FlagClass.Type type1, type2;\r
245 \r
246         FlagLabelingScheme scheme = DiagramFlagPreferences.getActiveFlagLabelingScheme(graph);\r
247         String commonLabel = scheme.generateLabel(graph, diagram);\r
248 \r
249         // Create flags and connect both disconnected ends to them.\r
250         Vector2d pos1, pos2;\r
251         double theta;\r
252         double flagDist = 3.0;\r
253         if(isHorizontal) {\r
254             theta = 0.0;\r
255             pos1 = new Vector2d(isectX-flagDist, isectY);\r
256             pos2 = new Vector2d(isectX+flagDist, isectY);\r
257         }\r
258         else {\r
259             theta = Math.PI*0.5;\r
260             pos1 = new Vector2d(isectX, isectY-flagDist);\r
261             pos2 = new Vector2d(isectX, isectY+flagDist);\r
262         }\r
263 \r
264         // Chooses flag directions\r
265         {\r
266             @SuppressWarnings("unused")\r
267             int inputs1 = 0, outputs1 = 0;\r
268             for(Resource connector : terminals1Resources) {\r
269                 if(graph.hasStatement(connector, DIA.IsHeadConnectorOf))\r
270                     ++inputs1;\r
271                 else\r
272                     ++outputs1;\r
273             }\r
274             @SuppressWarnings("unused")\r
275             int inputs2 = 0, outputs2 = 0;\r
276             for(Resource connector : terminals2Resources) {\r
277                 if(graph.hasStatement(connector, DIA.IsHeadConnectorOf))\r
278                     ++inputs2;\r
279                 else\r
280                     ++outputs2;\r
281             }\r
282             \r
283             if(outputs1 == 0) {\r
284                 type1 = FlagClass.Type.In;\r
285                 type2 = FlagClass.Type.Out;\r
286                 theta += Math.PI;\r
287             }\r
288             else {\r
289                 type1 = FlagClass.Type.Out;\r
290                 type2 = FlagClass.Type.In;\r
291             }\r
292             if (DEBUG) {\r
293                 System.out.println("inputs1:  " + inputs1);\r
294                 System.out.println("outputs1: " + outputs1);\r
295                 System.out.println("=> type1:  " + type1);\r
296                 System.out.println("inputs2:  " + inputs2);\r
297                 System.out.println("outputs2: " + outputs2);\r
298                 System.out.println("=> type2:  " + type2);\r
299             }\r
300         }\r
301         Resource flag1 = createFlag(graph, diagram, getFlagTransform(pos1, theta), type1, commonLabel);\r
302         Resource flag2 = createFlag(graph, diagram, getFlagTransform(pos2, theta), type2, commonLabel);\r
303         if (DEBUG) {\r
304             System.out.println("FLAG1: " + NameUtils.getSafeName(graph, flag1, true));\r
305             System.out.println("FLAG2: " + NameUtils.getSafeName(graph, flag2, true));\r
306         }\r
307 \r
308 //        System.out.println("conn1: " + NameUtils.getSafeLabel(graph, type1 == FlagClass.Type.In ? DIA.HasPlainConnector : DIA.HasArrowConnector));\r
309 //        System.out.println("conn2: " + NameUtils.getSafeLabel(graph, type2 == FlagClass.Type.In ? DIA.HasPlainConnector : DIA.HasArrowConnector));\r
310         Resource flagConnector1 = cu.newConnector(connection, \r
311                 type1 == FlagClass.Type.In ? DIA.HasPlainConnector : DIA.HasArrowConnector);\r
312         Resource flagConnector2 = cu.newConnector(newConnection, \r
313                 type2 == FlagClass.Type.In ? DIA.HasPlainConnector : DIA.HasArrowConnector);\r
314         graph.claim(flag1, DIA.Flag_ConnectionPoint, flagConnector1);\r
315         graph.claim(flag2, DIA.Flag_ConnectionPoint, flagConnector2);\r
316 \r
317         double position = isHorizontal ? isectY : isectX;\r
318         connectFlag(graph, isHorizontal, position, connection, flagConnector1, \r
319                 interfaceNodes1Resources);\r
320         connectFlag(graph, isHorizontal, position, newConnection, flagConnector2, \r
321                 interfaceNodes2Resources);\r
322 \r
323         FlagUtil.join(graph, flag1, flag2);\r
324 \r
325         // Move mapping relations to new connection if necessary\r
326         if(type1 == FlagClass.Type.In) {\r
327             moveStatements(graph, connection, newConnection, MOD.ElementToComponent);\r
328             moveStatements(graph, connection, newConnection, MOD.DiagramConnectionToConnection);\r
329             moveStatements(graph, connection, newConnection, MOD.DiagramConnectionToConnectionSpecial);\r
330             FlagUtil.fixBindsStatements(graph, graph.getPossibleObject(newConnection, MOD.DiagramConnectionToConnection));\r
331         }\r
332         else\r
333             FlagUtil.fixBindsStatements(graph, graph.getPossibleObject(connection, MOD.DiagramConnectionToConnection));\r
334     }\r
335 \r
336     /**\r
337      * A workaround for problems with mapping not removing the necessary\r
338      * STR.Joins relations from flags after a split.\r
339      * \r
340      * @param graph\r
341      * @param cu\r
342      * @param connection\r
343      * @param connectors\r
344      * @throws DatabaseException\r
345      */\r
346     private void removeFlagJoins(WriteGraph graph, ConnectionUtil cu, Resource connection, ArrayList<Resource> connectors) throws DatabaseException {\r
347         for (Resource connector : connectors) {\r
348             Resource e = cu.getConnectedComponent(connection, connector);\r
349             if (graph.isInstanceOf(e, DIA.Flag)) {\r
350                 Resource diagram = graph.syncRequest(new PossibleTypedParent(e, DIA.Diagram));\r
351                 if (diagram == null)\r
352                     continue;\r
353                 for (Resource join : graph.getObjects(e, DIA.FlagIsJoinedBy)) {\r
354                     Collection<Resource> joinsComposites = graph.getObjects(join, STR.JoinsComposite);\r
355                     if (joinsComposites.size() == 1) {\r
356                         // Only remove joins that are internal to a diagram.\r
357                         graph.deny(join, STR.Joins);\r
358                     } else if (joinsComposites.size() == 2) {\r
359                         // Only remove the joins relations that refer to\r
360                         // connections that are part of the same diagram.\r
361                         for (Resource joins : graph.getObjects(join, STR.Joins)) {\r
362                             Resource diagramConnection = graph.getPossibleObject(joins, MOD.ConnectionToDiagramConnection);\r
363                             if (diagramConnection == null)\r
364                                 continue;\r
365                             Resource partOfDiagram = graph.syncRequest(new PossibleTypedParent(diagramConnection, DIA.Diagram));\r
366                             if (diagram.equals(partOfDiagram)) {\r
367                                 graph.deny(join, STR.Joins, joins);\r
368                             }\r
369                         }\r
370                     }\r
371                 }\r
372             }\r
373         }\r
374     }\r
375 \r
376     private static void moveStatements(WriteGraph graph, Resource from, Resource to, Resource relation) throws DatabaseException {\r
377         if(from.equals(to))\r
378                 return;\r
379         for(Statement stat : graph.getStatements(from, relation))\r
380                 if(stat.getSubject().equals(from))\r
381                         graph.claim(to, stat.getPredicate(), stat.getObject());\r
382         graph.deny(from, relation);\r
383     }\r
384     \r
385     private void connectFlag(WriteGraph graph, boolean isHorizontal, double position, Resource connection, Resource flagConnector, Collection<Resource> interfaceNodes) \r
386             throws DatabaseException {\r
387         if(interfaceNodes.size() > 1) {\r
388             Resource routeLine = graph.newResource();\r
389             graph.claim(routeLine, L0.InstanceOf, DIA.RouteLine);\r
390             graph.claim(connection, DIA.HasInteriorRouteNode, routeLine);\r
391             graph.claimLiteral(routeLine, DIA.IsHorizontal, isHorizontal);\r
392             graph.claimLiteral(routeLine, DIA.HasPosition, position);\r
393             graph.claim(routeLine, DIA.AreConnected, flagConnector);\r
394             flagConnector = routeLine;\r
395         }\r
396         for(Resource rn : interfaceNodes) {\r
397             graph.claim(flagConnector, DIA.AreConnected, rn);\r
398         }\r
399     }\r
400 \r
401     private AffineTransform getFlagTransform(Tuple2d pos, double theta) {\r
402         AffineTransform at = AffineTransform.getTranslateInstance(pos.x, pos.y);\r
403         at.rotate(theta);\r
404         return at;\r
405     }\r
406 \r
407     private Resource createFlag(WriteGraph graph, Resource diagram, AffineTransform tr, FlagClass.Type type, String label) throws DatabaseException {\r
408         DiagramResource DIA = DiagramResource.getInstance(graph);\r
409 \r
410         Resource flag = graph.newResource();\r
411         graph.claim(flag, L0.InstanceOf, null, DIA.Flag);\r
412         AddElement.claimFreshElementName(graph, diagram, flag);\r
413         graph.claim(flag, L0.PartOf, L0.ConsistsOf, diagram);\r
414         \r
415         DiagramGraphUtil.setTransform(graph, flag, tr);\r
416         if (type != null)\r
417             FlagUtil.setFlagType(graph, flag, type);\r
418 \r
419         if (label != null)\r
420             graph.claimLiteral(flag, L0.HasLabel, DIA.FlagLabel, label, Bindings.STRING);\r
421 \r
422         OrderedSetUtils.add(graph, diagram, flag);\r
423         return flag;\r
424     }\r
425 \r
426     public static void splitConnection(WriteGraph graph, Resource connection, double x, double y) throws DatabaseException {\r
427         RouteGraph rg = RouteGraphUtils.load(graph, null, connection);\r
428         new RouteGraphConnectionSplitter(graph).split(graph, connection, rg, new Point2D.Double(x, y));\r
429     }\r
430 }