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.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;
72 * @author Tuukka Lehtonen
74 public final class ConnectionUtil {
76 private final ReadGraph rg;
77 private final WriteGraph g;
78 private final BasicResources br;
81 * Construct utility with read-only support.
84 public ConnectionUtil(ReadGraph g) {
86 this.g = (g instanceof WriteGraph) ? (WriteGraph) g : null;
87 this.br = BasicResources.getInstance(g);
91 * Construct utility with read-write support.
94 public ConnectionUtil(WriteGraph g) {
97 this.br = BasicResources.getInstance(g);
100 void assertWriteSupport() {
102 throw new UnsupportedOperationException("no write support, this ConnectionUtil is read-only");
106 * Creates a new connection element of the specified type and attaches it to
107 * the specified diagram composite.
109 * @param composite diagram composite
110 * @param type connection element type
111 * @return created connection
112 * @throws DatabaseException
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);
126 * Creates a new connection element of the specified type without attaching
127 * it to any diagram composite.
129 * @param type connection element type
130 * @return created connection
131 * @throws DatabaseException
133 public Resource newConnection(Resource type) throws DatabaseException {
134 assertWriteSupport();
135 return newInstance(g, type);
139 * Creates a new element terminal connector (DiagramResource) for the specified connector
141 * @param hasConnector
143 * @throws DatabaseException
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);
152 public Resource newBranchPoint(Resource connection, AffineTransform tr) throws DatabaseException {
153 return newBranchPoint(connection, tr, null);
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);
161 double[] mat = new double[6];
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);
168 Resource tag = toDirectionTag(g, direction);
170 g.claim(bp, tag, tag, bp);
172 g.claim(connection, br.DIA.HasBranchPoint, bp);
179 * @param isHorizontal
181 * @throws DatabaseException
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);
189 if (isHorizontal != null) {
190 g.addLiteral(rl, br.DIA.IsHorizontal, br.DIA.IsHorizontal_Inverse, br.L0.Boolean, isHorizontal, Bindings.BOOLEAN);
192 g.claim(connection, br.DIA.HasInteriorRouteNode, br.DIA.HasInteriorRouteNode_Inverse, rl);
196 ConnectionSegmentEnd getTerminalType(Terminal terminal, ConnectionSegmentEnd defaultValue) {
197 ConnectionSegmentEnd type = getTerminalType(terminal);
198 return type != null ? type : defaultValue;
201 ConnectionSegmentEnd getTerminalType(Terminal t) {
205 if (t instanceof ResourceTerminal) {
206 return ConnectionSegmentEnd.CONNECTOR;
207 } else if (t instanceof BranchPointTerminal) {
208 return ConnectionSegmentEnd.BRANCH;
210 throw new IllegalArgumentException("unsupported terminal '" + t + "'");
214 Resource resolveTerminalRelation(ReadGraph g, Terminal t) throws DatabaseException {
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)) {
223 return terminalRelation;
225 throw new IllegalArgumentException("unsupported terminal '" + t + "' for terminal relation resolution");
229 public Resource toHasConnectorRelation(EdgeEnd end) {
231 case Begin: return br.DIA.HasPlainConnector;
232 case End: return br.DIA.HasArrowConnector;
233 default: throw new IllegalArgumentException("unsupported edge end: " + end);
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))
245 public Resource getAttachmentRelationForConnector(Resource connector) throws DatabaseException {
246 Statement connection = g.getPossibleStatement(connector, br.DIA.IsConnectorOf);
247 if (connection == null)
249 Resource attachment = g.getInverse(connection.getPredicate());
258 * @param attachmentRelation the relation used for attaching the connector to the connector
260 * @throws DatabaseException
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);
268 switch (connectorType) {
270 // NOTE: should we ensure here that (connection, br.dr.HasBranchPoint, node) exists?
274 Resource terminalRelation = resolveTerminalRelation(g, terminal);
276 if (attachmentRelation == null)
277 attachmentRelation = toHasConnectorRelation(end);
279 if (!g.isSubrelationOf(attachmentRelation, br.DIA.HasConnector))
280 throw new AssumptionException("attachment relation not a subrelation of Has Connector", attachmentRelation);
282 // Create new connector for the specified node terminal
283 Resource terminalConnector = newConnector(connection, attachmentRelation);
284 g.claim(node, terminalRelation, terminalConnector);
285 return terminalConnector;
288 throw new Error("this should be unreachable code");
292 public void connect(Resource connector1, Resource connector2) throws DatabaseException {
293 assertWriteSupport();
294 g.claim(connector1, br.DIA.AreConnected, br.DIA.AreConnected, connector2);
297 public void disconnect(Resource connector1, Resource connector2) throws DatabaseException {
298 assertWriteSupport();
299 g.denyStatement(connector1, br.DIA.AreConnected, connector2);
302 public void disconnectFromAllRouteNodes(Resource connector) throws DatabaseException {
303 assertWriteSupport();
304 g.deny(connector, br.DIA.AreConnected);
307 public void disconnect(EdgeResource segment) throws DatabaseException {
308 assertWriteSupport();
309 disconnect(segment.first(), segment.second());
312 public boolean isConnected(Resource connector) throws DatabaseException {
313 return rg.hasStatement(connector, br.DIA.AreConnected);
316 public boolean isConnected(Resource connector, Resource toConnector) throws DatabaseException {
317 return rg.hasStatement(connector, br.DIA.AreConnected, toConnector);
320 public boolean isConnectionEmpty(Resource connection) throws DatabaseException {
321 return !rg.hasStatement(connection, br.DIA.HasConnector)
322 && !rg.hasStatement(connection, br.DIA.HasInteriorRouteNode);
325 private void removeConnectorOrBranchPoint(Resource connectorOrBranchPoint) throws DatabaseException {
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());
336 g.deny(connectorOrBranchPoint, br.DIA.AreConnected);
338 // Removes both the terminal relation and the HasConnector relation
339 // to the :Connection
340 g.deny(connectorOrBranchPoint, br.STR.Connects);
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);
348 * Removes a complete connection along with all its branch points and terminal connectors.
350 * @param connection the connection to remove
352 public void removeConnection(Resource connection) throws DatabaseException {
353 assertWriteSupport();
355 // Add comment to change set.
356 CommentMetadata cm = g.getMetadata(CommentMetadata.class);
357 g.addMetadata(cm.add("Remove connection " + connection));
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));
364 // 2. Remove all connectors/branch points
365 for (Resource connector : connectors) {
366 removeConnectorOrBranchPoint(connector);
367 RemoverUtil.remove(g, connector);
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);
377 * Removes a single connector part from the graph. A connection part can be
378 * either a branch point or a terminal connector.
380 * @param connectorOrBranchPoint
381 * @throws DatabaseException
383 public void removeConnectionPart(Resource connectorOrBranchPoint) throws DatabaseException {
384 removeConnectorOrBranchPoint(connectorOrBranchPoint);
385 RemoverUtil.remove(g, connectorOrBranchPoint);
389 * Removes the specified connection segment. Checks that both ends of the
390 * edge segment are part of to the same connection. Steps taken:
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>
400 * @param segment the connection segment to remove
402 public void remove(EdgeResource segment) throws DatabaseException {
403 remove(segment, false);
407 * Removes the specified connection segment. Steps taken:
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>
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
426 public void remove(EdgeResource segment, boolean unchecked) throws DatabaseException {
427 assertWriteSupport();
430 @SuppressWarnings("unused")
431 Resource connection = getConnection(g, segment);
434 // 1. disconnect segment ends
437 // 2. Remove connectors/branch points if they become fully disconnected
438 if (!isConnected(segment.first())) {
439 removeConnectorOrBranchPoint(segment.first());
441 if (!isConnected(segment.second())) {
442 removeConnectorOrBranchPoint(segment.second());
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);
455 * Removes all DIA.Connector instances from the specified connection that
456 * are not used for anything.
458 * @param connection connection to examine
459 * @return the amount of unused connectors removed
461 public int removeUnusedConnectors(Resource connection) throws DatabaseException {
463 for (Resource connector : getConnectors(connection, null)) {
464 if (!g.getObjects(connector, br.DIA.AreConnected).isEmpty())
466 Collection<Resource> connects = g.getObjects(connector, br.STR.Connects);
467 if (connects.size() > 1)
470 removeConnectionPart(connector);
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.
480 * @param connection connection to examine
481 * @return the amount of unused connectors removed
483 public int removeExtraInteriorRouteNodes(Resource connection) throws DatabaseException {
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)
490 removeConnectionPart(interiorRouteNode);
497 * Splits the specified connection segment by adding a new branch point in
498 * between the segment ends.
501 * @return the branch (route) point created by the split operation.
503 public Resource split(EdgeResource segment, AffineTransform splitPos) throws DatabaseException {
504 assertWriteSupport();
506 Resource connection = getConnection(g, segment);
508 Resource bp = newBranchPoint(connection, splitPos);
509 connect(segment.first(), bp);
510 connect(bp, segment.second());
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.
519 * @param interiorRouteNode
522 public void join(Resource interiorRouteNode) throws DatabaseException {
523 assertWriteSupport();
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);
540 public void getConnectionSegments(Resource connection, Collection<EdgeResource> result) throws DatabaseException {
542 ArrayList<EdgeResource> edges = new ArrayList<EdgeResource>();
543 Set<Resource> visited = new HashSet<Resource>();
544 Stack<Resource> todo = new Stack<Resource>();
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);
550 assert(!seeds.isEmpty());
552 Resource seed = seeds.iterator().next();
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);
568 for(EdgeResource uer : edges) {
569 // System.out.println("loaded edge " + uer.first() + " " + uer.second());
575 public Collection<Resource> getBranchPoints(Resource connection, Collection<Resource> result) throws DatabaseException {
577 result = new ArrayList<Resource>();
578 result.addAll(rg.getObjects(connection, br.DIA.HasBranchPoint));
582 public Collection<Resource> getConnectors(Resource connection, Collection<Resource> result) throws DatabaseException {
584 result = new ArrayList<Resource>();
585 result.addAll(rg.getObjects(connection, br.DIA.HasConnector));
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))
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))
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)) {
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)) {
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)) {
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)) {
646 public Collection<Resource> getConnectedComponents(Resource connection, Collection<Resource> result)
647 throws DatabaseException {
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))
654 result.add(connects);
660 public Collection<Resource> getConnectedConnectors(Resource connection, Collection<Resource> result)
661 throws DatabaseException {
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 );
672 public Collection<Resource> getTerminalConnectors(Resource node, Collection<Resource> result)
673 throws DatabaseException {
675 result = new ArrayList<Resource>(2);
676 result.addAll( rg.getObjects(node, br.STR.IsConnectedTo) );
683 * @return <code>null</code> if the specified resource is not part of any
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);
690 conn = g.getPossibleObject(routeNode, dr.HasInteriorRouteNode_Inverse);
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))
702 public static Resource tryGetMappedConnection(ReadGraph g, Resource connector) throws DatabaseException {
703 ModelingResources MOD = ModelingResources.getInstance(g);
704 return g.getPossibleObject(connector, MOD.ConnectorToConnection);
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
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");
733 public static IConnectionPoint toConnectionPoint(ReadGraph g, Resource element, Terminal terminal) throws DatabaseException {
734 if (terminal instanceof ResourceTerminal) {
735 Resource t = ((ResourceTerminal) terminal).getResource();
737 // TODO: remove this hack
738 if (element == null) { // Flag?
739 DiagramResource DIA = DiagramResource.getInstance(g);
740 Resource join = g.getPossibleObject(t, DIA.FlagIsJoinedBy);
743 return new CPConnectionJoin(join);
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;
755 connection = getConnection(g, element);
756 return new CPConnection(connection);
758 throw new IllegalArgumentException("Unrecognized Terminal class: " + terminal);
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);
766 public static IConnectionPoint toConnectionPoint(ReadGraph graph, TerminalInfo ti) throws DatabaseException {
767 return ConnectionUtil.toConnectionPoint(graph, ti.e, ti.t);
770 public static IConnectionPoint toConnectionPoint(ReadGraph graph, DesignatedTerminal t) throws DatabaseException {
771 return toConnectionPoint(graph, t.element, t.terminal);
774 public static Resource toDirectionTag(ReadGraph graph, BranchPoint.Direction direction) {
775 if (direction == null)
778 DiagramResource DIA = DiagramResource.getInstance(graph);
780 case Horizontal: return DIA.Horizontal;
781 case Vertical: return DIA.Vertical;
782 default: return null;
787 * Copied from ConnectionCommandHandler#splitConnection, duplicate code.
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);
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);
820 * @throws DatabaseException
822 public static IElement getSingleEdge(Object object) throws DatabaseException {
823 IElement e = AdaptionUtils.adaptToSingle(object, IElement.class);
827 if (PickFilter.FILTER_CONNECTION_EDGES.accept(e))
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();
843 * @throws DatabaseException
845 public static Collection<IElement> getEdges(Object object) throws DatabaseException {
846 IElement e = AdaptionUtils.adaptToSingle(object, IElement.class);
850 if (PickFilter.FILTER_CONNECTION_EDGES.accept(e))
851 return Collections.singleton(e);
853 if (PickFilter.FILTER_CONNECTIONS.accept(e)) {
854 ConnectionHandler ch = e.getElementClass().getSingleItem(ConnectionHandler.class);
855 return ch.getSegments(e, null);
864 * @throws DatabaseException
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))
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
885 * @throws DatabaseException
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))
904 * @throws DatabaseException
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);
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);
924 * Creates a route graph connection which has corners at the specified
925 * locations, starting/ending from/at the specified element terminals.
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.
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
937 * element to end the connection at
938 * @param endConnectionPoint
939 * STR.ConnectedTo relation of the end element terminal
941 * the corners to create for the connection
942 * @return the created diagram connection resource
943 * @throws DatabaseException
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;
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);
960 return createConnectionWithCorners(graph, DIA.RouteGraphConnection, startElement,
961 startConnectionPoint, DIA.HasPlainConnector, endElement, endConnectionPoint, DIA.HasArrowConnector,
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.
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
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;
988 if (corners.size() == 1)
989 throw new UnsupportedOperationException("1 corner currently not supported");
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);
997 if (corners.size() > 1) {
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;
1008 graph.claim(previousRouteNode, DIA.AreConnected, DIA.AreConnected, connector2);
1010 graph.claim(connector1, DIA.AreConnected, DIA.AreConnected, connector2);
1016 public Resource createConnectionWithSingleLine(WriteGraph graph, Resource connectionType,
1017 Collection<Triple<Resource,Resource,Resource>> endpoints,
1018 double coordinate, boolean horizontal)
1019 throws DatabaseException {
1021 DiagramResource DIA = br.DIA;
1023 Resource connection = newInstance(g, connectionType);
1025 Resource routeLine = ConnectionUtil.createRouteline(graph, connection, coordinate, horizontal);
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);
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 {
1042 DiagramResource DIA = br.DIA;
1044 Resource connection = newInstance(g, connectionType);
1046 Resource[] parts = new Resource[terminals.size() + routeLines.size()];
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;
1056 for(Pair<Double, Boolean> routeLine : routeLines) {
1057 Resource r = ConnectionUtil.createRouteline(graph, connection, routeLine.first, routeLine.second);
1061 // System.err.println("Connect " + parts.length + " parts.");
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);
1078 * @param isHorizontal
1079 * @return new route line that is attached to the specified diagram connection
1080 * @throws DatabaseException
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);
1094 * @param graph database write-only access
1095 * @param type type of the created resource
1096 * @return new instance of type
1097 * @throws DatabaseException
1099 private Resource newInstance(WriteOnlyGraph graph, Resource type) throws DatabaseException {
1100 Resource connection = graph.newResource();
1101 g.claim(connection, br.L0.InstanceOf, null, type);