1 /*******************************************************************************
\r
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
\r
3 * in Industry THTH ry.
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.diagram.content;
\r
14 import java.awt.geom.AffineTransform;
\r
15 import java.awt.geom.Line2D;
\r
16 import java.awt.geom.Point2D;
\r
17 import java.util.ArrayList;
\r
18 import java.util.Collection;
\r
19 import java.util.Collections;
\r
20 import java.util.HashSet;
\r
21 import java.util.Iterator;
\r
22 import java.util.List;
\r
23 import java.util.Set;
\r
24 import java.util.Stack;
\r
26 import org.simantics.databoard.Bindings;
\r
27 import org.simantics.db.ReadGraph;
\r
28 import org.simantics.db.Resource;
\r
29 import org.simantics.db.Statement;
\r
30 import org.simantics.db.WriteGraph;
\r
31 import org.simantics.db.WriteOnlyGraph;
\r
32 import org.simantics.db.common.CommentMetadata;
\r
33 import org.simantics.db.common.request.IndexRoot;
\r
34 import org.simantics.db.common.utils.NameUtils;
\r
35 import org.simantics.db.common.utils.OrderedSetUtils;
\r
36 import org.simantics.db.exception.AssumptionException;
\r
37 import org.simantics.db.exception.DatabaseException;
\r
38 import org.simantics.db.exception.ValidationException;
\r
39 import org.simantics.db.layer0.adapter.impl.EntityRemover;
\r
40 import org.simantics.db.layer0.util.RemoverUtil;
\r
41 import org.simantics.diagram.connection.ConnectionSegmentEnd;
\r
42 import org.simantics.diagram.stubs.DiagramResource;
\r
43 import org.simantics.diagram.synchronization.graph.BasicResources;
\r
44 import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;
\r
45 import org.simantics.g2d.connection.handler.ConnectionHandler;
\r
46 import org.simantics.g2d.diagram.handler.PickRequest.PickFilter;
\r
47 import org.simantics.g2d.diagram.handler.Topology.Terminal;
\r
48 import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil.TerminalInfo;
\r
49 import org.simantics.g2d.element.ElementUtils;
\r
50 import org.simantics.g2d.element.IElement;
\r
51 import org.simantics.g2d.element.handler.BendsHandler;
\r
52 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
\r
53 import org.simantics.g2d.element.handler.impl.BranchPointTerminal;
\r
54 import org.simantics.g2d.elementclass.BranchPoint;
\r
55 import org.simantics.g2d.elementclass.BranchPoint.Direction;
\r
56 import org.simantics.layer0.Layer0;
\r
57 import org.simantics.modeling.ModelingResources;
\r
58 import org.simantics.scenegraph.utils.GeometryUtils;
\r
59 import org.simantics.scl.commands.Commands;
\r
60 import org.simantics.structural.stubs.StructuralResource2;
\r
61 import org.simantics.structural2.modelingRules.CPConnection;
\r
62 import org.simantics.structural2.modelingRules.CPConnectionJoin;
\r
63 import org.simantics.structural2.modelingRules.CPTerminal;
\r
64 import org.simantics.structural2.modelingRules.IConnectionPoint;
\r
65 import org.simantics.utils.Development;
\r
66 import org.simantics.utils.datastructures.Pair;
\r
67 import org.simantics.utils.datastructures.Triple;
\r
68 import org.simantics.utils.ui.AdaptionUtils;
\r
71 * @author Tuukka Lehtonen
\r
73 public final class ConnectionUtil {
\r
75 private final ReadGraph rg;
\r
76 private final WriteGraph g;
\r
77 private final BasicResources br;
\r
80 * Construct utility with read-only support.
\r
83 public ConnectionUtil(ReadGraph g) {
\r
85 this.g = (g instanceof WriteGraph) ? (WriteGraph) g : null;
\r
86 this.br = BasicResources.getInstance(g);
\r
90 * Construct utility with read-write support.
\r
93 public ConnectionUtil(WriteGraph g) {
\r
96 this.br = BasicResources.getInstance(g);
\r
99 void assertWriteSupport() {
\r
101 throw new UnsupportedOperationException("no write support, this ConnectionUtil is read-only");
\r
105 * Creates a new connection element of the specified type and attaches it to
\r
106 * the specified diagram composite.
\r
108 * @param composite diagram composite
\r
109 * @param type connection element type
\r
110 * @return created connection
\r
111 * @throws DatabaseException
\r
113 public Resource newConnection(Resource composite, Resource type) throws DatabaseException {
\r
114 assertWriteSupport();
\r
115 Resource connection = newConnection(type);
\r
116 // By default, add connections to the front of the diagram since most
\r
117 // often it is visually the expected result.
\r
118 OrderedSetUtils.addFirst(g, composite, connection);
\r
119 g.claim(composite, br.L0.ConsistsOf, br.L0.PartOf, connection);
\r
124 * Creates a new connection element of the specified type without attaching
\r
125 * it to any diagram composite.
\r
127 * @param type connection element type
\r
128 * @return created connection
\r
129 * @throws DatabaseException
\r
131 public Resource newConnection(Resource type) throws DatabaseException {
\r
132 assertWriteSupport();
\r
133 return newInstance(g, type);
\r
137 * Creates a new element terminal connector (DiagramResource) for the specified connector
\r
138 * @param connection
\r
139 * @param hasConnector
\r
141 * @throws DatabaseException
\r
143 public Resource newConnector(Resource connection, Resource hasConnector) throws DatabaseException {
\r
144 assertWriteSupport();
\r
145 Resource connector = newInstance(g, br.DIA.Connector);
\r
146 g.claim(connection, hasConnector, connector);
\r
150 public Resource newBranchPoint(Resource connection, AffineTransform tr) throws DatabaseException {
\r
151 return newBranchPoint(connection, tr, null);
\r
154 public Resource newBranchPoint(Resource connection, AffineTransform tr, Direction direction) throws DatabaseException {
\r
155 assertWriteSupport();
\r
156 Resource bp = g.newResource();
\r
157 g.claim(bp, br.L0.InstanceOf, null, br.DIA.BranchPoint);
\r
159 double[] mat = new double[6];
\r
161 Resource transform = g.newResource();
\r
162 g.claim(transform, br.L0.InstanceOf, null, br.G2D.Transform);
\r
163 g.claimValue(transform, mat);
\r
164 g.claim(bp, br.DIA.HasTransform, transform);
\r
166 Resource tag = toDirectionTag(g, direction);
\r
168 g.claim(bp, tag, tag, bp);
\r
170 g.claim(connection, br.DIA.HasBranchPoint, bp);
\r
175 * @param connection
\r
177 * @param isHorizontal
\r
179 * @throws DatabaseException
\r
181 public Resource newRouteLine(Resource connection, Double position, Boolean isHorizontal) throws DatabaseException {
\r
182 assertWriteSupport();
\r
183 Resource rl = newInstance(g, br.DIA.RouteLine);
\r
184 if (position != null) {
\r
185 g.addLiteral(rl, br.DIA.HasPosition, br.DIA.HasPosition_Inverse, br.L0.Double, position, Bindings.DOUBLE);
\r
187 if (isHorizontal != null) {
\r
188 g.addLiteral(rl, br.DIA.IsHorizontal, br.DIA.IsHorizontal_Inverse, br.L0.Boolean, isHorizontal, Bindings.BOOLEAN);
\r
190 g.claim(connection, br.DIA.HasInteriorRouteNode, br.DIA.HasInteriorRouteNode_Inverse, rl);
\r
194 ConnectionSegmentEnd getTerminalType(Terminal terminal, ConnectionSegmentEnd defaultValue) {
\r
195 ConnectionSegmentEnd type = getTerminalType(terminal);
\r
196 return type != null ? type : defaultValue;
\r
199 ConnectionSegmentEnd getTerminalType(Terminal t) {
\r
203 if (t instanceof ResourceTerminal) {
\r
204 return ConnectionSegmentEnd.CONNECTOR;
\r
205 } else if (t instanceof BranchPointTerminal) {
\r
206 return ConnectionSegmentEnd.BRANCH;
\r
208 throw new IllegalArgumentException("unsupported terminal '" + t + "'");
\r
212 Resource resolveTerminalRelation(ReadGraph g, Terminal t) throws DatabaseException {
\r
215 if (t instanceof ResourceTerminal) {
\r
216 ResourceTerminal rt = (ResourceTerminal) t;
\r
217 Resource terminalRelation = DiagramGraphUtil.getConnectionPointOfTerminal(g, rt.getResource());
\r
218 if (!g.isSubrelationOf(terminalRelation, br.STR.IsConnectedTo)) {
\r
221 return terminalRelation;
\r
223 throw new IllegalArgumentException("unsupported terminal '" + t + "' for terminal relation resolution");
\r
227 public Resource toHasConnectorRelation(EdgeEnd end) {
\r
229 case Begin: return br.DIA.HasPlainConnector;
\r
230 case End: return br.DIA.HasArrowConnector;
\r
231 default: throw new IllegalArgumentException("unsupported edge end: " + end);
\r
235 public EdgeEnd toEdgeEnd(Resource attachmentRelation, EdgeEnd defaultValue) throws DatabaseException {
\r
236 if (g.isSubrelationOf(attachmentRelation, br.DIA.HasPlainConnector))
\r
237 return EdgeEnd.Begin;
\r
238 if (g.isSubrelationOf(attachmentRelation, br.DIA.HasArrowConnector))
\r
239 return EdgeEnd.End;
\r
240 return defaultValue;
\r
243 public Resource getAttachmentRelationForConnector(Resource connector) throws DatabaseException {
\r
244 Statement connection = g.getPossibleStatement(connector, br.DIA.IsConnectorOf);
\r
245 if (connection == null)
\r
247 Resource attachment = g.getInverse(connection.getPredicate());
\r
252 * @param connection
\r
256 * @param attachmentRelation the relation used for attaching the connector to the connector
\r
258 * @throws DatabaseException
\r
260 public Resource getOrCreateConnector(Resource connection, Resource node, Terminal terminal, EdgeEnd end, Resource attachmentRelation) throws DatabaseException {
\r
261 assertWriteSupport();
\r
262 ConnectionSegmentEnd connectorType = getTerminalType(terminal, null);
\r
263 if (connectorType == null)
\r
264 throw new AssumptionException("Invalid connection node", connection, node);
\r
266 switch (connectorType) {
\r
268 // NOTE: should we ensure here that (connection, br.dr.HasBranchPoint, node) exists?
\r
272 Resource terminalRelation = resolveTerminalRelation(g, terminal);
\r
274 if (attachmentRelation == null)
\r
275 attachmentRelation = toHasConnectorRelation(end);
\r
277 if (!g.isSubrelationOf(attachmentRelation, br.DIA.HasConnector))
\r
278 throw new AssumptionException("attachment relation not a subrelation of Has Connector", attachmentRelation);
\r
280 // Create new connector for the specified node terminal
\r
281 Resource terminalConnector = newConnector(connection, attachmentRelation);
\r
282 g.claim(node, terminalRelation, terminalConnector);
\r
283 return terminalConnector;
\r
286 throw new Error("this should be unreachable code");
\r
290 public void connect(Resource connector1, Resource connector2) throws DatabaseException {
\r
291 assertWriteSupport();
\r
292 g.claim(connector1, br.DIA.AreConnected, br.DIA.AreConnected, connector2);
\r
295 public void disconnect(Resource connector1, Resource connector2) throws DatabaseException {
\r
296 assertWriteSupport();
\r
297 g.denyStatement(connector1, br.DIA.AreConnected, connector2);
\r
300 public void disconnectFromAllRouteNodes(Resource connector) throws DatabaseException {
\r
301 assertWriteSupport();
\r
302 g.deny(connector, br.DIA.AreConnected);
\r
305 public void disconnect(EdgeResource segment) throws DatabaseException {
\r
306 assertWriteSupport();
\r
307 disconnect(segment.first(), segment.second());
\r
310 public boolean isConnected(Resource connector) throws DatabaseException {
\r
311 return rg.hasStatement(connector, br.DIA.AreConnected);
\r
314 public boolean isConnected(Resource connector, Resource toConnector) throws DatabaseException {
\r
315 return rg.hasStatement(connector, br.DIA.AreConnected, toConnector);
\r
318 public boolean isConnectionEmpty(Resource connection) throws DatabaseException {
\r
319 return !rg.hasStatement(connection, br.DIA.HasConnector)
\r
320 && !rg.hasStatement(connection, br.DIA.HasInteriorRouteNode);
\r
323 private void removeConnectorOrBranchPoint(Resource connectorOrBranchPoint) throws DatabaseException {
\r
325 // // Handle correct removal of route points
\r
326 // if(g.isInstanceOf(connectorOrBranchPoint, dr.BranchPoint)) {
\r
327 // Collection<Resource> connectedConnectors = g.getObjects(connectorOrBranchPoint, dr.AreConnected);
\r
328 // if(connectedConnectors.size() == 2) {
\r
329 // Iterator<Resource> it = connectedConnectors.iterator();
\r
330 // g.claim(it.next(), dr.AreConnected, it.next());
\r
334 g.deny(connectorOrBranchPoint, br.DIA.AreConnected);
\r
336 // Removes both the terminal relation and the HasConnector relation
\r
337 // to the :Connection
\r
338 g.deny(connectorOrBranchPoint, br.STR.Connects);
\r
340 // If this is a branch point/route node, remove it from the connection too.
\r
341 g.deny(connectorOrBranchPoint, br.DIA.IsBranchPointOf);
\r
342 g.deny(connectorOrBranchPoint, br.DIA.HasInteriorRouteNode_Inverse);
\r
346 * Removes a complete connection along with all its branch points and terminal connectors.
\r
348 * @param connection the connection to remove
\r
350 public void removeConnection(Resource connection) throws DatabaseException {
\r
351 assertWriteSupport();
\r
353 // Add comment to change set.
\r
354 CommentMetadata cm = g.getMetadata(CommentMetadata.class);
\r
355 g.addMetadata(cm.add("Remove connection " + connection));
\r
357 // 1. Get all connectors/branch points
\r
358 Collection<Resource> connectors = new ArrayList<Resource>();
\r
359 connectors.addAll(rg.getObjects(connection, br.DIA.HasConnector));
\r
360 connectors.addAll(rg.getObjects(connection, br.DIA.HasInteriorRouteNode));
\r
362 // 2. Remove all connectors/branch points
\r
363 for (Resource connector : connectors) {
\r
364 removeConnectorOrBranchPoint(connector);
\r
365 RemoverUtil.remove(g, connector);
\r
368 // 3. Remove whole connection
\r
369 for (Resource owner : OrderedSetUtils.getOwnerLists(g, connection, br.DIA.Diagram))
\r
370 OrderedSetUtils.remove(g, owner, connection);
\r
371 EntityRemover.remove(g, connection);
\r
375 * Removes a single connector part from the graph. A connection part can be
\r
376 * either a branch point or a terminal connector.
\r
378 * @param connectorOrBranchPoint
\r
379 * @throws DatabaseException
\r
381 public void removeConnectionPart(Resource connectorOrBranchPoint) throws DatabaseException {
\r
382 removeConnectorOrBranchPoint(connectorOrBranchPoint);
\r
383 RemoverUtil.remove(g, connectorOrBranchPoint);
\r
387 * Removes the specified connection segment. Checks that both ends of the
\r
388 * edge segment are part of to the same connection. Steps taken:
\r
390 * <li>Minimally this only disconnects the connection segment ends from each
\r
391 * other and nothing more.</li>
\r
392 * <li>After disconnecting, we check whether the segment ends are still
\r
393 * connected to something. If not, the :Connector/:BranchPoint at the
\r
394 * segment's end is destroyed and detached from the connection entity.</li>
\r
395 * <li>Finally, if the connection entity is empty (has no connectors/branch
\r
396 * points), it is also destroyed and removed from the diagram.</li>
\r
398 * @param segment the connection segment to remove
\r
400 public void remove(EdgeResource segment) throws DatabaseException {
\r
401 remove(segment, false);
\r
405 * Removes the specified connection segment. Steps taken:
\r
407 * <li>Minimally this only disconnects the connection segment ends from each
\r
408 * other and nothing more.</li>
\r
409 * <li>After disconnecting, we check whether the segment ends are still
\r
410 * connected to something. If not, the :Connector/:BranchPoint at the
\r
411 * segment's end is destroyed and detached from the connection entity.</li>
\r
412 * <li>Finally, if the connection entity is empty (has no connectors/branch
\r
413 * points), it is also destroyed and removed from the diagram.</li>
\r
415 * @param segment the connection segment to remove
\r
416 * @param unchecked <code>false</code> to check that both ends of the
\r
417 * segment are part of the same connection before removing,
\r
418 * <code>true</code> to just remove the segment without checking
\r
419 * this. Using <code>true</code> may help in cases where the
\r
420 * connection model has become corrupted for some reason, e.g. the
\r
421 * other end of the edge has lost its link to the connection while
\r
422 * the other has not,
\r
424 public void remove(EdgeResource segment, boolean unchecked) throws DatabaseException {
\r
425 assertWriteSupport();
\r
428 @SuppressWarnings("unused")
\r
429 Resource connection = getConnection(g, segment);
\r
432 // 1. disconnect segment ends
\r
433 disconnect(segment);
\r
435 // 2. Remove connectors/branch points if they become fully disconnected
\r
436 if (!isConnected(segment.first())) {
\r
437 removeConnectorOrBranchPoint(segment.first());
\r
439 if (!isConnected(segment.second())) {
\r
440 removeConnectorOrBranchPoint(segment.second());
\r
443 // 3. Remove whole connection entity if it becomes empty
\r
444 // if (isConnectionEmpty(connection)) {
\r
445 // for (Resource owner : OrderedSetUtils.getOwnerLists(g, connection, dr.Diagram))
\r
446 // OrderedSetUtils.remove(g, owner, connection);
\r
447 // RemoverUtil.remove(g, connection);
\r
453 * Removes all DIA.Connector instances from the specified connection that
\r
454 * are not used for anything.
\r
456 * @param connection connection to examine
\r
457 * @return the amount of unused connectors removed
\r
459 public int removeUnusedConnectors(Resource connection) throws DatabaseException {
\r
461 for (Resource connector : getConnectors(connection, null)) {
\r
462 if (!g.getObjects(connector, br.DIA.AreConnected).isEmpty())
\r
464 Collection<Resource> connects = g.getObjects(connector, br.STR.Connects);
\r
465 if (connects.size() > 1)
\r
468 removeConnectionPart(connector);
\r
475 * Removes all DIA.InteriorRouteNode instances from the specified connection that
\r
476 * are not used for anything, i.e. connect to less than 2 other route nodes.
\r
478 * @param connection connection to examine
\r
479 * @return the amount of unused connectors removed
\r
481 public int removeExtraInteriorRouteNodes(Resource connection) throws DatabaseException {
\r
483 for (Resource interiorRouteNode : g.getObjects(connection, br.DIA.HasInteriorRouteNode)) {
\r
484 Collection<Resource> connectedTo = g.getObjects(interiorRouteNode, br.DIA.AreConnected);
\r
485 if (connectedTo.size() > 1)
\r
488 removeConnectionPart(interiorRouteNode);
\r
495 * Splits the specified connection segment by adding a new branch point in
\r
496 * between the segment ends.
\r
499 * @return the branch (route) point created by the split operation.
\r
501 public Resource split(EdgeResource segment, AffineTransform splitPos) throws DatabaseException {
\r
502 assertWriteSupport();
\r
504 Resource connection = getConnection(g, segment);
\r
505 disconnect(segment);
\r
506 Resource bp = newBranchPoint(connection, splitPos);
\r
507 connect(segment.first(), bp);
\r
508 connect(bp, segment.second());
\r
513 * Joins the connection at the selected branch point if and only if the
\r
514 * branch point is a route point, i.e. it is connected to two other branch
\r
515 * points or connector.
\r
517 * @param interiorRouteNode
\r
520 public void join(Resource interiorRouteNode) throws DatabaseException {
\r
521 assertWriteSupport();
\r
523 if (!g.isInstanceOf(interiorRouteNode, br.DIA.InteriorRouteNode))
\r
524 throw new ValidationException("'" + NameUtils.getSafeName(g, interiorRouteNode) + "' is not an instance of DIA.InteriorRouteNode");
\r
525 @SuppressWarnings("unused")
\r
526 Resource connection = getConnection(g, interiorRouteNode);
\r
527 Collection<Resource> connectedTo = g.getObjects(interiorRouteNode, br.DIA.AreConnected);
\r
528 if (connectedTo.size() != 2)
\r
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() + ".");
\r
530 Iterator<Resource> it = connectedTo.iterator();
\r
531 Resource connector1 = it.next();
\r
532 Resource connector2 = it.next();
\r
533 //System.out.println("removing branch point " + GraphUtils.getReadableName(g, routeBranchPoint) + " which is connected to " + GraphUtils.getReadableName(g, connector1) + " and " + GraphUtils.getReadableName(g, connector2));
\r
534 removeConnectorOrBranchPoint(interiorRouteNode);
\r
535 connect(connector1, connector2);
\r
538 public void getConnectionSegments(Resource connection, Collection<EdgeResource> result) throws DatabaseException {
\r
540 ArrayList<EdgeResource> edges = new ArrayList<EdgeResource>();
\r
541 Set<Resource> visited = new HashSet<Resource>();
\r
542 Stack<Resource> todo = new Stack<Resource>();
\r
544 // Try to select input as root, this ensures correct order for simple paths
\r
545 Collection<Resource> seeds = rg.getObjects(connection, br.DIA.HasArrowConnector);
\r
546 if(seeds.isEmpty()) seeds = rg.getObjects(connection, br.DIA.HasPlainConnector);
\r
548 assert(!seeds.isEmpty());
\r
550 Resource seed = seeds.iterator().next();
\r
554 while(!todo.isEmpty()) {
\r
555 Resource location = todo.pop();
\r
556 if(!visited.contains(location)) {
\r
557 visited.add(location);
\r
558 for (Resource connectedTo : rg.getObjects(location, br.DIA.AreConnected)) {
\r
559 todo.add(connectedTo);
\r
560 EdgeResource edge = new EdgeResource(location, connectedTo);
\r
561 if(!edges.contains(edge)) edges.add(edge);
\r
566 for(EdgeResource uer : edges) {
\r
567 // System.out.println("loaded edge " + uer.first() + " " + uer.second());
\r
573 public Collection<Resource> getBranchPoints(Resource connection, Collection<Resource> result) throws DatabaseException {
\r
574 if (result == null)
\r
575 result = new ArrayList<Resource>();
\r
576 result.addAll(rg.getObjects(connection, br.DIA.HasBranchPoint));
\r
580 public Collection<Resource> getConnectors(Resource connection, Collection<Resource> result) throws DatabaseException {
\r
581 if (result == null)
\r
582 result = new ArrayList<Resource>();
\r
583 result.addAll(rg.getObjects(connection, br.DIA.HasConnector));
\r
587 public Resource getConnectedComponent(Resource connection, Resource connector) throws DatabaseException {
\r
588 for (Resource connects : rg.getObjects(connector, br.STR.Connects))
\r
589 if (!connects.equals(connection))
\r
594 public Statement getConnectedComponentStatement(Resource connection, Resource connector) throws DatabaseException {
\r
595 for (Statement connects : rg.getStatements(connector, br.STR.Connects))
\r
596 if (!connects.getObject().equals(connection))
\r
601 public void gatherEdges(Resource connection, Collection<EdgeResource> result) throws DatabaseException {
\r
602 Set<Object> visited = new HashSet<Object>();
\r
603 for (Resource connector : rg.getObjects(connection, br.DIA.HasConnector)) {
\r
604 for (Resource connectedTo : rg.getObjects(connector, br.DIA.AreConnected)) {
\r
605 EdgeResource p = new EdgeResource(connector, connectedTo);
\r
606 if (visited.add(p)) {
\r
611 Collection<Resource> routeNodes = rg.getObjects(connection, br.DIA.HasInteriorRouteNode);
\r
612 for (Resource routeNode : routeNodes) {
\r
613 for (Resource connectedTo : rg.getObjects(routeNode, br.DIA.AreConnected)) {
\r
614 EdgeResource p = new EdgeResource(routeNode, connectedTo);
\r
615 if (visited.add(p)) {
\r
622 public void gatherConnectionParts(Resource connection, Collection<Object> result) throws DatabaseException {
\r
623 Set<Object> visited = new HashSet<Object>();
\r
624 for (Resource connector : rg.getObjects(connection, br.DIA.HasConnector)) {
\r
625 for (Resource connectedTo : rg.getObjects(connector, br.DIA.AreConnected)) {
\r
626 EdgeResource p = new EdgeResource(connector, connectedTo);
\r
627 if (visited.add(p)) {
\r
632 Collection<Resource> routeNodes = rg.getObjects(connection, br.DIA.HasInteriorRouteNode);
\r
633 for (Resource routeNode : routeNodes) {
\r
634 result.add(routeNode);
\r
635 for (Resource connectedTo : rg.getObjects(routeNode, br.DIA.AreConnected)) {
\r
636 EdgeResource p = new EdgeResource(routeNode, connectedTo);
\r
637 if (visited.add(p)) {
\r
644 public Collection<Resource> getConnectedComponents(Resource connection, Collection<Resource> result)
\r
645 throws DatabaseException {
\r
646 if (result == null)
\r
647 result = new ArrayList<Resource>(2);
\r
648 for (Resource connector : rg.getObjects(connection, br.DIA.HasConnector)) {
\r
649 for (Resource connects : rg.getObjects(connector, br.STR.Connects)) {
\r
650 if (connects.equals(connection))
\r
652 result.add(connects);
\r
658 public Collection<Resource> getConnectedConnectors(Resource connection, Collection<Resource> result)
\r
659 throws DatabaseException {
\r
660 if (result == null)
\r
661 result = new ArrayList<Resource>(2);
\r
662 for (Resource connector : rg.getObjects(connection, br.DIA.HasConnector)) {
\r
663 Resource connects = getConnectedComponent(connection, connector);
\r
664 if (connects != null)
\r
665 result.add( connector );
\r
670 public Collection<Resource> getTerminalConnectors(Resource node, Collection<Resource> result)
\r
671 throws DatabaseException {
\r
672 if (result == null)
\r
673 result = new ArrayList<Resource>(2);
\r
674 result.addAll( rg.getObjects(node, br.STR.IsConnectedTo) );
\r
681 * @return <code>null</code> if the specified resource is not part of any
\r
684 public static Resource tryGetConnection(ReadGraph g, Resource routeNode) throws DatabaseException {
\r
685 DiagramResource dr = DiagramResource.getInstance(g);
\r
686 Resource conn = g.getPossibleObject(routeNode, dr.IsConnectorOf);
\r
688 conn = g.getPossibleObject(routeNode, dr.HasInteriorRouteNode_Inverse);
\r
692 public static Resource tryGetConnection(ReadGraph g, EdgeResource segment) throws DatabaseException {
\r
693 Resource first = tryGetConnection(g, segment.first());
\r
694 Resource second = tryGetConnection(g, segment.second());
\r
695 if (first == null || second == null || !first.equals(second))
\r
700 public static Resource tryGetMappedConnection(ReadGraph g, Resource connector) throws DatabaseException {
\r
701 ModelingResources MOD = ModelingResources.getInstance(g);
\r
702 return g.getPossibleObject(connector, MOD.ConnectorToConnection);
\r
705 public static Resource getConnection(ReadGraph g, EdgeResource segment) throws DatabaseException {
\r
706 Resource first = tryGetConnection(g, segment.first());
\r
707 Resource second = tryGetConnection(g, segment.second());
\r
708 if (first == null && second == null)
\r
709 throw new ValidationException(
\r
710 "neither connection segment end is attached to a Connection entity instance: "
\r
711 + segment.toString(g) + " - " + segment.toString());
\r
712 if (first != null ^ second != null)
\r
713 throw new ValidationException("both ends of connection segment "
\r
714 + segment.toString(g) + " - " + segment.toString() + " are not connected (first=" + first
\r
715 + ", second=" + second + ")");
\r
716 if (!first.equals(second))
\r
717 throw new ValidationException("connectors of connection segment "
\r
718 + segment.toString(g) + " - " + segment.toString() + " are part of different connections: " + first
\r
719 + " vs. " + second);
\r
723 public static Resource getConnection(ReadGraph g, Resource routeNode) throws DatabaseException {
\r
724 Resource connection = tryGetConnection(g, routeNode);
\r
725 if (connection == null)
\r
726 throw new ValidationException("route node '"
\r
727 + NameUtils.getSafeName(g, routeNode) + "' is not part of any connection");
\r
731 public static IConnectionPoint toConnectionPoint(ReadGraph g, Resource element, Terminal terminal) throws DatabaseException {
\r
732 if (terminal instanceof ResourceTerminal) {
\r
733 Resource t = ((ResourceTerminal) terminal).getResource();
\r
735 // TODO: remove this hack
\r
736 if (element == null) { // Flag?
\r
737 DiagramResource DIA = DiagramResource.getInstance(g);
\r
738 Resource join = g.getPossibleObject(t, DIA.FlagIsJoinedBy);
\r
741 return new CPConnectionJoin(join);
\r
743 // element should be :Element
\r
744 Resource bindingRelation = DiagramGraphUtil.getConnectionPointOfTerminal(g, t);
\r
745 return new CPTerminal(element, bindingRelation);
\r
746 } else if (terminal instanceof BranchPointTerminal) {
\r
747 // element should be either : DIA.InteriorRouteNode or DIA.Connection
\r
748 Resource connection = null;
\r
749 DiagramResource DIA = DiagramResource.getInstance(g);
\r
750 if (g.isInstanceOf(element, DIA.Connection))
\r
751 connection = element;
\r
753 connection = getConnection(g, element);
\r
754 return new CPConnection(connection);
\r
756 throw new IllegalArgumentException("Unrecognized Terminal class: " + terminal);
\r
759 public static IConnectionPoint toConnectionPoint(ReadGraph graph, IElement element, Terminal terminal) throws DatabaseException {
\r
760 Resource r = (Resource) ElementUtils.getObject(element);
\r
761 return ConnectionUtil.toConnectionPoint(graph, r, terminal);
\r
764 public static IConnectionPoint toConnectionPoint(ReadGraph graph, TerminalInfo ti) throws DatabaseException {
\r
765 return ConnectionUtil.toConnectionPoint(graph, ti.e, ti.t);
\r
768 public static IConnectionPoint toConnectionPoint(ReadGraph graph, DesignatedTerminal t) throws DatabaseException {
\r
769 return toConnectionPoint(graph, t.element, t.terminal);
\r
772 public static Resource toDirectionTag(ReadGraph graph, BranchPoint.Direction direction) {
\r
773 if (direction == null)
\r
776 DiagramResource DIA = DiagramResource.getInstance(graph);
\r
777 switch (direction) {
\r
778 case Horizontal: return DIA.Horizontal;
\r
779 case Vertical: return DIA.Vertical;
\r
780 default: return null;
\r
785 * Copied from ConnectionCommandHandler#splitConnection, duplicate code.
\r
787 * @param toCanvasPos
\r
791 public static Line2D resolveNearestEdgeLineSegment(Point2D toCanvasPos, IElement onEdge) {
\r
792 // Try to find an initial preferred direction for the new
\r
793 // branch point based on the direction of the edge's line
\r
794 // segment on which the split is done.
\r
795 List<Point2D> points = new ArrayList<Point2D>();
\r
796 BendsHandler bh = onEdge.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);
\r
798 org.simantics.g2d.utils.GeometryUtils.getPoints(bh.getPath(onEdge), points);
\r
799 Line2D nearestLine = null;
\r
800 double nearestDistanceToLine = Double.MAX_VALUE;
\r
801 for (int i = 0; i < points.size() - 1; ++i) {
\r
802 Point2D p1 = points.get(i);
\r
803 Point2D p2 = points.get(i+1);
\r
804 double distanceToLine = GeometryUtils.distanceFromLine(toCanvasPos, p1, p2);
\r
805 if (distanceToLine < nearestDistanceToLine) {
\r
806 nearestDistanceToLine = distanceToLine;
\r
807 if (nearestLine == null)
\r
808 nearestLine = new Line2D.Double();
\r
809 nearestLine.setLine(p1, p2);
\r
812 return nearestLine;
\r
818 * @throws DatabaseException
\r
820 public static IElement getSingleEdge(Object object) throws DatabaseException {
\r
821 IElement e = AdaptionUtils.adaptToSingle(object, IElement.class);
\r
825 if (PickFilter.FILTER_CONNECTION_EDGES.accept(e))
\r
828 if (PickFilter.FILTER_CONNECTIONS.accept(e)) {
\r
829 ConnectionHandler ch = e.getElementClass().getSingleItem(ConnectionHandler.class);
\r
830 Collection<IElement> bps = ch.getBranchPoints(e, null);
\r
831 Collection<IElement> segs = ch.getSegments(e, null);
\r
832 if (bps.isEmpty() && segs.size() == 1)
\r
833 return segs.iterator().next();
\r
841 * @throws DatabaseException
\r
843 public static Collection<IElement> getEdges(Object object) throws DatabaseException {
\r
844 IElement e = AdaptionUtils.adaptToSingle(object, IElement.class);
\r
848 if (PickFilter.FILTER_CONNECTION_EDGES.accept(e))
\r
849 return Collections.singleton(e);
\r
851 if (PickFilter.FILTER_CONNECTIONS.accept(e)) {
\r
852 ConnectionHandler ch = e.getElementClass().getSingleItem(ConnectionHandler.class);
\r
853 return ch.getSegments(e, null);
\r
860 * @param connection
\r
862 * @throws DatabaseException
\r
864 public static Resource getConnectionTailNode(ReadGraph graph, Resource connection) throws DatabaseException {
\r
865 DiagramResource DIA = DiagramResource.getInstance(graph);
\r
866 StructuralResource2 STR = StructuralResource2.getInstance(graph);
\r
867 for (Resource connector : graph.getObjects(connection, DIA.HasTailConnector)) {
\r
868 for (Resource node : graph.getObjects(connector, STR.Connects)) {
\r
869 if (node.equals(connection))
\r
879 * @param connection
\r
880 * @return the STR.Connects statement from the tail DIA.Connector to the
\r
881 * tail node (DIA.Element) or <code>null</code> if no tail node
\r
883 * @throws DatabaseException
\r
885 public static Statement getConnectionTailNodeStatement(ReadGraph graph, Resource connection) throws DatabaseException {
\r
886 DiagramResource DIA = DiagramResource.getInstance(graph);
\r
887 StructuralResource2 STR = StructuralResource2.getInstance(graph);
\r
888 for (Resource connector : graph.getObjects(connection, DIA.HasTailConnector)) {
\r
889 for (Statement connects : graph.getStatements(connector, STR.Connects)) {
\r
890 if (connects.getObject().equals(connection))
\r
899 * @param connection
\r
902 * @throws DatabaseException
\r
904 public void translateRouteNodes(Resource connection, double dx, double dy) throws DatabaseException {
\r
905 Commands.get(g, "Simantics/Diagram/translateRouteNodes")
\r
906 .execute(g, g.syncRequest(new IndexRoot(connection)), connection, dx, dy);
\r
909 public static void translateRouteNodes(WriteGraph g, Resource connection, double dx, double dy) throws DatabaseException {
\r
910 DiagramResource DIA = DiagramResource.getInstance(g);
\r
911 for (Resource routeNode : g.getObjects(connection, DIA.HasInteriorRouteNode)) {
\r
912 if (g.isInstanceOf(routeNode, DIA.RouteLine)) {
\r
913 Double pos = g.getRelatedValue(routeNode, DIA.HasPosition, Bindings.DOUBLE);
\r
914 Boolean isHorizontal = g.getRelatedValue(routeNode, DIA.IsHorizontal, Bindings.BOOLEAN);
\r
915 pos += isHorizontal ? dy : dx;
\r
916 g.claimLiteral(routeNode, DIA.HasPosition, pos);
\r
922 * Creates a route graph connection which has corners at the specified
\r
923 * locations, starting/ending from/at the specified element terminals.
\r
925 * if {@link Development#DEVELOPMENT} is <code>true</code> the code will
\r
926 * verify that both element end-points are part of the same diagram.
\r
929 * database write access
\r
930 * @param startElement
\r
931 * element to start connecting from
\r
932 * @param startConnectionPoint
\r
933 * STR.ConnectedTo relation of the start element terminal
\r
934 * @param endElement
\r
935 * element to end the connection at
\r
936 * @param endConnectionPoint
\r
937 * STR.ConnectedTo relation of the end element terminal
\r
939 * the corners to create for the connection
\r
940 * @return the created diagram connection resource
\r
941 * @throws DatabaseException
\r
943 public Resource createConnectionWithCorners(WriteGraph graph, Resource startElement,
\r
944 Resource startConnectionPoint, Resource endElement, Resource endConnectionPoint, List<Point2D> corners)
\r
945 throws DatabaseException {
\r
946 DiagramResource DIA = br.DIA;
\r
948 // Verify that both elements are part of the same diagram before connecting.
\r
949 if (Development.DEVELOPMENT) {
\r
950 Collection<Resource> startDiagram = OrderedSetUtils.getOwnerLists(graph, startElement, DIA.Diagram);
\r
951 Collection<Resource> endDiagram = OrderedSetUtils.getOwnerLists(graph, endElement, DIA.Diagram);
\r
952 if (Collections.disjoint(startDiagram, endDiagram))
\r
953 throw new IllegalArgumentException("start element " + startElement
\r
954 + " is not on same diagram as end element " + endElement + ". start diagram: " + startDiagram
\r
955 + ", end diagram: " + endDiagram);
\r
958 return createConnectionWithCorners(graph, DIA.RouteGraphConnection, startElement,
\r
959 startConnectionPoint, DIA.HasPlainConnector, endElement, endConnectionPoint, DIA.HasArrowConnector,
\r
964 * Creates a route graph connection which has corners at the specified
\r
965 * locations, starting/ending from/at the specified element terminals.
\r
966 * Verifies that both element end-points are part of the same diagram.
\r
968 * @param graph database write access
\r
969 * @param connectionType type of created connection (e.g. DIA.RouteGraphConnection)
\r
970 * @param element1 element to start connecting from
\r
971 * @param connectionPoint1 STR.ConnectedTo relation of the start element terminal
\r
972 * @param hasConnector1 connector to connection attachment relation to use for element1 and connectionPoint1
\r
973 * @param element2 element to end the connection at
\r
974 * @param connectionPoint2 STR.ConnectedTo relation of the end element terminal
\r
975 * @param hasConnector2 connector to connection attachment relation to use for element2 and connectionPoint2
\r
976 * @param corners the corners to create for the connection
\r
977 * @return the created diagram connection resource
\r
978 * @throws DatabaseException
\r
980 public Resource createConnectionWithCorners(WriteGraph graph, Resource connectionType,
\r
981 Resource element1, Resource connectionPoint1, Resource hasConnector1, Resource element2,
\r
982 Resource connectionPoint2, Resource hasConnector2, List<Point2D> corners)
\r
983 throws DatabaseException {
\r
984 DiagramResource DIA = br.DIA;
\r
986 if (corners.size() == 1)
\r
987 throw new UnsupportedOperationException("1 corner currently not supported");
\r
989 Resource connection = newInstance(g, connectionType);
\r
990 Resource connector1 = newConnector(connection, hasConnector1);
\r
991 Resource connector2 = newConnector(connection, hasConnector2);
\r
992 graph.claim(element1, connectionPoint1, connector1);
\r
993 graph.claim(element2, connectionPoint2, connector2);
\r
995 if (corners.size() > 1) {
\r
996 boolean horizontal;
\r
997 Resource previousRouteNode = connector1;
\r
998 for (int i=0; i<corners.size()-1; ++i) {
\r
999 Point2D p = corners.get(i);
\r
1000 Point2D p1 = corners.get(i+1);
\r
1001 horizontal = Math.abs(p1.getY() - p.getY()) < Math.abs(p1.getX() - p.getX());
\r
1002 Resource routeLine = ConnectionUtil.createRouteline(graph, connection, horizontal ? p.getY() : p.getX(), horizontal);
\r
1003 graph.claim(previousRouteNode, DIA.AreConnected, DIA.AreConnected, routeLine);
\r
1004 previousRouteNode = routeLine;
\r
1006 graph.claim(previousRouteNode, DIA.AreConnected, DIA.AreConnected, connector2);
\r
1008 graph.claim(connector1, DIA.AreConnected, DIA.AreConnected, connector2);
\r
1011 return connection;
\r
1014 public Resource createConnectionWithSingleLine(WriteGraph graph, Resource connectionType,
\r
1015 Collection<Triple<Resource,Resource,Resource>> endpoints,
\r
1016 double coordinate, boolean horizontal)
\r
1017 throws DatabaseException {
\r
1019 DiagramResource DIA = br.DIA;
\r
1021 Resource connection = newInstance(g, connectionType);
\r
1023 Resource routeLine = ConnectionUtil.createRouteline(graph, connection, coordinate, horizontal);
\r
1025 for(Triple<Resource,Resource,Resource> endpoint : endpoints) {
\r
1026 Resource connector = newConnector(connection, endpoint.third);
\r
1027 graph.claim(endpoint.first, endpoint.second, connector);
\r
1028 graph.claim(routeLine, DIA.AreConnected, DIA.AreConnected, connector);
\r
1031 return connection;
\r
1035 public Resource createConnection(WriteGraph graph, Resource connectionType,
\r
1036 List<Triple<Resource,Resource,Resource>> terminals,
\r
1037 List<Pair<Double, Boolean>> routeLines,
\r
1038 List<Pair<Integer,Integer>> connections) throws DatabaseException {
\r
1040 DiagramResource DIA = br.DIA;
\r
1042 Resource connection = newInstance(g, connectionType);
\r
1044 Resource[] parts = new Resource[terminals.size() + routeLines.size()];
\r
1048 for(Triple<Resource,Resource,Resource> terminal : terminals) {
\r
1049 Resource connector = newConnector(connection, terminal.third);
\r
1050 graph.claim(terminal.first, terminal.second, connector);
\r
1051 parts[index++] = connector;
\r
1054 for(Pair<Double, Boolean> routeLine : routeLines) {
\r
1055 Resource r = ConnectionUtil.createRouteline(graph, connection, routeLine.first, routeLine.second);
\r
1056 parts[index++] = r;
\r
1059 // System.err.println("Connect " + parts.length + " parts.");
\r
1061 for(Pair<Integer,Integer> conn : connections) {
\r
1062 // System.err.println("-" + conn.first + " " + conn.second);
\r
1063 Resource part1 = parts[conn.first];
\r
1064 Resource part2 = parts[conn.second];
\r
1065 graph.claim(part1, DIA.AreConnected, DIA.AreConnected, part2);
\r
1068 return connection;
\r
1074 * @param connection
\r
1076 * @param isHorizontal
\r
1077 * @return new route line that is attached to the specified diagram connection
\r
1078 * @throws DatabaseException
\r
1080 public static Resource createRouteline(WriteGraph graph, Resource connection, double pos, boolean isHorizontal) throws DatabaseException {
\r
1081 Layer0 L0 = Layer0.getInstance(graph);
\r
1082 DiagramResource DIA = DiagramResource.getInstance(graph);
\r
1083 Resource routeLine = graph.newResource();
\r
1084 graph.claim(routeLine, L0.InstanceOf, null, DIA.RouteLine);
\r
1085 graph.addLiteral(routeLine, DIA.HasPosition, DIA.HasPosition_Inverse, L0.Double, pos, Bindings.DOUBLE);
\r
1086 graph.addLiteral(routeLine, DIA.IsHorizontal, DIA.IsHorizontal_Inverse, L0.Boolean, isHorizontal, Bindings.BOOLEAN);
\r
1087 graph.claim(connection, DIA.HasInteriorRouteNode, DIA.HasInteriorRouteNode_Inverse, routeLine);
\r
1092 * @param graph database write-only access
\r
1093 * @param type type of the created resource
\r
1094 * @return new instance of type
\r
1095 * @throws DatabaseException
\r
1097 private Resource newInstance(WriteOnlyGraph graph, Resource type) throws DatabaseException {
\r
1098 Resource connection = graph.newResource();
\r
1099 g.claim(connection, br.L0.InstanceOf, null, type);
\r
1100 return connection;
\r