-/*******************************************************************************\r
- * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
- * in Industry THTH ry.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- *\r
- * Contributors:\r
- * VTT Technical Research Centre of Finland - initial API and implementation\r
- *******************************************************************************/\r
-package org.simantics.diagram.content;\r
-\r
-import java.awt.geom.AffineTransform;\r
-import java.awt.geom.Line2D;\r
-import java.awt.geom.Point2D;\r
-import java.util.ArrayList;\r
-import java.util.Collection;\r
-import java.util.Collections;\r
-import java.util.HashSet;\r
-import java.util.Iterator;\r
-import java.util.List;\r
-import java.util.Set;\r
-import java.util.Stack;\r
-\r
-import org.simantics.databoard.Bindings;\r
-import org.simantics.db.ReadGraph;\r
-import org.simantics.db.Resource;\r
-import org.simantics.db.Statement;\r
-import org.simantics.db.WriteGraph;\r
-import org.simantics.db.WriteOnlyGraph;\r
-import org.simantics.db.common.CommentMetadata;\r
-import org.simantics.db.common.request.IndexRoot;\r
-import org.simantics.db.common.utils.NameUtils;\r
-import org.simantics.db.common.utils.OrderedSetUtils;\r
-import org.simantics.db.exception.AssumptionException;\r
-import org.simantics.db.exception.DatabaseException;\r
-import org.simantics.db.exception.ValidationException;\r
-import org.simantics.db.layer0.adapter.impl.EntityRemover;\r
-import org.simantics.db.layer0.util.RemoverUtil;\r
-import org.simantics.diagram.connection.ConnectionSegmentEnd;\r
-import org.simantics.diagram.stubs.DiagramResource;\r
-import org.simantics.diagram.synchronization.graph.BasicResources;\r
-import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;\r
-import org.simantics.g2d.connection.handler.ConnectionHandler;\r
-import org.simantics.g2d.diagram.handler.PickRequest.PickFilter;\r
-import org.simantics.g2d.diagram.handler.Topology.Terminal;\r
-import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil.TerminalInfo;\r
-import org.simantics.g2d.element.ElementUtils;\r
-import org.simantics.g2d.element.IElement;\r
-import org.simantics.g2d.element.handler.BendsHandler;\r
-import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;\r
-import org.simantics.g2d.element.handler.impl.BranchPointTerminal;\r
-import org.simantics.g2d.elementclass.BranchPoint;\r
-import org.simantics.g2d.elementclass.BranchPoint.Direction;\r
-import org.simantics.layer0.Layer0;\r
-import org.simantics.modeling.ModelingResources;\r
-import org.simantics.scenegraph.utils.GeometryUtils;\r
-import org.simantics.scl.commands.Commands;\r
-import org.simantics.structural.stubs.StructuralResource2;\r
-import org.simantics.structural2.modelingRules.CPConnection;\r
-import org.simantics.structural2.modelingRules.CPConnectionJoin;\r
-import org.simantics.structural2.modelingRules.CPTerminal;\r
-import org.simantics.structural2.modelingRules.IConnectionPoint;\r
-import org.simantics.utils.Development;\r
-import org.simantics.utils.datastructures.Pair;\r
-import org.simantics.utils.datastructures.Triple;\r
-import org.simantics.utils.ui.AdaptionUtils;\r
-\r
-/**\r
- * @author Tuukka Lehtonen\r
- */\r
-public final class ConnectionUtil {\r
-\r
- private final ReadGraph rg;\r
- private final WriteGraph g;\r
- private final BasicResources br;\r
-\r
- /**\r
- * Construct utility with read-only support.\r
- * @param g\r
- */\r
- public ConnectionUtil(ReadGraph g) {\r
- this.rg = g;\r
- this.g = (g instanceof WriteGraph) ? (WriteGraph) g : null;\r
- this.br = BasicResources.getInstance(g);\r
- }\r
-\r
- /**\r
- * Construct utility with read-write support.\r
- * @param g\r
- */\r
- public ConnectionUtil(WriteGraph g) {\r
- this.rg = g;\r
- this.g = g;\r
- this.br = BasicResources.getInstance(g);\r
- }\r
-\r
- void assertWriteSupport() {\r
- if (g == null)\r
- throw new UnsupportedOperationException("no write support, this ConnectionUtil is read-only");\r
- }\r
-\r
- /**\r
- * Creates a new connection element of the specified type and attaches it to\r
- * the specified diagram composite.\r
- * \r
- * @param composite diagram composite\r
- * @param type connection element type\r
- * @return created connection\r
- * @throws DatabaseException\r
- */\r
- public Resource newConnection(Resource composite, Resource type) throws DatabaseException {\r
- assertWriteSupport();\r
- Resource connection = newConnection(type);\r
- // By default, add connections to the front of the diagram since most\r
- // often it is visually the expected result.\r
- OrderedSetUtils.addFirst(g, composite, connection);\r
- g.claim(composite, br.L0.ConsistsOf, br.L0.PartOf, connection);\r
- return connection;\r
- }\r
-\r
- /**\r
- * Creates a new connection element of the specified type without attaching\r
- * it to any diagram composite.\r
- * \r
- * @param type connection element type\r
- * @return created connection\r
- * @throws DatabaseException\r
- */\r
- public Resource newConnection(Resource type) throws DatabaseException {\r
- assertWriteSupport();\r
- return newInstance(g, type);\r
- }\r
-\r
- /**\r
- * Creates a new element terminal connector (DiagramResource) for the specified connector\r
- * @param connection\r
- * @param hasConnector\r
- * @return\r
- * @throws DatabaseException\r
- */\r
- public Resource newConnector(Resource connection, Resource hasConnector) throws DatabaseException {\r
- assertWriteSupport();\r
- Resource connector = newInstance(g, br.DIA.Connector);\r
- g.claim(connection, hasConnector, connector);\r
- return connector;\r
- }\r
-\r
- public Resource newBranchPoint(Resource connection, AffineTransform tr) throws DatabaseException {\r
- return newBranchPoint(connection, tr, null);\r
- }\r
-\r
- public Resource newBranchPoint(Resource connection, AffineTransform tr, Direction direction) throws DatabaseException {\r
- assertWriteSupport();\r
- Resource bp = g.newResource();\r
- g.claim(bp, br.L0.InstanceOf, null, br.DIA.BranchPoint);\r
- if (tr != null) {\r
- double[] mat = new double[6];\r
- tr.getMatrix(mat);\r
- Resource transform = g.newResource();\r
- g.claim(transform, br.L0.InstanceOf, null, br.G2D.Transform);\r
- g.claimValue(transform, mat);\r
- g.claim(bp, br.DIA.HasTransform, transform);\r
- }\r
- Resource tag = toDirectionTag(g, direction);\r
- if (tag != null) {\r
- g.claim(bp, tag, tag, bp);\r
- }\r
- g.claim(connection, br.DIA.HasBranchPoint, bp);\r
- return bp;\r
- }\r
-\r
- /**\r
- * @param connection\r
- * @param position\r
- * @param isHorizontal\r
- * @return\r
- * @throws DatabaseException\r
- */\r
- public Resource newRouteLine(Resource connection, Double position, Boolean isHorizontal) throws DatabaseException {\r
- assertWriteSupport();\r
- Resource rl = newInstance(g, br.DIA.RouteLine);\r
- if (position != null) {\r
- g.addLiteral(rl, br.DIA.HasPosition, br.DIA.HasPosition_Inverse, br.L0.Double, position, Bindings.DOUBLE);\r
- }\r
- if (isHorizontal != null) {\r
- g.addLiteral(rl, br.DIA.IsHorizontal, br.DIA.IsHorizontal_Inverse, br.L0.Boolean, isHorizontal, Bindings.BOOLEAN);\r
- }\r
- g.claim(connection, br.DIA.HasInteriorRouteNode, br.DIA.HasInteriorRouteNode_Inverse, rl);\r
- return rl;\r
- }\r
-\r
- ConnectionSegmentEnd getTerminalType(Terminal terminal, ConnectionSegmentEnd defaultValue) {\r
- ConnectionSegmentEnd type = getTerminalType(terminal);\r
- return type != null ? type : defaultValue;\r
- }\r
-\r
- ConnectionSegmentEnd getTerminalType(Terminal t) {\r
- if (t == null)\r
- return null;\r
-\r
- if (t instanceof ResourceTerminal) {\r
- return ConnectionSegmentEnd.CONNECTOR;\r
- } else if (t instanceof BranchPointTerminal) {\r
- return ConnectionSegmentEnd.BRANCH;\r
- } else {\r
- throw new IllegalArgumentException("unsupported terminal '" + t + "'");\r
- }\r
- }\r
-\r
- Resource resolveTerminalRelation(ReadGraph g, Terminal t) throws DatabaseException {\r
- if (t == null)\r
- return null;\r
- if (t instanceof ResourceTerminal) {\r
- ResourceTerminal rt = (ResourceTerminal) t;\r
- Resource terminalRelation = DiagramGraphUtil.getConnectionPointOfTerminal(g, rt.getResource());\r
- if (!g.isSubrelationOf(terminalRelation, br.STR.IsConnectedTo)) {\r
- // debug...\r
- }\r
- return terminalRelation;\r
- } else {\r
- throw new IllegalArgumentException("unsupported terminal '" + t + "' for terminal relation resolution");\r
- }\r
- }\r
-\r
- public Resource toHasConnectorRelation(EdgeEnd end) {\r
- switch (end) {\r
- case Begin: return br.DIA.HasPlainConnector;\r
- case End: return br.DIA.HasArrowConnector;\r
- default: throw new IllegalArgumentException("unsupported edge end: " + end);\r
- }\r
- }\r
-\r
- public EdgeEnd toEdgeEnd(Resource attachmentRelation, EdgeEnd defaultValue) throws DatabaseException {\r
- if (g.isSubrelationOf(attachmentRelation, br.DIA.HasPlainConnector))\r
- return EdgeEnd.Begin;\r
- if (g.isSubrelationOf(attachmentRelation, br.DIA.HasArrowConnector))\r
- return EdgeEnd.End;\r
- return defaultValue;\r
- }\r
-\r
- public Resource getAttachmentRelationForConnector(Resource connector) throws DatabaseException {\r
- Statement connection = g.getPossibleStatement(connector, br.DIA.IsConnectorOf);\r
- if (connection == null)\r
- return null;\r
- Resource attachment = g.getInverse(connection.getPredicate());\r
- return attachment;\r
- }\r
-\r
- /**\r
- * @param connection\r
- * @param node\r
- * @param c\r
- * @param end\r
- * @param attachmentRelation the relation used for attaching the connector to the connector\r
- * @return\r
- * @throws DatabaseException\r
- */\r
- public Resource getOrCreateConnector(Resource connection, Resource node, Terminal terminal, EdgeEnd end, Resource attachmentRelation) throws DatabaseException {\r
- assertWriteSupport();\r
- ConnectionSegmentEnd connectorType = getTerminalType(terminal, null);\r
- if (connectorType == null)\r
- throw new AssumptionException("Invalid connection node", connection, node);\r
-\r
- switch (connectorType) {\r
- case BRANCH:\r
- // NOTE: should we ensure here that (connection, br.dr.HasBranchPoint, node) exists?\r
- return node;\r
-\r
- case CONNECTOR: {\r
- Resource terminalRelation = resolveTerminalRelation(g, terminal);\r
-\r
- if (attachmentRelation == null)\r
- attachmentRelation = toHasConnectorRelation(end);\r
-\r
- if (!g.isSubrelationOf(attachmentRelation, br.DIA.HasConnector))\r
- throw new AssumptionException("attachment relation not a subrelation of Has Connector", attachmentRelation);\r
-\r
- // Create new connector for the specified node terminal\r
- Resource terminalConnector = newConnector(connection, attachmentRelation);\r
- g.claim(node, terminalRelation, terminalConnector);\r
- return terminalConnector;\r
- }\r
- default:\r
- throw new Error("this should be unreachable code");\r
- }\r
- }\r
-\r
- public void connect(Resource connector1, Resource connector2) throws DatabaseException {\r
- assertWriteSupport();\r
- g.claim(connector1, br.DIA.AreConnected, br.DIA.AreConnected, connector2);\r
- }\r
-\r
- public void disconnect(Resource connector1, Resource connector2) throws DatabaseException {\r
- assertWriteSupport();\r
- g.denyStatement(connector1, br.DIA.AreConnected, connector2);\r
- }\r
-\r
- public void disconnectFromAllRouteNodes(Resource connector) throws DatabaseException {\r
- assertWriteSupport();\r
- g.deny(connector, br.DIA.AreConnected);\r
- }\r
-\r
- public void disconnect(EdgeResource segment) throws DatabaseException {\r
- assertWriteSupport();\r
- disconnect(segment.first(), segment.second());\r
- }\r
-\r
- public boolean isConnected(Resource connector) throws DatabaseException {\r
- return rg.hasStatement(connector, br.DIA.AreConnected);\r
- }\r
-\r
- public boolean isConnected(Resource connector, Resource toConnector) throws DatabaseException {\r
- return rg.hasStatement(connector, br.DIA.AreConnected, toConnector);\r
- }\r
-\r
- public boolean isConnectionEmpty(Resource connection) throws DatabaseException {\r
- return !rg.hasStatement(connection, br.DIA.HasConnector)\r
- && !rg.hasStatement(connection, br.DIA.HasInteriorRouteNode);\r
- }\r
-\r
- private void removeConnectorOrBranchPoint(Resource connectorOrBranchPoint) throws DatabaseException {\r
-\r
-// // Handle correct removal of route points\r
-// if(g.isInstanceOf(connectorOrBranchPoint, dr.BranchPoint)) {\r
-// Collection<Resource> connectedConnectors = g.getObjects(connectorOrBranchPoint, dr.AreConnected);\r
-// if(connectedConnectors.size() == 2) {\r
-// Iterator<Resource> it = connectedConnectors.iterator();\r
-// g.claim(it.next(), dr.AreConnected, it.next());\r
-// }\r
-// }\r
-\r
- g.deny(connectorOrBranchPoint, br.DIA.AreConnected);\r
-\r
- // Removes both the terminal relation and the HasConnector relation\r
- // to the :Connection\r
- g.deny(connectorOrBranchPoint, br.STR.Connects);\r
-\r
- // If this is a branch point/route node, remove it from the connection too.\r
- g.deny(connectorOrBranchPoint, br.DIA.IsBranchPointOf);\r
- g.deny(connectorOrBranchPoint, br.DIA.HasInteriorRouteNode_Inverse);\r
- }\r
-\r
- /**\r
- * Removes a complete connection along with all its branch points and terminal connectors.\r
- * \r
- * @param connection the connection to remove\r
- */\r
- public void removeConnection(Resource connection) throws DatabaseException {\r
- assertWriteSupport();\r
-\r
- // Add comment to change set.\r
- CommentMetadata cm = g.getMetadata(CommentMetadata.class);\r
- g.addMetadata(cm.add("Remove connection " + connection));\r
-\r
- // 1. Get all connectors/branch points\r
- Collection<Resource> connectors = new ArrayList<Resource>();\r
- connectors.addAll(rg.getObjects(connection, br.DIA.HasConnector));\r
- connectors.addAll(rg.getObjects(connection, br.DIA.HasInteriorRouteNode));\r
-\r
- // 2. Remove all connectors/branch points\r
- for (Resource connector : connectors) {\r
- removeConnectorOrBranchPoint(connector);\r
- RemoverUtil.remove(g, connector);\r
- }\r
-\r
- // 3. Remove whole connection\r
- for (Resource owner : OrderedSetUtils.getOwnerLists(g, connection, br.DIA.Diagram))\r
- OrderedSetUtils.remove(g, owner, connection);\r
- EntityRemover.remove(g, connection);\r
- }\r
-\r
- /**\r
- * Removes a single connector part from the graph. A connection part can be\r
- * either a branch point or a terminal connector.\r
- * \r
- * @param connectorOrBranchPoint\r
- * @throws DatabaseException\r
- */\r
- public void removeConnectionPart(Resource connectorOrBranchPoint) throws DatabaseException {\r
- removeConnectorOrBranchPoint(connectorOrBranchPoint);\r
- RemoverUtil.remove(g, connectorOrBranchPoint);\r
- }\r
-\r
- /**\r
- * Removes the specified connection segment. Checks that both ends of the\r
- * edge segment are part of to the same connection. Steps taken:\r
- * <ul>\r
- * <li>Minimally this only disconnects the connection segment ends from each\r
- * other and nothing more.</li>\r
- * <li>After disconnecting, we check whether the segment ends are still\r
- * connected to something. If not, the :Connector/:BranchPoint at the\r
- * segment's end is destroyed and detached from the connection entity.</li>\r
- * <li>Finally, if the connection entity is empty (has no connectors/branch\r
- * points), it is also destroyed and removed from the diagram.</li>\r
- * \r
- * @param segment the connection segment to remove\r
- */\r
- public void remove(EdgeResource segment) throws DatabaseException {\r
- remove(segment, false);\r
- }\r
-\r
- /**\r
- * Removes the specified connection segment. Steps taken:\r
- * <ul>\r
- * <li>Minimally this only disconnects the connection segment ends from each\r
- * other and nothing more.</li>\r
- * <li>After disconnecting, we check whether the segment ends are still\r
- * connected to something. If not, the :Connector/:BranchPoint at the\r
- * segment's end is destroyed and detached from the connection entity.</li>\r
- * <li>Finally, if the connection entity is empty (has no connectors/branch\r
- * points), it is also destroyed and removed from the diagram.</li>\r
- * \r
- * @param segment the connection segment to remove\r
- * @param unchecked <code>false</code> to check that both ends of the\r
- * segment are part of the same connection before removing,\r
- * <code>true</code> to just remove the segment without checking\r
- * this. Using <code>true</code> may help in cases where the\r
- * connection model has become corrupted for some reason, e.g. the\r
- * other end of the edge has lost its link to the connection while\r
- * the other has not,\r
- */\r
- public void remove(EdgeResource segment, boolean unchecked) throws DatabaseException {\r
- assertWriteSupport();\r
-\r
- if (!unchecked) {\r
- @SuppressWarnings("unused")\r
- Resource connection = getConnection(g, segment);\r
- }\r
-\r
- // 1. disconnect segment ends\r
- disconnect(segment);\r
-\r
- // 2. Remove connectors/branch points if they become fully disconnected\r
- if (!isConnected(segment.first())) {\r
- removeConnectorOrBranchPoint(segment.first());\r
- }\r
- if (!isConnected(segment.second())) {\r
- removeConnectorOrBranchPoint(segment.second());\r
- }\r
-\r
- // 3. Remove whole connection entity if it becomes empty\r
-// if (isConnectionEmpty(connection)) {\r
-// for (Resource owner : OrderedSetUtils.getOwnerLists(g, connection, dr.Diagram))\r
-// OrderedSetUtils.remove(g, owner, connection);\r
-// RemoverUtil.remove(g, connection);\r
-// }\r
-\r
- }\r
-\r
- /**\r
- * Removes all DIA.Connector instances from the specified connection that\r
- * are not used for anything.\r
- * \r
- * @param connection connection to examine\r
- * @return the amount of unused connectors removed\r
- */\r
- public int removeUnusedConnectors(Resource connection) throws DatabaseException {\r
- int result = 0;\r
- for (Resource connector : getConnectors(connection, null)) {\r
- if (!g.getObjects(connector, br.DIA.AreConnected).isEmpty())\r
- continue;\r
- Collection<Resource> connects = g.getObjects(connector, br.STR.Connects);\r
- if (connects.size() > 1)\r
- continue;\r
-\r
- removeConnectionPart(connector);\r
- ++result;\r
- }\r
- return result;\r
- }\r
-\r
- /**\r
- * Removes all DIA.InteriorRouteNode instances from the specified connection that\r
- * are not used for anything, i.e. connect to less than 2 other route nodes.\r
- * \r
- * @param connection connection to examine\r
- * @return the amount of unused connectors removed\r
- */\r
- public int removeExtraInteriorRouteNodes(Resource connection) throws DatabaseException {\r
- int result = 0;\r
- for (Resource interiorRouteNode : g.getObjects(connection, br.DIA.HasInteriorRouteNode)) {\r
- Collection<Resource> connectedTo = g.getObjects(interiorRouteNode, br.DIA.AreConnected);\r
- if (connectedTo.size() > 1)\r
- continue;\r
-\r
- removeConnectionPart(interiorRouteNode);\r
- ++result;\r
- }\r
- return result;\r
- }\r
-\r
- /**\r
- * Splits the specified connection segment by adding a new branch point in\r
- * between the segment ends.\r
- * \r
- * @param segment\r
- * @return the branch (route) point created by the split operation.\r
- */\r
- public Resource split(EdgeResource segment, AffineTransform splitPos) throws DatabaseException {\r
- assertWriteSupport();\r
-\r
- Resource connection = getConnection(g, segment);\r
- disconnect(segment);\r
- Resource bp = newBranchPoint(connection, splitPos);\r
- connect(segment.first(), bp);\r
- connect(bp, segment.second());\r
- return bp;\r
- }\r
-\r
- /**\r
- * Joins the connection at the selected branch point if and only if the\r
- * branch point is a route point, i.e. it is connected to two other branch\r
- * points or connector.\r
- * \r
- * @param interiorRouteNode\r
- * @return\r
- */\r
- public void join(Resource interiorRouteNode) throws DatabaseException {\r
- assertWriteSupport();\r
-\r
- if (!g.isInstanceOf(interiorRouteNode, br.DIA.InteriorRouteNode))\r
- throw new ValidationException("'" + NameUtils.getSafeName(g, interiorRouteNode) + "' is not an instance of DIA.InteriorRouteNode");\r
- @SuppressWarnings("unused")\r
- Resource connection = getConnection(g, interiorRouteNode);\r
- Collection<Resource> connectedTo = g.getObjects(interiorRouteNode, br.DIA.AreConnected);\r
- if (connectedTo.size() != 2)\r
- 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
- Iterator<Resource> it = connectedTo.iterator();\r
- Resource connector1 = it.next();\r
- Resource connector2 = it.next();\r
- //System.out.println("removing branch point " + GraphUtils.getReadableName(g, routeBranchPoint) + " which is connected to " + GraphUtils.getReadableName(g, connector1) + " and " + GraphUtils.getReadableName(g, connector2));\r
- removeConnectorOrBranchPoint(interiorRouteNode);\r
- connect(connector1, connector2);\r
- }\r
-\r
- public void getConnectionSegments(Resource connection, Collection<EdgeResource> result) throws DatabaseException {\r
-\r
- ArrayList<EdgeResource> edges = new ArrayList<EdgeResource>();\r
- Set<Resource> visited = new HashSet<Resource>();\r
- Stack<Resource> todo = new Stack<Resource>();\r
-\r
- // Try to select input as root, this ensures correct order for simple paths\r
- Collection<Resource> seeds = rg.getObjects(connection, br.DIA.HasArrowConnector);\r
- if(seeds.isEmpty()) seeds = rg.getObjects(connection, br.DIA.HasPlainConnector);\r
-\r
- assert(!seeds.isEmpty());\r
-\r
- Resource seed = seeds.iterator().next();\r
-\r
- todo.push(seed);\r
-\r
- while(!todo.isEmpty()) {\r
- Resource location = todo.pop();\r
- if(!visited.contains(location)) {\r
- visited.add(location);\r
- for (Resource connectedTo : rg.getObjects(location, br.DIA.AreConnected)) {\r
- todo.add(connectedTo);\r
- EdgeResource edge = new EdgeResource(location, connectedTo);\r
- if(!edges.contains(edge)) edges.add(edge);\r
- }\r
- }\r
- }\r
-\r
- for(EdgeResource uer : edges) {\r
-// System.out.println("loaded edge " + uer.first() + " " + uer.second());\r
- result.add(uer);\r
- }\r
-\r
- }\r
-\r
- public Collection<Resource> getBranchPoints(Resource connection, Collection<Resource> result) throws DatabaseException {\r
- if (result == null)\r
- result = new ArrayList<Resource>();\r
- result.addAll(rg.getObjects(connection, br.DIA.HasBranchPoint));\r
- return result;\r
- }\r
-\r
- public Collection<Resource> getConnectors(Resource connection, Collection<Resource> result) throws DatabaseException {\r
- if (result == null)\r
- result = new ArrayList<Resource>();\r
- result.addAll(rg.getObjects(connection, br.DIA.HasConnector));\r
- return result;\r
- }\r
-\r
- public Resource getConnectedComponent(Resource connection, Resource connector) throws DatabaseException {\r
- for (Resource connects : rg.getObjects(connector, br.STR.Connects))\r
- if (!connects.equals(connection))\r
- return connects;\r
- return null;\r
- }\r
-\r
- public Statement getConnectedComponentStatement(Resource connection, Resource connector) throws DatabaseException {\r
- for (Statement connects : rg.getStatements(connector, br.STR.Connects))\r
- if (!connects.getObject().equals(connection))\r
- return connects;\r
- return null;\r
- }\r
-\r
- public void gatherEdges(Resource connection, Collection<EdgeResource> result) throws DatabaseException {\r
- Set<Object> visited = new HashSet<Object>();\r
- for (Resource connector : rg.getObjects(connection, br.DIA.HasConnector)) {\r
- for (Resource connectedTo : rg.getObjects(connector, br.DIA.AreConnected)) {\r
- EdgeResource p = new EdgeResource(connector, connectedTo);\r
- if (visited.add(p)) {\r
- result.add(p);\r
- }\r
- }\r
- }\r
- Collection<Resource> routeNodes = rg.getObjects(connection, br.DIA.HasInteriorRouteNode);\r
- for (Resource routeNode : routeNodes) {\r
- for (Resource connectedTo : rg.getObjects(routeNode, br.DIA.AreConnected)) {\r
- EdgeResource p = new EdgeResource(routeNode, connectedTo);\r
- if (visited.add(p)) {\r
- result.add(p);\r
- }\r
- }\r
- }\r
- }\r
-\r
- public void gatherConnectionParts(Resource connection, Collection<Object> result) throws DatabaseException {\r
- Set<Object> visited = new HashSet<Object>();\r
- for (Resource connector : rg.getObjects(connection, br.DIA.HasConnector)) {\r
- for (Resource connectedTo : rg.getObjects(connector, br.DIA.AreConnected)) {\r
- EdgeResource p = new EdgeResource(connector, connectedTo);\r
- if (visited.add(p)) {\r
- result.add(p);\r
- }\r
- }\r
- }\r
- Collection<Resource> routeNodes = rg.getObjects(connection, br.DIA.HasInteriorRouteNode);\r
- for (Resource routeNode : routeNodes) {\r
- result.add(routeNode);\r
- for (Resource connectedTo : rg.getObjects(routeNode, br.DIA.AreConnected)) {\r
- EdgeResource p = new EdgeResource(routeNode, connectedTo);\r
- if (visited.add(p)) {\r
- result.add(p);\r
- }\r
- }\r
- }\r
- }\r
-\r
- public Collection<Resource> getConnectedComponents(Resource connection, Collection<Resource> result)\r
- throws DatabaseException {\r
- if (result == null)\r
- result = new ArrayList<Resource>(2);\r
- for (Resource connector : rg.getObjects(connection, br.DIA.HasConnector)) {\r
- for (Resource connects : rg.getObjects(connector, br.STR.Connects)) {\r
- if (connects.equals(connection))\r
- continue;\r
- result.add(connects);\r
- }\r
- }\r
- return result;\r
- }\r
-\r
- public Collection<Resource> getConnectedConnectors(Resource connection, Collection<Resource> result)\r
- throws DatabaseException {\r
- if (result == null)\r
- result = new ArrayList<Resource>(2);\r
- for (Resource connector : rg.getObjects(connection, br.DIA.HasConnector)) {\r
- Resource connects = getConnectedComponent(connection, connector);\r
- if (connects != null)\r
- result.add( connector );\r
- }\r
- return result;\r
- }\r
-\r
- public Collection<Resource> getTerminalConnectors(Resource node, Collection<Resource> result)\r
- throws DatabaseException {\r
- if (result == null)\r
- result = new ArrayList<Resource>(2);\r
- result.addAll( rg.getObjects(node, br.STR.IsConnectedTo) );\r
- return result;\r
- }\r
-\r
- /**\r
- * @param g\r
- * @param routeNode\r
- * @return <code>null</code> if the specified resource is not part of any\r
- * connection\r
- */\r
- public static Resource tryGetConnection(ReadGraph g, Resource routeNode) throws DatabaseException {\r
- DiagramResource dr = DiagramResource.getInstance(g);\r
- Resource conn = g.getPossibleObject(routeNode, dr.IsConnectorOf);\r
- if (conn == null)\r
- conn = g.getPossibleObject(routeNode, dr.HasInteriorRouteNode_Inverse);\r
- return conn;\r
- }\r
-\r
- public static Resource tryGetConnection(ReadGraph g, EdgeResource segment) throws DatabaseException {\r
- Resource first = tryGetConnection(g, segment.first());\r
- Resource second = tryGetConnection(g, segment.second());\r
- if (first == null || second == null || !first.equals(second))\r
- return null;\r
- return first;\r
- }\r
-\r
- public static Resource tryGetMappedConnection(ReadGraph g, Resource connector) throws DatabaseException {\r
- ModelingResources MOD = ModelingResources.getInstance(g);\r
- return g.getPossibleObject(connector, MOD.ConnectorToConnection);\r
- }\r
- \r
- public static Resource getConnection(ReadGraph g, EdgeResource segment) throws DatabaseException {\r
- Resource first = tryGetConnection(g, segment.first());\r
- Resource second = tryGetConnection(g, segment.second());\r
- if (first == null && second == null)\r
- throw new ValidationException(\r
- "neither connection segment end is attached to a Connection entity instance: "\r
- + segment.toString(g) + " - " + segment.toString());\r
- if (first != null ^ second != null)\r
- throw new ValidationException("both ends of connection segment "\r
- + segment.toString(g) + " - " + segment.toString() + " are not connected (first=" + first\r
- + ", second=" + second + ")");\r
- if (!first.equals(second))\r
- throw new ValidationException("connectors of connection segment "\r
- + segment.toString(g) + " - " + segment.toString() + " are part of different connections: " + first\r
- + " vs. " + second);\r
- return first;\r
- }\r
-\r
- public static Resource getConnection(ReadGraph g, Resource routeNode) throws DatabaseException {\r
- Resource connection = tryGetConnection(g, routeNode);\r
- if (connection == null)\r
- throw new ValidationException("route node '"\r
- + NameUtils.getSafeName(g, routeNode) + "' is not part of any connection");\r
- return connection;\r
- }\r
-\r
- public static IConnectionPoint toConnectionPoint(ReadGraph g, Resource element, Terminal terminal) throws DatabaseException {\r
- if (terminal instanceof ResourceTerminal) {\r
- Resource t = ((ResourceTerminal) terminal).getResource();\r
-\r
- // TODO: remove this hack\r
- if (element == null) { // Flag?\r
- DiagramResource DIA = DiagramResource.getInstance(g);\r
- Resource join = g.getPossibleObject(t, DIA.FlagIsJoinedBy);\r
- if (join == null)\r
- return null;\r
- return new CPConnectionJoin(join);\r
- }\r
- // element should be :Element\r
- Resource bindingRelation = DiagramGraphUtil.getConnectionPointOfTerminal(g, t);\r
- return new CPTerminal(element, bindingRelation);\r
- } else if (terminal instanceof BranchPointTerminal) {\r
- // element should be either : DIA.InteriorRouteNode or DIA.Connection\r
- Resource connection = null;\r
- DiagramResource DIA = DiagramResource.getInstance(g);\r
- if (g.isInstanceOf(element, DIA.Connection))\r
- connection = element;\r
- else\r
- connection = getConnection(g, element);\r
- return new CPConnection(connection);\r
- }\r
- throw new IllegalArgumentException("Unrecognized Terminal class: " + terminal);\r
- }\r
-\r
- public static IConnectionPoint toConnectionPoint(ReadGraph graph, IElement element, Terminal terminal) throws DatabaseException {\r
- Resource r = (Resource) ElementUtils.getObject(element);\r
- return ConnectionUtil.toConnectionPoint(graph, r, terminal);\r
- }\r
-\r
- public static IConnectionPoint toConnectionPoint(ReadGraph graph, TerminalInfo ti) throws DatabaseException {\r
- return ConnectionUtil.toConnectionPoint(graph, ti.e, ti.t);\r
- }\r
-\r
- public static IConnectionPoint toConnectionPoint(ReadGraph graph, DesignatedTerminal t) throws DatabaseException {\r
- return toConnectionPoint(graph, t.element, t.terminal);\r
- }\r
-\r
- public static Resource toDirectionTag(ReadGraph graph, BranchPoint.Direction direction) {\r
- if (direction == null)\r
- return null;\r
-\r
- DiagramResource DIA = DiagramResource.getInstance(graph);\r
- switch (direction) {\r
- case Horizontal: return DIA.Horizontal;\r
- case Vertical: return DIA.Vertical;\r
- default: return null;\r
- }\r
- }\r
-\r
- /**\r
- * Copied from ConnectionCommandHandler#splitConnection, duplicate code.\r
- * \r
- * @param toCanvasPos\r
- * @param onEdge\r
- * @return\r
- */\r
- public static Line2D resolveNearestEdgeLineSegment(Point2D toCanvasPos, IElement onEdge) {\r
- // Try to find an initial preferred direction for the new\r
- // branch point based on the direction of the edge's line\r
- // segment on which the split is done.\r
- List<Point2D> points = new ArrayList<Point2D>();\r
- BendsHandler bh = onEdge.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);\r
- if (bh != null)\r
- org.simantics.g2d.utils.GeometryUtils.getPoints(bh.getPath(onEdge), points);\r
- Line2D nearestLine = null;\r
- double nearestDistanceToLine = Double.MAX_VALUE;\r
- for (int i = 0; i < points.size() - 1; ++i) {\r
- Point2D p1 = points.get(i);\r
- Point2D p2 = points.get(i+1);\r
- double distanceToLine = GeometryUtils.distanceFromLine(toCanvasPos, p1, p2);\r
- if (distanceToLine < nearestDistanceToLine) {\r
- nearestDistanceToLine = distanceToLine;\r
- if (nearestLine == null)\r
- nearestLine = new Line2D.Double();\r
- nearestLine.setLine(p1, p2);\r
- }\r
- }\r
- return nearestLine;\r
- }\r
-\r
- /**\r
- * @param object\r
- * @return\r
- * @throws DatabaseException\r
- */\r
- public static IElement getSingleEdge(Object object) throws DatabaseException {\r
- IElement e = AdaptionUtils.adaptToSingle(object, IElement.class);\r
- if (e == null)\r
- return null;\r
-\r
- if (PickFilter.FILTER_CONNECTION_EDGES.accept(e))\r
- return e;\r
-\r
- if (PickFilter.FILTER_CONNECTIONS.accept(e)) {\r
- ConnectionHandler ch = e.getElementClass().getSingleItem(ConnectionHandler.class);\r
- Collection<IElement> bps = ch.getBranchPoints(e, null);\r
- Collection<IElement> segs = ch.getSegments(e, null);\r
- if (bps.isEmpty() && segs.size() == 1)\r
- return segs.iterator().next();\r
- }\r
- return null;\r
- }\r
-\r
- /**\r
- * @param object\r
- * @return\r
- * @throws DatabaseException\r
- */\r
- public static Collection<IElement> getEdges(Object object) throws DatabaseException {\r
- IElement e = AdaptionUtils.adaptToSingle(object, IElement.class);\r
- if (e == null)\r
- return null;\r
-\r
- if (PickFilter.FILTER_CONNECTION_EDGES.accept(e))\r
- return Collections.singleton(e);\r
-\r
- if (PickFilter.FILTER_CONNECTIONS.accept(e)) {\r
- ConnectionHandler ch = e.getElementClass().getSingleItem(ConnectionHandler.class);\r
- return ch.getSegments(e, null);\r
- }\r
- return null;\r
- }\r
-\r
- /**\r
- * @param graph\r
- * @param connection\r
- * @return\r
- * @throws DatabaseException\r
- */\r
- public static Resource getConnectionTailNode(ReadGraph graph, Resource connection) throws DatabaseException {\r
- DiagramResource DIA = DiagramResource.getInstance(graph);\r
- StructuralResource2 STR = StructuralResource2.getInstance(graph);\r
- for (Resource connector : graph.getObjects(connection, DIA.HasTailConnector)) {\r
- for (Resource node : graph.getObjects(connector, STR.Connects)) {\r
- if (node.equals(connection))\r
- continue;\r
- return node;\r
- }\r
- }\r
- return null;\r
- }\r
-\r
- /**\r
- * @param graph\r
- * @param connection\r
- * @return the STR.Connects statement from the tail DIA.Connector to the\r
- * tail node (DIA.Element) or <code>null</code> if no tail node\r
- * exists\r
- * @throws DatabaseException\r
- */\r
- public static Statement getConnectionTailNodeStatement(ReadGraph graph, Resource connection) throws DatabaseException {\r
- DiagramResource DIA = DiagramResource.getInstance(graph);\r
- StructuralResource2 STR = StructuralResource2.getInstance(graph);\r
- for (Resource connector : graph.getObjects(connection, DIA.HasTailConnector)) {\r
- for (Statement connects : graph.getStatements(connector, STR.Connects)) {\r
- if (connects.getObject().equals(connection))\r
- continue;\r
- return connects;\r
- }\r
- }\r
- return null;\r
- }\r
-\r
- /**\r
- * @param connection\r
- * @param dx\r
- * @param dy\r
- * @throws DatabaseException\r
- */\r
- public void translateRouteNodes(Resource connection, double dx, double dy) throws DatabaseException {\r
- Commands.get(g, "Simantics/Diagram/translateRouteNodes")\r
- .execute(g, g.syncRequest(new IndexRoot(connection)), connection, dx, dy);\r
- }\r
- \r
- public static void translateRouteNodes(WriteGraph g, Resource connection, double dx, double dy) throws DatabaseException {\r
- DiagramResource DIA = DiagramResource.getInstance(g);\r
- for (Resource routeNode : g.getObjects(connection, DIA.HasInteriorRouteNode)) {\r
- if (g.isInstanceOf(routeNode, DIA.RouteLine)) {\r
- Double pos = g.getRelatedValue(routeNode, DIA.HasPosition, Bindings.DOUBLE);\r
- Boolean isHorizontal = g.getRelatedValue(routeNode, DIA.IsHorizontal, Bindings.BOOLEAN);\r
- pos += isHorizontal ? dy : dx;\r
- g.claimLiteral(routeNode, DIA.HasPosition, pos);\r
- }\r
- }\r
- }\r
-\r
- /**\r
- * Creates a route graph connection which has corners at the specified\r
- * locations, starting/ending from/at the specified element terminals.\r
- * \r
- * if {@link Development#DEVELOPMENT} is <code>true</code> the code will\r
- * verify that both element end-points are part of the same diagram.\r
- * \r
- * @param graph\r
- * database write access\r
- * @param startElement\r
- * element to start connecting from\r
- * @param startConnectionPoint\r
- * STR.ConnectedTo relation of the start element terminal\r
- * @param endElement\r
- * element to end the connection at\r
- * @param endConnectionPoint\r
- * STR.ConnectedTo relation of the end element terminal\r
- * @param corners\r
- * the corners to create for the connection\r
- * @return the created diagram connection resource\r
- * @throws DatabaseException\r
- */\r
- public Resource createConnectionWithCorners(WriteGraph graph, Resource startElement,\r
- Resource startConnectionPoint, Resource endElement, Resource endConnectionPoint, List<Point2D> corners)\r
- throws DatabaseException {\r
- DiagramResource DIA = br.DIA;\r
-\r
- // Verify that both elements are part of the same diagram before connecting.\r
- if (Development.DEVELOPMENT) {\r
- Collection<Resource> startDiagram = OrderedSetUtils.getOwnerLists(graph, startElement, DIA.Diagram);\r
- Collection<Resource> endDiagram = OrderedSetUtils.getOwnerLists(graph, endElement, DIA.Diagram);\r
- if (Collections.disjoint(startDiagram, endDiagram))\r
- throw new IllegalArgumentException("start element " + startElement\r
- + " is not on same diagram as end element " + endElement + ". start diagram: " + startDiagram\r
- + ", end diagram: " + endDiagram);\r
- }\r
-\r
- return createConnectionWithCorners(graph, DIA.RouteGraphConnection, startElement,\r
- startConnectionPoint, DIA.HasPlainConnector, endElement, endConnectionPoint, DIA.HasArrowConnector,\r
- corners);\r
- }\r
-\r
- /**\r
- * Creates a route graph connection which has corners at the specified\r
- * locations, starting/ending from/at the specified element terminals.\r
- * Verifies that both element end-points are part of the same diagram.\r
- * \r
- * @param graph database write access\r
- * @param connectionType type of created connection (e.g. DIA.RouteGraphConnection)\r
- * @param element1 element to start connecting from\r
- * @param connectionPoint1 STR.ConnectedTo relation of the start element terminal \r
- * @param hasConnector1 connector to connection attachment relation to use for element1 and connectionPoint1\r
- * @param element2 element to end the connection at\r
- * @param connectionPoint2 STR.ConnectedTo relation of the end element terminal\r
- * @param hasConnector2 connector to connection attachment relation to use for element2 and connectionPoint2\r
- * @param corners the corners to create for the connection\r
- * @return the created diagram connection resource\r
- * @throws DatabaseException\r
- */\r
- public Resource createConnectionWithCorners(WriteGraph graph, Resource connectionType,\r
- Resource element1, Resource connectionPoint1, Resource hasConnector1, Resource element2,\r
- Resource connectionPoint2, Resource hasConnector2, List<Point2D> corners)\r
- throws DatabaseException {\r
- DiagramResource DIA = br.DIA;\r
-\r
- if (corners.size() == 1)\r
- throw new UnsupportedOperationException("1 corner currently not supported");\r
-\r
- Resource connection = newInstance(g, connectionType);\r
- Resource connector1 = newConnector(connection, hasConnector1);\r
- Resource connector2 = newConnector(connection, hasConnector2);\r
- graph.claim(element1, connectionPoint1, connector1);\r
- graph.claim(element2, connectionPoint2, connector2);\r
-\r
- if (corners.size() > 1) {\r
- boolean horizontal;\r
- Resource previousRouteNode = connector1;\r
- for (int i=0; i<corners.size()-1; ++i) {\r
- Point2D p = corners.get(i);\r
- Point2D p1 = corners.get(i+1);\r
- horizontal = Math.abs(p1.getY() - p.getY()) < Math.abs(p1.getX() - p.getX());\r
- Resource routeLine = ConnectionUtil.createRouteline(graph, connection, horizontal ? p.getY() : p.getX(), horizontal);\r
- graph.claim(previousRouteNode, DIA.AreConnected, DIA.AreConnected, routeLine);\r
- previousRouteNode = routeLine;\r
- }\r
- graph.claim(previousRouteNode, DIA.AreConnected, DIA.AreConnected, connector2);\r
- } else {\r
- graph.claim(connector1, DIA.AreConnected, DIA.AreConnected, connector2);\r
- }\r
-\r
- return connection;\r
- }\r
-\r
- public Resource createConnectionWithSingleLine(WriteGraph graph, Resource connectionType,\r
- Collection<Triple<Resource,Resource,Resource>> endpoints,\r
- double coordinate, boolean horizontal)\r
- throws DatabaseException {\r
-\r
- DiagramResource DIA = br.DIA;\r
-\r
- Resource connection = newInstance(g, connectionType);\r
-\r
- Resource routeLine = ConnectionUtil.createRouteline(graph, connection, coordinate, horizontal);\r
-\r
- for(Triple<Resource,Resource,Resource> endpoint : endpoints) {\r
- Resource connector = newConnector(connection, endpoint.third); \r
- graph.claim(endpoint.first, endpoint.second, connector);\r
- graph.claim(routeLine, DIA.AreConnected, DIA.AreConnected, connector);\r
- }\r
-\r
- return connection;\r
- \r
- }\r
-\r
- public Resource createConnection(WriteGraph graph, Resource connectionType,\r
- List<Triple<Resource,Resource,Resource>> terminals,\r
- List<Pair<Double, Boolean>> routeLines,\r
- List<Pair<Integer,Integer>> connections) throws DatabaseException {\r
-\r
- DiagramResource DIA = br.DIA;\r
-\r
- Resource connection = newInstance(g, connectionType);\r
-\r
- Resource[] parts = new Resource[terminals.size() + routeLines.size()];\r
- \r
- int index = 0;\r
- \r
- for(Triple<Resource,Resource,Resource> terminal : terminals) {\r
- Resource connector = newConnector(connection, terminal.third); \r
- graph.claim(terminal.first, terminal.second, connector);\r
- parts[index++] = connector;\r
- }\r
-\r
- for(Pair<Double, Boolean> routeLine : routeLines) {\r
- Resource r = ConnectionUtil.createRouteline(graph, connection, routeLine.first, routeLine.second);\r
- parts[index++] = r;\r
- }\r
- \r
-// System.err.println("Connect " + parts.length + " parts.");\r
- \r
- for(Pair<Integer,Integer> conn : connections) {\r
-// System.err.println("-" + conn.first + " " + conn.second);\r
- Resource part1 = parts[conn.first];\r
- Resource part2 = parts[conn.second];\r
- graph.claim(part1, DIA.AreConnected, DIA.AreConnected, part2);\r
- }\r
-\r
- return connection;\r
- \r
- }\r
-\r
- /**\r
- * @param graph\r
- * @param connection\r
- * @param pos\r
- * @param isHorizontal\r
- * @return new route line that is attached to the specified diagram connection\r
- * @throws DatabaseException\r
- */\r
- public static Resource createRouteline(WriteGraph graph, Resource connection, double pos, boolean isHorizontal) throws DatabaseException {\r
- Layer0 L0 = Layer0.getInstance(graph);\r
- DiagramResource DIA = DiagramResource.getInstance(graph);\r
- Resource routeLine = graph.newResource();\r
- graph.claim(routeLine, L0.InstanceOf, null, DIA.RouteLine);\r
- graph.addLiteral(routeLine, DIA.HasPosition, DIA.HasPosition_Inverse, L0.Double, pos, Bindings.DOUBLE);\r
- graph.addLiteral(routeLine, DIA.IsHorizontal, DIA.IsHorizontal_Inverse, L0.Boolean, isHorizontal, Bindings.BOOLEAN);\r
- graph.claim(connection, DIA.HasInteriorRouteNode, DIA.HasInteriorRouteNode_Inverse, routeLine);\r
- return routeLine;\r
- }\r
-\r
- /**\r
- * @param graph database write-only access\r
- * @param type type of the created resource\r
- * @return new instance of type\r
- * @throws DatabaseException\r
- */\r
- private Resource newInstance(WriteOnlyGraph graph, Resource type) throws DatabaseException {\r
- Resource connection = graph.newResource();\r
- g.claim(connection, br.L0.InstanceOf, null, type);\r
- return connection;\r
- }\r
-\r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.diagram.content;
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Line2D;
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.Stack;
+
+import org.simantics.databoard.Bindings;
+import org.simantics.db.ReadGraph;
+import org.simantics.db.Resource;
+import org.simantics.db.Statement;
+import org.simantics.db.WriteGraph;
+import org.simantics.db.WriteOnlyGraph;
+import org.simantics.db.common.CommentMetadata;
+import org.simantics.db.common.request.IndexRoot;
+import org.simantics.db.common.utils.NameUtils;
+import org.simantics.db.common.utils.OrderedSetUtils;
+import org.simantics.db.exception.AssumptionException;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.exception.ValidationException;
+import org.simantics.db.layer0.adapter.impl.EntityRemover;
+import org.simantics.db.layer0.util.RemoverUtil;
+import org.simantics.diagram.connection.ConnectionSegmentEnd;
+import org.simantics.diagram.stubs.DiagramResource;
+import org.simantics.diagram.synchronization.graph.BasicResources;
+import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;
+import org.simantics.g2d.connection.handler.ConnectionHandler;
+import org.simantics.g2d.diagram.handler.PickRequest.PickFilter;
+import org.simantics.g2d.diagram.handler.Topology.Terminal;
+import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil.TerminalInfo;
+import org.simantics.g2d.element.ElementUtils;
+import org.simantics.g2d.element.IElement;
+import org.simantics.g2d.element.handler.BendsHandler;
+import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
+import org.simantics.g2d.element.handler.impl.BranchPointTerminal;
+import org.simantics.g2d.elementclass.BranchPoint;
+import org.simantics.g2d.elementclass.BranchPoint.Direction;
+import org.simantics.layer0.Layer0;
+import org.simantics.modeling.ModelingResources;
+import org.simantics.scenegraph.utils.GeometryUtils;
+import org.simantics.scl.commands.Commands;
+import org.simantics.structural.stubs.StructuralResource2;
+import org.simantics.structural2.modelingRules.CPConnection;
+import org.simantics.structural2.modelingRules.CPConnectionJoin;
+import org.simantics.structural2.modelingRules.CPTerminal;
+import org.simantics.structural2.modelingRules.IConnectionPoint;
+import org.simantics.utils.Development;
+import org.simantics.utils.datastructures.Pair;
+import org.simantics.utils.datastructures.Triple;
+import org.simantics.utils.ui.AdaptionUtils;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public final class ConnectionUtil {
+
+ private final ReadGraph rg;
+ private final WriteGraph g;
+ private final BasicResources br;
+
+ /**
+ * Construct utility with read-only support.
+ * @param g
+ */
+ public ConnectionUtil(ReadGraph g) {
+ this.rg = g;
+ this.g = (g instanceof WriteGraph) ? (WriteGraph) g : null;
+ this.br = BasicResources.getInstance(g);
+ }
+
+ /**
+ * Construct utility with read-write support.
+ * @param g
+ */
+ public ConnectionUtil(WriteGraph g) {
+ this.rg = g;
+ this.g = g;
+ this.br = BasicResources.getInstance(g);
+ }
+
+ void assertWriteSupport() {
+ if (g == null)
+ throw new UnsupportedOperationException("no write support, this ConnectionUtil is read-only");
+ }
+
+ /**
+ * Creates a new connection element of the specified type and attaches it to
+ * the specified diagram composite.
+ *
+ * @param composite diagram composite
+ * @param type connection element type
+ * @return created connection
+ * @throws DatabaseException
+ */
+ public Resource newConnection(Resource composite, Resource type) throws DatabaseException {
+ assertWriteSupport();
+ Resource connection = newConnection(type);
+ // By default, add connections to the front of the diagram since most
+ // often it is visually the expected result.
+ OrderedSetUtils.addFirst(g, composite, connection);
+ g.claim(composite, br.L0.ConsistsOf, br.L0.PartOf, connection);
+ return connection;
+ }
+
+ /**
+ * Creates a new connection element of the specified type without attaching
+ * it to any diagram composite.
+ *
+ * @param type connection element type
+ * @return created connection
+ * @throws DatabaseException
+ */
+ public Resource newConnection(Resource type) throws DatabaseException {
+ assertWriteSupport();
+ return newInstance(g, type);
+ }
+
+ /**
+ * Creates a new element terminal connector (DiagramResource) for the specified connector
+ * @param connection
+ * @param hasConnector
+ * @return
+ * @throws DatabaseException
+ */
+ public Resource newConnector(Resource connection, Resource hasConnector) throws DatabaseException {
+ assertWriteSupport();
+ Resource connector = newInstance(g, br.DIA.Connector);
+ g.claim(connection, hasConnector, connector);
+ return connector;
+ }
+
+ public Resource newBranchPoint(Resource connection, AffineTransform tr) throws DatabaseException {
+ return newBranchPoint(connection, tr, null);
+ }
+
+ public Resource newBranchPoint(Resource connection, AffineTransform tr, Direction direction) throws DatabaseException {
+ assertWriteSupport();
+ Resource bp = g.newResource();
+ g.claim(bp, br.L0.InstanceOf, null, br.DIA.BranchPoint);
+ if (tr != null) {
+ double[] mat = new double[6];
+ tr.getMatrix(mat);
+ Resource transform = g.newResource();
+ g.claim(transform, br.L0.InstanceOf, null, br.G2D.Transform);
+ g.claimValue(transform, mat);
+ g.claim(bp, br.DIA.HasTransform, transform);
+ }
+ Resource tag = toDirectionTag(g, direction);
+ if (tag != null) {
+ g.claim(bp, tag, tag, bp);
+ }
+ g.claim(connection, br.DIA.HasBranchPoint, bp);
+ return bp;
+ }
+
+ /**
+ * @param connection
+ * @param position
+ * @param isHorizontal
+ * @return
+ * @throws DatabaseException
+ */
+ public Resource newRouteLine(Resource connection, Double position, Boolean isHorizontal) throws DatabaseException {
+ assertWriteSupport();
+ Resource rl = newInstance(g, br.DIA.RouteLine);
+ if (position != null) {
+ g.addLiteral(rl, br.DIA.HasPosition, br.DIA.HasPosition_Inverse, br.L0.Double, position, Bindings.DOUBLE);
+ }
+ if (isHorizontal != null) {
+ g.addLiteral(rl, br.DIA.IsHorizontal, br.DIA.IsHorizontal_Inverse, br.L0.Boolean, isHorizontal, Bindings.BOOLEAN);
+ }
+ g.claim(connection, br.DIA.HasInteriorRouteNode, br.DIA.HasInteriorRouteNode_Inverse, rl);
+ return rl;
+ }
+
+ ConnectionSegmentEnd getTerminalType(Terminal terminal, ConnectionSegmentEnd defaultValue) {
+ ConnectionSegmentEnd type = getTerminalType(terminal);
+ return type != null ? type : defaultValue;
+ }
+
+ ConnectionSegmentEnd getTerminalType(Terminal t) {
+ if (t == null)
+ return null;
+
+ if (t instanceof ResourceTerminal) {
+ return ConnectionSegmentEnd.CONNECTOR;
+ } else if (t instanceof BranchPointTerminal) {
+ return ConnectionSegmentEnd.BRANCH;
+ } else {
+ throw new IllegalArgumentException("unsupported terminal '" + t + "'");
+ }
+ }
+
+ Resource resolveTerminalRelation(ReadGraph g, Terminal t) throws DatabaseException {
+ if (t == null)
+ return null;
+ if (t instanceof ResourceTerminal) {
+ ResourceTerminal rt = (ResourceTerminal) t;
+ Resource terminalRelation = DiagramGraphUtil.getConnectionPointOfTerminal(g, rt.getResource());
+ if (!g.isSubrelationOf(terminalRelation, br.STR.IsConnectedTo)) {
+ // debug...
+ }
+ return terminalRelation;
+ } else {
+ throw new IllegalArgumentException("unsupported terminal '" + t + "' for terminal relation resolution");
+ }
+ }
+
+ public Resource toHasConnectorRelation(EdgeEnd end) {
+ switch (end) {
+ case Begin: return br.DIA.HasPlainConnector;
+ case End: return br.DIA.HasArrowConnector;
+ default: throw new IllegalArgumentException("unsupported edge end: " + end);
+ }
+ }
+
+ public EdgeEnd toEdgeEnd(Resource attachmentRelation, EdgeEnd defaultValue) throws DatabaseException {
+ if (g.isSubrelationOf(attachmentRelation, br.DIA.HasPlainConnector))
+ return EdgeEnd.Begin;
+ if (g.isSubrelationOf(attachmentRelation, br.DIA.HasArrowConnector))
+ return EdgeEnd.End;
+ return defaultValue;
+ }
+
+ public Resource getAttachmentRelationForConnector(Resource connector) throws DatabaseException {
+ Statement connection = g.getPossibleStatement(connector, br.DIA.IsConnectorOf);
+ if (connection == null)
+ return null;
+ Resource attachment = g.getInverse(connection.getPredicate());
+ return attachment;
+ }
+
+ /**
+ * @param connection
+ * @param node
+ * @param c
+ * @param end
+ * @param attachmentRelation the relation used for attaching the connector to the connector
+ * @return
+ * @throws DatabaseException
+ */
+ public Resource getOrCreateConnector(Resource connection, Resource node, Terminal terminal, EdgeEnd end, Resource attachmentRelation) throws DatabaseException {
+ assertWriteSupport();
+ ConnectionSegmentEnd connectorType = getTerminalType(terminal, null);
+ if (connectorType == null)
+ throw new AssumptionException("Invalid connection node", connection, node);
+
+ switch (connectorType) {
+ case BRANCH:
+ // NOTE: should we ensure here that (connection, br.dr.HasBranchPoint, node) exists?
+ return node;
+
+ case CONNECTOR: {
+ Resource terminalRelation = resolveTerminalRelation(g, terminal);
+
+ if (attachmentRelation == null)
+ attachmentRelation = toHasConnectorRelation(end);
+
+ if (!g.isSubrelationOf(attachmentRelation, br.DIA.HasConnector))
+ throw new AssumptionException("attachment relation not a subrelation of Has Connector", attachmentRelation);
+
+ // Create new connector for the specified node terminal
+ Resource terminalConnector = newConnector(connection, attachmentRelation);
+ g.claim(node, terminalRelation, terminalConnector);
+ return terminalConnector;
+ }
+ default:
+ throw new Error("this should be unreachable code");
+ }
+ }
+
+ public void connect(Resource connector1, Resource connector2) throws DatabaseException {
+ assertWriteSupport();
+ g.claim(connector1, br.DIA.AreConnected, br.DIA.AreConnected, connector2);
+ }
+
+ public void disconnect(Resource connector1, Resource connector2) throws DatabaseException {
+ assertWriteSupport();
+ g.denyStatement(connector1, br.DIA.AreConnected, connector2);
+ }
+
+ public void disconnectFromAllRouteNodes(Resource connector) throws DatabaseException {
+ assertWriteSupport();
+ g.deny(connector, br.DIA.AreConnected);
+ }
+
+ public void disconnect(EdgeResource segment) throws DatabaseException {
+ assertWriteSupport();
+ disconnect(segment.first(), segment.second());
+ }
+
+ public boolean isConnected(Resource connector) throws DatabaseException {
+ return rg.hasStatement(connector, br.DIA.AreConnected);
+ }
+
+ public boolean isConnected(Resource connector, Resource toConnector) throws DatabaseException {
+ return rg.hasStatement(connector, br.DIA.AreConnected, toConnector);
+ }
+
+ public boolean isConnectionEmpty(Resource connection) throws DatabaseException {
+ return !rg.hasStatement(connection, br.DIA.HasConnector)
+ && !rg.hasStatement(connection, br.DIA.HasInteriorRouteNode);
+ }
+
+ private void removeConnectorOrBranchPoint(Resource connectorOrBranchPoint) throws DatabaseException {
+
+// // Handle correct removal of route points
+// if(g.isInstanceOf(connectorOrBranchPoint, dr.BranchPoint)) {
+// Collection<Resource> connectedConnectors = g.getObjects(connectorOrBranchPoint, dr.AreConnected);
+// if(connectedConnectors.size() == 2) {
+// Iterator<Resource> it = connectedConnectors.iterator();
+// g.claim(it.next(), dr.AreConnected, it.next());
+// }
+// }
+
+ g.deny(connectorOrBranchPoint, br.DIA.AreConnected);
+
+ // Removes both the terminal relation and the HasConnector relation
+ // to the :Connection
+ g.deny(connectorOrBranchPoint, br.STR.Connects);
+
+ // If this is a branch point/route node, remove it from the connection too.
+ g.deny(connectorOrBranchPoint, br.DIA.IsBranchPointOf);
+ g.deny(connectorOrBranchPoint, br.DIA.HasInteriorRouteNode_Inverse);
+ }
+
+ /**
+ * Removes a complete connection along with all its branch points and terminal connectors.
+ *
+ * @param connection the connection to remove
+ */
+ public void removeConnection(Resource connection) throws DatabaseException {
+ assertWriteSupport();
+
+ // Add comment to change set.
+ CommentMetadata cm = g.getMetadata(CommentMetadata.class);
+ g.addMetadata(cm.add("Remove connection " + connection));
+
+ // 1. Get all connectors/branch points
+ Collection<Resource> connectors = new ArrayList<Resource>();
+ connectors.addAll(rg.getObjects(connection, br.DIA.HasConnector));
+ connectors.addAll(rg.getObjects(connection, br.DIA.HasInteriorRouteNode));
+
+ // 2. Remove all connectors/branch points
+ for (Resource connector : connectors) {
+ removeConnectorOrBranchPoint(connector);
+ RemoverUtil.remove(g, connector);
+ }
+
+ // 3. Remove whole connection
+ for (Resource owner : OrderedSetUtils.getOwnerLists(g, connection, br.DIA.Diagram))
+ OrderedSetUtils.remove(g, owner, connection);
+ EntityRemover.remove(g, connection);
+ }
+
+ /**
+ * Removes a single connector part from the graph. A connection part can be
+ * either a branch point or a terminal connector.
+ *
+ * @param connectorOrBranchPoint
+ * @throws DatabaseException
+ */
+ public void removeConnectionPart(Resource connectorOrBranchPoint) throws DatabaseException {
+ removeConnectorOrBranchPoint(connectorOrBranchPoint);
+ RemoverUtil.remove(g, connectorOrBranchPoint);
+ }
+
+ /**
+ * Removes the specified connection segment. Checks that both ends of the
+ * edge segment are part of to the same connection. Steps taken:
+ * <ul>
+ * <li>Minimally this only disconnects the connection segment ends from each
+ * other and nothing more.</li>
+ * <li>After disconnecting, we check whether the segment ends are still
+ * connected to something. If not, the :Connector/:BranchPoint at the
+ * segment's end is destroyed and detached from the connection entity.</li>
+ * <li>Finally, if the connection entity is empty (has no connectors/branch
+ * points), it is also destroyed and removed from the diagram.</li>
+ *
+ * @param segment the connection segment to remove
+ */
+ public void remove(EdgeResource segment) throws DatabaseException {
+ remove(segment, false);
+ }
+
+ /**
+ * Removes the specified connection segment. Steps taken:
+ * <ul>
+ * <li>Minimally this only disconnects the connection segment ends from each
+ * other and nothing more.</li>
+ * <li>After disconnecting, we check whether the segment ends are still
+ * connected to something. If not, the :Connector/:BranchPoint at the
+ * segment's end is destroyed and detached from the connection entity.</li>
+ * <li>Finally, if the connection entity is empty (has no connectors/branch
+ * points), it is also destroyed and removed from the diagram.</li>
+ *
+ * @param segment the connection segment to remove
+ * @param unchecked <code>false</code> to check that both ends of the
+ * segment are part of the same connection before removing,
+ * <code>true</code> to just remove the segment without checking
+ * this. Using <code>true</code> may help in cases where the
+ * connection model has become corrupted for some reason, e.g. the
+ * other end of the edge has lost its link to the connection while
+ * the other has not,
+ */
+ public void remove(EdgeResource segment, boolean unchecked) throws DatabaseException {
+ assertWriteSupport();
+
+ if (!unchecked) {
+ @SuppressWarnings("unused")
+ Resource connection = getConnection(g, segment);
+ }
+
+ // 1. disconnect segment ends
+ disconnect(segment);
+
+ // 2. Remove connectors/branch points if they become fully disconnected
+ if (!isConnected(segment.first())) {
+ removeConnectorOrBranchPoint(segment.first());
+ }
+ if (!isConnected(segment.second())) {
+ removeConnectorOrBranchPoint(segment.second());
+ }
+
+ // 3. Remove whole connection entity if it becomes empty
+// if (isConnectionEmpty(connection)) {
+// for (Resource owner : OrderedSetUtils.getOwnerLists(g, connection, dr.Diagram))
+// OrderedSetUtils.remove(g, owner, connection);
+// RemoverUtil.remove(g, connection);
+// }
+
+ }
+
+ /**
+ * Removes all DIA.Connector instances from the specified connection that
+ * are not used for anything.
+ *
+ * @param connection connection to examine
+ * @return the amount of unused connectors removed
+ */
+ public int removeUnusedConnectors(Resource connection) throws DatabaseException {
+ int result = 0;
+ for (Resource connector : getConnectors(connection, null)) {
+ if (!g.getObjects(connector, br.DIA.AreConnected).isEmpty())
+ continue;
+ Collection<Resource> connects = g.getObjects(connector, br.STR.Connects);
+ if (connects.size() > 1)
+ continue;
+
+ removeConnectionPart(connector);
+ ++result;
+ }
+ return result;
+ }
+
+ /**
+ * Removes all DIA.InteriorRouteNode instances from the specified connection that
+ * are not used for anything, i.e. connect to less than 2 other route nodes.
+ *
+ * @param connection connection to examine
+ * @return the amount of unused connectors removed
+ */
+ public int removeExtraInteriorRouteNodes(Resource connection) throws DatabaseException {
+ int result = 0;
+ for (Resource interiorRouteNode : g.getObjects(connection, br.DIA.HasInteriorRouteNode)) {
+ Collection<Resource> connectedTo = g.getObjects(interiorRouteNode, br.DIA.AreConnected);
+ if (connectedTo.size() > 1)
+ continue;
+
+ removeConnectionPart(interiorRouteNode);
+ ++result;
+ }
+ return result;
+ }
+
+ /**
+ * Splits the specified connection segment by adding a new branch point in
+ * between the segment ends.
+ *
+ * @param segment
+ * @return the branch (route) point created by the split operation.
+ */
+ public Resource split(EdgeResource segment, AffineTransform splitPos) throws DatabaseException {
+ assertWriteSupport();
+
+ Resource connection = getConnection(g, segment);
+ disconnect(segment);
+ Resource bp = newBranchPoint(connection, splitPos);
+ connect(segment.first(), bp);
+ connect(bp, segment.second());
+ return bp;
+ }
+
+ /**
+ * Joins the connection at the selected branch point if and only if the
+ * branch point is a route point, i.e. it is connected to two other branch
+ * points or connector.
+ *
+ * @param interiorRouteNode
+ * @return
+ */
+ public void join(Resource interiorRouteNode) throws DatabaseException {
+ assertWriteSupport();
+
+ if (!g.isInstanceOf(interiorRouteNode, br.DIA.InteriorRouteNode))
+ throw new ValidationException("'" + NameUtils.getSafeName(g, interiorRouteNode) + "' is not an instance of DIA.InteriorRouteNode");
+ @SuppressWarnings("unused")
+ Resource connection = getConnection(g, interiorRouteNode);
+ Collection<Resource> connectedTo = g.getObjects(interiorRouteNode, br.DIA.AreConnected);
+ if (connectedTo.size() != 2)
+ throw new ValidationException("Interior route node is not a discardable route line/point. It is not connected to 2 route nodes, but " + connectedTo.size() + ".");
+ Iterator<Resource> it = connectedTo.iterator();
+ Resource connector1 = it.next();
+ Resource connector2 = it.next();
+ //System.out.println("removing branch point " + GraphUtils.getReadableName(g, routeBranchPoint) + " which is connected to " + GraphUtils.getReadableName(g, connector1) + " and " + GraphUtils.getReadableName(g, connector2));
+ removeConnectorOrBranchPoint(interiorRouteNode);
+ connect(connector1, connector2);
+ }
+
+ public void getConnectionSegments(Resource connection, Collection<EdgeResource> result) throws DatabaseException {
+
+ ArrayList<EdgeResource> edges = new ArrayList<EdgeResource>();
+ Set<Resource> visited = new HashSet<Resource>();
+ Stack<Resource> todo = new Stack<Resource>();
+
+ // Try to select input as root, this ensures correct order for simple paths
+ Collection<Resource> seeds = rg.getObjects(connection, br.DIA.HasArrowConnector);
+ if(seeds.isEmpty()) seeds = rg.getObjects(connection, br.DIA.HasPlainConnector);
+
+ assert(!seeds.isEmpty());
+
+ Resource seed = seeds.iterator().next();
+
+ todo.push(seed);
+
+ while(!todo.isEmpty()) {
+ Resource location = todo.pop();
+ if(!visited.contains(location)) {
+ visited.add(location);
+ for (Resource connectedTo : rg.getObjects(location, br.DIA.AreConnected)) {
+ todo.add(connectedTo);
+ EdgeResource edge = new EdgeResource(location, connectedTo);
+ if(!edges.contains(edge)) edges.add(edge);
+ }
+ }
+ }
+
+ for(EdgeResource uer : edges) {
+// System.out.println("loaded edge " + uer.first() + " " + uer.second());
+ result.add(uer);
+ }
+
+ }
+
+ public Collection<Resource> getBranchPoints(Resource connection, Collection<Resource> result) throws DatabaseException {
+ if (result == null)
+ result = new ArrayList<Resource>();
+ result.addAll(rg.getObjects(connection, br.DIA.HasBranchPoint));
+ return result;
+ }
+
+ public Collection<Resource> getConnectors(Resource connection, Collection<Resource> result) throws DatabaseException {
+ if (result == null)
+ result = new ArrayList<Resource>();
+ result.addAll(rg.getObjects(connection, br.DIA.HasConnector));
+ return result;
+ }
+
+ public Resource getConnectedComponent(Resource connection, Resource connector) throws DatabaseException {
+ for (Resource connects : rg.getObjects(connector, br.STR.Connects))
+ if (!connects.equals(connection))
+ return connects;
+ return null;
+ }
+
+ public Statement getConnectedComponentStatement(Resource connection, Resource connector) throws DatabaseException {
+ for (Statement connects : rg.getStatements(connector, br.STR.Connects))
+ if (!connects.getObject().equals(connection))
+ return connects;
+ return null;
+ }
+
+ public void gatherEdges(Resource connection, Collection<EdgeResource> result) throws DatabaseException {
+ Set<Object> visited = new HashSet<Object>();
+ for (Resource connector : rg.getObjects(connection, br.DIA.HasConnector)) {
+ for (Resource connectedTo : rg.getObjects(connector, br.DIA.AreConnected)) {
+ EdgeResource p = new EdgeResource(connector, connectedTo);
+ if (visited.add(p)) {
+ result.add(p);
+ }
+ }
+ }
+ Collection<Resource> routeNodes = rg.getObjects(connection, br.DIA.HasInteriorRouteNode);
+ for (Resource routeNode : routeNodes) {
+ for (Resource connectedTo : rg.getObjects(routeNode, br.DIA.AreConnected)) {
+ EdgeResource p = new EdgeResource(routeNode, connectedTo);
+ if (visited.add(p)) {
+ result.add(p);
+ }
+ }
+ }
+ }
+
+ public void gatherConnectionParts(Resource connection, Collection<Object> result) throws DatabaseException {
+ Set<Object> visited = new HashSet<Object>();
+ for (Resource connector : rg.getObjects(connection, br.DIA.HasConnector)) {
+ for (Resource connectedTo : rg.getObjects(connector, br.DIA.AreConnected)) {
+ EdgeResource p = new EdgeResource(connector, connectedTo);
+ if (visited.add(p)) {
+ result.add(p);
+ }
+ }
+ }
+ Collection<Resource> routeNodes = rg.getObjects(connection, br.DIA.HasInteriorRouteNode);
+ for (Resource routeNode : routeNodes) {
+ result.add(routeNode);
+ for (Resource connectedTo : rg.getObjects(routeNode, br.DIA.AreConnected)) {
+ EdgeResource p = new EdgeResource(routeNode, connectedTo);
+ if (visited.add(p)) {
+ result.add(p);
+ }
+ }
+ }
+ }
+
+ public Collection<Resource> getConnectedComponents(Resource connection, Collection<Resource> result)
+ throws DatabaseException {
+ if (result == null)
+ result = new ArrayList<Resource>(2);
+ for (Resource connector : rg.getObjects(connection, br.DIA.HasConnector)) {
+ for (Resource connects : rg.getObjects(connector, br.STR.Connects)) {
+ if (connects.equals(connection))
+ continue;
+ result.add(connects);
+ }
+ }
+ return result;
+ }
+
+ public Collection<Resource> getConnectedConnectors(Resource connection, Collection<Resource> result)
+ throws DatabaseException {
+ if (result == null)
+ result = new ArrayList<Resource>(2);
+ for (Resource connector : rg.getObjects(connection, br.DIA.HasConnector)) {
+ Resource connects = getConnectedComponent(connection, connector);
+ if (connects != null)
+ result.add( connector );
+ }
+ return result;
+ }
+
+ public Collection<Resource> getTerminalConnectors(Resource node, Collection<Resource> result)
+ throws DatabaseException {
+ if (result == null)
+ result = new ArrayList<Resource>(2);
+ result.addAll( rg.getObjects(node, br.STR.IsConnectedTo) );
+ return result;
+ }
+
+ /**
+ * @param g
+ * @param routeNode
+ * @return <code>null</code> if the specified resource is not part of any
+ * connection
+ */
+ public static Resource tryGetConnection(ReadGraph g, Resource routeNode) throws DatabaseException {
+ DiagramResource dr = DiagramResource.getInstance(g);
+ Resource conn = g.getPossibleObject(routeNode, dr.IsConnectorOf);
+ if (conn == null)
+ conn = g.getPossibleObject(routeNode, dr.HasInteriorRouteNode_Inverse);
+ return conn;
+ }
+
+ public static Resource tryGetConnection(ReadGraph g, EdgeResource segment) throws DatabaseException {
+ Resource first = tryGetConnection(g, segment.first());
+ Resource second = tryGetConnection(g, segment.second());
+ if (first == null || second == null || !first.equals(second))
+ return null;
+ return first;
+ }
+
+ public static Resource tryGetMappedConnection(ReadGraph g, Resource connector) throws DatabaseException {
+ ModelingResources MOD = ModelingResources.getInstance(g);
+ return g.getPossibleObject(connector, MOD.ConnectorToConnection);
+ }
+
+ public static Resource getConnection(ReadGraph g, EdgeResource segment) throws DatabaseException {
+ Resource first = tryGetConnection(g, segment.first());
+ Resource second = tryGetConnection(g, segment.second());
+ if (first == null && second == null)
+ throw new ValidationException(
+ "neither connection segment end is attached to a Connection entity instance: "
+ + segment.toString(g) + " - " + segment.toString());
+ if (first != null ^ second != null)
+ throw new ValidationException("both ends of connection segment "
+ + segment.toString(g) + " - " + segment.toString() + " are not connected (first=" + first
+ + ", second=" + second + ")");
+ if (!first.equals(second))
+ throw new ValidationException("connectors of connection segment "
+ + segment.toString(g) + " - " + segment.toString() + " are part of different connections: " + first
+ + " vs. " + second);
+ return first;
+ }
+
+ public static Resource getConnection(ReadGraph g, Resource routeNode) throws DatabaseException {
+ Resource connection = tryGetConnection(g, routeNode);
+ if (connection == null)
+ throw new ValidationException("route node '"
+ + NameUtils.getSafeName(g, routeNode) + "' is not part of any connection");
+ return connection;
+ }
+
+ public static IConnectionPoint toConnectionPoint(ReadGraph g, Resource element, Terminal terminal) throws DatabaseException {
+ if (terminal instanceof ResourceTerminal) {
+ Resource t = ((ResourceTerminal) terminal).getResource();
+
+ // TODO: remove this hack
+ if (element == null) { // Flag?
+ DiagramResource DIA = DiagramResource.getInstance(g);
+ Resource join = g.getPossibleObject(t, DIA.FlagIsJoinedBy);
+ if (join == null)
+ return null;
+ return new CPConnectionJoin(join);
+ }
+ // element should be :Element
+ Resource bindingRelation = DiagramGraphUtil.getConnectionPointOfTerminal(g, t);
+ return new CPTerminal(element, bindingRelation);
+ } else if (terminal instanceof BranchPointTerminal) {
+ // element should be either : DIA.InteriorRouteNode or DIA.Connection
+ Resource connection = null;
+ DiagramResource DIA = DiagramResource.getInstance(g);
+ if (g.isInstanceOf(element, DIA.Connection))
+ connection = element;
+ else
+ connection = getConnection(g, element);
+ return new CPConnection(connection);
+ }
+ throw new IllegalArgumentException("Unrecognized Terminal class: " + terminal);
+ }
+
+ public static IConnectionPoint toConnectionPoint(ReadGraph graph, IElement element, Terminal terminal) throws DatabaseException {
+ Resource r = (Resource) ElementUtils.getObject(element);
+ return ConnectionUtil.toConnectionPoint(graph, r, terminal);
+ }
+
+ public static IConnectionPoint toConnectionPoint(ReadGraph graph, TerminalInfo ti) throws DatabaseException {
+ return ConnectionUtil.toConnectionPoint(graph, ti.e, ti.t);
+ }
+
+ public static IConnectionPoint toConnectionPoint(ReadGraph graph, DesignatedTerminal t) throws DatabaseException {
+ return toConnectionPoint(graph, t.element, t.terminal);
+ }
+
+ public static Resource toDirectionTag(ReadGraph graph, BranchPoint.Direction direction) {
+ if (direction == null)
+ return null;
+
+ DiagramResource DIA = DiagramResource.getInstance(graph);
+ switch (direction) {
+ case Horizontal: return DIA.Horizontal;
+ case Vertical: return DIA.Vertical;
+ default: return null;
+ }
+ }
+
+ /**
+ * Copied from ConnectionCommandHandler#splitConnection, duplicate code.
+ *
+ * @param toCanvasPos
+ * @param onEdge
+ * @return
+ */
+ public static Line2D resolveNearestEdgeLineSegment(Point2D toCanvasPos, IElement onEdge) {
+ // Try to find an initial preferred direction for the new
+ // branch point based on the direction of the edge's line
+ // segment on which the split is done.
+ List<Point2D> points = new ArrayList<Point2D>();
+ BendsHandler bh = onEdge.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);
+ if (bh != null)
+ org.simantics.g2d.utils.GeometryUtils.getPoints(bh.getPath(onEdge), points);
+ Line2D nearestLine = null;
+ double nearestDistanceToLine = Double.MAX_VALUE;
+ for (int i = 0; i < points.size() - 1; ++i) {
+ Point2D p1 = points.get(i);
+ Point2D p2 = points.get(i+1);
+ double distanceToLine = GeometryUtils.distanceFromLine(toCanvasPos, p1, p2);
+ if (distanceToLine < nearestDistanceToLine) {
+ nearestDistanceToLine = distanceToLine;
+ if (nearestLine == null)
+ nearestLine = new Line2D.Double();
+ nearestLine.setLine(p1, p2);
+ }
+ }
+ return nearestLine;
+ }
+
+ /**
+ * @param object
+ * @return
+ * @throws DatabaseException
+ */
+ public static IElement getSingleEdge(Object object) throws DatabaseException {
+ IElement e = AdaptionUtils.adaptToSingle(object, IElement.class);
+ if (e == null)
+ return null;
+
+ if (PickFilter.FILTER_CONNECTION_EDGES.accept(e))
+ return e;
+
+ if (PickFilter.FILTER_CONNECTIONS.accept(e)) {
+ ConnectionHandler ch = e.getElementClass().getSingleItem(ConnectionHandler.class);
+ Collection<IElement> bps = ch.getBranchPoints(e, null);
+ Collection<IElement> segs = ch.getSegments(e, null);
+ if (bps.isEmpty() && segs.size() == 1)
+ return segs.iterator().next();
+ }
+ return null;
+ }
+
+ /**
+ * @param object
+ * @return
+ * @throws DatabaseException
+ */
+ public static Collection<IElement> getEdges(Object object) throws DatabaseException {
+ IElement e = AdaptionUtils.adaptToSingle(object, IElement.class);
+ if (e == null)
+ return null;
+
+ if (PickFilter.FILTER_CONNECTION_EDGES.accept(e))
+ return Collections.singleton(e);
+
+ if (PickFilter.FILTER_CONNECTIONS.accept(e)) {
+ ConnectionHandler ch = e.getElementClass().getSingleItem(ConnectionHandler.class);
+ return ch.getSegments(e, null);
+ }
+ return null;
+ }
+
+ /**
+ * @param graph
+ * @param connection
+ * @return
+ * @throws DatabaseException
+ */
+ public static Resource getConnectionTailNode(ReadGraph graph, Resource connection) throws DatabaseException {
+ DiagramResource DIA = DiagramResource.getInstance(graph);
+ StructuralResource2 STR = StructuralResource2.getInstance(graph);
+ for (Resource connector : graph.getObjects(connection, DIA.HasTailConnector)) {
+ for (Resource node : graph.getObjects(connector, STR.Connects)) {
+ if (node.equals(connection))
+ continue;
+ return node;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @param graph
+ * @param connection
+ * @return the STR.Connects statement from the tail DIA.Connector to the
+ * tail node (DIA.Element) or <code>null</code> if no tail node
+ * exists
+ * @throws DatabaseException
+ */
+ public static Statement getConnectionTailNodeStatement(ReadGraph graph, Resource connection) throws DatabaseException {
+ DiagramResource DIA = DiagramResource.getInstance(graph);
+ StructuralResource2 STR = StructuralResource2.getInstance(graph);
+ for (Resource connector : graph.getObjects(connection, DIA.HasTailConnector)) {
+ for (Statement connects : graph.getStatements(connector, STR.Connects)) {
+ if (connects.getObject().equals(connection))
+ continue;
+ return connects;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @param connection
+ * @param dx
+ * @param dy
+ * @throws DatabaseException
+ */
+ public void translateRouteNodes(Resource connection, double dx, double dy) throws DatabaseException {
+ Commands.get(g, "Simantics/Diagram/translateRouteNodes")
+ .execute(g, g.syncRequest(new IndexRoot(connection)), connection, dx, dy);
+ }
+
+ public static void translateRouteNodes(WriteGraph g, Resource connection, double dx, double dy) throws DatabaseException {
+ DiagramResource DIA = DiagramResource.getInstance(g);
+ for (Resource routeNode : g.getObjects(connection, DIA.HasInteriorRouteNode)) {
+ if (g.isInstanceOf(routeNode, DIA.RouteLine)) {
+ Double pos = g.getRelatedValue(routeNode, DIA.HasPosition, Bindings.DOUBLE);
+ Boolean isHorizontal = g.getRelatedValue(routeNode, DIA.IsHorizontal, Bindings.BOOLEAN);
+ pos += isHorizontal ? dy : dx;
+ g.claimLiteral(routeNode, DIA.HasPosition, pos);
+ }
+ }
+ }
+
+ /**
+ * Creates a route graph connection which has corners at the specified
+ * locations, starting/ending from/at the specified element terminals.
+ *
+ * if {@link Development#DEVELOPMENT} is <code>true</code> the code will
+ * verify that both element end-points are part of the same diagram.
+ *
+ * @param graph
+ * database write access
+ * @param startElement
+ * element to start connecting from
+ * @param startConnectionPoint
+ * STR.ConnectedTo relation of the start element terminal
+ * @param endElement
+ * element to end the connection at
+ * @param endConnectionPoint
+ * STR.ConnectedTo relation of the end element terminal
+ * @param corners
+ * the corners to create for the connection
+ * @return the created diagram connection resource
+ * @throws DatabaseException
+ */
+ public Resource createConnectionWithCorners(WriteGraph graph, Resource startElement,
+ Resource startConnectionPoint, Resource endElement, Resource endConnectionPoint, List<Point2D> corners)
+ throws DatabaseException {
+ DiagramResource DIA = br.DIA;
+
+ // Verify that both elements are part of the same diagram before connecting.
+ if (Development.DEVELOPMENT) {
+ Collection<Resource> startDiagram = OrderedSetUtils.getOwnerLists(graph, startElement, DIA.Diagram);
+ Collection<Resource> endDiagram = OrderedSetUtils.getOwnerLists(graph, endElement, DIA.Diagram);
+ if (Collections.disjoint(startDiagram, endDiagram))
+ throw new IllegalArgumentException("start element " + startElement
+ + " is not on same diagram as end element " + endElement + ". start diagram: " + startDiagram
+ + ", end diagram: " + endDiagram);
+ }
+
+ return createConnectionWithCorners(graph, DIA.RouteGraphConnection, startElement,
+ startConnectionPoint, DIA.HasPlainConnector, endElement, endConnectionPoint, DIA.HasArrowConnector,
+ corners);
+ }
+
+ /**
+ * Creates a route graph connection which has corners at the specified
+ * locations, starting/ending from/at the specified element terminals.
+ * Verifies that both element end-points are part of the same diagram.
+ *
+ * @param graph database write access
+ * @param connectionType type of created connection (e.g. DIA.RouteGraphConnection)
+ * @param element1 element to start connecting from
+ * @param connectionPoint1 STR.ConnectedTo relation of the start element terminal
+ * @param hasConnector1 connector to connection attachment relation to use for element1 and connectionPoint1
+ * @param element2 element to end the connection at
+ * @param connectionPoint2 STR.ConnectedTo relation of the end element terminal
+ * @param hasConnector2 connector to connection attachment relation to use for element2 and connectionPoint2
+ * @param corners the corners to create for the connection
+ * @return the created diagram connection resource
+ * @throws DatabaseException
+ */
+ public Resource createConnectionWithCorners(WriteGraph graph, Resource connectionType,
+ Resource element1, Resource connectionPoint1, Resource hasConnector1, Resource element2,
+ Resource connectionPoint2, Resource hasConnector2, List<Point2D> corners)
+ throws DatabaseException {
+ DiagramResource DIA = br.DIA;
+
+ if (corners.size() == 1)
+ throw new UnsupportedOperationException("1 corner currently not supported");
+
+ Resource connection = newInstance(g, connectionType);
+ Resource connector1 = newConnector(connection, hasConnector1);
+ Resource connector2 = newConnector(connection, hasConnector2);
+ graph.claim(element1, connectionPoint1, connector1);
+ graph.claim(element2, connectionPoint2, connector2);
+
+ if (corners.size() > 1) {
+ boolean horizontal;
+ Resource previousRouteNode = connector1;
+ for (int i=0; i<corners.size()-1; ++i) {
+ Point2D p = corners.get(i);
+ Point2D p1 = corners.get(i+1);
+ horizontal = Math.abs(p1.getY() - p.getY()) < Math.abs(p1.getX() - p.getX());
+ Resource routeLine = ConnectionUtil.createRouteline(graph, connection, horizontal ? p.getY() : p.getX(), horizontal);
+ graph.claim(previousRouteNode, DIA.AreConnected, DIA.AreConnected, routeLine);
+ previousRouteNode = routeLine;
+ }
+ graph.claim(previousRouteNode, DIA.AreConnected, DIA.AreConnected, connector2);
+ } else {
+ graph.claim(connector1, DIA.AreConnected, DIA.AreConnected, connector2);
+ }
+
+ return connection;
+ }
+
+ public Resource createConnectionWithSingleLine(WriteGraph graph, Resource connectionType,
+ Collection<Triple<Resource,Resource,Resource>> endpoints,
+ double coordinate, boolean horizontal)
+ throws DatabaseException {
+
+ DiagramResource DIA = br.DIA;
+
+ Resource connection = newInstance(g, connectionType);
+
+ Resource routeLine = ConnectionUtil.createRouteline(graph, connection, coordinate, horizontal);
+
+ for(Triple<Resource,Resource,Resource> endpoint : endpoints) {
+ Resource connector = newConnector(connection, endpoint.third);
+ graph.claim(endpoint.first, endpoint.second, connector);
+ graph.claim(routeLine, DIA.AreConnected, DIA.AreConnected, connector);
+ }
+
+ return connection;
+
+ }
+
+ public Resource createConnection(WriteGraph graph, Resource connectionType,
+ List<Triple<Resource,Resource,Resource>> terminals,
+ List<Pair<Double, Boolean>> routeLines,
+ List<Pair<Integer,Integer>> connections) throws DatabaseException {
+
+ DiagramResource DIA = br.DIA;
+
+ Resource connection = newInstance(g, connectionType);
+
+ Resource[] parts = new Resource[terminals.size() + routeLines.size()];
+
+ int index = 0;
+
+ for(Triple<Resource,Resource,Resource> terminal : terminals) {
+ Resource connector = newConnector(connection, terminal.third);
+ graph.claim(terminal.first, terminal.second, connector);
+ parts[index++] = connector;
+ }
+
+ for(Pair<Double, Boolean> routeLine : routeLines) {
+ Resource r = ConnectionUtil.createRouteline(graph, connection, routeLine.first, routeLine.second);
+ parts[index++] = r;
+ }
+
+// System.err.println("Connect " + parts.length + " parts.");
+
+ for(Pair<Integer,Integer> conn : connections) {
+// System.err.println("-" + conn.first + " " + conn.second);
+ Resource part1 = parts[conn.first];
+ Resource part2 = parts[conn.second];
+ graph.claim(part1, DIA.AreConnected, DIA.AreConnected, part2);
+ }
+
+ return connection;
+
+ }
+
+ /**
+ * @param graph
+ * @param connection
+ * @param pos
+ * @param isHorizontal
+ * @return new route line that is attached to the specified diagram connection
+ * @throws DatabaseException
+ */
+ public static Resource createRouteline(WriteGraph graph, Resource connection, double pos, boolean isHorizontal) throws DatabaseException {
+ Layer0 L0 = Layer0.getInstance(graph);
+ DiagramResource DIA = DiagramResource.getInstance(graph);
+ Resource routeLine = graph.newResource();
+ graph.claim(routeLine, L0.InstanceOf, null, DIA.RouteLine);
+ graph.addLiteral(routeLine, DIA.HasPosition, DIA.HasPosition_Inverse, L0.Double, pos, Bindings.DOUBLE);
+ graph.addLiteral(routeLine, DIA.IsHorizontal, DIA.IsHorizontal_Inverse, L0.Boolean, isHorizontal, Bindings.BOOLEAN);
+ graph.claim(connection, DIA.HasInteriorRouteNode, DIA.HasInteriorRouteNode_Inverse, routeLine);
+ return routeLine;
+ }
+
+ /**
+ * @param graph database write-only access
+ * @param type type of the created resource
+ * @return new instance of type
+ * @throws DatabaseException
+ */
+ private Resource newInstance(WriteOnlyGraph graph, Resource type) throws DatabaseException {
+ Resource connection = graph.newResource();
+ g.claim(connection, br.L0.InstanceOf, null, type);
+ return connection;
+ }
+
+}