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