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