X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.diagram%2Fsrc%2Forg%2Fsimantics%2Fdiagram%2Fparticipant%2FConnectionBuilder.java;fp=bundles%2Forg.simantics.diagram%2Fsrc%2Forg%2Fsimantics%2Fdiagram%2Fparticipant%2FConnectionBuilder.java;h=6b78af1b8b9730d11aba55591a8b3834f6c91442;hb=969bd23cab98a79ca9101af33334000879fb60c5;hp=0000000000000000000000000000000000000000;hpb=866dba5cd5a3929bbeae85991796acb212338a08;p=simantics%2Fplatform.git 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 new file mode 100644 index 000000000..6b78af1b8 --- /dev/null +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/participant/ConnectionBuilder.java @@ -0,0 +1,919 @@ +/******************************************************************************* + * 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.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) + 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) { + IElement endFlag = createFlag(graph, attachToConnection, EdgeEnd.End, controlPoints.getLast(), FlagClass.Type.Out, null); + endConnector = createConnectorForNode(graph, attachToConnection, (Resource) ElementUtils.getObject(endFlag), + ElementUtils.getSingleTerminal(endFlag), EdgeEnd.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); + + // 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); + } + } + + for (Statement routeNode : graph.getStatements(flagConnection, DIA.HasInteriorRouteNode)) { + graph.deny(routeNode); + graph.claim(attachToConnection, routeNode.getPredicate(), routeNode.getObject()); + } + for (Statement connector : graph.getStatements(flagConnection, DIA.HasConnector)) { + graph.deny(connector); + graph.claim(attachToConnection, connector.getPredicate(), connector.getObject()); + } + + CommentMetadata cm = graph.getMetadata(CommentMetadata.class); + graph.addMetadata(cm.add("Joined connection to disconnected flag")); + } + + 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