X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.diagram%2Fsrc%2Forg%2Fsimantics%2Fdiagram%2Fcontent%2FConnectionUtil.java;fp=bundles%2Forg.simantics.diagram%2Fsrc%2Forg%2Fsimantics%2Fdiagram%2Fcontent%2FConnectionUtil.java;h=3bcb26de77ce2d492c3a18958ea8f64e73c152f3;hp=be9a5947c5a7eeb57a50459a475e1d92cbebb1fc;hb=0ae2b770234dfc3cbb18bd38f324125cf0faca07;hpb=24e2b34260f219f0d1644ca7a138894980e25b14 diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/content/ConnectionUtil.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/content/ConnectionUtil.java index be9a5947c..3bcb26de7 100644 --- a/bundles/org.simantics.diagram/src/org/simantics/diagram/content/ConnectionUtil.java +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/content/ConnectionUtil.java @@ -1,1103 +1,1103 @@ -/******************************************************************************* - * 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 connectedConnectors = g.getObjects(connectorOrBranchPoint, dr.AreConnected); -// if(connectedConnectors.size() == 2) { -// Iterator 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 connectors = new ArrayList(); - 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: - *
    - *
  • Minimally this only disconnects the connection segment ends from each - * other and nothing more.
  • - *
  • 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.
  • - *
  • Finally, if the connection entity is empty (has no connectors/branch - * points), it is also destroyed and removed from the diagram.
  • - * - * @param segment the connection segment to remove - */ - public void remove(EdgeResource segment) throws DatabaseException { - remove(segment, false); - } - - /** - * Removes the specified connection segment. Steps taken: - *
      - *
    • Minimally this only disconnects the connection segment ends from each - * other and nothing more.
    • - *
    • 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.
    • - *
    • Finally, if the connection entity is empty (has no connectors/branch - * points), it is also destroyed and removed from the diagram.
    • - * - * @param segment the connection segment to remove - * @param unchecked false to check that both ends of the - * segment are part of the same connection before removing, - * true to just remove the segment without checking - * this. Using true 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 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 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 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 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 result) throws DatabaseException { - - ArrayList edges = new ArrayList(); - Set visited = new HashSet(); - Stack todo = new Stack(); - - // Try to select input as root, this ensures correct order for simple paths - Collection 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 getBranchPoints(Resource connection, Collection result) throws DatabaseException { - if (result == null) - result = new ArrayList(); - result.addAll(rg.getObjects(connection, br.DIA.HasBranchPoint)); - return result; - } - - public Collection getConnectors(Resource connection, Collection result) throws DatabaseException { - if (result == null) - result = new ArrayList(); - 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 result) throws DatabaseException { - Set visited = new HashSet(); - 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 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 result) throws DatabaseException { - Set visited = new HashSet(); - 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 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 getConnectedComponents(Resource connection, Collection result) - throws DatabaseException { - if (result == null) - result = new ArrayList(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 getConnectedConnectors(Resource connection, Collection result) - throws DatabaseException { - if (result == null) - result = new ArrayList(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 getTerminalConnectors(Resource node, Collection result) - throws DatabaseException { - if (result == null) - result = new ArrayList(2); - result.addAll( rg.getObjects(node, br.STR.IsConnectedTo) ); - return result; - } - - /** - * @param g - * @param routeNode - * @return null 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 points = new ArrayList(); - 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 bps = ch.getBranchPoints(e, null); - Collection 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 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 null 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 true 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 corners) - throws DatabaseException { - DiagramResource DIA = br.DIA; - - // Verify that both elements are part of the same diagram before connecting. - if (Development.DEVELOPMENT) { - Collection startDiagram = OrderedSetUtils.getOwnerLists(graph, startElement, DIA.Diagram); - Collection 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 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> 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 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> terminals, - List> routeLines, - List> 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 terminal : terminals) { - Resource connector = newConnector(connection, terminal.third); - graph.claim(terminal.first, terminal.second, connector); - parts[index++] = connector; - } - - for(Pair routeLine : routeLines) { - Resource r = ConnectionUtil.createRouteline(graph, connection, routeLine.first, routeLine.second); - parts[index++] = r; - } - -// System.err.println("Connect " + parts.length + " parts."); - - for(Pair 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; - } - -} +/******************************************************************************* + * 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 connectedConnectors = g.getObjects(connectorOrBranchPoint, dr.AreConnected); +// if(connectedConnectors.size() == 2) { +// Iterator 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 connectors = new ArrayList(); + 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: + *
        + *
      • Minimally this only disconnects the connection segment ends from each + * other and nothing more.
      • + *
      • 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.
      • + *
      • Finally, if the connection entity is empty (has no connectors/branch + * points), it is also destroyed and removed from the diagram.
      • + * + * @param segment the connection segment to remove + */ + public void remove(EdgeResource segment) throws DatabaseException { + remove(segment, false); + } + + /** + * Removes the specified connection segment. Steps taken: + *
          + *
        • Minimally this only disconnects the connection segment ends from each + * other and nothing more.
        • + *
        • 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.
        • + *
        • Finally, if the connection entity is empty (has no connectors/branch + * points), it is also destroyed and removed from the diagram.
        • + * + * @param segment the connection segment to remove + * @param unchecked false to check that both ends of the + * segment are part of the same connection before removing, + * true to just remove the segment without checking + * this. Using true 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 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 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 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 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 result) throws DatabaseException { + + ArrayList edges = new ArrayList(); + Set visited = new HashSet(); + Stack todo = new Stack(); + + // Try to select input as root, this ensures correct order for simple paths + Collection 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 getBranchPoints(Resource connection, Collection result) throws DatabaseException { + if (result == null) + result = new ArrayList(); + result.addAll(rg.getObjects(connection, br.DIA.HasBranchPoint)); + return result; + } + + public Collection getConnectors(Resource connection, Collection result) throws DatabaseException { + if (result == null) + result = new ArrayList(); + 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 result) throws DatabaseException { + Set visited = new HashSet(); + 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 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 result) throws DatabaseException { + Set visited = new HashSet(); + 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 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 getConnectedComponents(Resource connection, Collection result) + throws DatabaseException { + if (result == null) + result = new ArrayList(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 getConnectedConnectors(Resource connection, Collection result) + throws DatabaseException { + if (result == null) + result = new ArrayList(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 getTerminalConnectors(Resource node, Collection result) + throws DatabaseException { + if (result == null) + result = new ArrayList(2); + result.addAll( rg.getObjects(node, br.STR.IsConnectedTo) ); + return result; + } + + /** + * @param g + * @param routeNode + * @return null 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 points = new ArrayList(); + 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 bps = ch.getBranchPoints(e, null); + Collection 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 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 null 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 true 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 corners) + throws DatabaseException { + DiagramResource DIA = br.DIA; + + // Verify that both elements are part of the same diagram before connecting. + if (Development.DEVELOPMENT) { + Collection startDiagram = OrderedSetUtils.getOwnerLists(graph, startElement, DIA.Diagram); + Collection 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 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> 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 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> terminals, + List> routeLines, + List> 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 terminal : terminals) { + Resource connector = newConnector(connection, terminal.third); + graph.claim(terminal.first, terminal.second, connector); + parts[index++] = connector; + } + + for(Pair routeLine : routeLines) { + Resource r = ConnectionUtil.createRouteline(graph, connection, routeLine.first, routeLine.second); + parts[index++] = r; + } + +// System.err.println("Connect " + parts.length + " parts."); + + for(Pair 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; + } + +}