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