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