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%2Fparticipant%2FConnectionBuilder.java;h=72cdb17524fa3def3ae8acc771836153590ffcfe;hp=b36961f87a589595d939d3045c0482bd3e71939c;hb=refs%2Fchanges%2F38%2F238%2F2;hpb=24e2b34260f219f0d1644ca7a138894980e25b14 diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/ConnectionBuilder.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/ConnectionBuilder.java index b36961f87..72cdb1752 100644 --- a/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/ConnectionBuilder.java +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/ConnectionBuilder.java @@ -1,945 +1,945 @@ -/******************************************************************************* - * Copyright (c) 2007, 2016 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 - * Semantum Oy - Fixed bug #6364 - *******************************************************************************/ -package org.simantics.diagram.participant; - -import java.awt.geom.AffineTransform; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Deque; -import java.util.Iterator; -import java.util.List; - -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.swt.SWT; -import org.eclipse.ui.IWorkbenchWindow; -import org.eclipse.ui.PlatformUI; -import org.simantics.databoard.Bindings; -import org.simantics.db.ReadGraph; -import org.simantics.db.Resource; -import org.simantics.db.Session; -import org.simantics.db.Statement; -import org.simantics.db.VirtualGraph; -import org.simantics.db.WriteGraph; -import org.simantics.db.common.CommentMetadata; -import org.simantics.db.common.request.WriteRequest; -import org.simantics.db.common.utils.OrderedSetUtils; -import org.simantics.db.exception.DatabaseException; -import org.simantics.diagram.content.ConnectionUtil; -import org.simantics.diagram.content.ResourceTerminal; -import org.simantics.diagram.flag.DiagramFlagPreferences; -import org.simantics.diagram.flag.FlagLabelingScheme; -import org.simantics.diagram.flag.FlagUtil; -import org.simantics.diagram.flag.IOTableUtil; -import org.simantics.diagram.flag.IOTablesInfo; -import org.simantics.diagram.flag.Joiner; -import org.simantics.diagram.stubs.DiagramResource; -import org.simantics.diagram.stubs.G2DResource; -import org.simantics.diagram.synchronization.ISynchronizationContext; -import org.simantics.diagram.synchronization.SynchronizationHints; -import org.simantics.diagram.synchronization.graph.AddElement; -import org.simantics.diagram.synchronization.graph.DiagramGraphUtil; -import org.simantics.diagram.synchronization.graph.GraphSynchronizationHints; -import org.simantics.diagram.synchronization.graph.RemoveElement; -import org.simantics.diagram.synchronization.graph.layer.GraphLayerManager; -import org.simantics.diagram.ui.DiagramModelHints; -import org.simantics.g2d.connection.handler.ConnectionHandler; -import org.simantics.g2d.diagram.DiagramHints; -import org.simantics.g2d.diagram.IDiagram; -import org.simantics.g2d.diagram.handler.Topology.Terminal; -import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil.TerminalInfo; -import org.simantics.g2d.element.ElementClass; -import org.simantics.g2d.element.ElementClasses; -import org.simantics.g2d.element.ElementHints; -import org.simantics.g2d.element.ElementUtils; -import org.simantics.g2d.element.IElement; -import org.simantics.g2d.element.IElementClassProvider; -import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd; -import org.simantics.g2d.element.impl.Element; -import org.simantics.g2d.elementclass.BranchPoint; -import org.simantics.g2d.elementclass.FlagClass; -import org.simantics.g2d.elementclass.FlagClass.Type; -import org.simantics.layer0.Layer0; -import org.simantics.modeling.ModelingResources; -import org.simantics.scl.runtime.tuple.Tuple2; -import org.simantics.structural.stubs.StructuralResource2; -import org.simantics.structural2.modelingRules.CPTerminal; -import org.simantics.structural2.modelingRules.ConnectionJudgement; -import org.simantics.structural2.modelingRules.IConnectionPoint; -import org.simantics.structural2.modelingRules.IModelingRules; -import org.simantics.utils.datastructures.Callback; -import org.simantics.utils.datastructures.Pair; -import org.simantics.utils.ui.ErrorLogger; - -/** - * @author Tuukka Lehtonen - */ -public class ConnectionBuilder { - - protected static class Connector extends Tuple2 { - public Connector(Resource attachmentRelation, Resource connector) { - super(attachmentRelation, connector); - } - public Resource getAttachment() { - return (Resource) c0; - } - public Resource getConnector() { - return (Resource) c1; - } - } - - protected final IDiagram diagram; - protected final Resource diagramResource; - protected final boolean createFlags; - - protected final ISynchronizationContext ctx; - protected final IElementClassProvider elementClassProvider; - protected final GraphLayerManager layerManager; - - protected ConnectionUtil cu; - - protected Layer0 L0; - protected DiagramResource DIA; - protected StructuralResource2 STR; - protected ModelingResources MOD; - - public ConnectionBuilder(IDiagram diagram) { - this.diagram = diagram; - this.diagramResource = diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE); - this.createFlags = Boolean.TRUE.equals(diagram.getHint(DiagramHints.KEY_USE_CONNECTION_FLAGS)); - - ctx = diagram.getHint(SynchronizationHints.CONTEXT); - if (ctx != null) { - this.elementClassProvider = ctx.get(SynchronizationHints.ELEMENT_CLASS_PROVIDER); - this.layerManager = ctx.get(GraphSynchronizationHints.GRAPH_LAYER_MANAGER); - } else { - this.elementClassProvider = null; - this.layerManager = null; - } - } - - protected void initializeResources(ReadGraph graph) { - if (this.L0 == null) { - this.L0 = Layer0.getInstance(graph); - this.DIA = DiagramResource.getInstance(graph); - this.STR = StructuralResource2.getInstance(graph); - this.MOD = ModelingResources.getInstance(graph); - } - } - - /** - * @param graph - * @param judgment - * @param controlPoints - * @param startTerminal - * @param endTerminal - * @throws DatabaseException - */ - public void create(WriteGraph graph, final ConnectionJudgement judgment, Deque controlPoints, - TerminalInfo startTerminal, TerminalInfo endTerminal) throws DatabaseException { - this.cu = new ConnectionUtil(graph); - initializeResources(graph); - - final IModelingRules modelingRules = diagram.getHint(DiagramModelHints.KEY_MODELING_RULES); - - final Resource startDisconnectedFlag = getDisconnectedFlag(graph, startTerminal); - final Resource endDisconnectedFlag = getDisconnectedFlag(graph, endTerminal); - if (startDisconnectedFlag != null || endDisconnectedFlag != null) { - if (startDisconnectedFlag != null && endDisconnectedFlag != null) { - - // Ask the user which operation to perform: - // a) connect the disconnected flags together with a connection join - // b) join the flags into a single connection - - final VirtualGraph graphProvider = graph.getProvider(); - final Session session = graph.getSession(); - - PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { - @Override - public void run() { - IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); - if (window == null) - return; - MessageDialog dialog = new MessageDialog(window.getShell(), "Connect or Join Flags?", null, - "Connect flags together or join them visually into a connection?", - MessageDialog.QUESTION_WITH_CANCEL, new String[] { "Connect Flags", "Join Flags", - "Cancel" }, 0) { - { - setShellStyle(getShellStyle() | SWT.SHEET); - } - }; - final int choice = dialog.open(); - - if (choice != 2 && choice != SWT.DEFAULT) { - session.asyncRequest(new WriteRequest(graphProvider) { - @Override - public void perform(WriteGraph graph) throws DatabaseException { - graph.markUndoPoint(); - switch (choice) { - case 0: { - Resource join = FlagUtil.join(graph, startDisconnectedFlag, endDisconnectedFlag); - FlagLabelingScheme scheme = DiagramFlagPreferences.getActiveFlagLabelingScheme(graph); - String commonLabel = scheme.generateLabel(graph, diagramResource); - graph.claimLiteral(startDisconnectedFlag, L0.HasLabel, DIA.FlagLabel, commonLabel); - graph.claimLiteral(endDisconnectedFlag, L0.HasLabel, DIA.FlagLabel, commonLabel); - - // Set connection type according to modeling rules - setJoinedConnectionTypes(graph, modelingRules, judgment, join); - - // Add comment to change set. - CommentMetadata cm = graph.getMetadata(CommentMetadata.class); - graph.addMetadata(cm.add("Connected flags")); - - return; - } - case 1: { - // First connect the flags together - Resource join = FlagUtil.join(graph, startDisconnectedFlag, endDisconnectedFlag); - - // Set connection type according to modeling rules - setJoinedConnectionTypes(graph, modelingRules, judgment, join); - - // Join the flags into a direct connection - new Joiner(graph).joinLocal(graph, Arrays.asList(startDisconnectedFlag, endDisconnectedFlag)); - - // Add comment to change set. - CommentMetadata cm = graph.getMetadata(CommentMetadata.class); - graph.addMetadata(cm.add("Joined flags")); - - return; - } - } - } - }, new Callback() { - @Override - public void run(DatabaseException e) { - if (e != null) - ErrorLogger.defaultLogError(e); - } - }); - } - } - }); - - return; - } - - TerminalInfo normalTerminal = null; - Resource flagToRemove = null; - Resource connection = null; - if (startDisconnectedFlag != null) { - flagToRemove = startDisconnectedFlag; - normalTerminal = endTerminal; - connection = attachedToExistingConnection(graph, startTerminal); - } - if (endDisconnectedFlag != null) { - flagToRemove = endDisconnectedFlag; - normalTerminal = startTerminal; - connection = attachedToExistingConnection(graph, endTerminal); - } - if (connection != null) { - // OK, continuing a connection from an existing disconnected flag. - - // STEPS TO PERFORM: - // 1. remove flag - // 2. connect normal terminal directly to the existing connection - Statement stm = graph.getSingleStatement(flagToRemove, STR.IsConnectedTo); - Collection areConnecteds = graph.getObjects(stm.getObject(), DIA.AreConnected); - - // Remove statement to connection connector before removing flag - // to prevent removal of connector and the connection. - graph.deny(stm); - new RemoveElement((Resource)diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE), flagToRemove).perform(graph); - - // Disconnect the connector from the connection and create a - // new connector for the element terminal. - cu.removeConnectionPart(stm.getObject()); - Connector newConnector = createConnectorForNode(graph, connection, - (Resource) ElementUtils.getObject(normalTerminal.e), normalTerminal.t, - startDisconnectedFlag != null ? EdgeEnd.End : EdgeEnd.Begin, judgment); - - for (Resource areConnected : areConnecteds) - graph.claim(newConnector.getConnector(), DIA.AreConnected, areConnected); - - if (modelingRules != null && judgment.connectionType != null) - modelingRules.setConnectionType(graph, connection, judgment.connectionType); - - // Add comment to change set. - CommentMetadata cm = graph.getMetadata(CommentMetadata.class); - graph.addMetadata(cm.add("Joined flags")); - graph.markUndoPoint(); - this.cu = null; - return; - } - } - - // 1. Get diagram connection to construct. - Resource connection = getOrCreateConnection(graph, startTerminal, endTerminal); - - // 1.1 Give running name to connection and increment the counter attached to the diagram. - AddElement.claimFreshElementName(graph, diagramResource, connection); - - // 2. Add branch points - // 3. Create edges between branch points. - List> bps = Collections.emptyList(); - Resource firstBranchPoint = null; - Resource lastBranchPoint = null; - if (!isRouteGraphConnection(graph, connection)) { - bps = createBranchPoints(graph, connection, controlPoints); - if (!bps.isEmpty()) { - Iterator> it = bps.iterator(); - Pair prev = it.next(); - firstBranchPoint = prev.second; - while (it.hasNext()) { - Pair next = it.next(); - cu.connect(prev.second, next.second); - prev = next; - } - lastBranchPoint = prev.second; - } - } - - // 4. Connect start/end terminals if those exist. - // If first/lastBranchPoint != null, connect to those. - // Otherwise connect the start/end terminals together. - Connector startConnector = null; - Connector endConnector = null; - IElement startFlag = null; - IElement endFlag = null; - - //FlagLabelingScheme scheme = DiagramFlagPreferences.getActiveFlagLabelingScheme(graph); - - if (startTerminal != null && endTerminal != null) { - Resource startAttachment = chooseAttachmentRelationForNode(graph, connection, startTerminal, judgment); - Resource endAttachment = chooseAttachmentRelationForNode(graph, connection, endTerminal, judgment); - Pair attachments = resolveEndAttachments(graph, startAttachment, endAttachment); - startConnector = createConnectorForNodeWithAttachment(graph, connection, startTerminal, attachments.first); - endConnector = createConnectorForNodeWithAttachment(graph, connection, endTerminal, attachments.second); - } else if (startTerminal != null) { - startConnector = createConnectorForNode(graph, connection, startTerminal, EdgeEnd.Begin, judgment); - if (createFlags) { - EdgeEnd flagEnd = cu.toEdgeEnd( cu.getAttachmentRelationForConnector(startConnector.getConnector()), EdgeEnd.End ).other(); - endFlag = createFlag(graph, connection, flagEnd, controlPoints.getLast(), FlagClass.Type.Out, - //scheme.generateLabel(graph, diagramResource)); - null); - endConnector = createConnectorForNode(graph, connection, (Resource) ElementUtils.getObject(endFlag), - ElementUtils.getSingleTerminal(endFlag), flagEnd, judgment); - } - } else if (endTerminal != null) { - endConnector = createConnectorForNode(graph, connection, endTerminal, EdgeEnd.End, judgment); - if (createFlags) { - EdgeEnd flagEnd = cu.toEdgeEnd( cu.getAttachmentRelationForConnector(endConnector.getConnector()), EdgeEnd.Begin ).other(); - startFlag = createFlag(graph, connection, flagEnd, controlPoints.getFirst(), FlagClass.Type.In, - //scheme.generateLabel(graph, diagramResource)); - null); - startConnector = createConnectorForNode(graph, connection, (Resource) ElementUtils.getObject(startFlag), - ElementUtils.getSingleTerminal(startFlag), flagEnd, judgment); - } - } else if (createFlags) { - startFlag = createFlag(graph, connection, EdgeEnd.Begin, controlPoints.getFirst(), FlagClass.Type.In, - //scheme.generateLabel(graph, diagramResource)); - null); - startConnector = createConnectorForNode(graph, connection, (Resource) ElementUtils.getObject(startFlag), - ElementUtils.getSingleTerminal(startFlag), EdgeEnd.Begin, judgment); - - endFlag = createFlag(graph, connection, EdgeEnd.End, controlPoints.getLast(), FlagClass.Type.Out, - //scheme.generateLabel(graph, diagramResource)); - null); - endConnector = createConnectorForNode(graph, connection, (Resource) ElementUtils.getObject(endFlag), - ElementUtils.getSingleTerminal(endFlag), EdgeEnd.End, judgment); - } - - if (firstBranchPoint == null || lastBranchPoint == null) { - cu.connect(startConnector.getConnector(), endConnector.getConnector()); - } else { - cu.connect(startConnector.getConnector(), firstBranchPoint); - cu.connect(lastBranchPoint, endConnector.getConnector()); - } - - // 5. Finally, set connection type according to modeling rules - if (judgment.connectionType != null && modelingRules != null) - modelingRules.setConnectionType(graph, connection, judgment.connectionType); - - // 5.1 Verify created flag types - if (startFlag != null) - verifyFlagType(graph, modelingRules, startFlag); - if (endFlag != null) - verifyFlagType(graph, modelingRules, endFlag); - - // 5.2 Write ConnectionMappingSpecification to connector if necessary - writeConnectionMappingSpecification(graph, startTerminal, startConnector, judgment.connectionType); - writeConnectionMappingSpecification(graph, endTerminal, endConnector, judgment.connectionType); - - // 6. Add comment to change set. - CommentMetadata cm = graph.getMetadata(CommentMetadata.class); - graph.addMetadata(cm.add("Added connection " + connection)); - graph.markUndoPoint(); - this.cu = null; - } - - private boolean writeConnectionMappingSpecification(WriteGraph graph, TerminalInfo terminal, Connector connector, Resource connectionType) - throws DatabaseException { - Resource diagramConnRel = getConnectionRelation(graph, terminal); - if (diagramConnRel == null) - return false; - Resource connRel = graph.getPossibleObject(diagramConnRel, MOD.DiagramConnectionRelationToConnectionRelation); - if (connRel == null || !graph.hasStatement(connRel, MOD.NeedsConnectionMappingSpecification)) - return false; - Resource mappingSpecification = graph.getPossibleObject(connectionType, MOD.ConnectionTypeToConnectionMappingSpecification); - if (mappingSpecification == null) - return false; - graph.claim(connector.getConnector(), MOD.HasConnectionMappingSpecification, null, mappingSpecification); - return true; - } - - private static Resource getConnectionRelation(ReadGraph graph, TerminalInfo ti) throws DatabaseException { - if (ti != null && ti.t instanceof ResourceTerminal) { - Resource t = ((ResourceTerminal) ti.t).getResource(); - Resource bindingRelation = DiagramGraphUtil.getConnectionPointOfTerminal(graph, t); - return bindingRelation; - } - return null; - } - - /** - * @param graph - * @param startAttachment - * @param endAttachment - * @return - * @throws DatabaseException - */ - protected Pair resolveEndAttachments(WriteGraph graph, - Resource startAttachment, Resource endAttachment) throws DatabaseException { - if (startAttachment != null && endAttachment != null) - return Pair.make(startAttachment, endAttachment); - - if (startAttachment != null && endAttachment == null) - return Pair.make(startAttachment, getInverseAttachment(graph, startAttachment, DIA.HasArrowConnector)); - if (startAttachment == null && endAttachment != null) - return Pair.make(getInverseAttachment(graph, endAttachment, DIA.HasPlainConnector), endAttachment); - - return Pair.make(DIA.HasPlainConnector, DIA.HasArrowConnector); - } - - /** - * @param graph - * @param attachment - * @return - * @throws DatabaseException - */ - protected Resource getInverseAttachment(ReadGraph graph, Resource attachment, Resource defaultValue) throws DatabaseException { - Resource inverse = attachment != null ? graph.getPossibleObject(attachment, DIA.HasInverseAttachment) : defaultValue; - return inverse != null ? inverse : defaultValue; - } - - /** - * @param graph - * @param modelingRules - * @param flagElement - * @throws DatabaseException - */ - protected void verifyFlagType(WriteGraph graph, IModelingRules modelingRules, IElement flagElement) throws DatabaseException { - if (modelingRules != null) { - Resource flag = flagElement.getHint(ElementHints.KEY_OBJECT); - FlagClass.Type flagType = flagElement.getHint(FlagClass.KEY_FLAG_TYPE); - FlagUtil.verifyFlagType(graph, modelingRules, flag, flagType); - } - } - - /** - * @param graph - * @param judgment - * @param connection - * @param attachToLine - * @param controlPoints - * @param endTerminal - * @return the DIA.Connector instance created for attaching the connection - * to the specified end terminal - * @throws DatabaseException - */ - public Pair attachToRouteGraph( - WriteGraph graph, - ConnectionJudgement judgment, - Resource attachToConnection, - Resource attachToLine, - Deque controlPoints, - TerminalInfo endTerminal, - FlagClass.Type flagType) - throws DatabaseException - { - initializeResources(graph); - this.cu = new ConnectionUtil(graph); - try { - Resource endElement = endTerminal != null ? ElementUtils.getObject(endTerminal.e) : null; - if (endElement != null - && graph.isInstanceOf(endElement, DIA.Flag) - && FlagUtil.isDisconnected(graph, endElement)) - { - // Connection ends in an existing but disconnected flag that - // should be all right to connect to because the connection - // judgment implies it makes a valid connection. - // Check that we are attaching the connection to an existing - // disconnected flag that is however attached to a connection. - Resource endTerminalConnection = ConnectionBuilder.attachedToExistingConnection(graph, endTerminal); - if (endTerminalConnection != null) { - attachConnectionToFlag(graph, judgment, attachToConnection, attachToLine, controlPoints, endTerminal); - return null; - } - } - - Connector endConnector = null; - if (endTerminal != null) { - endConnector = createConnectorForNode(graph, attachToConnection, endTerminal, EdgeEnd.End, judgment); - } else if (createFlags) { - EdgeEnd end = flagType == FlagClass.Type.In ? EdgeEnd.Begin : EdgeEnd.End; - IElement endFlag = createFlag(graph, attachToConnection, end, controlPoints.getLast(), flagType, null); - endConnector = createConnectorForNode(graph, attachToConnection, (Resource) ElementUtils.getObject(endFlag), - ElementUtils.getSingleTerminal(endFlag), end, judgment); - } - - cu.connect(attachToLine, endConnector.getConnector()); - - IModelingRules modelingRules = diagram.getHint(DiagramModelHints.KEY_MODELING_RULES); - if (judgment.connectionType != null && modelingRules != null) { - modelingRules.setConnectionType(graph, attachToConnection, judgment.connectionType); - } - - writeConnectionMappingSpecification(graph, endTerminal, endConnector, judgment.connectionType); - - CommentMetadata cm = graph.getMetadata(CommentMetadata.class); - graph.addMetadata(cm.add("Branched connection " + attachToConnection)); - - return Pair.make(endConnector.getAttachment(), endConnector.getConnector()); - } finally { - this.cu = null; - } - } - - protected void attachConnectionToFlag( - WriteGraph graph, - ConnectionJudgement judgment, - Resource attachToConnection, - Resource attachToLine, - Deque controlPoints, - TerminalInfo toFlag) - throws DatabaseException - { - // Attaching attachedConnection to an existing disconnected flag that is - // however attached to a connection. - // STEPS: - // 1. remove flag and its connector - // 2. attach the two connections together by moving the route nodes - // of the removed flag-side connection under the remaining connection - // and ensuring that the route node chain will be valid after the - // switch. In a chain route lines, each line must have an opposite - // direction compared to the lines connected to it. - Resource flagToRemove = ElementUtils.getObject(toFlag.e); - Statement flagToConnector = graph.getSingleStatement(flagToRemove, STR.IsConnectedTo); - Resource flagConnector = flagToConnector.getObject(); - Resource flagConnection = ConnectionUtil.getConnection(graph, flagConnector); - Collection flagRouteNodes = graph.getObjects(flagConnector, DIA.AreConnected); - - Resource connectionToKeep = attachToConnection; - Resource connectionToRemove = flagConnection; - if (!connectionToKeep.equals(connectionToRemove)) { - Resource hasElementToComponent1 = graph.getPossibleObject(attachToConnection, MOD.ElementToComponent); - Resource hasElementToComponent2 = graph.getPossibleObject(flagConnection, MOD.ElementToComponent); - Type flagType = FlagUtil.getFlagType(graph, flagToRemove); - if (hasElementToComponent1 != null && hasElementToComponent2 != null) - throw new UnsupportedOperationException( - "Both attached connection " + attachToConnection + " and flag connection " + flagConnection - + " have mapped components, can't decide which connection to remove in join operation"); - if (hasElementToComponent2 != null || flagType == Type.Out) { - connectionToKeep = flagConnection; - connectionToRemove = attachToConnection; - } - } - - // Remove flag and its connector. - graph.deny(flagToConnector); - new RemoveElement((Resource)diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE), flagToRemove).perform(graph); - cu.removeConnectionPart(flagConnector); - - // Attached routeline must have opposite direction than the line - // attached to in order for the connection to be valid. - Boolean attachingToHorizontalLine = graph.getPossibleRelatedValue(attachToLine, DIA.IsHorizontal, Bindings.BOOLEAN); - if (attachingToHorizontalLine != null) { - for (Resource routeNode : flagRouteNodes) { - Collection routeNodesToAttachTo = removeUntilOrientedRouteline(graph, !attachingToHorizontalLine, routeNode); - for (Resource rn : routeNodesToAttachTo) - cu.connect(attachToLine, rn); - } - } - - moveStatements(graph, connectionToRemove, connectionToKeep, DIA.HasInteriorRouteNode); - moveStatements(graph, connectionToRemove, connectionToKeep, DIA.HasConnector); - - // Remove obsolete connection - if (!connectionToKeep.equals(connectionToRemove)) - cu.removeConnection(connectionToRemove); - - CommentMetadata cm = graph.getMetadata(CommentMetadata.class); - graph.addMetadata(cm.add("Joined connection to disconnected flag")); - } - - private void moveStatements(WriteGraph graph, Resource source, Resource target, Resource movedRelation) throws DatabaseException { - if (!source.equals(target)) { - for (Statement s : graph.getStatements(source, movedRelation)) { - graph.deny(s); - graph.claim(target, s.getPredicate(), s.getObject()); - } - } - } - - private Collection removeUntilOrientedRouteline(WriteGraph graph, boolean expectedOrientation, Resource routeNode) throws DatabaseException { - List result = new ArrayList<>(2); - Deque work = new ArrayDeque<>(2); - work.addLast(routeNode); - while (!work.isEmpty()) { - Resource rn = work.removeFirst(); - if (graph.isInstanceOf(rn, DIA.RouteLine)) { - Boolean isHorizontal = graph.getPossibleRelatedValue(rn, DIA.IsHorizontal, Bindings.BOOLEAN); - if (isHorizontal != null && expectedOrientation != isHorizontal) { - for (Resource rnn : graph.getObjects(rn, DIA.AreConnected)) - work.addLast(rnn); - cu.removeConnectionPart(rn); - continue; - } - } - result.add(rn); - } - return result; - } - - protected boolean isRouteGraphConnection(ReadGraph graph, Resource connection) throws DatabaseException { - initializeResources(graph); - return graph.isInstanceOf(connection, DIA.RouteGraphConnection); - } - - /** - * @param graph - * @param ti - * @return - * @throws DatabaseException - */ - public static Resource attachedToExistingConnection(ReadGraph graph, TerminalInfo ti) throws DatabaseException { - Object obj = ElementUtils.getObject(ti.e); - Resource cp = DiagramGraphUtil.getConnectionPointOfTerminal(graph, ti.t); - if (obj instanceof Resource && cp != null) { - Resource e = (Resource) obj; - for (Resource connector : graph.getObjects(e, cp)) { - Resource connection = ConnectionUtil.tryGetConnection(graph, connector); - if (connection != null) - return connection; - } - } - return null; - } - - /** - * @param graph - * @param tis - * @return - * @throws DatabaseException - */ - public Resource getOrCreateConnection(ReadGraph graph, TerminalInfo... tis) throws DatabaseException { - // Resolve if adding to existing connection. - Resource connection = null; - for (TerminalInfo ti : tis) { - connection = getExistingConnection(graph, ti); - if (connection != null) - break; - } - - if (connection == null) { - // No existing connection, create new. - ElementClass connectionClass = elementClassProvider.get(ElementClasses.CONNECTION); - Resource connectionClassResource = ElementUtils.checkedAdapt(connectionClass, Resource.class); - connection = cu.newConnection(diagramResource, connectionClassResource); - } - - return connection; - } - - /** - * @param graph - * @param connection - * @param controlPoints - * @return - * @throws DatabaseException - */ - public List> createBranchPoints(WriteGraph graph, Resource connection, - Collection controlPoints) throws DatabaseException { - List> bps = new ArrayList>(controlPoints.size()); - for(ControlPoint cp : controlPoints) { - if (cp.isAttachedToTerminal()) - // Terminal attachments do not need branch points. - continue; - - Resource bp = cu.newBranchPoint(connection, - AffineTransform.getTranslateInstance(cp.getPosition().getX(), cp.getPosition().getY()), - cp.getDirection()); - bps.add(Pair.make(cp, bp)); - } - return bps; - } - - /** - * @param graph - * @param connection - * @param ti - * @param end - * @param judgment - * @return - * @throws DatabaseException - */ - protected Resource chooseAttachmentRelationForNode(ReadGraph graph, - Resource connection, TerminalInfo ti, ConnectionJudgement judgment) - throws DatabaseException { - Resource node = (Resource) ElementUtils.getObject(ti.e); - return chooseAttachmentRelationForNode(graph, connection, node, ti.t, judgment); - } - - /** - * @param graph - * @param connection - * @param element - * @param terminal - * @param end - * @param judgment - * @return the calculated attachment relation or null if the - * result is ambiguous - * @throws DatabaseException - */ - protected Resource chooseAttachmentRelationForNode(ReadGraph graph, - Resource connection, Resource element, Terminal terminal, - ConnectionJudgement judgment) throws DatabaseException { - IConnectionPoint cp = ConnectionUtil.toConnectionPoint(graph, element, terminal); - CPTerminal cpt = (cp instanceof CPTerminal) ? (CPTerminal) cp : null; - Resource attachment = judgment.attachmentRelations.get(graph, cpt); - return attachment; - } - - /** - * @param graph - * @param connection - * @param ti - * @param connectTo resource to connect the new connector to if not - * null - * @param judgment - * @return . The - * attachment relation is null if it was chosen based - * on EdgeEnd instead of being defined - * @throws DatabaseException - */ - protected Connector createConnectorForNode(WriteGraph graph, Resource connection, TerminalInfo ti, EdgeEnd end, - ConnectionJudgement judgment) throws DatabaseException { - Resource node = (Resource) ElementUtils.getObject(ti.e); - return createConnectorForNode(graph, connection, node, ti.t, end, judgment); - } - - /** - * @param graph - * @param connection - * @param element - * @param terminal - * @param end - * @param connectTo - * @param judgment - * @return . The - * attachment relation is null if it was chosen based - * on EdgeEnd instead of being defined - * @throws DatabaseException - */ - protected Connector createConnectorForNode(WriteGraph graph, Resource connection, Resource element, Terminal terminal, - EdgeEnd end, ConnectionJudgement judgment) throws DatabaseException { - IConnectionPoint cp = ConnectionUtil.toConnectionPoint(graph, element, terminal); - CPTerminal cpt = (cp instanceof CPTerminal) ? (CPTerminal) cp : null; - Resource attachment = judgment.attachmentRelations.get(graph, cpt); - if (attachment == null) - attachment = cu.toHasConnectorRelation(end); - Resource connector = cu.getOrCreateConnector(connection, element, terminal, end, attachment); - return new Connector(attachment, connector); - } - - /** - * @param graph - * @param connection - * @param ti - * @param attachment - * @return - * @throws DatabaseException - */ - protected Connector createConnectorForNodeWithAttachment(WriteGraph graph, - Resource connection, TerminalInfo ti, Resource attachment) - throws DatabaseException { - Resource node = (Resource) ElementUtils.getObject(ti.e); - return createConnectorForNodeWithAttachment(graph, connection, node, ti.t, attachment); - } - - /** - * @param graph - * @param connection - * @param element - * @param terminal - * @param attachment - * @return - * @throws DatabaseException - */ - protected Connector createConnectorForNodeWithAttachment(WriteGraph graph, - Resource connection, Resource element, Terminal terminal, - Resource attachment) throws DatabaseException { - Resource connector = cu.getOrCreateConnector(connection, element, terminal, null, attachment); - return new Connector(attachment, connector); - } - - /** - * @param graph - * @param connection - * @param end - * @param cp - * @param type - * @param label null to leave flag without label - * @return an element describing the new created flag resource - * @throws DatabaseException - */ - public IElement createFlag(WriteGraph graph, Resource connection, EdgeEnd end, ControlPoint cp, - FlagClass.Type type, String label) throws DatabaseException { - ElementClass flagClass = elementClassProvider.get(ElementClasses.FLAG); - IElement flagElement = Element.spawnNew(flagClass); - Resource flagClassResource = ElementUtils.checkedAdapt(flagClass, Resource.class); - - Layer0 L0 = Layer0.getInstance(graph); - G2DResource G2D = G2DResource.getInstance(graph); - DiagramResource DIA = DiagramResource.getInstance(graph); - - Resource flag = graph.newResource(); - graph.claim(flag, L0.InstanceOf, null, flagClassResource); - flagElement.setHint(ElementHints.KEY_OBJECT, flag); - - OrderedSetUtils.add(graph, diagramResource, flag); - - AffineTransform at = AffineTransform.getTranslateInstance(cp.getPosition().getX(), cp.getPosition().getY()); - flagElement.setHint(ElementHints.KEY_TRANSFORM, at); - double[] matrix = new double[6]; - at.getMatrix(matrix); - graph.claimLiteral(flag, DIA.HasTransform, G2D.Transform, matrix); - - flagElement.setHint(FlagClass.KEY_FLAG_TYPE, type); - graph.claim(flag, DIA.HasFlagType, null, DiagramGraphUtil.toFlagTypeResource(DIA, type)); - if (label != null) - graph.claimLiteral(flag, L0.HasLabel, DIA.FlagLabel, label, Bindings.STRING); - - // Give running name to flag and increment the counter attached to the diagram. - AddElement.claimFreshElementName(graph, diagramResource, flag); - - // Make the diagram consist of the new element - graph.claim(diagramResource, L0.ConsistsOf, flag); - - // Put the element on all the currently active layers if possible. - if (layerManager != null) { - layerManager.removeFromAllLayers(graph, flag); - layerManager.putElementOnVisibleLayers(diagram, graph, flag); - } - - // Add flag to possible IO table - IOTablesInfo ioTablesInfo = IOTableUtil.getIOTablesInfo(graph, - (Resource)diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE)); - ioTablesInfo.updateBinding(graph, DIA, flag, at.getTranslateX(), at.getTranslateY()); - - return flagElement; - } - - /** - * @param graph - * @param ti - * @return - * @throws DatabaseException - */ - protected static Resource getExistingConnection(ReadGraph graph, TerminalInfo ti) throws DatabaseException { - if (ti != null) { - if (isConnection(ti.e)) { - Object obj = ElementUtils.getObject(ti.e); - if (obj instanceof Resource) { - Resource c = (Resource) obj; - return graph.isInstanceOf(c, DiagramResource.getInstance(graph).Connection) ? c : null; - } - } else if (isBranchPoint(ti.e)) { - Object obj = ElementUtils.getObject(ti.e); - if (obj instanceof Resource) { - return ConnectionUtil.tryGetConnection(graph, (Resource) obj); - } - } - } - return null; - } - - protected static boolean isConnection(IElement e) { - return e.getElementClass().containsClass(ConnectionHandler.class); - } - - /** - * @param e - * @return - */ - protected static boolean isBranchPoint(IElement e) { - return e.getElementClass().containsClass(BranchPoint.class); - } - - /** - * @param graph - * @param terminal - * @return - * @throws DatabaseException - */ - protected static Resource getDisconnectedFlag(ReadGraph graph, TerminalInfo terminal) throws DatabaseException { - if (terminal != null) { - Object obj = ElementUtils.getObject(terminal.e); - if (obj instanceof Resource) { - Resource flag = (Resource) obj; - if (graph.isInstanceOf(flag, DiagramResource.getInstance(graph).Flag) - && FlagUtil.isDisconnected(graph, flag)) - return flag; - } - } - return null; - } - - /** - * @param graph - * @param modelingRules - * @param judgment - * @param join - * @throws DatabaseException - */ - protected static void setJoinedConnectionTypes(WriteGraph graph, IModelingRules modelingRules, - ConnectionJudgement judgment, Resource join) throws DatabaseException { - if (modelingRules != null && judgment != null && judgment.connectionType != null) { - DiagramResource DIA = DiagramResource.getInstance(graph); - StructuralResource2 STR = StructuralResource2.getInstance(graph); - List connections = new ArrayList(2); - for (Resource flag : graph.getObjects(join, DIA.FlagIsJoinedBy)) { - for (Resource connector : graph.getObjects(flag, STR.IsConnectedTo)) { - Resource connection = ConnectionUtil.tryGetConnection(graph, connector); - if (connection != null) - connections.add(connection); - } - } - for (Resource connection : connections) - modelingRules.setConnectionType(graph, connection, judgment.connectionType); - } - } - +/******************************************************************************* + * Copyright (c) 2007, 2016 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 + * Semantum Oy - Fixed bug #6364 + *******************************************************************************/ +package org.simantics.diagram.participant; + +import java.awt.geom.AffineTransform; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Deque; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.swt.SWT; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; +import org.simantics.databoard.Bindings; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.Session; +import org.simantics.db.Statement; +import org.simantics.db.VirtualGraph; +import org.simantics.db.WriteGraph; +import org.simantics.db.common.CommentMetadata; +import org.simantics.db.common.request.WriteRequest; +import org.simantics.db.common.utils.OrderedSetUtils; +import org.simantics.db.exception.DatabaseException; +import org.simantics.diagram.content.ConnectionUtil; +import org.simantics.diagram.content.ResourceTerminal; +import org.simantics.diagram.flag.DiagramFlagPreferences; +import org.simantics.diagram.flag.FlagLabelingScheme; +import org.simantics.diagram.flag.FlagUtil; +import org.simantics.diagram.flag.IOTableUtil; +import org.simantics.diagram.flag.IOTablesInfo; +import org.simantics.diagram.flag.Joiner; +import org.simantics.diagram.stubs.DiagramResource; +import org.simantics.diagram.stubs.G2DResource; +import org.simantics.diagram.synchronization.ISynchronizationContext; +import org.simantics.diagram.synchronization.SynchronizationHints; +import org.simantics.diagram.synchronization.graph.AddElement; +import org.simantics.diagram.synchronization.graph.DiagramGraphUtil; +import org.simantics.diagram.synchronization.graph.GraphSynchronizationHints; +import org.simantics.diagram.synchronization.graph.RemoveElement; +import org.simantics.diagram.synchronization.graph.layer.GraphLayerManager; +import org.simantics.diagram.ui.DiagramModelHints; +import org.simantics.g2d.connection.handler.ConnectionHandler; +import org.simantics.g2d.diagram.DiagramHints; +import org.simantics.g2d.diagram.IDiagram; +import org.simantics.g2d.diagram.handler.Topology.Terminal; +import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil.TerminalInfo; +import org.simantics.g2d.element.ElementClass; +import org.simantics.g2d.element.ElementClasses; +import org.simantics.g2d.element.ElementHints; +import org.simantics.g2d.element.ElementUtils; +import org.simantics.g2d.element.IElement; +import org.simantics.g2d.element.IElementClassProvider; +import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd; +import org.simantics.g2d.element.impl.Element; +import org.simantics.g2d.elementclass.BranchPoint; +import org.simantics.g2d.elementclass.FlagClass; +import org.simantics.g2d.elementclass.FlagClass.Type; +import org.simantics.layer0.Layer0; +import org.simantics.modeling.ModelingResources; +import org.simantics.scl.runtime.tuple.Tuple2; +import org.simantics.structural.stubs.StructuralResource2; +import org.simantics.structural2.modelingRules.CPTerminal; +import org.simantics.structural2.modelingRules.ConnectionJudgement; +import org.simantics.structural2.modelingRules.IConnectionPoint; +import org.simantics.structural2.modelingRules.IModelingRules; +import org.simantics.utils.datastructures.Callback; +import org.simantics.utils.datastructures.Pair; +import org.simantics.utils.ui.ErrorLogger; + +/** + * @author Tuukka Lehtonen + */ +public class ConnectionBuilder { + + protected static class Connector extends Tuple2 { + public Connector(Resource attachmentRelation, Resource connector) { + super(attachmentRelation, connector); + } + public Resource getAttachment() { + return (Resource) c0; + } + public Resource getConnector() { + return (Resource) c1; + } + } + + protected final IDiagram diagram; + protected final Resource diagramResource; + protected final boolean createFlags; + + protected final ISynchronizationContext ctx; + protected final IElementClassProvider elementClassProvider; + protected final GraphLayerManager layerManager; + + protected ConnectionUtil cu; + + protected Layer0 L0; + protected DiagramResource DIA; + protected StructuralResource2 STR; + protected ModelingResources MOD; + + public ConnectionBuilder(IDiagram diagram) { + this.diagram = diagram; + this.diagramResource = diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE); + this.createFlags = Boolean.TRUE.equals(diagram.getHint(DiagramHints.KEY_USE_CONNECTION_FLAGS)); + + ctx = diagram.getHint(SynchronizationHints.CONTEXT); + if (ctx != null) { + this.elementClassProvider = ctx.get(SynchronizationHints.ELEMENT_CLASS_PROVIDER); + this.layerManager = ctx.get(GraphSynchronizationHints.GRAPH_LAYER_MANAGER); + } else { + this.elementClassProvider = null; + this.layerManager = null; + } + } + + protected void initializeResources(ReadGraph graph) { + if (this.L0 == null) { + this.L0 = Layer0.getInstance(graph); + this.DIA = DiagramResource.getInstance(graph); + this.STR = StructuralResource2.getInstance(graph); + this.MOD = ModelingResources.getInstance(graph); + } + } + + /** + * @param graph + * @param judgment + * @param controlPoints + * @param startTerminal + * @param endTerminal + * @throws DatabaseException + */ + public void create(WriteGraph graph, final ConnectionJudgement judgment, Deque controlPoints, + TerminalInfo startTerminal, TerminalInfo endTerminal) throws DatabaseException { + this.cu = new ConnectionUtil(graph); + initializeResources(graph); + + final IModelingRules modelingRules = diagram.getHint(DiagramModelHints.KEY_MODELING_RULES); + + final Resource startDisconnectedFlag = getDisconnectedFlag(graph, startTerminal); + final Resource endDisconnectedFlag = getDisconnectedFlag(graph, endTerminal); + if (startDisconnectedFlag != null || endDisconnectedFlag != null) { + if (startDisconnectedFlag != null && endDisconnectedFlag != null) { + + // Ask the user which operation to perform: + // a) connect the disconnected flags together with a connection join + // b) join the flags into a single connection + + final VirtualGraph graphProvider = graph.getProvider(); + final Session session = graph.getSession(); + + PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { + @Override + public void run() { + IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + if (window == null) + return; + MessageDialog dialog = new MessageDialog(window.getShell(), "Connect or Join Flags?", null, + "Connect flags together or join them visually into a connection?", + MessageDialog.QUESTION_WITH_CANCEL, new String[] { "Connect Flags", "Join Flags", + "Cancel" }, 0) { + { + setShellStyle(getShellStyle() | SWT.SHEET); + } + }; + final int choice = dialog.open(); + + if (choice != 2 && choice != SWT.DEFAULT) { + session.asyncRequest(new WriteRequest(graphProvider) { + @Override + public void perform(WriteGraph graph) throws DatabaseException { + graph.markUndoPoint(); + switch (choice) { + case 0: { + Resource join = FlagUtil.join(graph, startDisconnectedFlag, endDisconnectedFlag); + FlagLabelingScheme scheme = DiagramFlagPreferences.getActiveFlagLabelingScheme(graph); + String commonLabel = scheme.generateLabel(graph, diagramResource); + graph.claimLiteral(startDisconnectedFlag, L0.HasLabel, DIA.FlagLabel, commonLabel); + graph.claimLiteral(endDisconnectedFlag, L0.HasLabel, DIA.FlagLabel, commonLabel); + + // Set connection type according to modeling rules + setJoinedConnectionTypes(graph, modelingRules, judgment, join); + + // Add comment to change set. + CommentMetadata cm = graph.getMetadata(CommentMetadata.class); + graph.addMetadata(cm.add("Connected flags")); + + return; + } + case 1: { + // First connect the flags together + Resource join = FlagUtil.join(graph, startDisconnectedFlag, endDisconnectedFlag); + + // Set connection type according to modeling rules + setJoinedConnectionTypes(graph, modelingRules, judgment, join); + + // Join the flags into a direct connection + new Joiner(graph).joinLocal(graph, Arrays.asList(startDisconnectedFlag, endDisconnectedFlag)); + + // Add comment to change set. + CommentMetadata cm = graph.getMetadata(CommentMetadata.class); + graph.addMetadata(cm.add("Joined flags")); + + return; + } + } + } + }, new Callback() { + @Override + public void run(DatabaseException e) { + if (e != null) + ErrorLogger.defaultLogError(e); + } + }); + } + } + }); + + return; + } + + TerminalInfo normalTerminal = null; + Resource flagToRemove = null; + Resource connection = null; + if (startDisconnectedFlag != null) { + flagToRemove = startDisconnectedFlag; + normalTerminal = endTerminal; + connection = attachedToExistingConnection(graph, startTerminal); + } + if (endDisconnectedFlag != null) { + flagToRemove = endDisconnectedFlag; + normalTerminal = startTerminal; + connection = attachedToExistingConnection(graph, endTerminal); + } + if (connection != null) { + // OK, continuing a connection from an existing disconnected flag. + + // STEPS TO PERFORM: + // 1. remove flag + // 2. connect normal terminal directly to the existing connection + Statement stm = graph.getSingleStatement(flagToRemove, STR.IsConnectedTo); + Collection areConnecteds = graph.getObjects(stm.getObject(), DIA.AreConnected); + + // Remove statement to connection connector before removing flag + // to prevent removal of connector and the connection. + graph.deny(stm); + new RemoveElement((Resource)diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE), flagToRemove).perform(graph); + + // Disconnect the connector from the connection and create a + // new connector for the element terminal. + cu.removeConnectionPart(stm.getObject()); + Connector newConnector = createConnectorForNode(graph, connection, + (Resource) ElementUtils.getObject(normalTerminal.e), normalTerminal.t, + startDisconnectedFlag != null ? EdgeEnd.End : EdgeEnd.Begin, judgment); + + for (Resource areConnected : areConnecteds) + graph.claim(newConnector.getConnector(), DIA.AreConnected, areConnected); + + if (modelingRules != null && judgment.connectionType != null) + modelingRules.setConnectionType(graph, connection, judgment.connectionType); + + // Add comment to change set. + CommentMetadata cm = graph.getMetadata(CommentMetadata.class); + graph.addMetadata(cm.add("Joined flags")); + graph.markUndoPoint(); + this.cu = null; + return; + } + } + + // 1. Get diagram connection to construct. + Resource connection = getOrCreateConnection(graph, startTerminal, endTerminal); + + // 1.1 Give running name to connection and increment the counter attached to the diagram. + AddElement.claimFreshElementName(graph, diagramResource, connection); + + // 2. Add branch points + // 3. Create edges between branch points. + List> bps = Collections.emptyList(); + Resource firstBranchPoint = null; + Resource lastBranchPoint = null; + if (!isRouteGraphConnection(graph, connection)) { + bps = createBranchPoints(graph, connection, controlPoints); + if (!bps.isEmpty()) { + Iterator> it = bps.iterator(); + Pair prev = it.next(); + firstBranchPoint = prev.second; + while (it.hasNext()) { + Pair next = it.next(); + cu.connect(prev.second, next.second); + prev = next; + } + lastBranchPoint = prev.second; + } + } + + // 4. Connect start/end terminals if those exist. + // If first/lastBranchPoint != null, connect to those. + // Otherwise connect the start/end terminals together. + Connector startConnector = null; + Connector endConnector = null; + IElement startFlag = null; + IElement endFlag = null; + + //FlagLabelingScheme scheme = DiagramFlagPreferences.getActiveFlagLabelingScheme(graph); + + if (startTerminal != null && endTerminal != null) { + Resource startAttachment = chooseAttachmentRelationForNode(graph, connection, startTerminal, judgment); + Resource endAttachment = chooseAttachmentRelationForNode(graph, connection, endTerminal, judgment); + Pair attachments = resolveEndAttachments(graph, startAttachment, endAttachment); + startConnector = createConnectorForNodeWithAttachment(graph, connection, startTerminal, attachments.first); + endConnector = createConnectorForNodeWithAttachment(graph, connection, endTerminal, attachments.second); + } else if (startTerminal != null) { + startConnector = createConnectorForNode(graph, connection, startTerminal, EdgeEnd.Begin, judgment); + if (createFlags) { + EdgeEnd flagEnd = cu.toEdgeEnd( cu.getAttachmentRelationForConnector(startConnector.getConnector()), EdgeEnd.End ).other(); + endFlag = createFlag(graph, connection, flagEnd, controlPoints.getLast(), FlagClass.Type.Out, + //scheme.generateLabel(graph, diagramResource)); + null); + endConnector = createConnectorForNode(graph, connection, (Resource) ElementUtils.getObject(endFlag), + ElementUtils.getSingleTerminal(endFlag), flagEnd, judgment); + } + } else if (endTerminal != null) { + endConnector = createConnectorForNode(graph, connection, endTerminal, EdgeEnd.End, judgment); + if (createFlags) { + EdgeEnd flagEnd = cu.toEdgeEnd( cu.getAttachmentRelationForConnector(endConnector.getConnector()), EdgeEnd.Begin ).other(); + startFlag = createFlag(graph, connection, flagEnd, controlPoints.getFirst(), FlagClass.Type.In, + //scheme.generateLabel(graph, diagramResource)); + null); + startConnector = createConnectorForNode(graph, connection, (Resource) ElementUtils.getObject(startFlag), + ElementUtils.getSingleTerminal(startFlag), flagEnd, judgment); + } + } else if (createFlags) { + startFlag = createFlag(graph, connection, EdgeEnd.Begin, controlPoints.getFirst(), FlagClass.Type.In, + //scheme.generateLabel(graph, diagramResource)); + null); + startConnector = createConnectorForNode(graph, connection, (Resource) ElementUtils.getObject(startFlag), + ElementUtils.getSingleTerminal(startFlag), EdgeEnd.Begin, judgment); + + endFlag = createFlag(graph, connection, EdgeEnd.End, controlPoints.getLast(), FlagClass.Type.Out, + //scheme.generateLabel(graph, diagramResource)); + null); + endConnector = createConnectorForNode(graph, connection, (Resource) ElementUtils.getObject(endFlag), + ElementUtils.getSingleTerminal(endFlag), EdgeEnd.End, judgment); + } + + if (firstBranchPoint == null || lastBranchPoint == null) { + cu.connect(startConnector.getConnector(), endConnector.getConnector()); + } else { + cu.connect(startConnector.getConnector(), firstBranchPoint); + cu.connect(lastBranchPoint, endConnector.getConnector()); + } + + // 5. Finally, set connection type according to modeling rules + if (judgment.connectionType != null && modelingRules != null) + modelingRules.setConnectionType(graph, connection, judgment.connectionType); + + // 5.1 Verify created flag types + if (startFlag != null) + verifyFlagType(graph, modelingRules, startFlag); + if (endFlag != null) + verifyFlagType(graph, modelingRules, endFlag); + + // 5.2 Write ConnectionMappingSpecification to connector if necessary + writeConnectionMappingSpecification(graph, startTerminal, startConnector, judgment.connectionType); + writeConnectionMappingSpecification(graph, endTerminal, endConnector, judgment.connectionType); + + // 6. Add comment to change set. + CommentMetadata cm = graph.getMetadata(CommentMetadata.class); + graph.addMetadata(cm.add("Added connection " + connection)); + graph.markUndoPoint(); + this.cu = null; + } + + private boolean writeConnectionMappingSpecification(WriteGraph graph, TerminalInfo terminal, Connector connector, Resource connectionType) + throws DatabaseException { + Resource diagramConnRel = getConnectionRelation(graph, terminal); + if (diagramConnRel == null) + return false; + Resource connRel = graph.getPossibleObject(diagramConnRel, MOD.DiagramConnectionRelationToConnectionRelation); + if (connRel == null || !graph.hasStatement(connRel, MOD.NeedsConnectionMappingSpecification)) + return false; + Resource mappingSpecification = graph.getPossibleObject(connectionType, MOD.ConnectionTypeToConnectionMappingSpecification); + if (mappingSpecification == null) + return false; + graph.claim(connector.getConnector(), MOD.HasConnectionMappingSpecification, null, mappingSpecification); + return true; + } + + private static Resource getConnectionRelation(ReadGraph graph, TerminalInfo ti) throws DatabaseException { + if (ti != null && ti.t instanceof ResourceTerminal) { + Resource t = ((ResourceTerminal) ti.t).getResource(); + Resource bindingRelation = DiagramGraphUtil.getConnectionPointOfTerminal(graph, t); + return bindingRelation; + } + return null; + } + + /** + * @param graph + * @param startAttachment + * @param endAttachment + * @return + * @throws DatabaseException + */ + protected Pair resolveEndAttachments(WriteGraph graph, + Resource startAttachment, Resource endAttachment) throws DatabaseException { + if (startAttachment != null && endAttachment != null) + return Pair.make(startAttachment, endAttachment); + + if (startAttachment != null && endAttachment == null) + return Pair.make(startAttachment, getInverseAttachment(graph, startAttachment, DIA.HasArrowConnector)); + if (startAttachment == null && endAttachment != null) + return Pair.make(getInverseAttachment(graph, endAttachment, DIA.HasPlainConnector), endAttachment); + + return Pair.make(DIA.HasPlainConnector, DIA.HasArrowConnector); + } + + /** + * @param graph + * @param attachment + * @return + * @throws DatabaseException + */ + protected Resource getInverseAttachment(ReadGraph graph, Resource attachment, Resource defaultValue) throws DatabaseException { + Resource inverse = attachment != null ? graph.getPossibleObject(attachment, DIA.HasInverseAttachment) : defaultValue; + return inverse != null ? inverse : defaultValue; + } + + /** + * @param graph + * @param modelingRules + * @param flagElement + * @throws DatabaseException + */ + protected void verifyFlagType(WriteGraph graph, IModelingRules modelingRules, IElement flagElement) throws DatabaseException { + if (modelingRules != null) { + Resource flag = flagElement.getHint(ElementHints.KEY_OBJECT); + FlagClass.Type flagType = flagElement.getHint(FlagClass.KEY_FLAG_TYPE); + FlagUtil.verifyFlagType(graph, modelingRules, flag, flagType); + } + } + + /** + * @param graph + * @param judgment + * @param connection + * @param attachToLine + * @param controlPoints + * @param endTerminal + * @return the DIA.Connector instance created for attaching the connection + * to the specified end terminal + * @throws DatabaseException + */ + public Pair attachToRouteGraph( + WriteGraph graph, + ConnectionJudgement judgment, + Resource attachToConnection, + Resource attachToLine, + Deque controlPoints, + TerminalInfo endTerminal, + FlagClass.Type flagType) + throws DatabaseException + { + initializeResources(graph); + this.cu = new ConnectionUtil(graph); + try { + Resource endElement = endTerminal != null ? ElementUtils.getObject(endTerminal.e) : null; + if (endElement != null + && graph.isInstanceOf(endElement, DIA.Flag) + && FlagUtil.isDisconnected(graph, endElement)) + { + // Connection ends in an existing but disconnected flag that + // should be all right to connect to because the connection + // judgment implies it makes a valid connection. + // Check that we are attaching the connection to an existing + // disconnected flag that is however attached to a connection. + Resource endTerminalConnection = ConnectionBuilder.attachedToExistingConnection(graph, endTerminal); + if (endTerminalConnection != null) { + attachConnectionToFlag(graph, judgment, attachToConnection, attachToLine, controlPoints, endTerminal); + return null; + } + } + + Connector endConnector = null; + if (endTerminal != null) { + endConnector = createConnectorForNode(graph, attachToConnection, endTerminal, EdgeEnd.End, judgment); + } else if (createFlags) { + EdgeEnd end = flagType == FlagClass.Type.In ? EdgeEnd.Begin : EdgeEnd.End; + IElement endFlag = createFlag(graph, attachToConnection, end, controlPoints.getLast(), flagType, null); + endConnector = createConnectorForNode(graph, attachToConnection, (Resource) ElementUtils.getObject(endFlag), + ElementUtils.getSingleTerminal(endFlag), end, judgment); + } + + cu.connect(attachToLine, endConnector.getConnector()); + + IModelingRules modelingRules = diagram.getHint(DiagramModelHints.KEY_MODELING_RULES); + if (judgment.connectionType != null && modelingRules != null) { + modelingRules.setConnectionType(graph, attachToConnection, judgment.connectionType); + } + + writeConnectionMappingSpecification(graph, endTerminal, endConnector, judgment.connectionType); + + CommentMetadata cm = graph.getMetadata(CommentMetadata.class); + graph.addMetadata(cm.add("Branched connection " + attachToConnection)); + + return Pair.make(endConnector.getAttachment(), endConnector.getConnector()); + } finally { + this.cu = null; + } + } + + protected void attachConnectionToFlag( + WriteGraph graph, + ConnectionJudgement judgment, + Resource attachToConnection, + Resource attachToLine, + Deque controlPoints, + TerminalInfo toFlag) + throws DatabaseException + { + // Attaching attachedConnection to an existing disconnected flag that is + // however attached to a connection. + // STEPS: + // 1. remove flag and its connector + // 2. attach the two connections together by moving the route nodes + // of the removed flag-side connection under the remaining connection + // and ensuring that the route node chain will be valid after the + // switch. In a chain route lines, each line must have an opposite + // direction compared to the lines connected to it. + Resource flagToRemove = ElementUtils.getObject(toFlag.e); + Statement flagToConnector = graph.getSingleStatement(flagToRemove, STR.IsConnectedTo); + Resource flagConnector = flagToConnector.getObject(); + Resource flagConnection = ConnectionUtil.getConnection(graph, flagConnector); + Collection flagRouteNodes = graph.getObjects(flagConnector, DIA.AreConnected); + + Resource connectionToKeep = attachToConnection; + Resource connectionToRemove = flagConnection; + if (!connectionToKeep.equals(connectionToRemove)) { + Resource hasElementToComponent1 = graph.getPossibleObject(attachToConnection, MOD.ElementToComponent); + Resource hasElementToComponent2 = graph.getPossibleObject(flagConnection, MOD.ElementToComponent); + Type flagType = FlagUtil.getFlagType(graph, flagToRemove); + if (hasElementToComponent1 != null && hasElementToComponent2 != null) + throw new UnsupportedOperationException( + "Both attached connection " + attachToConnection + " and flag connection " + flagConnection + + " have mapped components, can't decide which connection to remove in join operation"); + if (hasElementToComponent2 != null || flagType == Type.Out) { + connectionToKeep = flagConnection; + connectionToRemove = attachToConnection; + } + } + + // Remove flag and its connector. + graph.deny(flagToConnector); + new RemoveElement((Resource)diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE), flagToRemove).perform(graph); + cu.removeConnectionPart(flagConnector); + + // Attached routeline must have opposite direction than the line + // attached to in order for the connection to be valid. + Boolean attachingToHorizontalLine = graph.getPossibleRelatedValue(attachToLine, DIA.IsHorizontal, Bindings.BOOLEAN); + if (attachingToHorizontalLine != null) { + for (Resource routeNode : flagRouteNodes) { + Collection routeNodesToAttachTo = removeUntilOrientedRouteline(graph, !attachingToHorizontalLine, routeNode); + for (Resource rn : routeNodesToAttachTo) + cu.connect(attachToLine, rn); + } + } + + moveStatements(graph, connectionToRemove, connectionToKeep, DIA.HasInteriorRouteNode); + moveStatements(graph, connectionToRemove, connectionToKeep, DIA.HasConnector); + + // Remove obsolete connection + if (!connectionToKeep.equals(connectionToRemove)) + cu.removeConnection(connectionToRemove); + + CommentMetadata cm = graph.getMetadata(CommentMetadata.class); + graph.addMetadata(cm.add("Joined connection to disconnected flag")); + } + + private void moveStatements(WriteGraph graph, Resource source, Resource target, Resource movedRelation) throws DatabaseException { + if (!source.equals(target)) { + for (Statement s : graph.getStatements(source, movedRelation)) { + graph.deny(s); + graph.claim(target, s.getPredicate(), s.getObject()); + } + } + } + + private Collection removeUntilOrientedRouteline(WriteGraph graph, boolean expectedOrientation, Resource routeNode) throws DatabaseException { + List result = new ArrayList<>(2); + Deque work = new ArrayDeque<>(2); + work.addLast(routeNode); + while (!work.isEmpty()) { + Resource rn = work.removeFirst(); + if (graph.isInstanceOf(rn, DIA.RouteLine)) { + Boolean isHorizontal = graph.getPossibleRelatedValue(rn, DIA.IsHorizontal, Bindings.BOOLEAN); + if (isHorizontal != null && expectedOrientation != isHorizontal) { + for (Resource rnn : graph.getObjects(rn, DIA.AreConnected)) + work.addLast(rnn); + cu.removeConnectionPart(rn); + continue; + } + } + result.add(rn); + } + return result; + } + + protected boolean isRouteGraphConnection(ReadGraph graph, Resource connection) throws DatabaseException { + initializeResources(graph); + return graph.isInstanceOf(connection, DIA.RouteGraphConnection); + } + + /** + * @param graph + * @param ti + * @return + * @throws DatabaseException + */ + public static Resource attachedToExistingConnection(ReadGraph graph, TerminalInfo ti) throws DatabaseException { + Object obj = ElementUtils.getObject(ti.e); + Resource cp = DiagramGraphUtil.getConnectionPointOfTerminal(graph, ti.t); + if (obj instanceof Resource && cp != null) { + Resource e = (Resource) obj; + for (Resource connector : graph.getObjects(e, cp)) { + Resource connection = ConnectionUtil.tryGetConnection(graph, connector); + if (connection != null) + return connection; + } + } + return null; + } + + /** + * @param graph + * @param tis + * @return + * @throws DatabaseException + */ + public Resource getOrCreateConnection(ReadGraph graph, TerminalInfo... tis) throws DatabaseException { + // Resolve if adding to existing connection. + Resource connection = null; + for (TerminalInfo ti : tis) { + connection = getExistingConnection(graph, ti); + if (connection != null) + break; + } + + if (connection == null) { + // No existing connection, create new. + ElementClass connectionClass = elementClassProvider.get(ElementClasses.CONNECTION); + Resource connectionClassResource = ElementUtils.checkedAdapt(connectionClass, Resource.class); + connection = cu.newConnection(diagramResource, connectionClassResource); + } + + return connection; + } + + /** + * @param graph + * @param connection + * @param controlPoints + * @return + * @throws DatabaseException + */ + public List> createBranchPoints(WriteGraph graph, Resource connection, + Collection controlPoints) throws DatabaseException { + List> bps = new ArrayList>(controlPoints.size()); + for(ControlPoint cp : controlPoints) { + if (cp.isAttachedToTerminal()) + // Terminal attachments do not need branch points. + continue; + + Resource bp = cu.newBranchPoint(connection, + AffineTransform.getTranslateInstance(cp.getPosition().getX(), cp.getPosition().getY()), + cp.getDirection()); + bps.add(Pair.make(cp, bp)); + } + return bps; + } + + /** + * @param graph + * @param connection + * @param ti + * @param end + * @param judgment + * @return + * @throws DatabaseException + */ + protected Resource chooseAttachmentRelationForNode(ReadGraph graph, + Resource connection, TerminalInfo ti, ConnectionJudgement judgment) + throws DatabaseException { + Resource node = (Resource) ElementUtils.getObject(ti.e); + return chooseAttachmentRelationForNode(graph, connection, node, ti.t, judgment); + } + + /** + * @param graph + * @param connection + * @param element + * @param terminal + * @param end + * @param judgment + * @return the calculated attachment relation or null if the + * result is ambiguous + * @throws DatabaseException + */ + protected Resource chooseAttachmentRelationForNode(ReadGraph graph, + Resource connection, Resource element, Terminal terminal, + ConnectionJudgement judgment) throws DatabaseException { + IConnectionPoint cp = ConnectionUtil.toConnectionPoint(graph, element, terminal); + CPTerminal cpt = (cp instanceof CPTerminal) ? (CPTerminal) cp : null; + Resource attachment = judgment.attachmentRelations.get(graph, cpt); + return attachment; + } + + /** + * @param graph + * @param connection + * @param ti + * @param connectTo resource to connect the new connector to if not + * null + * @param judgment + * @return . The + * attachment relation is null if it was chosen based + * on EdgeEnd instead of being defined + * @throws DatabaseException + */ + protected Connector createConnectorForNode(WriteGraph graph, Resource connection, TerminalInfo ti, EdgeEnd end, + ConnectionJudgement judgment) throws DatabaseException { + Resource node = (Resource) ElementUtils.getObject(ti.e); + return createConnectorForNode(graph, connection, node, ti.t, end, judgment); + } + + /** + * @param graph + * @param connection + * @param element + * @param terminal + * @param end + * @param connectTo + * @param judgment + * @return . The + * attachment relation is null if it was chosen based + * on EdgeEnd instead of being defined + * @throws DatabaseException + */ + protected Connector createConnectorForNode(WriteGraph graph, Resource connection, Resource element, Terminal terminal, + EdgeEnd end, ConnectionJudgement judgment) throws DatabaseException { + IConnectionPoint cp = ConnectionUtil.toConnectionPoint(graph, element, terminal); + CPTerminal cpt = (cp instanceof CPTerminal) ? (CPTerminal) cp : null; + Resource attachment = judgment.attachmentRelations.get(graph, cpt); + if (attachment == null) + attachment = cu.toHasConnectorRelation(end); + Resource connector = cu.getOrCreateConnector(connection, element, terminal, end, attachment); + return new Connector(attachment, connector); + } + + /** + * @param graph + * @param connection + * @param ti + * @param attachment + * @return + * @throws DatabaseException + */ + protected Connector createConnectorForNodeWithAttachment(WriteGraph graph, + Resource connection, TerminalInfo ti, Resource attachment) + throws DatabaseException { + Resource node = (Resource) ElementUtils.getObject(ti.e); + return createConnectorForNodeWithAttachment(graph, connection, node, ti.t, attachment); + } + + /** + * @param graph + * @param connection + * @param element + * @param terminal + * @param attachment + * @return + * @throws DatabaseException + */ + protected Connector createConnectorForNodeWithAttachment(WriteGraph graph, + Resource connection, Resource element, Terminal terminal, + Resource attachment) throws DatabaseException { + Resource connector = cu.getOrCreateConnector(connection, element, terminal, null, attachment); + return new Connector(attachment, connector); + } + + /** + * @param graph + * @param connection + * @param end + * @param cp + * @param type + * @param label null to leave flag without label + * @return an element describing the new created flag resource + * @throws DatabaseException + */ + public IElement createFlag(WriteGraph graph, Resource connection, EdgeEnd end, ControlPoint cp, + FlagClass.Type type, String label) throws DatabaseException { + ElementClass flagClass = elementClassProvider.get(ElementClasses.FLAG); + IElement flagElement = Element.spawnNew(flagClass); + Resource flagClassResource = ElementUtils.checkedAdapt(flagClass, Resource.class); + + Layer0 L0 = Layer0.getInstance(graph); + G2DResource G2D = G2DResource.getInstance(graph); + DiagramResource DIA = DiagramResource.getInstance(graph); + + Resource flag = graph.newResource(); + graph.claim(flag, L0.InstanceOf, null, flagClassResource); + flagElement.setHint(ElementHints.KEY_OBJECT, flag); + + OrderedSetUtils.add(graph, diagramResource, flag); + + AffineTransform at = AffineTransform.getTranslateInstance(cp.getPosition().getX(), cp.getPosition().getY()); + flagElement.setHint(ElementHints.KEY_TRANSFORM, at); + double[] matrix = new double[6]; + at.getMatrix(matrix); + graph.claimLiteral(flag, DIA.HasTransform, G2D.Transform, matrix); + + flagElement.setHint(FlagClass.KEY_FLAG_TYPE, type); + graph.claim(flag, DIA.HasFlagType, null, DiagramGraphUtil.toFlagTypeResource(DIA, type)); + if (label != null) + graph.claimLiteral(flag, L0.HasLabel, DIA.FlagLabel, label, Bindings.STRING); + + // Give running name to flag and increment the counter attached to the diagram. + AddElement.claimFreshElementName(graph, diagramResource, flag); + + // Make the diagram consist of the new element + graph.claim(diagramResource, L0.ConsistsOf, flag); + + // Put the element on all the currently active layers if possible. + if (layerManager != null) { + layerManager.removeFromAllLayers(graph, flag); + layerManager.putElementOnVisibleLayers(diagram, graph, flag); + } + + // Add flag to possible IO table + IOTablesInfo ioTablesInfo = IOTableUtil.getIOTablesInfo(graph, + (Resource)diagram.getHint(DiagramModelHints.KEY_DIAGRAM_RESOURCE)); + ioTablesInfo.updateBinding(graph, DIA, flag, at.getTranslateX(), at.getTranslateY()); + + return flagElement; + } + + /** + * @param graph + * @param ti + * @return + * @throws DatabaseException + */ + protected static Resource getExistingConnection(ReadGraph graph, TerminalInfo ti) throws DatabaseException { + if (ti != null) { + if (isConnection(ti.e)) { + Object obj = ElementUtils.getObject(ti.e); + if (obj instanceof Resource) { + Resource c = (Resource) obj; + return graph.isInstanceOf(c, DiagramResource.getInstance(graph).Connection) ? c : null; + } + } else if (isBranchPoint(ti.e)) { + Object obj = ElementUtils.getObject(ti.e); + if (obj instanceof Resource) { + return ConnectionUtil.tryGetConnection(graph, (Resource) obj); + } + } + } + return null; + } + + protected static boolean isConnection(IElement e) { + return e.getElementClass().containsClass(ConnectionHandler.class); + } + + /** + * @param e + * @return + */ + protected static boolean isBranchPoint(IElement e) { + return e.getElementClass().containsClass(BranchPoint.class); + } + + /** + * @param graph + * @param terminal + * @return + * @throws DatabaseException + */ + protected static Resource getDisconnectedFlag(ReadGraph graph, TerminalInfo terminal) throws DatabaseException { + if (terminal != null) { + Object obj = ElementUtils.getObject(terminal.e); + if (obj instanceof Resource) { + Resource flag = (Resource) obj; + if (graph.isInstanceOf(flag, DiagramResource.getInstance(graph).Flag) + && FlagUtil.isDisconnected(graph, flag)) + return flag; + } + } + return null; + } + + /** + * @param graph + * @param modelingRules + * @param judgment + * @param join + * @throws DatabaseException + */ + protected static void setJoinedConnectionTypes(WriteGraph graph, IModelingRules modelingRules, + ConnectionJudgement judgment, Resource join) throws DatabaseException { + if (modelingRules != null && judgment != null && judgment.connectionType != null) { + DiagramResource DIA = DiagramResource.getInstance(graph); + StructuralResource2 STR = StructuralResource2.getInstance(graph); + List connections = new ArrayList(2); + for (Resource flag : graph.getObjects(join, DIA.FlagIsJoinedBy)) { + for (Resource connector : graph.getObjects(flag, STR.IsConnectedTo)) { + Resource connection = ConnectionUtil.tryGetConnection(graph, connector); + if (connection != null) + connections.add(connection); + } + } + for (Resource connection : connections) + modelingRules.setConnectionType(graph, connection, judgment.connectionType); + } + } + } \ No newline at end of file