Sync git svn branch with SVN repository r33324.
[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             FlagClass.Type flagType)\r
480                     throws DatabaseException\r
481     {\r
482         initializeResources(graph);\r
483         this.cu = new ConnectionUtil(graph);\r
484         try {\r
485             Resource endElement = endTerminal != null ? ElementUtils.getObject(endTerminal.e) : null;\r
486             if (endElement != null\r
487                     && graph.isInstanceOf(endElement, DIA.Flag)\r
488                     && FlagUtil.isDisconnected(graph, endElement))\r
489             {\r
490                 // Connection ends in an existing but disconnected flag that\r
491                 // should be all right to connect to because the connection\r
492                 // judgment implies it makes a valid connection.\r
493                 // Check that we are attaching the connection to an existing\r
494                 // disconnected flag that is however attached to a connection.\r
495                 Resource endTerminalConnection = ConnectionBuilder.attachedToExistingConnection(graph, endTerminal);\r
496                 if (endTerminalConnection != null) {\r
497                     attachConnectionToFlag(graph, judgment, attachToConnection, attachToLine, controlPoints, endTerminal);\r
498                     return null;\r
499                 }\r
500             }\r
501 \r
502             Connector endConnector = null;\r
503             if (endTerminal != null) {\r
504                 endConnector = createConnectorForNode(graph, attachToConnection, endTerminal, EdgeEnd.End, judgment);\r
505             } else if (createFlags) {\r
506                 EdgeEnd end = flagType == FlagClass.Type.In ? EdgeEnd.Begin : EdgeEnd.End;\r
507                 IElement endFlag = createFlag(graph, attachToConnection, end, controlPoints.getLast(), flagType, null);\r
508                 endConnector = createConnectorForNode(graph, attachToConnection, (Resource) ElementUtils.getObject(endFlag),\r
509                         ElementUtils.getSingleTerminal(endFlag), end, judgment);\r
510             }\r
511 \r
512             cu.connect(attachToLine, endConnector.getConnector());\r
513 \r
514             IModelingRules modelingRules = diagram.getHint(DiagramModelHints.KEY_MODELING_RULES);\r
515             if (judgment.connectionType != null && modelingRules != null) {\r
516                 modelingRules.setConnectionType(graph, attachToConnection, judgment.connectionType);\r
517             }\r
518 \r
519             writeConnectionMappingSpecification(graph, endTerminal, endConnector, judgment.connectionType);\r
520 \r
521             CommentMetadata cm = graph.getMetadata(CommentMetadata.class);\r
522             graph.addMetadata(cm.add("Branched connection " + attachToConnection));\r
523 \r
524             return Pair.make(endConnector.getAttachment(), endConnector.getConnector());\r
525         } finally {\r
526             this.cu = null;\r
527         }\r
528     }\r
529 \r
530     protected void attachConnectionToFlag(\r
531             WriteGraph graph,\r
532             ConnectionJudgement judgment,\r
533             Resource attachToConnection,\r
534             Resource attachToLine,\r
535             Deque<ControlPoint> controlPoints,\r
536             TerminalInfo toFlag)\r
537                     throws DatabaseException\r
538     {\r
539         // Attaching attachedConnection to an existing disconnected flag that is\r
540         // however attached to a connection.\r
541         // STEPS:\r
542         // 1. remove flag and its connector\r
543         // 2. attach the two connections together by moving the route nodes\r
544         //    of the removed flag-side connection under the remaining connection\r
545         //    and ensuring that the route node chain will be valid after the\r
546         //    switch. In a chain route lines, each line must have an opposite\r
547         //    direction compared to the lines connected to it.\r
548         Resource flagToRemove = ElementUtils.getObject(toFlag.e);\r
549         Statement flagToConnector = graph.getSingleStatement(flagToRemove, STR.IsConnectedTo);\r
550         Resource flagConnector = flagToConnector.getObject();\r
551         Resource flagConnection = ConnectionUtil.getConnection(graph, flagConnector);\r
552         Collection<Resource> flagRouteNodes = graph.getObjects(flagConnector, DIA.AreConnected);\r
553 \r
554         Resource connectionToKeep = attachToConnection;\r
555         Resource connectionToRemove = flagConnection;\r
556         if (!connectionToKeep.equals(connectionToRemove)) {\r
557             Resource hasElementToComponent1 = graph.getPossibleObject(attachToConnection, MOD.ElementToComponent);\r
558             Resource hasElementToComponent2 = graph.getPossibleObject(flagConnection, MOD.ElementToComponent);\r
559             Type flagType = FlagUtil.getFlagType(graph, flagToRemove);\r
560             if (hasElementToComponent1 != null && hasElementToComponent2 != null)\r
561                 throw new UnsupportedOperationException(\r
562                         "Both attached connection " + attachToConnection + " and flag connection " + flagConnection\r
563                         + " have mapped components, can't decide which connection to remove in join operation");\r
564             if (hasElementToComponent2 != null || flagType == Type.Out) {\r
565                 connectionToKeep = flagConnection;\r
566                 connectionToRemove = attachToConnection;\r
567             }\r
568         }\r
569 \r
570         // Remove flag and its connector.\r
571         graph.deny(flagToConnector);\r
572         new RemoveElement((Resource)diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE), flagToRemove).perform(graph);\r
573         cu.removeConnectionPart(flagConnector);\r
574 \r
575         // Attached routeline must have opposite direction than the line\r
576         // attached to in order for the connection to be valid.\r
577         Boolean attachingToHorizontalLine = graph.getPossibleRelatedValue(attachToLine, DIA.IsHorizontal, Bindings.BOOLEAN);\r
578         if (attachingToHorizontalLine != null) {\r
579             for (Resource routeNode : flagRouteNodes) {\r
580                 Collection<Resource> routeNodesToAttachTo = removeUntilOrientedRouteline(graph, !attachingToHorizontalLine, routeNode);\r
581                 for (Resource rn : routeNodesToAttachTo)\r
582                     cu.connect(attachToLine, rn);\r
583             }\r
584         }\r
585 \r
586         moveStatements(graph, connectionToRemove, connectionToKeep, DIA.HasInteriorRouteNode);\r
587         moveStatements(graph, connectionToRemove, connectionToKeep, DIA.HasConnector);\r
588 \r
589         // Remove obsolete connection\r
590         if (!connectionToKeep.equals(connectionToRemove))\r
591             cu.removeConnection(connectionToRemove);\r
592 \r
593         CommentMetadata cm = graph.getMetadata(CommentMetadata.class);\r
594         graph.addMetadata(cm.add("Joined connection to disconnected flag"));\r
595     }\r
596 \r
597     private void moveStatements(WriteGraph graph, Resource source, Resource target, Resource movedRelation) throws DatabaseException {\r
598         if (!source.equals(target)) {\r
599             for (Statement s : graph.getStatements(source, movedRelation)) {\r
600                 graph.deny(s);\r
601                 graph.claim(target, s.getPredicate(), s.getObject());\r
602             }\r
603         }\r
604     }\r
605 \r
606     private Collection<Resource> removeUntilOrientedRouteline(WriteGraph graph, boolean expectedOrientation, Resource routeNode) throws DatabaseException {\r
607         List<Resource> result = new ArrayList<>(2);\r
608         Deque<Resource> work = new ArrayDeque<>(2);\r
609         work.addLast(routeNode);\r
610         while (!work.isEmpty()) {\r
611             Resource rn = work.removeFirst();\r
612             if (graph.isInstanceOf(rn, DIA.RouteLine)) {\r
613                 Boolean isHorizontal = graph.getPossibleRelatedValue(rn, DIA.IsHorizontal, Bindings.BOOLEAN);\r
614                 if (isHorizontal != null && expectedOrientation != isHorizontal) {\r
615                     for (Resource rnn : graph.getObjects(rn, DIA.AreConnected))\r
616                         work.addLast(rnn);\r
617                     cu.removeConnectionPart(rn);\r
618                     continue;\r
619                 }\r
620             }\r
621             result.add(rn);\r
622         }\r
623         return result;\r
624     }\r
625 \r
626     protected boolean isRouteGraphConnection(ReadGraph graph, Resource connection) throws DatabaseException {\r
627         initializeResources(graph);\r
628         return graph.isInstanceOf(connection, DIA.RouteGraphConnection);\r
629     }\r
630 \r
631     /**\r
632      * @param graph\r
633      * @param ti\r
634      * @return\r
635      * @throws DatabaseException\r
636      */\r
637     public static Resource attachedToExistingConnection(ReadGraph graph, TerminalInfo ti) throws DatabaseException {\r
638         Object obj = ElementUtils.getObject(ti.e);\r
639         Resource cp = DiagramGraphUtil.getConnectionPointOfTerminal(graph, ti.t);\r
640         if (obj instanceof Resource && cp != null) {\r
641             Resource e = (Resource) obj;\r
642             for (Resource connector : graph.getObjects(e, cp)) {\r
643                 Resource connection = ConnectionUtil.tryGetConnection(graph, connector);\r
644                 if (connection != null)\r
645                     return connection;\r
646             }\r
647         }\r
648         return null;\r
649     }\r
650 \r
651     /**\r
652      * @param graph\r
653      * @param tis\r
654      * @return\r
655      * @throws DatabaseException\r
656      */\r
657     public Resource getOrCreateConnection(ReadGraph graph, TerminalInfo... tis) throws DatabaseException {\r
658         // Resolve if adding to existing connection.\r
659         Resource connection = null;\r
660         for (TerminalInfo ti : tis) {\r
661             connection = getExistingConnection(graph, ti);\r
662             if (connection != null)\r
663                 break;\r
664         }\r
665 \r
666         if (connection == null) {\r
667             // No existing connection, create new.\r
668             ElementClass connectionClass = elementClassProvider.get(ElementClasses.CONNECTION);\r
669             Resource connectionClassResource = ElementUtils.checkedAdapt(connectionClass, Resource.class);\r
670             connection = cu.newConnection(diagramResource, connectionClassResource);\r
671         }\r
672 \r
673         return connection;\r
674     }\r
675 \r
676     /**\r
677      * @param graph\r
678      * @param connection\r
679      * @param controlPoints\r
680      * @return\r
681      * @throws DatabaseException\r
682      */\r
683     public List<Pair<ControlPoint, Resource>> createBranchPoints(WriteGraph graph, Resource connection,\r
684             Collection<ControlPoint> controlPoints)    throws DatabaseException {\r
685         List<Pair<ControlPoint, Resource>> bps = new ArrayList<Pair<ControlPoint, Resource>>(controlPoints.size());\r
686         for(ControlPoint cp : controlPoints) {\r
687             if (cp.isAttachedToTerminal())\r
688                 // Terminal attachments do not need branch points.\r
689                 continue;\r
690 \r
691             Resource bp = cu.newBranchPoint(connection,\r
692                     AffineTransform.getTranslateInstance(cp.getPosition().getX(), cp.getPosition().getY()),\r
693                     cp.getDirection());\r
694             bps.add(Pair.make(cp, bp));\r
695         }\r
696         return bps;\r
697     }\r
698 \r
699     /**\r
700      * @param graph\r
701      * @param connection\r
702      * @param ti\r
703      * @param end\r
704      * @param judgment\r
705      * @return\r
706      * @throws DatabaseException\r
707      */\r
708     protected Resource chooseAttachmentRelationForNode(ReadGraph graph,\r
709             Resource connection, TerminalInfo ti, ConnectionJudgement judgment)\r
710             throws DatabaseException {\r
711         Resource node = (Resource) ElementUtils.getObject(ti.e);\r
712         return chooseAttachmentRelationForNode(graph, connection, node, ti.t, judgment);\r
713     }\r
714 \r
715     /**\r
716      * @param graph\r
717      * @param connection\r
718      * @param element\r
719      * @param terminal\r
720      * @param end\r
721      * @param judgment\r
722      * @return the calculated attachment relation or <code>null</code> if the\r
723      *         result is ambiguous\r
724      * @throws DatabaseException\r
725      */\r
726     protected Resource chooseAttachmentRelationForNode(ReadGraph graph,\r
727             Resource connection, Resource element, Terminal terminal,\r
728             ConnectionJudgement judgment) throws DatabaseException {\r
729         IConnectionPoint cp = ConnectionUtil.toConnectionPoint(graph, element, terminal);\r
730         CPTerminal cpt = (cp instanceof CPTerminal) ? (CPTerminal) cp : null;\r
731         Resource attachment = judgment.attachmentRelations.get(graph, cpt);\r
732         return attachment;\r
733     }\r
734 \r
735     /**\r
736      * @param graph\r
737      * @param connection\r
738      * @param ti\r
739      * @param connectTo resource to connect the new connector to if not\r
740      *        <code>null</code>\r
741      * @param judgment\r
742      * @return <used attachment relation, the new DIA.Connector instance>. The\r
743      *         attachment relation is <code>null</code> if it was chosen based\r
744      *         on EdgeEnd instead of being defined\r
745      * @throws DatabaseException\r
746      */\r
747     protected Connector createConnectorForNode(WriteGraph graph, Resource connection, TerminalInfo ti, EdgeEnd end,\r
748             ConnectionJudgement judgment) throws DatabaseException {\r
749         Resource node = (Resource) ElementUtils.getObject(ti.e);\r
750         return createConnectorForNode(graph, connection, node, ti.t, end, judgment);\r
751     }\r
752 \r
753     /**\r
754      * @param graph\r
755      * @param connection\r
756      * @param element\r
757      * @param terminal\r
758      * @param end\r
759      * @param connectTo\r
760      * @param judgment\r
761      * @return <used attachment relation, the new DIA.Connector instance>. The\r
762      *         attachment relation is <code>null</code> if it was chosen based\r
763      *         on EdgeEnd instead of being defined\r
764      * @throws DatabaseException\r
765      */\r
766     protected Connector createConnectorForNode(WriteGraph graph, Resource connection, Resource element, Terminal terminal,\r
767             EdgeEnd end, ConnectionJudgement judgment) throws DatabaseException {\r
768         IConnectionPoint cp = ConnectionUtil.toConnectionPoint(graph, element, terminal);\r
769         CPTerminal cpt = (cp instanceof CPTerminal) ? (CPTerminal) cp : null;\r
770         Resource attachment = judgment.attachmentRelations.get(graph, cpt);\r
771         if (attachment == null)\r
772             attachment = cu.toHasConnectorRelation(end);\r
773         Resource connector = cu.getOrCreateConnector(connection, element, terminal, end, attachment);\r
774         return new Connector(attachment, connector);\r
775     }\r
776 \r
777     /**\r
778      * @param graph\r
779      * @param connection\r
780      * @param ti\r
781      * @param attachment\r
782      * @return <used attachment relation, the new DIA.Connector instance>\r
783      * @throws DatabaseException\r
784      */\r
785     protected Connector createConnectorForNodeWithAttachment(WriteGraph graph,\r
786             Resource connection, TerminalInfo ti, Resource attachment)\r
787             throws DatabaseException {\r
788         Resource node = (Resource) ElementUtils.getObject(ti.e);\r
789         return createConnectorForNodeWithAttachment(graph, connection, node, ti.t, attachment);\r
790     }\r
791 \r
792     /**\r
793      * @param graph\r
794      * @param connection\r
795      * @param element\r
796      * @param terminal\r
797      * @param attachment\r
798      * @return <used attachment relation, the new DIA.Connector instance>\r
799      * @throws DatabaseException\r
800      */\r
801     protected Connector createConnectorForNodeWithAttachment(WriteGraph graph,\r
802             Resource connection, Resource element, Terminal terminal,\r
803             Resource attachment) throws DatabaseException {\r
804         Resource connector = cu.getOrCreateConnector(connection, element, terminal, null, attachment);\r
805         return new Connector(attachment, connector);\r
806     }\r
807 \r
808     /**\r
809      * @param graph\r
810      * @param connection\r
811      * @param end\r
812      * @param cp\r
813      * @param type\r
814      * @param label <code>null</code> to leave flag without label\r
815      * @return an element describing the new created flag resource\r
816      * @throws DatabaseException\r
817      */\r
818     public IElement createFlag(WriteGraph graph, Resource connection, EdgeEnd end, ControlPoint cp,\r
819             FlagClass.Type type, String label) throws DatabaseException {\r
820         ElementClass flagClass = elementClassProvider.get(ElementClasses.FLAG);\r
821         IElement flagElement = Element.spawnNew(flagClass);\r
822         Resource flagClassResource = ElementUtils.checkedAdapt(flagClass, Resource.class);\r
823 \r
824         Layer0 L0 = Layer0.getInstance(graph);\r
825         G2DResource G2D = G2DResource.getInstance(graph);\r
826         DiagramResource DIA = DiagramResource.getInstance(graph);\r
827 \r
828         Resource flag = graph.newResource();\r
829         graph.claim(flag, L0.InstanceOf, null, flagClassResource);\r
830         flagElement.setHint(ElementHints.KEY_OBJECT, flag);\r
831 \r
832         OrderedSetUtils.add(graph, diagramResource, flag);\r
833 \r
834         AffineTransform at = AffineTransform.getTranslateInstance(cp.getPosition().getX(), cp.getPosition().getY());\r
835         flagElement.setHint(ElementHints.KEY_TRANSFORM, at);\r
836         double[] matrix = new double[6];\r
837         at.getMatrix(matrix);\r
838         graph.claimLiteral(flag, DIA.HasTransform, G2D.Transform, matrix);\r
839 \r
840         flagElement.setHint(FlagClass.KEY_FLAG_TYPE, type);\r
841         graph.claim(flag, DIA.HasFlagType, null, DiagramGraphUtil.toFlagTypeResource(DIA, type));\r
842         if (label != null)\r
843             graph.claimLiteral(flag, L0.HasLabel, DIA.FlagLabel, label, Bindings.STRING);\r
844 \r
845         // Give running name to flag and increment the counter attached to the diagram.\r
846         AddElement.claimFreshElementName(graph, diagramResource, flag);\r
847 \r
848         // Make the diagram consist of the new element\r
849         graph.claim(diagramResource, L0.ConsistsOf, flag);\r
850 \r
851         // Put the element on all the currently active layers if possible.\r
852         if (layerManager != null) {\r
853             layerManager.removeFromAllLayers(graph, flag);\r
854             layerManager.putElementOnVisibleLayers(diagram, graph, flag);\r
855         }\r
856         \r
857         // Add flag to possible IO table\r
858         IOTablesInfo ioTablesInfo = IOTableUtil.getIOTablesInfo(graph, \r
859                 (Resource)diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE));\r
860         ioTablesInfo.updateBinding(graph, DIA, flag, at.getTranslateX(), at.getTranslateY());\r
861         \r
862         return flagElement;\r
863     }\r
864 \r
865     /**\r
866      * @param graph\r
867      * @param ti\r
868      * @return\r
869      * @throws DatabaseException\r
870      */\r
871     protected static Resource getExistingConnection(ReadGraph graph, TerminalInfo ti) throws DatabaseException {\r
872         if (ti != null) {\r
873             if (isConnection(ti.e)) {\r
874                 Object obj = ElementUtils.getObject(ti.e);\r
875                 if (obj instanceof Resource) {\r
876                     Resource c = (Resource) obj;\r
877                     return graph.isInstanceOf(c, DiagramResource.getInstance(graph).Connection) ? c : null;\r
878                 }\r
879             } else if (isBranchPoint(ti.e)) {\r
880                 Object obj = ElementUtils.getObject(ti.e);\r
881                 if (obj instanceof Resource) {\r
882                     return ConnectionUtil.tryGetConnection(graph, (Resource) obj);\r
883                 }\r
884             }\r
885         }\r
886         return null;\r
887     }\r
888 \r
889     protected static boolean isConnection(IElement e) {\r
890         return e.getElementClass().containsClass(ConnectionHandler.class);\r
891     }\r
892 \r
893     /**\r
894      * @param e\r
895      * @return\r
896      */\r
897     protected static boolean isBranchPoint(IElement e) {\r
898         return e.getElementClass().containsClass(BranchPoint.class);\r
899     }\r
900 \r
901     /**\r
902      * @param graph\r
903      * @param terminal\r
904      * @return\r
905      * @throws DatabaseException \r
906      */\r
907     protected static Resource getDisconnectedFlag(ReadGraph graph, TerminalInfo terminal) throws DatabaseException {\r
908         if (terminal != null) {\r
909             Object obj = ElementUtils.getObject(terminal.e);\r
910             if (obj instanceof Resource) {\r
911                 Resource flag = (Resource) obj;\r
912                 if (graph.isInstanceOf(flag, DiagramResource.getInstance(graph).Flag)\r
913                         && FlagUtil.isDisconnected(graph, flag))\r
914                     return flag;\r
915             }\r
916         }\r
917         return null;\r
918     }\r
919 \r
920     /**\r
921      * @param graph\r
922      * @param modelingRules\r
923      * @param judgment\r
924      * @param join\r
925      * @throws DatabaseException\r
926      */\r
927     protected static void setJoinedConnectionTypes(WriteGraph graph, IModelingRules modelingRules,\r
928             ConnectionJudgement judgment, Resource join) throws DatabaseException {\r
929         if (modelingRules != null && judgment != null && judgment.connectionType != null) {\r
930             DiagramResource DIA = DiagramResource.getInstance(graph);\r
931             StructuralResource2 STR = StructuralResource2.getInstance(graph);\r
932             List<Resource> connections = new ArrayList<Resource>(2);\r
933             for (Resource flag : graph.getObjects(join, DIA.FlagIsJoinedBy)) {\r
934                 for (Resource connector : graph.getObjects(flag, STR.IsConnectedTo)) {\r
935                     Resource connection = ConnectionUtil.tryGetConnection(graph, connector);\r
936                     if (connection != null)\r
937                         connections.add(connection);\r
938                 }\r
939             }\r
940             for (Resource connection : connections)\r
941                 modelingRules.setConnectionType(graph, connection, judgment.connectionType);\r
942         }\r
943     }\r
944 \r
945 }