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