]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram/src/org/simantics/diagram/participant/ConnectionBuilder.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / participant / ConnectionBuilder.java
1 /*******************************************************************************\r
2  * Copyright (c) 2007, 2016 Association for Decentralized Information Management\r
3  * in Industry THTH ry.\r
4  * All rights reserved. This program and the accompanying materials\r
5  * are made available under the terms of the Eclipse Public License v1.0\r
6  * which accompanies this distribution, and is available at\r
7  * http://www.eclipse.org/legal/epl-v10.html\r
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *     Semantum Oy - Fixed bug #6364\r
12  *******************************************************************************/\r
13 package org.simantics.diagram.participant;\r
14 \r
15 import java.awt.geom.AffineTransform;\r
16 import java.util.ArrayDeque;\r
17 import java.util.ArrayList;\r
18 import java.util.Arrays;\r
19 import java.util.Collection;\r
20 import java.util.Collections;\r
21 import java.util.Deque;\r
22 import java.util.Iterator;\r
23 import java.util.List;\r
24 \r
25 import org.eclipse.jface.dialogs.MessageDialog;\r
26 import org.eclipse.swt.SWT;\r
27 import org.eclipse.ui.IWorkbenchWindow;\r
28 import org.eclipse.ui.PlatformUI;\r
29 import org.simantics.databoard.Bindings;\r
30 import org.simantics.db.ReadGraph;\r
31 import org.simantics.db.Resource;\r
32 import org.simantics.db.Session;\r
33 import org.simantics.db.Statement;\r
34 import org.simantics.db.VirtualGraph;\r
35 import org.simantics.db.WriteGraph;\r
36 import org.simantics.db.common.CommentMetadata;\r
37 import org.simantics.db.common.request.WriteRequest;\r
38 import org.simantics.db.common.utils.OrderedSetUtils;\r
39 import org.simantics.db.exception.DatabaseException;\r
40 import org.simantics.diagram.content.ConnectionUtil;\r
41 import org.simantics.diagram.content.ResourceTerminal;\r
42 import org.simantics.diagram.flag.DiagramFlagPreferences;\r
43 import org.simantics.diagram.flag.FlagLabelingScheme;\r
44 import org.simantics.diagram.flag.FlagUtil;\r
45 import org.simantics.diagram.flag.IOTableUtil;\r
46 import org.simantics.diagram.flag.IOTablesInfo;\r
47 import org.simantics.diagram.flag.Joiner;\r
48 import org.simantics.diagram.stubs.DiagramResource;\r
49 import org.simantics.diagram.stubs.G2DResource;\r
50 import org.simantics.diagram.synchronization.ISynchronizationContext;\r
51 import org.simantics.diagram.synchronization.SynchronizationHints;\r
52 import org.simantics.diagram.synchronization.graph.AddElement;\r
53 import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;\r
54 import org.simantics.diagram.synchronization.graph.GraphSynchronizationHints;\r
55 import org.simantics.diagram.synchronization.graph.RemoveElement;\r
56 import org.simantics.diagram.synchronization.graph.layer.GraphLayerManager;\r
57 import org.simantics.diagram.ui.DiagramModelHints;\r
58 import org.simantics.g2d.connection.handler.ConnectionHandler;\r
59 import org.simantics.g2d.diagram.DiagramHints;\r
60 import org.simantics.g2d.diagram.IDiagram;\r
61 import org.simantics.g2d.diagram.handler.Topology.Terminal;\r
62 import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil.TerminalInfo;\r
63 import org.simantics.g2d.element.ElementClass;\r
64 import org.simantics.g2d.element.ElementClasses;\r
65 import org.simantics.g2d.element.ElementHints;\r
66 import org.simantics.g2d.element.ElementUtils;\r
67 import org.simantics.g2d.element.IElement;\r
68 import org.simantics.g2d.element.IElementClassProvider;\r
69 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;\r
70 import org.simantics.g2d.element.impl.Element;\r
71 import org.simantics.g2d.elementclass.BranchPoint;\r
72 import org.simantics.g2d.elementclass.FlagClass;\r
73 import org.simantics.layer0.Layer0;\r
74 import org.simantics.modeling.ModelingResources;\r
75 import org.simantics.scl.runtime.tuple.Tuple2;\r
76 import org.simantics.structural.stubs.StructuralResource2;\r
77 import org.simantics.structural2.modelingRules.CPTerminal;\r
78 import org.simantics.structural2.modelingRules.ConnectionJudgement;\r
79 import org.simantics.structural2.modelingRules.IConnectionPoint;\r
80 import org.simantics.structural2.modelingRules.IModelingRules;\r
81 import org.simantics.utils.datastructures.Callback;\r
82 import org.simantics.utils.datastructures.Pair;\r
83 import org.simantics.utils.ui.ErrorLogger;\r
84 \r
85 /**\r
86  * @author Tuukka Lehtonen\r
87  */\r
88 public class ConnectionBuilder {\r
89 \r
90     protected static class Connector extends Tuple2 {\r
91         public Connector(Resource attachmentRelation, Resource connector) {\r
92             super(attachmentRelation, connector);\r
93         }\r
94         public Resource getAttachment() {\r
95             return (Resource) c0;\r
96         }\r
97         public Resource getConnector() {\r
98             return (Resource) c1;\r
99         }\r
100     }\r
101 \r
102     protected final IDiagram                diagram;\r
103     protected final Resource                diagramResource;\r
104     protected final boolean                 createFlags;\r
105 \r
106     protected final ISynchronizationContext ctx;\r
107     protected final IElementClassProvider   elementClassProvider;\r
108     protected final GraphLayerManager       layerManager;\r
109 \r
110     protected ConnectionUtil                cu;\r
111 \r
112     protected Layer0                        L0;\r
113     protected DiagramResource               DIA;\r
114     protected StructuralResource2           STR;\r
115     protected ModelingResources             MOD;\r
116 \r
117     public ConnectionBuilder(IDiagram diagram) {\r
118         this.diagram = diagram;\r
119         this.diagramResource = diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE);\r
120         this.createFlags = Boolean.TRUE.equals(diagram.getHint(DiagramHints.KEY_USE_CONNECTION_FLAGS));\r
121 \r
122         ctx = diagram.getHint(SynchronizationHints.CONTEXT);\r
123         if (ctx != null) {\r
124             this.elementClassProvider = ctx.get(SynchronizationHints.ELEMENT_CLASS_PROVIDER);\r
125             this.layerManager = ctx.get(GraphSynchronizationHints.GRAPH_LAYER_MANAGER);\r
126         } else {\r
127             this.elementClassProvider = null;\r
128             this.layerManager = null;\r
129         }\r
130     }\r
131 \r
132     protected void initializeResources(ReadGraph graph) {\r
133         if (this.L0 == null) {\r
134             this.L0 = Layer0.getInstance(graph);\r
135             this.DIA = DiagramResource.getInstance(graph);\r
136             this.STR = StructuralResource2.getInstance(graph);\r
137             this.MOD = ModelingResources.getInstance(graph);\r
138         }\r
139     }\r
140 \r
141     /**\r
142      * @param graph\r
143      * @param judgment\r
144      * @param controlPoints\r
145      * @param startTerminal\r
146      * @param endTerminal\r
147      * @throws DatabaseException\r
148      */\r
149     public void create(WriteGraph graph, final ConnectionJudgement judgment, Deque<ControlPoint> controlPoints,\r
150             TerminalInfo startTerminal, TerminalInfo endTerminal) throws DatabaseException {\r
151         this.cu = new ConnectionUtil(graph);\r
152         initializeResources(graph);\r
153 \r
154         final IModelingRules modelingRules = diagram.getHint(DiagramModelHints.KEY_MODELING_RULES);\r
155 \r
156         final Resource startDisconnectedFlag = getDisconnectedFlag(graph, startTerminal);\r
157         final Resource endDisconnectedFlag = getDisconnectedFlag(graph, endTerminal);\r
158         if (startDisconnectedFlag != null || endDisconnectedFlag != null) {\r
159             if (startDisconnectedFlag != null && endDisconnectedFlag != null) {\r
160 \r
161                 // Ask the user which operation to perform:\r
162                 // a) connect the disconnected flags together with a connection join\r
163                 // b) join the flags into a single connection\r
164 \r
165                 final VirtualGraph graphProvider = graph.getProvider();\r
166                 final Session session = graph.getSession();\r
167 \r
168                 PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {\r
169                     @Override\r
170                     public void run() {\r
171                         IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();\r
172                         if (window == null)\r
173                             return;\r
174                         MessageDialog dialog = new MessageDialog(window.getShell(), "Connect or Join Flags?", null,\r
175                                 "Connect flags together or join them visually into a connection?",\r
176                                 MessageDialog.QUESTION_WITH_CANCEL, new String[] { "Connect Flags", "Join Flags",\r
177                                         "Cancel" }, 0) {\r
178                             {\r
179                                 setShellStyle(getShellStyle() | SWT.SHEET);\r
180                             }\r
181                         };\r
182                         final int choice = dialog.open();\r
183 \r
184                         if (choice != 2 && choice != SWT.DEFAULT) {\r
185                             session.asyncRequest(new WriteRequest(graphProvider) {\r
186                                 @Override\r
187                                 public void perform(WriteGraph graph) throws DatabaseException {\r
188                                     graph.markUndoPoint();\r
189                                     switch (choice) {\r
190                                         case 0: {\r
191                                             Resource join = FlagUtil.join(graph, startDisconnectedFlag, endDisconnectedFlag);\r
192                                             FlagLabelingScheme scheme = DiagramFlagPreferences.getActiveFlagLabelingScheme(graph);\r
193                                             String commonLabel = scheme.generateLabel(graph, diagramResource);\r
194                                             graph.claimLiteral(startDisconnectedFlag, L0.HasLabel, DIA.FlagLabel, commonLabel);\r
195                                             graph.claimLiteral(endDisconnectedFlag, L0.HasLabel, DIA.FlagLabel, commonLabel);\r
196 \r
197                                             // Set connection type according to modeling rules\r
198                                             setJoinedConnectionTypes(graph, modelingRules, judgment, join);\r
199 \r
200                                             // Add comment to change set.\r
201                                             CommentMetadata cm = graph.getMetadata(CommentMetadata.class);\r
202                                             graph.addMetadata(cm.add("Connected flags"));\r
203 \r
204                                             return;\r
205                                         }\r
206                                         case 1: {\r
207                                             // First connect the flags together\r
208                                             Resource join = FlagUtil.join(graph, startDisconnectedFlag, endDisconnectedFlag);\r
209 \r
210                                             // Set connection type according to modeling rules\r
211                                             setJoinedConnectionTypes(graph, modelingRules, judgment, join);\r
212 \r
213                                             // Join the flags into a direct connection\r
214                                             new Joiner(graph).joinLocal(graph, Arrays.asList(startDisconnectedFlag, endDisconnectedFlag));\r
215 \r
216                                             // Add comment to change set.\r
217                                             CommentMetadata cm = graph.getMetadata(CommentMetadata.class);\r
218                                             graph.addMetadata(cm.add("Joined flags"));\r
219 \r
220                                             return;\r
221                                         }\r
222                                     }\r
223                                 }\r
224                             }, new Callback<DatabaseException>() {\r
225                                 @Override\r
226                                 public void run(DatabaseException e) {\r
227                                     if (e != null)\r
228                                         ErrorLogger.defaultLogError(e);\r
229                                 }\r
230                             });\r
231                         }\r
232                     }\r
233                 });\r
234 \r
235                 return;\r
236             }\r
237 \r
238             TerminalInfo normalTerminal = null;\r
239             Resource flagToRemove = null;\r
240             Resource connection = null;\r
241             if (startDisconnectedFlag != null) {\r
242                 flagToRemove = startDisconnectedFlag;\r
243                 normalTerminal = endTerminal;\r
244                 connection = attachedToExistingConnection(graph, startTerminal);\r
245             }\r
246             if (endDisconnectedFlag != null) {\r
247                 flagToRemove = endDisconnectedFlag;\r
248                 normalTerminal = startTerminal;\r
249                 connection = attachedToExistingConnection(graph, endTerminal);\r
250             }\r
251             if (connection != null) {\r
252                 // OK, continuing a connection from an existing disconnected flag.\r
253 \r
254                 // STEPS TO PERFORM:\r
255                 // 1. remove flag\r
256                 // 2. connect normal terminal directly to the existing connection\r
257                 Statement stm = graph.getSingleStatement(flagToRemove, STR.IsConnectedTo);\r
258                 Collection<Resource> areConnecteds = graph.getObjects(stm.getObject(), DIA.AreConnected);\r
259 \r
260                 // Remove statement to connection connector before removing flag\r
261                 // to prevent removal of connector and the connection.\r
262                 graph.deny(stm);\r
263                 new RemoveElement((Resource)diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE), flagToRemove).perform(graph);\r
264 \r
265                 // Disconnect the connector from the connection and create a\r
266                 // new connector for the element terminal.\r
267                 cu.removeConnectionPart(stm.getObject());\r
268                 Connector newConnector = createConnectorForNode(graph, connection,\r
269                         (Resource) ElementUtils.getObject(normalTerminal.e), normalTerminal.t,\r
270                         startDisconnectedFlag != null ? EdgeEnd.End : EdgeEnd.Begin, judgment);\r
271 \r
272                 for (Resource areConnected : areConnecteds)\r
273                     graph.claim(newConnector.getConnector(), DIA.AreConnected, areConnected);\r
274 \r
275                 if (modelingRules != null && judgment.connectionType != null)\r
276                     modelingRules.setConnectionType(graph, connection, judgment.connectionType);\r
277 \r
278                 // Add comment to change set.\r
279                 CommentMetadata cm = graph.getMetadata(CommentMetadata.class);\r
280                 graph.addMetadata(cm.add("Joined flags"));\r
281                 graph.markUndoPoint();\r
282                 this.cu = null;\r
283                 return;\r
284             }\r
285         }\r
286 \r
287         // 1. Get diagram connection to construct.\r
288         Resource connection = getOrCreateConnection(graph, startTerminal, endTerminal);\r
289 \r
290         // 1.1 Give running name to connection and increment the counter attached to the diagram.\r
291         AddElement.claimFreshElementName(graph, diagramResource, connection);\r
292 \r
293         // 2. Add branch points\r
294         // 3. Create edges between branch points.\r
295         List<Pair<ControlPoint, Resource>> bps = Collections.emptyList();\r
296         Resource firstBranchPoint = null;\r
297         Resource lastBranchPoint = null;\r
298         if (!isRouteGraphConnection(graph, connection)) {\r
299             bps = createBranchPoints(graph, connection, controlPoints);\r
300             if (!bps.isEmpty()) {\r
301                 Iterator<Pair<ControlPoint, Resource>> it = bps.iterator();\r
302                 Pair<ControlPoint, Resource> prev = it.next();\r
303                 firstBranchPoint = prev.second;\r
304                 while (it.hasNext()) {\r
305                     Pair<ControlPoint, Resource> next = it.next();\r
306                     cu.connect(prev.second, next.second);\r
307                     prev = next;\r
308                 }\r
309                 lastBranchPoint = prev.second;\r
310             }\r
311         }\r
312 \r
313         // 4. Connect start/end terminals if those exist.\r
314         // If first/lastBranchPoint != null, connect to those.\r
315         // Otherwise connect the start/end terminals together.\r
316         Connector startConnector = null;\r
317         Connector endConnector = null;\r
318         IElement startFlag = null;\r
319         IElement endFlag = null;\r
320 \r
321         //FlagLabelingScheme scheme = DiagramFlagPreferences.getActiveFlagLabelingScheme(graph);\r
322 \r
323         if (startTerminal != null && endTerminal != null) {\r
324             Resource startAttachment = chooseAttachmentRelationForNode(graph, connection, startTerminal, judgment);\r
325             Resource endAttachment = chooseAttachmentRelationForNode(graph, connection, endTerminal, judgment);\r
326             Pair<Resource, Resource> attachments = resolveEndAttachments(graph, startAttachment, endAttachment);\r
327             startConnector = createConnectorForNodeWithAttachment(graph, connection, startTerminal, attachments.first);\r
328             endConnector = createConnectorForNodeWithAttachment(graph, connection, endTerminal, attachments.second);\r
329         } else if (startTerminal != null) {\r
330             startConnector = createConnectorForNode(graph, connection, startTerminal, EdgeEnd.Begin, judgment);\r
331             if (createFlags) {\r
332                 EdgeEnd flagEnd = cu.toEdgeEnd( cu.getAttachmentRelationForConnector(startConnector.getConnector()), EdgeEnd.End ).other();\r
333                 endFlag = createFlag(graph, connection, flagEnd, controlPoints.getLast(), FlagClass.Type.Out,\r
334                         //scheme.generateLabel(graph, diagramResource));\r
335                         null);\r
336                 endConnector = createConnectorForNode(graph, connection, (Resource) ElementUtils.getObject(endFlag),\r
337                         ElementUtils.getSingleTerminal(endFlag), flagEnd, judgment);\r
338             }\r
339         } else if (endTerminal != null) {\r
340             endConnector = createConnectorForNode(graph, connection, endTerminal, EdgeEnd.End, judgment);\r
341             if (createFlags) {\r
342                 EdgeEnd flagEnd = cu.toEdgeEnd( cu.getAttachmentRelationForConnector(endConnector.getConnector()), EdgeEnd.Begin ).other();\r
343                 startFlag = createFlag(graph, connection, flagEnd, controlPoints.getFirst(), FlagClass.Type.In,\r
344                         //scheme.generateLabel(graph, diagramResource));\r
345                         null);\r
346                 startConnector = createConnectorForNode(graph, connection, (Resource) ElementUtils.getObject(startFlag),\r
347                         ElementUtils.getSingleTerminal(startFlag), flagEnd, judgment);\r
348             }\r
349         } else if (createFlags) {\r
350             startFlag = createFlag(graph, connection, EdgeEnd.Begin, controlPoints.getFirst(), FlagClass.Type.In,\r
351                     //scheme.generateLabel(graph, diagramResource));\r
352                     null);\r
353             startConnector = createConnectorForNode(graph, connection, (Resource) ElementUtils.getObject(startFlag),\r
354                     ElementUtils.getSingleTerminal(startFlag), EdgeEnd.Begin, judgment);\r
355 \r
356             endFlag = createFlag(graph, connection, EdgeEnd.End, controlPoints.getLast(), FlagClass.Type.Out,\r
357                     //scheme.generateLabel(graph, diagramResource));\r
358                     null);\r
359             endConnector = createConnectorForNode(graph, connection, (Resource) ElementUtils.getObject(endFlag),\r
360                     ElementUtils.getSingleTerminal(endFlag), EdgeEnd.End, judgment);\r
361         }\r
362 \r
363         if (firstBranchPoint == null || lastBranchPoint == null) {\r
364             cu.connect(startConnector.getConnector(), endConnector.getConnector());\r
365         } else {\r
366             cu.connect(startConnector.getConnector(), firstBranchPoint);\r
367             cu.connect(lastBranchPoint, endConnector.getConnector());\r
368         }\r
369 \r
370         // 5. Finally, set connection type according to modeling rules\r
371         if (judgment.connectionType != null && modelingRules != null)\r
372             modelingRules.setConnectionType(graph, connection, judgment.connectionType);\r
373 \r
374         // 5.1 Verify created flag types\r
375         if (startFlag != null)\r
376             verifyFlagType(graph, modelingRules, startFlag);\r
377         if (endFlag != null)\r
378             verifyFlagType(graph, modelingRules, endFlag);\r
379 \r
380         // 5.2 Write ConnectionMappingSpecification to connector if necessary\r
381         writeConnectionMappingSpecification(graph, startTerminal, startConnector, judgment.connectionType);\r
382         writeConnectionMappingSpecification(graph, endTerminal, endConnector, judgment.connectionType);\r
383 \r
384         // 6. Add comment to change set.\r
385         CommentMetadata cm = graph.getMetadata(CommentMetadata.class);\r
386         graph.addMetadata(cm.add("Added connection " + connection));\r
387         graph.markUndoPoint();\r
388         this.cu = null;\r
389     }\r
390 \r
391     private boolean writeConnectionMappingSpecification(WriteGraph graph, TerminalInfo terminal, Connector connector, Resource connectionType)\r
392             throws DatabaseException {\r
393         Resource diagramConnRel = getConnectionRelation(graph, terminal);\r
394         if (diagramConnRel == null)\r
395             return false;\r
396         Resource connRel = graph.getPossibleObject(diagramConnRel, MOD.DiagramConnectionRelationToConnectionRelation);\r
397         if (connRel == null || !graph.hasStatement(connRel, MOD.NeedsConnectionMappingSpecification))\r
398             return false;\r
399         Resource mappingSpecification = graph.getPossibleObject(connectionType, MOD.ConnectionTypeToConnectionMappingSpecification);\r
400         if (mappingSpecification == null)\r
401             return false;\r
402         graph.claim(connector.getConnector(), MOD.HasConnectionMappingSpecification, null, mappingSpecification);\r
403         return true;\r
404     }\r
405 \r
406     private static Resource getConnectionRelation(ReadGraph graph, TerminalInfo ti) throws DatabaseException {\r
407         if (ti != null && ti.t instanceof ResourceTerminal) {\r
408             Resource t = ((ResourceTerminal) ti.t).getResource();\r
409             Resource bindingRelation = DiagramGraphUtil.getConnectionPointOfTerminal(graph, t);\r
410             return bindingRelation;\r
411         }\r
412         return null;\r
413     }\r
414 \r
415     /**\r
416      * @param graph\r
417      * @param startAttachment\r
418      * @param endAttachment\r
419      * @return\r
420      * @throws DatabaseException \r
421      */\r
422     protected Pair<Resource, Resource> resolveEndAttachments(WriteGraph graph,\r
423             Resource startAttachment, Resource endAttachment) throws DatabaseException {\r
424         if (startAttachment != null && endAttachment != null)\r
425             return Pair.make(startAttachment, endAttachment);\r
426 \r
427         if (startAttachment != null && endAttachment == null)\r
428             return Pair.make(startAttachment, getInverseAttachment(graph, startAttachment, DIA.HasArrowConnector));\r
429         if (startAttachment == null && endAttachment != null)\r
430             return Pair.make(getInverseAttachment(graph, endAttachment, DIA.HasPlainConnector), endAttachment);\r
431 \r
432         return Pair.make(DIA.HasPlainConnector, DIA.HasArrowConnector);\r
433     }\r
434 \r
435     /**\r
436      * @param graph\r
437      * @param attachment\r
438      * @return\r
439      * @throws DatabaseException\r
440      */\r
441     protected Resource getInverseAttachment(ReadGraph graph, Resource attachment, Resource defaultValue) throws DatabaseException {\r
442         Resource inverse = attachment != null ? graph.getPossibleObject(attachment, DIA.HasInverseAttachment) : defaultValue;\r
443         return inverse != null ? inverse : defaultValue;\r
444     }\r
445 \r
446     /**\r
447      * @param graph\r
448      * @param modelingRules\r
449      * @param flagElement\r
450      * @throws DatabaseException\r
451      */\r
452     protected void verifyFlagType(WriteGraph graph, IModelingRules modelingRules, IElement flagElement) throws DatabaseException {\r
453         if (modelingRules != null) {\r
454             Resource flag = flagElement.getHint(ElementHints.KEY_OBJECT);\r
455             FlagClass.Type flagType = flagElement.getHint(FlagClass.KEY_FLAG_TYPE);\r
456             FlagUtil.verifyFlagType(graph, modelingRules, flag, flagType);\r
457         }\r
458     }\r
459 \r
460     /**\r
461      * @param graph\r
462      * @param judgment\r
463      * @param connection\r
464      * @param attachToLine\r
465      * @param controlPoints\r
466      * @param endTerminal\r
467      * @return the DIA.Connector instance created for attaching the connection\r
468      *         to the specified end terminal\r
469      * @throws DatabaseException\r
470      */\r
471     public Pair<Resource, Resource> attachToRouteGraph(\r
472             WriteGraph graph,\r
473             ConnectionJudgement judgment,\r
474             Resource attachToConnection,\r
475             Resource attachToLine,\r
476             Deque<ControlPoint> controlPoints,\r
477             TerminalInfo endTerminal)\r
478                     throws DatabaseException\r
479     {\r
480         initializeResources(graph);\r
481         this.cu = new ConnectionUtil(graph);\r
482         try {\r
483             Resource endElement = endTerminal != null ? ElementUtils.getObject(endTerminal.e) : null;\r
484             if (endElement != null\r
485                     && graph.isInstanceOf(endElement, DIA.Flag)\r
486                     && FlagUtil.isDisconnected(graph, endElement))\r
487             {\r
488                 // Connection ends in an existing but disconnected flag that\r
489                 // should be all right to connect to because the connection\r
490                 // judgment implies it makes a valid connection.\r
491                 // Check that we are attaching the connection to an existing\r
492                 // disconnected flag that is however attached to a connection.\r
493                 Resource endTerminalConnection = ConnectionBuilder.attachedToExistingConnection(graph, endTerminal);\r
494                 if (endTerminalConnection != null) {\r
495                     attachConnectionToFlag(graph, judgment, attachToConnection, attachToLine, controlPoints, endTerminal);\r
496                     return null;\r
497                 }\r
498             }\r
499 \r
500             Connector endConnector = null;\r
501             if (endTerminal != null) {\r
502                 endConnector = createConnectorForNode(graph, attachToConnection, endTerminal, EdgeEnd.End, judgment);\r
503             } else if (createFlags) {\r
504                 IElement endFlag = createFlag(graph, attachToConnection, EdgeEnd.End, controlPoints.getLast(), FlagClass.Type.Out, null);\r
505                 endConnector = createConnectorForNode(graph, attachToConnection, (Resource) ElementUtils.getObject(endFlag),\r
506                         ElementUtils.getSingleTerminal(endFlag), EdgeEnd.End, judgment);\r
507             }\r
508 \r
509             cu.connect(attachToLine, endConnector.getConnector());\r
510 \r
511             IModelingRules modelingRules = diagram.getHint(DiagramModelHints.KEY_MODELING_RULES);\r
512             if (judgment.connectionType != null && modelingRules != null) {\r
513                 modelingRules.setConnectionType(graph, attachToConnection, judgment.connectionType);\r
514             }\r
515 \r
516             writeConnectionMappingSpecification(graph, endTerminal, endConnector, judgment.connectionType);\r
517 \r
518             CommentMetadata cm = graph.getMetadata(CommentMetadata.class);\r
519             graph.addMetadata(cm.add("Branched connection " + attachToConnection));\r
520 \r
521             return Pair.make(endConnector.getAttachment(), endConnector.getConnector());\r
522         } finally {\r
523             this.cu = null;\r
524         }\r
525     }\r
526 \r
527     protected void attachConnectionToFlag(\r
528             WriteGraph graph,\r
529             ConnectionJudgement judgment,\r
530             Resource attachToConnection,\r
531             Resource attachToLine,\r
532             Deque<ControlPoint> controlPoints,\r
533             TerminalInfo toFlag)\r
534                     throws DatabaseException\r
535     {\r
536         // Attaching attachedConnection to an existing disconnected flag that is\r
537         // however attached to a connection.\r
538         // STEPS:\r
539         // 1. remove flag and its connector\r
540         // 2. attach the two connections together by moving the route nodes\r
541         //    of the removed flag-side connection under the remaining connection\r
542         //    and ensuring that the route node chain will be valid after the\r
543         //    switch. In a chain route lines, each line must have an opposite\r
544         //    direction compared to the lines connected to it.\r
545         Resource flagToRemove = ElementUtils.getObject(toFlag.e);\r
546         Statement flagToConnector = graph.getSingleStatement(flagToRemove, STR.IsConnectedTo);\r
547         Resource flagConnector = flagToConnector.getObject();\r
548         Resource flagConnection = ConnectionUtil.getConnection(graph, flagConnector);\r
549         Collection<Resource> flagRouteNodes = graph.getObjects(flagConnector, DIA.AreConnected);\r
550 \r
551         // Remove flag and its connector.\r
552         graph.deny(flagToConnector);\r
553         new RemoveElement((Resource)diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE), flagToRemove).perform(graph);\r
554         cu.removeConnectionPart(flagConnector);\r
555 \r
556         // Attached routeline must have opposite direction than the line\r
557         // attached to in order for the connection to be valid.\r
558         Boolean attachingToHorizontalLine = graph.getPossibleRelatedValue(attachToLine, DIA.IsHorizontal, Bindings.BOOLEAN);\r
559         if (attachingToHorizontalLine != null) {\r
560             for (Resource routeNode : flagRouteNodes) {\r
561                 Collection<Resource> routeNodesToAttachTo = removeUntilOrientedRouteline(graph, !attachingToHorizontalLine, routeNode);\r
562                 for (Resource rn : routeNodesToAttachTo)\r
563                     cu.connect(attachToLine, rn);\r
564             }\r
565         }\r
566 \r
567         for (Statement routeNode : graph.getStatements(flagConnection, DIA.HasInteriorRouteNode)) {\r
568             graph.deny(routeNode);\r
569             graph.claim(attachToConnection, routeNode.getPredicate(), routeNode.getObject());\r
570         }\r
571         for (Statement connector : graph.getStatements(flagConnection, DIA.HasConnector)) {\r
572             graph.deny(connector);\r
573             graph.claim(attachToConnection, connector.getPredicate(), connector.getObject());\r
574         }\r
575 \r
576         CommentMetadata cm = graph.getMetadata(CommentMetadata.class);\r
577         graph.addMetadata(cm.add("Joined connection to disconnected flag"));\r
578     }\r
579 \r
580     private Collection<Resource> removeUntilOrientedRouteline(WriteGraph graph, boolean expectedOrientation, Resource routeNode) throws DatabaseException {\r
581         List<Resource> result = new ArrayList<>(2);\r
582         Deque<Resource> work = new ArrayDeque<>(2);\r
583         work.addLast(routeNode);\r
584         while (!work.isEmpty()) {\r
585             Resource rn = work.removeFirst();\r
586             if (graph.isInstanceOf(rn, DIA.RouteLine)) {\r
587                 Boolean isHorizontal = graph.getPossibleRelatedValue(rn, DIA.IsHorizontal, Bindings.BOOLEAN);\r
588                 if (isHorizontal != null && expectedOrientation != isHorizontal) {\r
589                     for (Resource rnn : graph.getObjects(rn, DIA.AreConnected))\r
590                         work.addLast(rnn);\r
591                     cu.removeConnectionPart(rn);\r
592                     continue;\r
593                 }\r
594             }\r
595             result.add(rn);\r
596         }\r
597         return result;\r
598     }\r
599 \r
600     protected boolean isRouteGraphConnection(ReadGraph graph, Resource connection) throws DatabaseException {\r
601         initializeResources(graph);\r
602         return graph.isInstanceOf(connection, DIA.RouteGraphConnection);\r
603     }\r
604 \r
605     /**\r
606      * @param graph\r
607      * @param ti\r
608      * @return\r
609      * @throws DatabaseException\r
610      */\r
611     public static Resource attachedToExistingConnection(ReadGraph graph, TerminalInfo ti) throws DatabaseException {\r
612         Object obj = ElementUtils.getObject(ti.e);\r
613         Resource cp = DiagramGraphUtil.getConnectionPointOfTerminal(graph, ti.t);\r
614         if (obj instanceof Resource && cp != null) {\r
615             Resource e = (Resource) obj;\r
616             for (Resource connector : graph.getObjects(e, cp)) {\r
617                 Resource connection = ConnectionUtil.tryGetConnection(graph, connector);\r
618                 if (connection != null)\r
619                     return connection;\r
620             }\r
621         }\r
622         return null;\r
623     }\r
624 \r
625     /**\r
626      * @param graph\r
627      * @param tis\r
628      * @return\r
629      * @throws DatabaseException\r
630      */\r
631     public Resource getOrCreateConnection(ReadGraph graph, TerminalInfo... tis) throws DatabaseException {\r
632         // Resolve if adding to existing connection.\r
633         Resource connection = null;\r
634         for (TerminalInfo ti : tis) {\r
635             connection = getExistingConnection(graph, ti);\r
636             if (connection != null)\r
637                 break;\r
638         }\r
639 \r
640         if (connection == null) {\r
641             // No existing connection, create new.\r
642             ElementClass connectionClass = elementClassProvider.get(ElementClasses.CONNECTION);\r
643             Resource connectionClassResource = ElementUtils.checkedAdapt(connectionClass, Resource.class);\r
644             connection = cu.newConnection(diagramResource, connectionClassResource);\r
645         }\r
646 \r
647         return connection;\r
648     }\r
649 \r
650     /**\r
651      * @param graph\r
652      * @param connection\r
653      * @param controlPoints\r
654      * @return\r
655      * @throws DatabaseException\r
656      */\r
657     public List<Pair<ControlPoint, Resource>> createBranchPoints(WriteGraph graph, Resource connection,\r
658             Collection<ControlPoint> controlPoints)    throws DatabaseException {\r
659         List<Pair<ControlPoint, Resource>> bps = new ArrayList<Pair<ControlPoint, Resource>>(controlPoints.size());\r
660         for(ControlPoint cp : controlPoints) {\r
661             if (cp.isAttachedToTerminal())\r
662                 // Terminal attachments do not need branch points.\r
663                 continue;\r
664 \r
665             Resource bp = cu.newBranchPoint(connection,\r
666                     AffineTransform.getTranslateInstance(cp.getPosition().getX(), cp.getPosition().getY()),\r
667                     cp.getDirection());\r
668             bps.add(Pair.make(cp, bp));\r
669         }\r
670         return bps;\r
671     }\r
672 \r
673     /**\r
674      * @param graph\r
675      * @param connection\r
676      * @param ti\r
677      * @param end\r
678      * @param judgment\r
679      * @return\r
680      * @throws DatabaseException\r
681      */\r
682     protected Resource chooseAttachmentRelationForNode(ReadGraph graph,\r
683             Resource connection, TerminalInfo ti, ConnectionJudgement judgment)\r
684             throws DatabaseException {\r
685         Resource node = (Resource) ElementUtils.getObject(ti.e);\r
686         return chooseAttachmentRelationForNode(graph, connection, node, ti.t, judgment);\r
687     }\r
688 \r
689     /**\r
690      * @param graph\r
691      * @param connection\r
692      * @param element\r
693      * @param terminal\r
694      * @param end\r
695      * @param judgment\r
696      * @return the calculated attachment relation or <code>null</code> if the\r
697      *         result is ambiguous\r
698      * @throws DatabaseException\r
699      */\r
700     protected Resource chooseAttachmentRelationForNode(ReadGraph graph,\r
701             Resource connection, Resource element, Terminal terminal,\r
702             ConnectionJudgement judgment) throws DatabaseException {\r
703         IConnectionPoint cp = ConnectionUtil.toConnectionPoint(graph, element, terminal);\r
704         CPTerminal cpt = (cp instanceof CPTerminal) ? (CPTerminal) cp : null;\r
705         Resource attachment = judgment.attachmentRelations.get(graph, cpt);\r
706         return attachment;\r
707     }\r
708 \r
709     /**\r
710      * @param graph\r
711      * @param connection\r
712      * @param ti\r
713      * @param connectTo resource to connect the new connector to if not\r
714      *        <code>null</code>\r
715      * @param judgment\r
716      * @return <used attachment relation, the new DIA.Connector instance>. The\r
717      *         attachment relation is <code>null</code> if it was chosen based\r
718      *         on EdgeEnd instead of being defined\r
719      * @throws DatabaseException\r
720      */\r
721     protected Connector createConnectorForNode(WriteGraph graph, Resource connection, TerminalInfo ti, EdgeEnd end,\r
722             ConnectionJudgement judgment) throws DatabaseException {\r
723         Resource node = (Resource) ElementUtils.getObject(ti.e);\r
724         return createConnectorForNode(graph, connection, node, ti.t, end, judgment);\r
725     }\r
726 \r
727     /**\r
728      * @param graph\r
729      * @param connection\r
730      * @param element\r
731      * @param terminal\r
732      * @param end\r
733      * @param connectTo\r
734      * @param judgment\r
735      * @return <used attachment relation, the new DIA.Connector instance>. The\r
736      *         attachment relation is <code>null</code> if it was chosen based\r
737      *         on EdgeEnd instead of being defined\r
738      * @throws DatabaseException\r
739      */\r
740     protected Connector createConnectorForNode(WriteGraph graph, Resource connection, Resource element, Terminal terminal,\r
741             EdgeEnd end, ConnectionJudgement judgment) throws DatabaseException {\r
742         IConnectionPoint cp = ConnectionUtil.toConnectionPoint(graph, element, terminal);\r
743         CPTerminal cpt = (cp instanceof CPTerminal) ? (CPTerminal) cp : null;\r
744         Resource attachment = judgment.attachmentRelations.get(graph, cpt);\r
745         if (attachment == null)\r
746             attachment = cu.toHasConnectorRelation(end);\r
747         Resource connector = cu.getOrCreateConnector(connection, element, terminal, end, attachment);\r
748         return new Connector(attachment, connector);\r
749     }\r
750 \r
751     /**\r
752      * @param graph\r
753      * @param connection\r
754      * @param ti\r
755      * @param attachment\r
756      * @return <used attachment relation, the new DIA.Connector instance>\r
757      * @throws DatabaseException\r
758      */\r
759     protected Connector createConnectorForNodeWithAttachment(WriteGraph graph,\r
760             Resource connection, TerminalInfo ti, Resource attachment)\r
761             throws DatabaseException {\r
762         Resource node = (Resource) ElementUtils.getObject(ti.e);\r
763         return createConnectorForNodeWithAttachment(graph, connection, node, ti.t, attachment);\r
764     }\r
765 \r
766     /**\r
767      * @param graph\r
768      * @param connection\r
769      * @param element\r
770      * @param terminal\r
771      * @param attachment\r
772      * @return <used attachment relation, the new DIA.Connector instance>\r
773      * @throws DatabaseException\r
774      */\r
775     protected Connector createConnectorForNodeWithAttachment(WriteGraph graph,\r
776             Resource connection, Resource element, Terminal terminal,\r
777             Resource attachment) throws DatabaseException {\r
778         Resource connector = cu.getOrCreateConnector(connection, element, terminal, null, attachment);\r
779         return new Connector(attachment, connector);\r
780     }\r
781 \r
782     /**\r
783      * @param graph\r
784      * @param connection\r
785      * @param end\r
786      * @param cp\r
787      * @param type\r
788      * @param label <code>null</code> to leave flag without label\r
789      * @return an element describing the new created flag resource\r
790      * @throws DatabaseException\r
791      */\r
792     public IElement createFlag(WriteGraph graph, Resource connection, EdgeEnd end, ControlPoint cp,\r
793             FlagClass.Type type, String label) throws DatabaseException {\r
794         ElementClass flagClass = elementClassProvider.get(ElementClasses.FLAG);\r
795         IElement flagElement = Element.spawnNew(flagClass);\r
796         Resource flagClassResource = ElementUtils.checkedAdapt(flagClass, Resource.class);\r
797 \r
798         Layer0 L0 = Layer0.getInstance(graph);\r
799         G2DResource G2D = G2DResource.getInstance(graph);\r
800         DiagramResource DIA = DiagramResource.getInstance(graph);\r
801 \r
802         Resource flag = graph.newResource();\r
803         graph.claim(flag, L0.InstanceOf, null, flagClassResource);\r
804         flagElement.setHint(ElementHints.KEY_OBJECT, flag);\r
805 \r
806         OrderedSetUtils.add(graph, diagramResource, flag);\r
807 \r
808         AffineTransform at = AffineTransform.getTranslateInstance(cp.getPosition().getX(), cp.getPosition().getY());\r
809         flagElement.setHint(ElementHints.KEY_TRANSFORM, at);\r
810         double[] matrix = new double[6];\r
811         at.getMatrix(matrix);\r
812         graph.claimLiteral(flag, DIA.HasTransform, G2D.Transform, matrix);\r
813 \r
814         flagElement.setHint(FlagClass.KEY_FLAG_TYPE, type);\r
815         graph.claim(flag, DIA.HasFlagType, null, DiagramGraphUtil.toFlagTypeResource(DIA, type));\r
816         if (label != null)\r
817             graph.claimLiteral(flag, L0.HasLabel, DIA.FlagLabel, label, Bindings.STRING);\r
818 \r
819         // Give running name to flag and increment the counter attached to the diagram.\r
820         AddElement.claimFreshElementName(graph, diagramResource, flag);\r
821 \r
822         // Make the diagram consist of the new element\r
823         graph.claim(diagramResource, L0.ConsistsOf, flag);\r
824 \r
825         // Put the element on all the currently active layers if possible.\r
826         if (layerManager != null) {\r
827             layerManager.removeFromAllLayers(graph, flag);\r
828             layerManager.putElementOnVisibleLayers(diagram, graph, flag);\r
829         }\r
830         \r
831         // Add flag to possible IO table\r
832         IOTablesInfo ioTablesInfo = IOTableUtil.getIOTablesInfo(graph, \r
833                 (Resource)diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE));\r
834         ioTablesInfo.updateBinding(graph, DIA, flag, at.getTranslateX(), at.getTranslateY());\r
835         \r
836         return flagElement;\r
837     }\r
838 \r
839     /**\r
840      * @param graph\r
841      * @param ti\r
842      * @return\r
843      * @throws DatabaseException\r
844      */\r
845     protected static Resource getExistingConnection(ReadGraph graph, TerminalInfo ti) throws DatabaseException {\r
846         if (ti != null) {\r
847             if (isConnection(ti.e)) {\r
848                 Object obj = ElementUtils.getObject(ti.e);\r
849                 if (obj instanceof Resource) {\r
850                     Resource c = (Resource) obj;\r
851                     return graph.isInstanceOf(c, DiagramResource.getInstance(graph).Connection) ? c : null;\r
852                 }\r
853             } else if (isBranchPoint(ti.e)) {\r
854                 Object obj = ElementUtils.getObject(ti.e);\r
855                 if (obj instanceof Resource) {\r
856                     return ConnectionUtil.tryGetConnection(graph, (Resource) obj);\r
857                 }\r
858             }\r
859         }\r
860         return null;\r
861     }\r
862 \r
863     protected static boolean isConnection(IElement e) {\r
864         return e.getElementClass().containsClass(ConnectionHandler.class);\r
865     }\r
866 \r
867     /**\r
868      * @param e\r
869      * @return\r
870      */\r
871     protected static boolean isBranchPoint(IElement e) {\r
872         return e.getElementClass().containsClass(BranchPoint.class);\r
873     }\r
874 \r
875     /**\r
876      * @param graph\r
877      * @param terminal\r
878      * @return\r
879      * @throws DatabaseException \r
880      */\r
881     protected static Resource getDisconnectedFlag(ReadGraph graph, TerminalInfo terminal) throws DatabaseException {\r
882         if (terminal != null) {\r
883             Object obj = ElementUtils.getObject(terminal.e);\r
884             if (obj instanceof Resource) {\r
885                 Resource flag = (Resource) obj;\r
886                 if (graph.isInstanceOf(flag, DiagramResource.getInstance(graph).Flag)\r
887                         && FlagUtil.isDisconnected(graph, flag))\r
888                     return flag;\r
889             }\r
890         }\r
891         return null;\r
892     }\r
893 \r
894     /**\r
895      * @param graph\r
896      * @param modelingRules\r
897      * @param judgment\r
898      * @param join\r
899      * @throws DatabaseException\r
900      */\r
901     protected static void setJoinedConnectionTypes(WriteGraph graph, IModelingRules modelingRules,\r
902             ConnectionJudgement judgment, Resource join) throws DatabaseException {\r
903         if (modelingRules != null && judgment != null && judgment.connectionType != null) {\r
904             DiagramResource DIA = DiagramResource.getInstance(graph);\r
905             StructuralResource2 STR = StructuralResource2.getInstance(graph);\r
906             List<Resource> connections = new ArrayList<Resource>(2);\r
907             for (Resource flag : graph.getObjects(join, DIA.FlagIsJoinedBy)) {\r
908                 for (Resource connector : graph.getObjects(flag, STR.IsConnectedTo)) {\r
909                     Resource connection = ConnectionUtil.tryGetConnection(graph, connector);\r
910                     if (connection != null)\r
911                         connections.add(connection);\r
912                 }\r
913             }\r
914             for (Resource connection : connections)\r
915                 modelingRules.setConnectionType(graph, connection, judgment.connectionType);\r
916         }\r
917     }\r
918 \r
919 }