]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram/src/org/simantics/diagram/content/ConnectionUtil.java
Fixed multiple issues causing dangling references to discarded queries
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / content / ConnectionUtil.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 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  *******************************************************************************/
12 package org.simantics.diagram.content;
13
14 import java.awt.geom.AffineTransform;
15 import java.awt.geom.Line2D;
16 import java.awt.geom.Point2D;
17 import java.util.ArrayList;
18 import java.util.Collection;
19 import java.util.Collections;
20 import java.util.HashSet;
21 import java.util.Iterator;
22 import java.util.List;
23 import java.util.Set;
24 import java.util.Stack;
25
26 import org.simantics.databoard.Bindings;
27 import org.simantics.db.ReadGraph;
28 import org.simantics.db.Resource;
29 import org.simantics.db.Statement;
30 import org.simantics.db.WriteGraph;
31 import org.simantics.db.WriteOnlyGraph;
32 import org.simantics.db.common.CommentMetadata;
33 import org.simantics.db.common.request.IndexRoot;
34 import org.simantics.db.common.utils.NameUtils;
35 import org.simantics.db.common.utils.OrderedSetUtils;
36 import org.simantics.db.exception.AssumptionException;
37 import org.simantics.db.exception.DatabaseException;
38 import org.simantics.db.exception.ValidationException;
39 import org.simantics.db.layer0.adapter.impl.EntityRemover;
40 import org.simantics.db.layer0.util.RemoverUtil;
41 import org.simantics.diagram.connection.ConnectionSegmentEnd;
42 import org.simantics.diagram.stubs.DiagramResource;
43 import org.simantics.diagram.synchronization.graph.BasicResources;
44 import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;
45 import org.simantics.g2d.connection.handler.ConnectionHandler;
46 import org.simantics.g2d.diagram.handler.PickRequest.PickFilter;
47 import org.simantics.g2d.diagram.handler.Topology.Terminal;
48 import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil.TerminalInfo;
49 import org.simantics.g2d.element.ElementUtils;
50 import org.simantics.g2d.element.IElement;
51 import org.simantics.g2d.element.handler.BendsHandler;
52 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
53 import org.simantics.g2d.element.handler.impl.BranchPointTerminal;
54 import org.simantics.g2d.elementclass.BranchPoint;
55 import org.simantics.g2d.elementclass.BranchPoint.Direction;
56 import org.simantics.layer0.Layer0;
57 import org.simantics.modeling.ModelingResources;
58 import org.simantics.scenegraph.utils.GeometryUtils;
59 import org.simantics.scl.commands.Commands;
60 import org.simantics.structural.stubs.StructuralResource2;
61 import org.simantics.structural2.modelingRules.CPConnection;
62 import org.simantics.structural2.modelingRules.CPConnectionJoin;
63 import org.simantics.structural2.modelingRules.CPTerminal;
64 import org.simantics.structural2.modelingRules.IConnectionPoint;
65 import org.simantics.utils.Development;
66 import org.simantics.utils.datastructures.Pair;
67 import org.simantics.utils.datastructures.Triple;
68 import org.simantics.utils.ui.AdaptionUtils;
69
70 /**
71  * @author Tuukka Lehtonen
72  */
73 public final class ConnectionUtil {
74
75     private final ReadGraph rg;
76     private final WriteGraph g;
77     private final BasicResources br;
78
79     /**
80      * Construct utility with read-only support.
81      * @param g
82      */
83     public ConnectionUtil(ReadGraph g) {
84         this.rg = g;
85         this.g = (g instanceof WriteGraph) ? (WriteGraph) g : null;
86         this.br = BasicResources.getInstance(g);
87     }
88
89     /**
90      * Construct utility with read-write support.
91      * @param g
92      */
93     public ConnectionUtil(WriteGraph g) {
94         this.rg = g;
95         this.g = g;
96         this.br = BasicResources.getInstance(g);
97     }
98
99     void assertWriteSupport() {
100         if (g == null)
101             throw new UnsupportedOperationException("no write support, this ConnectionUtil is read-only");
102     }
103
104     /**
105      * Creates a new connection element of the specified type and attaches it to
106      * the specified diagram composite.
107      * 
108      * @param composite diagram composite
109      * @param type connection element type
110      * @return created connection
111      * @throws DatabaseException
112      */
113     public Resource newConnection(Resource composite, Resource type) throws DatabaseException {
114         assertWriteSupport();
115         Resource connection = newConnection(type);
116         // By default, add connections to the front of the diagram since most
117         // often it is visually the expected result.
118         OrderedSetUtils.addFirst(g, composite, connection);
119         g.claim(composite, br.L0.ConsistsOf, br.L0.PartOf, connection);
120         return connection;
121     }
122
123     /**
124      * Creates a new connection element of the specified type without attaching
125      * it to any diagram composite.
126      * 
127      * @param type connection element type
128      * @return created connection
129      * @throws DatabaseException
130      */
131     public Resource newConnection(Resource type) throws DatabaseException {
132         assertWriteSupport();
133         return newInstance(g, type);
134     }
135
136     /**
137      * Creates a new element terminal connector (DiagramResource) for the specified connector
138      * @param connection
139      * @param hasConnector
140      * @return
141      * @throws DatabaseException
142      */
143     public Resource newConnector(Resource connection, Resource hasConnector) throws DatabaseException {
144         assertWriteSupport();
145         Resource connector = newInstance(g, br.DIA.Connector);
146         g.claim(connection, hasConnector, connector);
147         return connector;
148     }
149
150     public Resource newBranchPoint(Resource connection, AffineTransform tr) throws DatabaseException {
151         return newBranchPoint(connection, tr, null);
152     }
153
154     public Resource newBranchPoint(Resource connection, AffineTransform tr, Direction direction) throws DatabaseException {
155         assertWriteSupport();
156         Resource bp = g.newResource();
157         g.claim(bp, br.L0.InstanceOf, null, br.DIA.BranchPoint);
158         if (tr != null) {
159             double[] mat = new double[6];
160             tr.getMatrix(mat);
161             Resource transform = g.newResource();
162             g.claim(transform, br.L0.InstanceOf, null, br.G2D.Transform);
163             g.claimValue(transform, mat);
164             g.claim(bp, br.DIA.HasTransform, transform);
165         }
166         Resource tag = toDirectionTag(g, direction);
167         if (tag != null) {
168             g.claim(bp, tag, tag, bp);
169         }
170         g.claim(connection, br.DIA.HasBranchPoint, bp);
171         return bp;
172     }
173
174     /**
175      * @param connection
176      * @param position
177      * @param isHorizontal
178      * @return
179      * @throws DatabaseException
180      */
181     public Resource newRouteLine(Resource connection, Double position, Boolean isHorizontal) throws DatabaseException {
182         assertWriteSupport();
183         Resource rl = newInstance(g, br.DIA.RouteLine);
184         if (position != null) {
185             g.addLiteral(rl, br.DIA.HasPosition, br.DIA.HasPosition_Inverse, br.L0.Double, position, Bindings.DOUBLE);
186         }
187         if (isHorizontal != null) {
188             g.addLiteral(rl, br.DIA.IsHorizontal, br.DIA.IsHorizontal_Inverse, br.L0.Boolean, isHorizontal, Bindings.BOOLEAN);
189         }
190         g.claim(connection, br.DIA.HasInteriorRouteNode, br.DIA.HasInteriorRouteNode_Inverse, rl);
191         return rl;
192     }
193
194     ConnectionSegmentEnd getTerminalType(Terminal terminal, ConnectionSegmentEnd defaultValue) {
195         ConnectionSegmentEnd type = getTerminalType(terminal);
196         return type != null ? type : defaultValue;
197     }
198
199     ConnectionSegmentEnd getTerminalType(Terminal t) {
200         if (t == null)
201             return null;
202
203         if (t instanceof ResourceTerminal) {
204             return ConnectionSegmentEnd.CONNECTOR;
205         } else if (t instanceof BranchPointTerminal) {
206             return ConnectionSegmentEnd.BRANCH;
207         } else {
208             throw new IllegalArgumentException("unsupported terminal '" + t + "'");
209         }
210     }
211
212     Resource resolveTerminalRelation(ReadGraph g, Terminal t) throws DatabaseException {
213         if (t == null)
214             return null;
215         if (t instanceof ResourceTerminal) {
216             ResourceTerminal rt = (ResourceTerminal) t;
217             Resource terminalRelation = DiagramGraphUtil.getConnectionPointOfTerminal(g, rt.getResource());
218             if (!g.isSubrelationOf(terminalRelation, br.STR.IsConnectedTo)) {
219                 // debug...
220             }
221             return terminalRelation;
222         } else {
223             throw new IllegalArgumentException("unsupported terminal '" + t + "' for terminal relation resolution");
224         }
225     }
226
227     public Resource toHasConnectorRelation(EdgeEnd end) {
228         switch (end) {
229             case Begin: return br.DIA.HasPlainConnector;
230             case End: return br.DIA.HasArrowConnector;
231             default: throw new IllegalArgumentException("unsupported edge end: " + end);
232         }
233     }
234
235     public EdgeEnd toEdgeEnd(Resource attachmentRelation, EdgeEnd defaultValue) throws DatabaseException {
236         if (g.isSubrelationOf(attachmentRelation, br.DIA.HasPlainConnector))
237             return EdgeEnd.Begin;
238         if (g.isSubrelationOf(attachmentRelation, br.DIA.HasArrowConnector))
239             return EdgeEnd.End;
240         return defaultValue;
241     }
242
243     public Resource getAttachmentRelationForConnector(Resource connector) throws DatabaseException {
244         Statement connection = g.getPossibleStatement(connector, br.DIA.IsConnectorOf);
245         if (connection == null)
246             return null;
247         Resource attachment = g.getInverse(connection.getPredicate());
248         return attachment;
249     }
250
251     /**
252      * @param connection
253      * @param node
254      * @param c
255      * @param end
256      * @param attachmentRelation the relation used for attaching the connector to the connector
257      * @return
258      * @throws DatabaseException
259      */
260     public Resource getOrCreateConnector(Resource connection, Resource node, Terminal terminal, EdgeEnd end, Resource attachmentRelation) throws DatabaseException {
261         assertWriteSupport();
262         ConnectionSegmentEnd connectorType = getTerminalType(terminal, null);
263         if (connectorType == null)
264             throw new AssumptionException("Invalid connection node", connection, node);
265
266         switch (connectorType) {
267             case BRANCH:
268                 // NOTE: should we ensure here that (connection, br.dr.HasBranchPoint, node) exists?
269                 return node;
270
271             case CONNECTOR: {
272                 Resource terminalRelation = resolveTerminalRelation(g, terminal);
273
274                 if (attachmentRelation == null)
275                     attachmentRelation = toHasConnectorRelation(end);
276
277                 if (!g.isSubrelationOf(attachmentRelation, br.DIA.HasConnector))
278                     throw new AssumptionException("attachment relation not a subrelation of Has Connector", attachmentRelation);
279
280                 // Create new connector for the specified node terminal
281                 Resource terminalConnector = newConnector(connection, attachmentRelation);
282                 g.claim(node, terminalRelation, terminalConnector);
283                 return terminalConnector;
284             }
285             default:
286                 throw new Error("this should be unreachable code");
287         }
288     }
289
290     public void connect(Resource connector1, Resource connector2) throws DatabaseException {
291         assertWriteSupport();
292         g.claim(connector1, br.DIA.AreConnected, br.DIA.AreConnected, connector2);
293     }
294
295     public void disconnect(Resource connector1, Resource connector2) throws DatabaseException {
296         assertWriteSupport();
297         g.denyStatement(connector1, br.DIA.AreConnected, connector2);
298     }
299
300     public void disconnectFromAllRouteNodes(Resource connector) throws DatabaseException {
301         assertWriteSupport();
302         g.deny(connector, br.DIA.AreConnected);
303     }
304
305     public void disconnect(EdgeResource segment) throws DatabaseException {
306         assertWriteSupport();
307         disconnect(segment.first(), segment.second());
308     }
309
310     public boolean isConnected(Resource connector) throws DatabaseException {
311         return rg.hasStatement(connector, br.DIA.AreConnected);
312     }
313
314     public boolean isConnected(Resource connector, Resource toConnector) throws DatabaseException {
315         return rg.hasStatement(connector, br.DIA.AreConnected, toConnector);
316     }
317
318     public boolean isConnectionEmpty(Resource connection) throws DatabaseException {
319         return !rg.hasStatement(connection, br.DIA.HasConnector)
320                 && !rg.hasStatement(connection, br.DIA.HasInteriorRouteNode);
321     }
322
323     private void removeConnectorOrBranchPoint(Resource connectorOrBranchPoint) throws DatabaseException {
324
325 //        // Handle correct removal of route points
326 //        if(g.isInstanceOf(connectorOrBranchPoint, dr.BranchPoint)) {
327 //            Collection<Resource> connectedConnectors = g.getObjects(connectorOrBranchPoint, dr.AreConnected);
328 //            if(connectedConnectors.size() == 2) {
329 //                Iterator<Resource> it = connectedConnectors.iterator();
330 //                g.claim(it.next(), dr.AreConnected, it.next());
331 //            }
332 //        }
333
334         g.deny(connectorOrBranchPoint, br.DIA.AreConnected);
335
336         // Removes both the terminal relation and the HasConnector relation
337         // to the :Connection
338         g.deny(connectorOrBranchPoint, br.STR.Connects);
339
340         // If this is a branch point/route node, remove it from the connection too.
341         g.deny(connectorOrBranchPoint, br.DIA.IsBranchPointOf);
342         g.deny(connectorOrBranchPoint, br.DIA.HasInteriorRouteNode_Inverse);
343     }
344
345     /**
346      * Removes a complete connection along with all its branch points and terminal connectors.
347      * 
348      * @param connection the connection to remove
349      */
350     public void removeConnection(Resource connection) throws DatabaseException {
351         assertWriteSupport();
352
353         // Add comment to change set.
354         CommentMetadata cm = g.getMetadata(CommentMetadata.class);
355         g.addMetadata(cm.add("Remove connection " + connection));
356
357         // 1. Get all connectors/branch points
358         Collection<Resource> connectors = new ArrayList<Resource>();
359         connectors.addAll(rg.getObjects(connection, br.DIA.HasConnector));
360         connectors.addAll(rg.getObjects(connection, br.DIA.HasInteriorRouteNode));
361
362         // 2. Remove all connectors/branch points
363         for (Resource connector : connectors) {
364             removeConnectorOrBranchPoint(connector);
365             RemoverUtil.remove(g, connector);
366         }
367
368         // 3. Remove whole connection
369         for (Resource owner : OrderedSetUtils.getOwnerLists(g, connection, br.DIA.Diagram))
370             OrderedSetUtils.remove(g, owner, connection);
371         EntityRemover.remove(g, connection);
372     }
373
374     /**
375      * Removes a single connector part from the graph. A connection part can be
376      * either a branch point or a terminal connector.
377      * 
378      * @param connectorOrBranchPoint
379      * @throws DatabaseException
380      */
381     public void removeConnectionPart(Resource connectorOrBranchPoint) throws DatabaseException {
382         removeConnectorOrBranchPoint(connectorOrBranchPoint);
383         RemoverUtil.remove(g, connectorOrBranchPoint);
384     }
385
386     /**
387      * Removes the specified connection segment. Checks that both ends of the
388      * edge segment are part of to the same connection. Steps taken:
389      * <ul>
390      * <li>Minimally this only disconnects the connection segment ends from each
391      * other and nothing more.</li>
392      * <li>After disconnecting, we check whether the segment ends are still
393      * connected to something. If not, the :Connector/:BranchPoint at the
394      * segment's end is destroyed and detached from the connection entity.</li>
395      * <li>Finally, if the connection entity is empty (has no connectors/branch
396      * points), it is also destroyed and removed from the diagram.</li>
397      * 
398      * @param segment the connection segment to remove
399      */
400     public void remove(EdgeResource segment) throws DatabaseException {
401         remove(segment, false);
402     }
403
404     /**
405      * Removes the specified connection segment. Steps taken:
406      * <ul>
407      * <li>Minimally this only disconnects the connection segment ends from each
408      * other and nothing more.</li>
409      * <li>After disconnecting, we check whether the segment ends are still
410      * connected to something. If not, the :Connector/:BranchPoint at the
411      * segment's end is destroyed and detached from the connection entity.</li>
412      * <li>Finally, if the connection entity is empty (has no connectors/branch
413      * points), it is also destroyed and removed from the diagram.</li>
414      * 
415      * @param segment the connection segment to remove
416      * @param unchecked <code>false</code> to check that both ends of the
417      *        segment are part of the same connection before removing,
418      *        <code>true</code> to just remove the segment without checking
419      *        this. Using <code>true</code> may help in cases where the
420      *        connection model has become corrupted for some reason, e.g. the
421      *        other end of the edge has lost its link to the connection while
422      *        the other has not,
423      */
424     public void remove(EdgeResource segment, boolean unchecked) throws DatabaseException {
425         assertWriteSupport();
426
427         if (!unchecked) {
428             @SuppressWarnings("unused")
429             Resource connection = getConnection(g, segment);
430         }
431
432         // 1. disconnect segment ends
433         disconnect(segment);
434
435         // 2. Remove connectors/branch points if they become fully disconnected
436         if (!isConnected(segment.first())) {
437             removeConnectorOrBranchPoint(segment.first());
438         }
439         if (!isConnected(segment.second())) {
440             removeConnectorOrBranchPoint(segment.second());
441         }
442
443         // 3. Remove whole connection entity if it becomes empty
444 //        if (isConnectionEmpty(connection)) {
445 //            for (Resource owner : OrderedSetUtils.getOwnerLists(g, connection, dr.Diagram))
446 //                OrderedSetUtils.remove(g, owner, connection);
447 //            RemoverUtil.remove(g, connection);
448 //        }
449
450     }
451
452     /**
453      * Removes all DIA.Connector instances from the specified connection that
454      * are not used for anything.
455      * 
456      * @param connection connection to examine
457      * @return the amount of unused connectors removed
458      */
459     public int removeUnusedConnectors(Resource connection) throws DatabaseException {
460         int result = 0;
461         for (Resource connector : getConnectors(connection, null)) {
462             if (!g.getObjects(connector, br.DIA.AreConnected).isEmpty())
463                 continue;
464             Collection<Resource> connects = g.getObjects(connector, br.STR.Connects);
465             if (connects.size() > 1)
466                 continue;
467
468             removeConnectionPart(connector);
469             ++result;
470         }
471         return result;
472     }
473
474     /**
475      * Removes all DIA.InteriorRouteNode instances from the specified connection that
476      * are not used for anything, i.e. connect to less than 2 other route nodes.
477      * 
478      * @param connection connection to examine
479      * @return the amount of unused connectors removed
480      */
481     public int removeExtraInteriorRouteNodes(Resource connection) throws DatabaseException {
482         int result = 0;
483         for (Resource interiorRouteNode : g.getObjects(connection, br.DIA.HasInteriorRouteNode)) {
484             Collection<Resource> connectedTo = g.getObjects(interiorRouteNode, br.DIA.AreConnected);
485             if (connectedTo.size() > 1)
486                 continue;
487
488             removeConnectionPart(interiorRouteNode);
489             ++result;
490         }
491         return result;
492     }
493
494     /**
495      * Splits the specified connection segment by adding a new branch point in
496      * between the segment ends.
497      * 
498      * @param segment
499      * @return the branch (route) point created by the split operation.
500      */
501     public Resource split(EdgeResource segment, AffineTransform splitPos) throws DatabaseException {
502         assertWriteSupport();
503
504         Resource connection = getConnection(g, segment);
505         disconnect(segment);
506         Resource bp = newBranchPoint(connection, splitPos);
507         connect(segment.first(), bp);
508         connect(bp, segment.second());
509         return bp;
510     }
511
512     /**
513      * Joins the connection at the selected branch point if and only if the
514      * branch point is a route point, i.e. it is connected to two other branch
515      * points or connector.
516      * 
517      * @param interiorRouteNode
518      * @return
519      */
520     public void join(Resource interiorRouteNode) throws DatabaseException {
521         assertWriteSupport();
522
523         if (!g.isInstanceOf(interiorRouteNode, br.DIA.InteriorRouteNode))
524             throw new ValidationException("'" + NameUtils.getSafeName(g, interiorRouteNode) + "' is not an instance of DIA.InteriorRouteNode");
525         @SuppressWarnings("unused")
526         Resource connection = getConnection(g, interiorRouteNode);
527         Collection<Resource> connectedTo = g.getObjects(interiorRouteNode, br.DIA.AreConnected);
528         if (connectedTo.size() != 2)
529             throw new ValidationException("Interior route node is not a discardable route line/point. It is not connected to 2 route nodes, but " + connectedTo.size() + ".");
530         Iterator<Resource> it = connectedTo.iterator();
531         Resource connector1 = it.next();
532         Resource connector2 = it.next();
533         //System.out.println("removing branch point " + GraphUtils.getReadableName(g, routeBranchPoint) + " which is connected to " + GraphUtils.getReadableName(g, connector1) + " and " + GraphUtils.getReadableName(g, connector2));
534         removeConnectorOrBranchPoint(interiorRouteNode);
535         connect(connector1, connector2);
536     }
537
538     public void getConnectionSegments(Resource connection, Collection<EdgeResource> result) throws DatabaseException {
539
540         ArrayList<EdgeResource> edges = new ArrayList<EdgeResource>();
541         Set<Resource> visited = new HashSet<Resource>();
542         Stack<Resource> todo = new Stack<Resource>();
543
544         // Try to select input as root, this ensures correct order for simple paths
545         Collection<Resource> seeds = rg.getObjects(connection, br.DIA.HasArrowConnector);
546         if(seeds.isEmpty()) seeds = rg.getObjects(connection, br.DIA.HasPlainConnector);
547
548         assert(!seeds.isEmpty());
549
550         Resource seed = seeds.iterator().next();
551
552         todo.push(seed);
553
554         while(!todo.isEmpty()) {
555             Resource location = todo.pop();
556             if(!visited.contains(location)) {
557                 visited.add(location);
558                 for (Resource connectedTo : rg.getObjects(location, br.DIA.AreConnected)) {
559                     todo.add(connectedTo);
560                     EdgeResource edge = new EdgeResource(location, connectedTo);
561                     if(!edges.contains(edge)) edges.add(edge);
562                 }
563             }
564         }
565
566         for(EdgeResource uer : edges) {
567 //            System.out.println("loaded edge " + uer.first() + " " + uer.second());
568             result.add(uer);
569         }
570
571     }
572
573     public Collection<Resource> getBranchPoints(Resource connection, Collection<Resource> result) throws DatabaseException {
574         if (result == null)
575             result = new ArrayList<Resource>();
576         result.addAll(rg.getObjects(connection, br.DIA.HasBranchPoint));
577         return result;
578     }
579
580     public Collection<Resource> getConnectors(Resource connection, Collection<Resource> result) throws DatabaseException {
581         if (result == null)
582             result = new ArrayList<Resource>();
583         result.addAll(rg.getObjects(connection, br.DIA.HasConnector));
584         return result;
585     }
586
587     public Resource getConnectedComponent(Resource connection, Resource connector) throws DatabaseException {
588         for (Resource connects : rg.getObjects(connector, br.STR.Connects))
589             if (!connects.equals(connection))
590                 return connects;
591         return null;
592     }
593
594     public Statement getConnectedComponentStatement(Resource connection, Resource connector) throws DatabaseException {
595         for (Statement connects : rg.getStatements(connector, br.STR.Connects))
596             if (!connects.getObject().equals(connection))
597                 return connects;
598         return null;
599     }
600
601     public void gatherEdges(Resource connection, Collection<EdgeResource> result) throws DatabaseException {
602         Set<Object> visited = new HashSet<Object>();
603         for (Resource connector : rg.getObjects(connection, br.DIA.HasConnector)) {
604             for (Resource connectedTo : rg.getObjects(connector, br.DIA.AreConnected)) {
605                 EdgeResource p = new EdgeResource(connector, connectedTo);
606                 if (visited.add(p)) {
607                     result.add(p);
608                 }
609             }
610         }
611         Collection<Resource> routeNodes = rg.getObjects(connection, br.DIA.HasInteriorRouteNode);
612         for (Resource routeNode : routeNodes) {
613             for (Resource connectedTo : rg.getObjects(routeNode, br.DIA.AreConnected)) {
614                 EdgeResource p = new EdgeResource(routeNode, connectedTo);
615                 if (visited.add(p)) {
616                     result.add(p);
617                 }
618             }
619         }
620     }
621
622     public void gatherConnectionParts(Resource connection, Collection<Object> result) throws DatabaseException {
623         Set<Object> visited = new HashSet<Object>();
624         for (Resource connector : rg.getObjects(connection, br.DIA.HasConnector)) {
625             for (Resource connectedTo : rg.getObjects(connector, br.DIA.AreConnected)) {
626                 EdgeResource p = new EdgeResource(connector, connectedTo);
627                 if (visited.add(p)) {
628                     result.add(p);
629                 }
630             }
631         }
632         Collection<Resource> routeNodes = rg.getObjects(connection, br.DIA.HasInteriorRouteNode);
633         for (Resource routeNode : routeNodes) {
634             result.add(routeNode);
635             for (Resource connectedTo : rg.getObjects(routeNode, br.DIA.AreConnected)) {
636                 EdgeResource p = new EdgeResource(routeNode, connectedTo);
637                 if (visited.add(p)) {
638                     result.add(p);
639                 }
640             }
641         }
642     }
643
644     public Collection<Resource> getConnectedComponents(Resource connection, Collection<Resource> result)
645     throws DatabaseException {
646         if (result == null)
647             result = new ArrayList<Resource>(2);
648         for (Resource connector : rg.getObjects(connection, br.DIA.HasConnector)) {
649             for (Resource connects : rg.getObjects(connector, br.STR.Connects)) {
650                 if (connects.equals(connection))
651                     continue;
652                 result.add(connects);
653             }
654         }
655         return result;
656     }
657
658     public Collection<Resource> getConnectedConnectors(Resource connection, Collection<Resource> result)
659     throws DatabaseException {
660         if (result == null)
661             result = new ArrayList<Resource>(2);
662         for (Resource connector : rg.getObjects(connection, br.DIA.HasConnector)) {
663             Resource connects = getConnectedComponent(connection, connector);
664             if (connects != null)
665                 result.add( connector );
666         }
667         return result;
668     }
669
670     public Collection<Resource> getTerminalConnectors(Resource node, Collection<Resource> result)
671     throws DatabaseException {
672         if (result == null)
673             result = new ArrayList<Resource>(2);
674         result.addAll( rg.getObjects(node, br.STR.IsConnectedTo) );
675         return result;
676     }
677
678     /**
679      * @param g
680      * @param routeNode
681      * @return <code>null</code> if the specified resource is not part of any
682      *         connection
683      */
684     public static Resource tryGetConnection(ReadGraph g, Resource routeNode) throws DatabaseException {
685         DiagramResource dr = DiagramResource.getInstance(g);
686         Resource conn = g.getPossibleObject(routeNode, dr.IsConnectorOf);
687         if (conn == null)
688             conn = g.getPossibleObject(routeNode, dr.HasInteriorRouteNode_Inverse);
689         return conn;
690     }
691
692     public static Resource tryGetConnection(ReadGraph g, EdgeResource segment) throws DatabaseException {
693         Resource first = tryGetConnection(g, segment.first());
694         Resource second = tryGetConnection(g, segment.second());
695         if (first == null || second == null || !first.equals(second))
696             return null;
697         return first;
698     }
699
700     public static Resource tryGetMappedConnection(ReadGraph g, Resource connector) throws DatabaseException {
701         ModelingResources MOD = ModelingResources.getInstance(g);
702         return g.getPossibleObject(connector, MOD.ConnectorToConnection);
703     }
704     
705     public static Resource getConnection(ReadGraph g, EdgeResource segment) throws DatabaseException {
706         Resource first = tryGetConnection(g, segment.first());
707         Resource second = tryGetConnection(g, segment.second());
708         if (first == null && second == null)
709             throw new ValidationException(
710                     "neither connection segment end is attached to a Connection entity instance: "
711                     + segment.toString(g) + " - " + segment.toString());
712         if (first != null ^ second != null)
713             throw new ValidationException("both ends of connection segment "
714                     + segment.toString(g) + " - " + segment.toString() + " are not connected (first=" + first
715                     + ", second=" + second + ")");
716         if (!first.equals(second))
717             throw new ValidationException("connectors of connection segment "
718                     + segment.toString(g) + " - " + segment.toString() + " are part of different connections: " + first
719                     + " vs. " + second);
720         return first;
721     }
722
723     public static Resource getConnection(ReadGraph g, Resource routeNode) throws DatabaseException {
724         Resource connection = tryGetConnection(g, routeNode);
725         if (connection == null)
726             throw new ValidationException("route node '"
727                     + NameUtils.getSafeName(g, routeNode) + "' is not part of any connection");
728         return connection;
729     }
730
731     public static IConnectionPoint toConnectionPoint(ReadGraph g, Resource element, Terminal terminal) throws DatabaseException {
732         if (terminal instanceof ResourceTerminal) {
733             Resource t = ((ResourceTerminal) terminal).getResource();
734
735             // TODO: remove this hack
736             if (element == null) { // Flag?
737                 DiagramResource DIA = DiagramResource.getInstance(g);
738                 Resource join = g.getPossibleObject(t, DIA.FlagIsJoinedBy);
739                 if (join == null)
740                     return null;
741                 return new CPConnectionJoin(join);
742             }
743             // element should be :Element
744             Resource bindingRelation = DiagramGraphUtil.getConnectionPointOfTerminal(g, t);
745             return new CPTerminal(element, bindingRelation);
746         } else if (terminal instanceof BranchPointTerminal) {
747             // element should be either : DIA.InteriorRouteNode or DIA.Connection
748             Resource connection = null;
749             DiagramResource DIA = DiagramResource.getInstance(g);
750             if (g.isInstanceOf(element, DIA.Connection))
751                 connection = element;
752             else
753                 connection = getConnection(g, element);
754             return new CPConnection(connection);
755         }
756         throw new IllegalArgumentException("Unrecognized Terminal class: " + terminal);
757     }
758
759     public static IConnectionPoint toConnectionPoint(ReadGraph graph, IElement element, Terminal terminal) throws DatabaseException {
760         Resource r = (Resource) ElementUtils.getObject(element);
761         return ConnectionUtil.toConnectionPoint(graph, r, terminal);
762     }
763
764     public static IConnectionPoint toConnectionPoint(ReadGraph graph, TerminalInfo ti) throws DatabaseException {
765         return ConnectionUtil.toConnectionPoint(graph, ti.e, ti.t);
766     }
767
768     public static IConnectionPoint toConnectionPoint(ReadGraph graph, DesignatedTerminal t) throws DatabaseException {
769         return toConnectionPoint(graph, t.element, t.terminal);
770     }
771
772     public static Resource toDirectionTag(ReadGraph graph, BranchPoint.Direction direction) {
773         if (direction == null)
774             return null;
775
776         DiagramResource DIA = DiagramResource.getInstance(graph);
777         switch (direction) {
778             case Horizontal: return DIA.Horizontal;
779             case Vertical: return DIA.Vertical;
780             default: return null;
781         }
782     }
783
784     /**
785      * Copied from ConnectionCommandHandler#splitConnection, duplicate code.
786      * 
787      * @param toCanvasPos
788      * @param onEdge
789      * @return
790      */
791     public static Line2D resolveNearestEdgeLineSegment(Point2D toCanvasPos, IElement onEdge) {
792         // Try to find an initial preferred direction for the new
793         // branch point based on the direction of the edge's line
794         // segment on which the split is done.
795         List<Point2D> points = new ArrayList<Point2D>();
796         BendsHandler bh = onEdge.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);
797         if (bh != null)
798             org.simantics.g2d.utils.GeometryUtils.getPoints(bh.getPath(onEdge), points);
799         Line2D nearestLine = null;
800         double nearestDistanceToLine = Double.MAX_VALUE;
801         for (int i = 0; i < points.size() - 1; ++i) {
802             Point2D p1 = points.get(i);
803             Point2D p2 = points.get(i+1);
804             double distanceToLine = GeometryUtils.distanceFromLine(toCanvasPos, p1, p2);
805             if (distanceToLine < nearestDistanceToLine) {
806                 nearestDistanceToLine = distanceToLine;
807                 if (nearestLine == null)
808                     nearestLine = new Line2D.Double();
809                 nearestLine.setLine(p1, p2);
810             }
811         }
812         return nearestLine;
813     }
814
815     /**
816      * @param object
817      * @return
818      * @throws DatabaseException
819      */
820     public static IElement getSingleEdge(Object object) throws DatabaseException {
821         IElement e = AdaptionUtils.adaptToSingle(object, IElement.class);
822         if (e == null)
823             return null;
824
825         if (PickFilter.FILTER_CONNECTION_EDGES.accept(e))
826             return e;
827
828         if (PickFilter.FILTER_CONNECTIONS.accept(e)) {
829             ConnectionHandler ch = e.getElementClass().getSingleItem(ConnectionHandler.class);
830             Collection<IElement> bps = ch.getBranchPoints(e, null);
831             Collection<IElement> segs = ch.getSegments(e, null);
832             if (bps.isEmpty() && segs.size() == 1)
833                 return segs.iterator().next();
834         }
835         return null;
836     }
837
838     /**
839      * @param object
840      * @return
841      * @throws DatabaseException
842      */
843     public static Collection<IElement> getEdges(Object object) throws DatabaseException {
844         IElement e = AdaptionUtils.adaptToSingle(object, IElement.class);
845         if (e == null)
846             return null;
847
848         if (PickFilter.FILTER_CONNECTION_EDGES.accept(e))
849             return Collections.singleton(e);
850
851         if (PickFilter.FILTER_CONNECTIONS.accept(e)) {
852             ConnectionHandler ch = e.getElementClass().getSingleItem(ConnectionHandler.class);
853             return ch.getSegments(e, null);
854         }
855         return null;
856     }
857
858     /**
859      * @param graph
860      * @param connection
861      * @return
862      * @throws DatabaseException
863      */
864     public static Resource getConnectionTailNode(ReadGraph graph, Resource connection) throws DatabaseException {
865         DiagramResource DIA = DiagramResource.getInstance(graph);
866         StructuralResource2 STR = StructuralResource2.getInstance(graph);
867         for (Resource connector : graph.getObjects(connection, DIA.HasTailConnector)) {
868             for (Resource node : graph.getObjects(connector, STR.Connects)) {
869                 if (node.equals(connection))
870                     continue;
871                 return node;
872             }
873         }
874         return null;
875     }
876
877     /**
878      * @param graph
879      * @param connection
880      * @return the STR.Connects statement from the tail DIA.Connector to the
881      *         tail node (DIA.Element) or <code>null</code> if no tail node
882      *         exists
883      * @throws DatabaseException
884      */
885     public static Statement getConnectionTailNodeStatement(ReadGraph graph, Resource connection) throws DatabaseException {
886         DiagramResource DIA = DiagramResource.getInstance(graph);
887         StructuralResource2 STR = StructuralResource2.getInstance(graph);
888         for (Resource connector : graph.getObjects(connection, DIA.HasTailConnector)) {
889             for (Statement connects : graph.getStatements(connector, STR.Connects)) {
890                 if (connects.getObject().equals(connection))
891                     continue;
892                 return connects;
893             }
894         }
895         return null;
896     }
897
898     /**
899      * @param connection
900      * @param dx
901      * @param dy
902      * @throws DatabaseException
903      */
904     public void translateRouteNodes(Resource connection, double dx, double dy) throws DatabaseException {
905         Commands.get(g, "Simantics/Diagram/translateRouteNodes")
906             .execute(g, g.syncRequest(new IndexRoot(connection)), connection, dx, dy);
907     }
908     
909     public static void translateRouteNodes(WriteGraph g, Resource connection, double dx, double dy) throws DatabaseException {
910         DiagramResource DIA = DiagramResource.getInstance(g);
911         for (Resource routeNode : g.getObjects(connection, DIA.HasInteriorRouteNode)) {
912             if (g.isInstanceOf(routeNode, DIA.RouteLine)) {
913                 Double pos = g.getRelatedValue(routeNode, DIA.HasPosition, Bindings.DOUBLE);
914                 Boolean isHorizontal = g.getRelatedValue(routeNode, DIA.IsHorizontal, Bindings.BOOLEAN);
915                 pos += isHorizontal ? dy : dx;
916                 g.claimLiteral(routeNode, DIA.HasPosition, pos);
917             }
918         }
919     }
920
921     /**
922      * Creates a route graph connection which has corners at the specified
923      * locations, starting/ending from/at the specified element terminals.
924      * 
925      * if {@link Development#DEVELOPMENT} is <code>true</code> the code will
926      * verify that both element end-points are part of the same diagram.
927      * 
928      * @param graph
929      *            database write access
930      * @param startElement
931      *            element to start connecting from
932      * @param startConnectionPoint
933      *            STR.ConnectedTo relation of the start element terminal
934      * @param endElement
935      *            element to end the connection at
936      * @param endConnectionPoint
937      *            STR.ConnectedTo relation of the end element terminal
938      * @param corners
939      *            the corners to create for the connection
940      * @return the created diagram connection resource
941      * @throws DatabaseException
942      */
943     public Resource createConnectionWithCorners(WriteGraph graph, Resource startElement,
944             Resource startConnectionPoint, Resource endElement, Resource endConnectionPoint, List<Point2D> corners)
945                     throws DatabaseException {
946         DiagramResource DIA = br.DIA;
947
948         // Verify that both elements are part of the same diagram before connecting.
949         if (Development.DEVELOPMENT) {
950             Collection<Resource> startDiagram = OrderedSetUtils.getOwnerLists(graph, startElement, DIA.Diagram);
951             Collection<Resource> endDiagram = OrderedSetUtils.getOwnerLists(graph, endElement, DIA.Diagram);
952             if (Collections.disjoint(startDiagram, endDiagram))
953                 throw new IllegalArgumentException("start element " + startElement
954                         + " is not on same diagram as end element " + endElement + ". start diagram: " + startDiagram
955                         + ", end diagram: " + endDiagram);
956         }
957
958         return createConnectionWithCorners(graph, DIA.RouteGraphConnection, startElement,
959                 startConnectionPoint, DIA.HasPlainConnector, endElement, endConnectionPoint, DIA.HasArrowConnector,
960                 corners);
961     }
962
963     /**
964      * Creates a route graph connection which has corners at the specified
965      * locations, starting/ending from/at the specified element terminals.
966      * Verifies that both element end-points are part of the same diagram.
967      * 
968      * @param graph database write access
969      * @param connectionType type of created connection (e.g. DIA.RouteGraphConnection)
970      * @param element1 element to start connecting from
971      * @param connectionPoint1 STR.ConnectedTo relation of the start element terminal 
972      * @param hasConnector1 connector to connection attachment relation to use for element1 and connectionPoint1
973      * @param element2 element to end the connection at
974      * @param connectionPoint2 STR.ConnectedTo relation of the end element terminal
975      * @param hasConnector2 connector to connection attachment relation to use for element2 and connectionPoint2
976      * @param corners the corners to create for the connection
977      * @return the created diagram connection resource
978      * @throws DatabaseException
979      */
980     public Resource createConnectionWithCorners(WriteGraph graph, Resource connectionType,
981             Resource element1, Resource connectionPoint1, Resource hasConnector1, Resource element2,
982             Resource connectionPoint2, Resource hasConnector2, List<Point2D> corners)
983             throws DatabaseException {
984         DiagramResource DIA = br.DIA;
985
986         if (corners.size() == 1)
987             throw new UnsupportedOperationException("1 corner currently not supported");
988
989         Resource connection = newInstance(g, connectionType);
990         Resource connector1 = newConnector(connection, hasConnector1);
991         Resource connector2 = newConnector(connection, hasConnector2);
992         graph.claim(element1, connectionPoint1, connector1);
993         graph.claim(element2, connectionPoint2, connector2);
994
995         if (corners.size() > 1) {
996             boolean horizontal;
997             Resource previousRouteNode = connector1;
998             for (int i=0; i<corners.size()-1; ++i) {
999                 Point2D p = corners.get(i);
1000                 Point2D p1 = corners.get(i+1);
1001                 horizontal = Math.abs(p1.getY() - p.getY()) < Math.abs(p1.getX() - p.getX());
1002                 Resource routeLine = ConnectionUtil.createRouteline(graph, connection, horizontal ? p.getY() : p.getX(), horizontal);
1003                 graph.claim(previousRouteNode, DIA.AreConnected, DIA.AreConnected, routeLine);
1004                 previousRouteNode = routeLine;
1005             }
1006             graph.claim(previousRouteNode, DIA.AreConnected, DIA.AreConnected, connector2);
1007         } else {
1008             graph.claim(connector1, DIA.AreConnected, DIA.AreConnected, connector2);
1009         }
1010
1011         return connection;
1012     }
1013
1014     public Resource createConnectionWithSingleLine(WriteGraph graph, Resource connectionType,
1015                 Collection<Triple<Resource,Resource,Resource>> endpoints,
1016                 double coordinate, boolean horizontal)
1017             throws DatabaseException {
1018
1019         DiagramResource DIA = br.DIA;
1020
1021         Resource connection = newInstance(g, connectionType);
1022
1023         Resource routeLine = ConnectionUtil.createRouteline(graph, connection, coordinate, horizontal);
1024
1025         for(Triple<Resource,Resource,Resource> endpoint : endpoints) {
1026                 Resource connector = newConnector(connection, endpoint.third);  
1027             graph.claim(endpoint.first, endpoint.second, connector);
1028             graph.claim(routeLine, DIA.AreConnected, DIA.AreConnected, connector);
1029         }
1030
1031         return connection;
1032         
1033     }
1034
1035     public Resource createConnection(WriteGraph graph, Resource connectionType,
1036                 List<Triple<Resource,Resource,Resource>> terminals,
1037                 List<Pair<Double, Boolean>> routeLines,
1038                 List<Pair<Integer,Integer>> connections) throws DatabaseException {
1039
1040         DiagramResource DIA = br.DIA;
1041
1042         Resource connection = newInstance(g, connectionType);
1043
1044         Resource[] parts = new Resource[terminals.size() + routeLines.size()];
1045         
1046         int index = 0;
1047         
1048         for(Triple<Resource,Resource,Resource> terminal : terminals) {
1049                 Resource connector = newConnector(connection, terminal.third);  
1050             graph.claim(terminal.first, terminal.second, connector);
1051             parts[index++] = connector;
1052         }
1053
1054         for(Pair<Double, Boolean> routeLine : routeLines) {
1055             Resource r = ConnectionUtil.createRouteline(graph, connection, routeLine.first, routeLine.second);
1056             parts[index++] = r;
1057         }
1058         
1059 //        System.err.println("Connect " + parts.length + " parts.");
1060         
1061         for(Pair<Integer,Integer> conn : connections) {
1062 //            System.err.println("-" + conn.first + " " + conn.second);
1063                 Resource part1 = parts[conn.first];
1064                 Resource part2 = parts[conn.second];
1065             graph.claim(part1, DIA.AreConnected, DIA.AreConnected, part2);
1066         }
1067
1068         return connection;
1069         
1070     }
1071
1072     /**
1073      * @param graph
1074      * @param connection
1075      * @param pos
1076      * @param isHorizontal
1077      * @return new route line that is attached to the specified diagram connection
1078      * @throws DatabaseException
1079      */
1080     public static Resource createRouteline(WriteGraph graph, Resource connection, double pos, boolean isHorizontal) throws DatabaseException {
1081         Layer0 L0 = Layer0.getInstance(graph);
1082         DiagramResource DIA = DiagramResource.getInstance(graph);
1083         Resource routeLine = graph.newResource();
1084         graph.claim(routeLine, L0.InstanceOf, null, DIA.RouteLine);
1085         graph.addLiteral(routeLine, DIA.HasPosition, DIA.HasPosition_Inverse, L0.Double, pos, Bindings.DOUBLE);
1086         graph.addLiteral(routeLine, DIA.IsHorizontal, DIA.IsHorizontal_Inverse, L0.Boolean, isHorizontal, Bindings.BOOLEAN);
1087         graph.claim(connection, DIA.HasInteriorRouteNode, DIA.HasInteriorRouteNode_Inverse, routeLine);
1088         return routeLine;
1089     }
1090
1091     /**
1092      * @param graph database write-only access
1093      * @param type type of the created resource
1094      * @return new instance of type
1095      * @throws DatabaseException
1096      */
1097     private Resource newInstance(WriteOnlyGraph graph, Resource type) throws DatabaseException {
1098         Resource connection = graph.newResource();
1099         g.claim(connection, br.L0.InstanceOf, null, type);
1100         return connection;
1101     }
1102
1103 }