1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
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
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.diagram.content;
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;
24 import java.util.Stack;
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;
71 * @author Tuukka Lehtonen
73 public final class ConnectionUtil {
75 private final ReadGraph rg;
76 private final WriteGraph g;
77 private final BasicResources br;
80 * Construct utility with read-only support.
83 public ConnectionUtil(ReadGraph g) {
85 this.g = (g instanceof WriteGraph) ? (WriteGraph) g : null;
86 this.br = BasicResources.getInstance(g);
90 * Construct utility with read-write support.
93 public ConnectionUtil(WriteGraph g) {
96 this.br = BasicResources.getInstance(g);
99 void assertWriteSupport() {
101 throw new UnsupportedOperationException("no write support, this ConnectionUtil is read-only");
105 * Creates a new connection element of the specified type and attaches it to
106 * the specified diagram composite.
108 * @param composite diagram composite
109 * @param type connection element type
110 * @return created connection
111 * @throws DatabaseException
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);
124 * Creates a new connection element of the specified type without attaching
125 * it to any diagram composite.
127 * @param type connection element type
128 * @return created connection
129 * @throws DatabaseException
131 public Resource newConnection(Resource type) throws DatabaseException {
132 assertWriteSupport();
133 return newInstance(g, type);
137 * Creates a new element terminal connector (DiagramResource) for the specified connector
139 * @param hasConnector
141 * @throws DatabaseException
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);
150 public Resource newBranchPoint(Resource connection, AffineTransform tr) throws DatabaseException {
151 return newBranchPoint(connection, tr, null);
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);
159 double[] mat = new double[6];
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);
166 Resource tag = toDirectionTag(g, direction);
168 g.claim(bp, tag, tag, bp);
170 g.claim(connection, br.DIA.HasBranchPoint, bp);
177 * @param isHorizontal
179 * @throws DatabaseException
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);
187 if (isHorizontal != null) {
188 g.addLiteral(rl, br.DIA.IsHorizontal, br.DIA.IsHorizontal_Inverse, br.L0.Boolean, isHorizontal, Bindings.BOOLEAN);
190 g.claim(connection, br.DIA.HasInteriorRouteNode, br.DIA.HasInteriorRouteNode_Inverse, rl);
194 ConnectionSegmentEnd getTerminalType(Terminal terminal, ConnectionSegmentEnd defaultValue) {
195 ConnectionSegmentEnd type = getTerminalType(terminal);
196 return type != null ? type : defaultValue;
199 ConnectionSegmentEnd getTerminalType(Terminal t) {
203 if (t instanceof ResourceTerminal) {
204 return ConnectionSegmentEnd.CONNECTOR;
205 } else if (t instanceof BranchPointTerminal) {
206 return ConnectionSegmentEnd.BRANCH;
208 throw new IllegalArgumentException("unsupported terminal '" + t + "'");
212 Resource resolveTerminalRelation(ReadGraph g, Terminal t) throws DatabaseException {
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)) {
221 return terminalRelation;
223 throw new IllegalArgumentException("unsupported terminal '" + t + "' for terminal relation resolution");
227 public Resource toHasConnectorRelation(EdgeEnd end) {
229 case Begin: return br.DIA.HasPlainConnector;
230 case End: return br.DIA.HasArrowConnector;
231 default: throw new IllegalArgumentException("unsupported edge end: " + end);
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))
243 public Resource getAttachmentRelationForConnector(Resource connector) throws DatabaseException {
244 Statement connection = g.getPossibleStatement(connector, br.DIA.IsConnectorOf);
245 if (connection == null)
247 Resource attachment = g.getInverse(connection.getPredicate());
256 * @param attachmentRelation the relation used for attaching the connector to the connector
258 * @throws DatabaseException
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);
266 switch (connectorType) {
268 // NOTE: should we ensure here that (connection, br.dr.HasBranchPoint, node) exists?
272 Resource terminalRelation = resolveTerminalRelation(g, terminal);
274 if (attachmentRelation == null)
275 attachmentRelation = toHasConnectorRelation(end);
277 if (!g.isSubrelationOf(attachmentRelation, br.DIA.HasConnector))
278 throw new AssumptionException("attachment relation not a subrelation of Has Connector", attachmentRelation);
280 // Create new connector for the specified node terminal
281 Resource terminalConnector = newConnector(connection, attachmentRelation);
282 g.claim(node, terminalRelation, terminalConnector);
283 return terminalConnector;
286 throw new Error("this should be unreachable code");
290 public void connect(Resource connector1, Resource connector2) throws DatabaseException {
291 assertWriteSupport();
292 g.claim(connector1, br.DIA.AreConnected, br.DIA.AreConnected, connector2);
295 public void disconnect(Resource connector1, Resource connector2) throws DatabaseException {
296 assertWriteSupport();
297 g.denyStatement(connector1, br.DIA.AreConnected, connector2);
300 public void disconnectFromAllRouteNodes(Resource connector) throws DatabaseException {
301 assertWriteSupport();
302 g.deny(connector, br.DIA.AreConnected);
305 public void disconnect(EdgeResource segment) throws DatabaseException {
306 assertWriteSupport();
307 disconnect(segment.first(), segment.second());
310 public boolean isConnected(Resource connector) throws DatabaseException {
311 return rg.hasStatement(connector, br.DIA.AreConnected);
314 public boolean isConnected(Resource connector, Resource toConnector) throws DatabaseException {
315 return rg.hasStatement(connector, br.DIA.AreConnected, toConnector);
318 public boolean isConnectionEmpty(Resource connection) throws DatabaseException {
319 return !rg.hasStatement(connection, br.DIA.HasConnector)
320 && !rg.hasStatement(connection, br.DIA.HasInteriorRouteNode);
323 private void removeConnectorOrBranchPoint(Resource connectorOrBranchPoint) throws DatabaseException {
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());
334 g.deny(connectorOrBranchPoint, br.DIA.AreConnected);
336 // Removes both the terminal relation and the HasConnector relation
337 // to the :Connection
338 g.deny(connectorOrBranchPoint, br.STR.Connects);
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);
346 * Removes a complete connection along with all its branch points and terminal connectors.
348 * @param connection the connection to remove
350 public void removeConnection(Resource connection) throws DatabaseException {
351 assertWriteSupport();
353 // Add comment to change set.
354 CommentMetadata cm = g.getMetadata(CommentMetadata.class);
355 g.addMetadata(cm.add("Remove connection " + connection));
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));
362 // 2. Remove all connectors/branch points
363 for (Resource connector : connectors) {
364 removeConnectorOrBranchPoint(connector);
365 RemoverUtil.remove(g, connector);
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);
375 * Removes a single connector part from the graph. A connection part can be
376 * either a branch point or a terminal connector.
378 * @param connectorOrBranchPoint
379 * @throws DatabaseException
381 public void removeConnectionPart(Resource connectorOrBranchPoint) throws DatabaseException {
382 removeConnectorOrBranchPoint(connectorOrBranchPoint);
383 RemoverUtil.remove(g, connectorOrBranchPoint);
387 * Removes the specified connection segment. Checks that both ends of the
388 * edge segment are part of to the same connection. Steps taken:
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>
398 * @param segment the connection segment to remove
400 public void remove(EdgeResource segment) throws DatabaseException {
401 remove(segment, false);
405 * Removes the specified connection segment. Steps taken:
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>
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
424 public void remove(EdgeResource segment, boolean unchecked) throws DatabaseException {
425 assertWriteSupport();
428 @SuppressWarnings("unused")
429 Resource connection = getConnection(g, segment);
432 // 1. disconnect segment ends
435 // 2. Remove connectors/branch points if they become fully disconnected
436 if (!isConnected(segment.first())) {
437 removeConnectorOrBranchPoint(segment.first());
439 if (!isConnected(segment.second())) {
440 removeConnectorOrBranchPoint(segment.second());
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);
453 * Removes all DIA.Connector instances from the specified connection that
454 * are not used for anything.
456 * @param connection connection to examine
457 * @return the amount of unused connectors removed
459 public int removeUnusedConnectors(Resource connection) throws DatabaseException {
461 for (Resource connector : getConnectors(connection, null)) {
462 if (!g.getObjects(connector, br.DIA.AreConnected).isEmpty())
464 Collection<Resource> connects = g.getObjects(connector, br.STR.Connects);
465 if (connects.size() > 1)
468 removeConnectionPart(connector);
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.
478 * @param connection connection to examine
479 * @return the amount of unused connectors removed
481 public int removeExtraInteriorRouteNodes(Resource connection) throws DatabaseException {
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)
488 removeConnectionPart(interiorRouteNode);
495 * Splits the specified connection segment by adding a new branch point in
496 * between the segment ends.
499 * @return the branch (route) point created by the split operation.
501 public Resource split(EdgeResource segment, AffineTransform splitPos) throws DatabaseException {
502 assertWriteSupport();
504 Resource connection = getConnection(g, segment);
506 Resource bp = newBranchPoint(connection, splitPos);
507 connect(segment.first(), bp);
508 connect(bp, segment.second());
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.
517 * @param interiorRouteNode
520 public void join(Resource interiorRouteNode) throws DatabaseException {
521 assertWriteSupport();
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);
538 public void getConnectionSegments(Resource connection, Collection<EdgeResource> result) throws DatabaseException {
540 ArrayList<EdgeResource> edges = new ArrayList<EdgeResource>();
541 Set<Resource> visited = new HashSet<Resource>();
542 Stack<Resource> todo = new Stack<Resource>();
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);
548 assert(!seeds.isEmpty());
550 Resource seed = seeds.iterator().next();
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);
566 for(EdgeResource uer : edges) {
567 // System.out.println("loaded edge " + uer.first() + " " + uer.second());
573 public Collection<Resource> getBranchPoints(Resource connection, Collection<Resource> result) throws DatabaseException {
575 result = new ArrayList<Resource>();
576 result.addAll(rg.getObjects(connection, br.DIA.HasBranchPoint));
580 public Collection<Resource> getConnectors(Resource connection, Collection<Resource> result) throws DatabaseException {
582 result = new ArrayList<Resource>();
583 result.addAll(rg.getObjects(connection, br.DIA.HasConnector));
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))
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))
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)) {
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)) {
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)) {
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)) {
644 public Collection<Resource> getConnectedComponents(Resource connection, Collection<Resource> result)
645 throws DatabaseException {
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))
652 result.add(connects);
658 public Collection<Resource> getConnectedConnectors(Resource connection, Collection<Resource> result)
659 throws DatabaseException {
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 );
670 public Collection<Resource> getTerminalConnectors(Resource node, Collection<Resource> result)
671 throws DatabaseException {
673 result = new ArrayList<Resource>(2);
674 result.addAll( rg.getObjects(node, br.STR.IsConnectedTo) );
681 * @return <code>null</code> if the specified resource is not part of any
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);
688 conn = g.getPossibleObject(routeNode, dr.HasInteriorRouteNode_Inverse);
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))
700 public static Resource tryGetMappedConnection(ReadGraph g, Resource connector) throws DatabaseException {
701 ModelingResources MOD = ModelingResources.getInstance(g);
702 return g.getPossibleObject(connector, MOD.ConnectorToConnection);
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
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");
731 public static IConnectionPoint toConnectionPoint(ReadGraph g, Resource element, Terminal terminal) throws DatabaseException {
732 if (terminal instanceof ResourceTerminal) {
733 Resource t = ((ResourceTerminal) terminal).getResource();
735 // TODO: remove this hack
736 if (element == null) { // Flag?
737 DiagramResource DIA = DiagramResource.getInstance(g);
738 Resource join = g.getPossibleObject(t, DIA.FlagIsJoinedBy);
741 return new CPConnectionJoin(join);
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;
753 connection = getConnection(g, element);
754 return new CPConnection(connection);
756 throw new IllegalArgumentException("Unrecognized Terminal class: " + terminal);
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);
764 public static IConnectionPoint toConnectionPoint(ReadGraph graph, TerminalInfo ti) throws DatabaseException {
765 return ConnectionUtil.toConnectionPoint(graph, ti.e, ti.t);
768 public static IConnectionPoint toConnectionPoint(ReadGraph graph, DesignatedTerminal t) throws DatabaseException {
769 return toConnectionPoint(graph, t.element, t.terminal);
772 public static Resource toDirectionTag(ReadGraph graph, BranchPoint.Direction direction) {
773 if (direction == null)
776 DiagramResource DIA = DiagramResource.getInstance(graph);
778 case Horizontal: return DIA.Horizontal;
779 case Vertical: return DIA.Vertical;
780 default: return null;
785 * Copied from ConnectionCommandHandler#splitConnection, duplicate code.
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);
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);
818 * @throws DatabaseException
820 public static IElement getSingleEdge(Object object) throws DatabaseException {
821 IElement e = AdaptionUtils.adaptToSingle(object, IElement.class);
825 if (PickFilter.FILTER_CONNECTION_EDGES.accept(e))
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();
841 * @throws DatabaseException
843 public static Collection<IElement> getEdges(Object object) throws DatabaseException {
844 IElement e = AdaptionUtils.adaptToSingle(object, IElement.class);
848 if (PickFilter.FILTER_CONNECTION_EDGES.accept(e))
849 return Collections.singleton(e);
851 if (PickFilter.FILTER_CONNECTIONS.accept(e)) {
852 ConnectionHandler ch = e.getElementClass().getSingleItem(ConnectionHandler.class);
853 return ch.getSegments(e, null);
862 * @throws DatabaseException
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))
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
883 * @throws DatabaseException
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))
902 * @throws DatabaseException
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);
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);
922 * Creates a route graph connection which has corners at the specified
923 * locations, starting/ending from/at the specified element terminals.
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.
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
935 * element to end the connection at
936 * @param endConnectionPoint
937 * STR.ConnectedTo relation of the end element terminal
939 * the corners to create for the connection
940 * @return the created diagram connection resource
941 * @throws DatabaseException
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;
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);
958 return createConnectionWithCorners(graph, DIA.RouteGraphConnection, startElement,
959 startConnectionPoint, DIA.HasPlainConnector, endElement, endConnectionPoint, DIA.HasArrowConnector,
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.
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
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;
986 if (corners.size() == 1)
987 throw new UnsupportedOperationException("1 corner currently not supported");
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);
995 if (corners.size() > 1) {
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;
1006 graph.claim(previousRouteNode, DIA.AreConnected, DIA.AreConnected, connector2);
1008 graph.claim(connector1, DIA.AreConnected, DIA.AreConnected, connector2);
1014 public Resource createConnectionWithSingleLine(WriteGraph graph, Resource connectionType,
1015 Collection<Triple<Resource,Resource,Resource>> endpoints,
1016 double coordinate, boolean horizontal)
1017 throws DatabaseException {
1019 DiagramResource DIA = br.DIA;
1021 Resource connection = newInstance(g, connectionType);
1023 Resource routeLine = ConnectionUtil.createRouteline(graph, connection, coordinate, horizontal);
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);
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 {
1040 DiagramResource DIA = br.DIA;
1042 Resource connection = newInstance(g, connectionType);
1044 Resource[] parts = new Resource[terminals.size() + routeLines.size()];
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;
1054 for(Pair<Double, Boolean> routeLine : routeLines) {
1055 Resource r = ConnectionUtil.createRouteline(graph, connection, routeLine.first, routeLine.second);
1059 // System.err.println("Connect " + parts.length + " parts.");
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);
1076 * @param isHorizontal
1077 * @return new route line that is attached to the specified diagram connection
1078 * @throws DatabaseException
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);
1092 * @param graph database write-only access
1093 * @param type type of the created resource
1094 * @return new instance of type
1095 * @throws DatabaseException
1097 private Resource newInstance(WriteOnlyGraph graph, Resource type) throws DatabaseException {
1098 Resource connection = graph.newResource();
1099 g.claim(connection, br.L0.InstanceOf, null, type);