-package org.simantics.diagram.handler;\r
-\r
-import static org.simantics.diagram.handler.Paster.ComposedCutProcedure.compose;\r
-import gnu.trove.map.hash.THashMap;\r
-import gnu.trove.set.hash.THashSet;\r
-\r
-import java.awt.geom.AffineTransform;\r
-import java.awt.geom.Point2D;\r
-import java.util.ArrayDeque;\r
-import java.util.Collection;\r
-import java.util.Deque;\r
-import java.util.HashMap;\r
-import java.util.HashSet;\r
-import java.util.Map;\r
-import java.util.Queue;\r
-import java.util.Set;\r
-\r
-import org.simantics.databoard.Bindings;\r
-import org.simantics.db.ReadGraph;\r
-import org.simantics.db.Resource;\r
-import org.simantics.db.Session;\r
-import org.simantics.db.Statement;\r
-import org.simantics.db.WriteGraph;\r
-import org.simantics.db.common.CommentMetadata;\r
-import org.simantics.db.common.request.IndexRoot;\r
-import org.simantics.db.common.request.PossibleTypedParent;\r
-import org.simantics.db.common.request.WriteRequest;\r
-import org.simantics.db.common.utils.CommonDBUtils;\r
-import org.simantics.db.common.utils.NameUtils;\r
-import org.simantics.db.common.utils.OrderedSetUtils;\r
-import org.simantics.db.exception.DatabaseException;\r
-import org.simantics.db.layer0.util.RemoverUtil;\r
-import org.simantics.db.request.Write;\r
-import org.simantics.diagram.content.ConnectionUtil;\r
-import org.simantics.diagram.flag.DiagramFlagPreferences;\r
-import org.simantics.diagram.flag.FlagLabelingScheme;\r
-import org.simantics.diagram.flag.FlagUtil;\r
-import org.simantics.diagram.flag.IOTableUtil;\r
-import org.simantics.diagram.flag.IOTablesInfo;\r
-import org.simantics.diagram.handler.PasteOperation.ForceCopyReferences;\r
-import org.simantics.diagram.internal.DebugPolicy;\r
-import org.simantics.diagram.stubs.DiagramResource;\r
-import org.simantics.diagram.synchronization.CopyAdvisor;\r
-import org.simantics.diagram.synchronization.ErrorHandler;\r
-import org.simantics.diagram.synchronization.IModifiableSynchronizationContext;\r
-import org.simantics.diagram.synchronization.StatementEvaluation;\r
-import org.simantics.diagram.synchronization.SynchronizationException;\r
-import org.simantics.diagram.synchronization.SynchronizationHints;\r
-import org.simantics.diagram.synchronization.graph.AddConnection;\r
-import org.simantics.diagram.synchronization.graph.AddElement;\r
-import org.simantics.diagram.synchronization.graph.CopyAdvisorUtil;\r
-import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;\r
-import org.simantics.diagram.synchronization.graph.GraphSynchronizationHints;\r
-import org.simantics.diagram.synchronization.graph.layer.GraphLayerManager;\r
-import org.simantics.g2d.element.IElement;\r
-import org.simantics.layer0.Layer0;\r
-import org.simantics.modeling.ModelingResources;\r
-import org.simantics.operation.Layer0X;\r
-import org.simantics.scl.runtime.tuple.Tuple2;\r
-import org.simantics.scl.runtime.tuple.Tuple3;\r
-import org.simantics.structural.stubs.StructuralResource2;\r
-import org.simantics.structural2.modelingRules.CPTerminal;\r
-import org.simantics.structural2.modelingRules.ConnectionJudgement;\r
-import org.simantics.structural2.modelingRules.IConnectionPoint;\r
-import org.simantics.utils.datastructures.BinaryFunction;\r
-import org.simantics.utils.datastructures.map.Tuple;\r
-\r
-/**\r
- * @author Tuukka Lehtonen\r
- */\r
-public class Paster {\r
-\r
- private final boolean DEBUG = DebugPolicy.DEBUG_COPY_PASTE | true;\r
-\r
- private final Session session;\r
-\r
- private final PasteOperation op;\r
-\r
- private final Resource sourceDiagram;\r
-\r
- private final Resource targetDiagram;\r
-\r
- private final IModifiableSynchronizationContext targetContext;\r
-\r
- /**\r
- * Set for the duration of the write.\r
- */\r
- private WriteGraph graph;\r
- private ConnectionUtil cu;\r
-\r
- private Layer0 L0;\r
- private Layer0X L0X;\r
- private DiagramResource DIA;\r
- private ModelingResources MOD;\r
- private StructuralResource2 STR;\r
-\r
- /**\r
- * A translating affine transform that can be pre-concatenated to existing\r
- * affine transforms to move them around according to the paste operation\r
- * offset specification.\r
- */\r
- private AffineTransform offsetTransform;\r
- \r
- /**\r
- * A node map for post-processing copied resources\r
- */\r
- private NodeMap nodeMap;\r
-\r
- private Resource sourceRoot;\r
- private Resource targetRoot;\r
- private String sourceRootUri;\r
- private String targetRootUri;\r
- private boolean operateWithinSameRoot;\r
-\r
- /**\r
- * @param session\r
- * @param op\r
- * @throws DatabaseException\r
- */\r
- public Paster(Session session, PasteOperation op) throws DatabaseException {\r
- this.session = session;\r
- this.op = op;\r
-\r
- this.sourceDiagram = op.sourceDiagram;\r
- this.targetDiagram = op.targetDiagram;\r
- if (this.sourceDiagram == null)\r
- throw new IllegalArgumentException("source diagram has no resource");\r
- if (this.targetDiagram == null)\r
- throw new IllegalArgumentException("target diagram has no resource");\r
-\r
- this.targetContext = (IModifiableSynchronizationContext) op.target.getHint(SynchronizationHints.CONTEXT);\r
- if (this.targetContext == null)\r
- throw new IllegalArgumentException("target diagram has no synchronization context");\r
-\r
- this.offsetTransform = AffineTransform.getTranslateInstance(op.offset.getX(), op.offset.getY());\r
- }\r
-\r
- private String toString(PasteOperation op) {\r
- StringBuilder sb = new StringBuilder();\r
- sb.append("Diagram paste ");\r
- sb.append(op.ea.all.size());\r
- sb.append(" element(s) ");\r
- if (op.cut)\r
- sb.append("cut");\r
- else\r
- sb.append("copied");\r
- sb.append(" from ");\r
- sb.append(op.sourceDiagram);\r
- sb.append(" to ");\r
- sb.append(op.targetDiagram);\r
- return sb.toString();\r
- }\r
-\r
- public void perform() throws DatabaseException {\r
- final String comment = toString(op);\r
- Write request = new WriteRequest() {\r
- @Override\r
- public void perform(WriteGraph graph) throws DatabaseException {\r
- graph.markUndoPoint();\r
- Paster.this.perform(graph);\r
- // Add comment to change set.\r
- CommentMetadata cm = graph.getMetadata(CommentMetadata.class);\r
- graph.addMetadata(cm.add(comment));\r
- }\r
- };\r
- session.syncRequest(request);\r
- }\r
-\r
- public void perform(WriteGraph graph) throws DatabaseException {\r
- L0 = Layer0.getInstance(graph);\r
- L0X = Layer0X.getInstance(graph);\r
- STR = StructuralResource2.getInstance(graph);\r
- DIA = DiagramResource.getInstance(graph);\r
- MOD = ModelingResources.getInstance(graph);\r
- this.graph = graph;\r
- this.cu = new ConnectionUtil(graph);\r
- this.sourceRoot = graph.sync(new IndexRoot(sourceDiagram));\r
- this.targetRoot = graph.sync(new IndexRoot(targetDiagram));\r
- this.sourceRootUri = graph.getURI(sourceRoot);\r
- this.targetRootUri = graph.getURI(targetRoot);\r
- this.operateWithinSameRoot = sourceRoot.equals(targetRoot);\r
- try {\r
- if (op.cut)\r
- cut();\r
- else\r
- copy();\r
- } catch (DatabaseException e) {\r
- throw e;\r
- } catch (Exception e) {\r
- throw new DatabaseException(e);\r
- } finally {\r
- this.cu = null;\r
- this.graph = null;\r
- }\r
- }\r
-\r
- private void onFinish() {\r
- final CopyAdvisor advisor = op.target.getHint(SynchronizationHints.COPY_ADVISOR);\r
- if (advisor != null) {\r
- try {\r
- targetContext.set(GraphSynchronizationHints.READ_TRANSACTION, graph);\r
- targetContext.set(GraphSynchronizationHints.WRITE_TRANSACTION, graph);\r
- advisor.onFinish(targetContext);\r
- } catch (SynchronizationException e) {\r
- ErrorHandler eh = targetContext.get(SynchronizationHints.ERROR_HANDLER);\r
- eh.error(e.getMessage(), e);\r
- } finally {\r
- targetContext.set(GraphSynchronizationHints.READ_TRANSACTION, null);\r
- targetContext.set(GraphSynchronizationHints.WRITE_TRANSACTION, null);\r
- }\r
- }\r
- }\r
-\r
- // ------------------------------------------------------------------------\r
- // SIMPLIFICATIONS\r
- // ------------------------------------------------------------------------\r
-\r
- interface Procedure {\r
- void execute(Resource resource) throws Exception;\r
- }\r
-\r
- public void forEachResourceElement(String description, Collection<?> elements, Procedure procedure)\r
- throws Exception {\r
- for (Object object : elements) {\r
- if (object instanceof Resource) {\r
- procedure.execute((Resource) object);\r
- } else {\r
- if (DEBUG) {\r
- System.out.println("[" + description + "] Skipping non-resource element: " + object);\r
- }\r
- }\r
- }\r
- }\r
-\r
- private void applyPasteOffset(Resource forResource) throws DatabaseException {\r
- applyOffset(forResource, op.offset);\r
- }\r
-\r
- private void applyOffset(Resource forResource, Point2D offset) throws DatabaseException {\r
- AffineTransform at = DiagramGraphUtil.getTransform(graph, forResource);\r
- at.preConcatenate(AffineTransform.getTranslateInstance(offset.getX(), offset.getY()));\r
- DiagramGraphUtil.setTransform(graph, forResource, at);\r
- }\r
-\r
- private void applyPasteOffsetToRouteLine(Resource routeLine) throws DatabaseException {\r
- Boolean isHorizontal = graph.getPossibleRelatedValue(routeLine, DIA.IsHorizontal, Bindings.BOOLEAN);\r
- Double pos = graph.getPossibleRelatedValue(routeLine, DIA.HasPosition, Bindings.DOUBLE);\r
- if (pos == null)\r
- pos = 0.0;\r
- if (Boolean.TRUE.equals(isHorizontal))\r
- pos += op.offset.getY();\r
- else\r
- pos += op.offset.getX();\r
- graph.claimLiteral(routeLine, DIA.HasPosition, pos, Bindings.DOUBLE);\r
- }\r
-\r
- // ------------------------------------------------------------------------\r
- // CUT LOGIC\r
- // ------------------------------------------------------------------------\r
-\r
- Resource parentElement(Resource resource, Resource referenceRelation) throws DatabaseException {\r
- // Only allow cutting if reference element has a parent and it is selected for cutting also.\r
- Resource referencedParentComponent = graph.getPossibleObject(resource, referenceRelation);\r
- if (referencedParentComponent == null)\r
- return null;\r
- return graph.getPossibleObject(referencedParentComponent, MOD.ComponentToElement);\r
- }\r
-\r
- boolean parentIsIncludedInCut(Resource resource, Resource referenceRelation, boolean noParentElementReturnValue) throws DatabaseException {\r
- Resource referencedElement = parentElement(resource, referenceRelation);\r
- if (referencedElement != null)\r
- return op.ea.all.contains(referencedElement);\r
- return noParentElementReturnValue;\r
- }\r
-\r
- private void cut() throws Exception {\r
- final GraphLayerManager glm = targetContext.get(GraphSynchronizationHints.GRAPH_LAYER_MANAGER);\r
-\r
- final THashSet<Resource> cutElements = new THashSet<Resource>();\r
- final CutProcedure registerNames = new CutProcedure() {\r
- void postCut(Resource resource, Object cutResult) throws Exception {\r
- String name = graph.getPossibleRelatedValue(resource, L0.HasName, Bindings.STRING);\r
- if (name != null) {\r
- cutElements.add(resource);\r
- }\r
- }\r
- };\r
-\r
- final CutProcedure nodeCutProcedure = new CutProcedure() {\r
- @Override\r
- void postCut(Resource resource, Object cutResult) throws Exception {\r
- if (cutResult != null) {\r
- applyPasteOffset(resource);\r
-\r
- if (glm != null) {\r
- glm.removeFromAllLayers(graph, resource);\r
- glm.putElementOnVisibleLayers(op.target, graph, resource);\r
- }\r
- }\r
- }\r
- };\r
-\r
- CutProcedure flagCutProcedure = new CutProcedure() {\r
- @Override\r
- boolean preCut(Resource resource) throws Exception {\r
- return nodeCutProcedure.preCut(resource);\r
- }\r
- @Override\r
- void postCut(Resource resource, Object cutResult) throws Exception {\r
- nodeCutProcedure.postCut(resource, cutResult);\r
-\r
- if (FlagUtil.isJoinedInSingleDiagram(graph, resource)) {\r
- FlagLabelingScheme scheme = DiagramFlagPreferences.getActiveFlagLabelingScheme(graph);\r
- String commonLabel = scheme.generateLabel(graph, targetDiagram);\r
- graph.claimLiteral(resource, L0.HasLabel, DIA.FlagLabel, commonLabel);\r
- for (Resource otherFlag : FlagUtil.getCounterparts(graph, resource))\r
- graph.claimLiteral(otherFlag, L0.HasLabel, DIA.FlagLabel, commonLabel, Bindings.STRING);\r
- }\r
-\r
- IOTablesInfo ioTablesInfo = IOTableUtil.getIOTablesInfo(graph, op.targetDiagram);\r
- double[] transform = graph.getRelatedValue(resource, DIA.HasTransform, Bindings.DOUBLE_ARRAY);\r
- ioTablesInfo.updateBinding(graph, DIA, resource, transform[4], transform[5]);\r
-\r
- // #11077: fix STR.JoinsComposite relations from joins related to moved flags.\r
- // Otherwise the JoinsComposite relations will be wrong after the cut-operation.\r
- for (Resource join : graph.getObjects(resource, DIA.FlagIsJoinedBy))\r
- fixConnectionJoin(join);\r
- }\r
-\r
- Set<Resource> flagComposites = new HashSet<>();\r
- Set<Resource> joinedComposites = new HashSet<>();\r
- Set<Resource> invalidJoinedComposites = new HashSet<>();\r
-\r
- void fixConnectionJoin(Resource join) throws DatabaseException {\r
- Collection<Resource> flags = graph.getObjects(join, DIA.JoinsFlag);\r
- if (flags.size() < 2) {\r
- // Broken join, remove it. Joins that have\r
- // < 2 flags attached to it shouldn't exist.\r
- graph.deny(join);\r
- } else {\r
- flagComposites.clear();\r
- possibleCompositesOfElements(flags, flagComposites);\r
- joinedComposites.clear();\r
- joinedComposites.addAll( graph.getObjects(join, STR.JoinsComposite) );\r
-\r
- // Calculate which JoinsComposites need to be added and which removed.\r
- invalidJoinedComposites.clear();\r
- invalidJoinedComposites.addAll(joinedComposites);\r
- invalidJoinedComposites.removeAll(flagComposites);\r
- flagComposites.removeAll(joinedComposites);\r
-\r
- if (!invalidJoinedComposites.isEmpty()) {\r
- for (Resource invalidComposite : invalidJoinedComposites)\r
- graph.deny(join, STR.JoinsComposite, invalidComposite);\r
- }\r
- if (!flagComposites.isEmpty()) {\r
- for (Resource joinedComposite : flagComposites)\r
- graph.claim(join, STR.JoinsComposite, joinedComposite);\r
- }\r
- }\r
- }\r
-\r
- Set<Resource> possibleCompositesOfElements(Collection<Resource> elements, Set<Resource> result) throws DatabaseException {\r
- for (Resource e : elements) {\r
- Resource composite = possibleCompositeOfElement(e);\r
- if (composite != null)\r
- result.add(composite);\r
- }\r
- return result;\r
- }\r
-\r
- Resource possibleCompositeOfElement(Resource element) throws DatabaseException {\r
- Resource diagram = graph.getPossibleObject(element, L0.PartOf);\r
- return diagram != null ? graph.getPossibleObject(diagram, MOD.DiagramToComposite) : null;\r
- }\r
- };\r
-\r
- CutProcedure monitorCutProcedure = new CutProcedure() {\r
- @Override\r
- void postCut(Resource resource, Object cutResult) throws DatabaseException {\r
- if (cutResult != null) {\r
- Resource parentElement = parentElement(resource, DIA.HasMonitorComponent);\r
- if (parentElement == null) {\r
- applyPasteOffset(resource);\r
- } else if (!op.ea.all.contains(parentElement)) {\r
- Point2D offset = op.offset;\r
- if (!op.sameDiagram()) {\r
- Resource parentDiagram = graph.sync(new PossibleTypedParent(parentElement, DIA.Diagram));\r
- AffineTransform monitoredComponentTr = DiagramGraphUtil.getWorldTransform(graph, parentElement);\r
- if (op.targetDiagram.equals(parentDiagram)) {\r
- // Monitor is moved back to the parent element diagram.\r
- // Must make monitor position relative to the parent position.\r
- offset = new Point2D.Double(\r
- op.offset.getX() - monitoredComponentTr.getTranslateX(),\r
- op.offset.getY() - monitoredComponentTr.getTranslateY());\r
- } else {\r
- // Monitor is moved to another diagram than the parent element diagram.\r
- // Must make monitor position absolute.\r
- offset = new Point2D.Double(\r
- op.offset.getX() + monitoredComponentTr.getTranslateX(),\r
- op.offset.getY() + monitoredComponentTr.getTranslateY());\r
- }\r
- }\r
- applyOffset(resource, offset);\r
- }\r
-\r
- if (glm != null) {\r
- glm.removeFromAllLayers(graph, resource);\r
- glm.putElementOnVisibleLayers(op.target, graph, resource);\r
- }\r
- }\r
- }\r
- };\r
-\r
- CutProcedure referenceElementCutProcedure = new CutProcedure() {\r
- @Override\r
- boolean preCut(Resource resource) throws DatabaseException {\r
- // Only allow cutting if reference element has a parent and it is selected for cutting also.\r
- return parentIsIncludedInCut(resource, MOD.HasParentComponent, true);\r
- }\r
- @Override\r
- void postCut(Resource resource, Object cutResult) throws Exception {\r
- if (cutResult != null) {\r
- if (!parentIsIncludedInCut(resource, MOD.HasParentComponent, false)) {\r
- applyPasteOffset(resource);\r
- }\r
-\r
- if (glm != null) {\r
- glm.removeFromAllLayers(graph, resource);\r
- glm.putElementOnVisibleLayers(op.target, graph, resource);\r
- }\r
- }\r
- }\r
- };\r
-\r
- CutProcedure connectionCutProcedure = new CutProcedure() {\r
- @Override\r
- void postCut(Resource resource, Object cutResult) throws Exception {\r
- if (cutResult != null) {\r
- for (Resource rn : graph.getObjects(resource, DIA.HasInteriorRouteNode)) {\r
- if (graph.isInstanceOf(rn, DIA.BranchPoint))\r
- applyPasteOffset(rn);\r
- else if (graph.isInstanceOf(rn, DIA.RouteLine))\r
- applyPasteOffsetToRouteLine(rn);\r
- }\r
-\r
- if (glm != null) {\r
- glm.removeFromAllLayers(graph, resource);\r
- glm.putElementOnVisibleLayers(op.target, graph, resource);\r
- }\r
- }\r
- }\r
- };\r
-\r
- // Before cutting, disconnect all nodes from connections that are not in\r
- // the cut connection set.\r
-\r
- final Set<Resource> selectedConnections = new HashSet<Resource>();\r
- forEachResourceElement("Gather connections", op.ea.connections, new Procedure() {\r
- @Override\r
- public void execute(Resource resource) throws Exception {\r
- selectedConnections.add(resource);\r
- }\r
- });\r
-\r
- Set<Resource> affectedConnections = new HashSet<Resource>();\r
- disconnectExcludedConnections("Disconnect Nodes", op.ea.nodeList, selectedConnections, affectedConnections);\r
- disconnectExcludedConnections("Disconnect Flags", op.ea.flags, selectedConnections, affectedConnections);\r
-\r
- for (Resource connection : affectedConnections) {\r
- // Leave the connection only if it has:\r
- // - at least one truly connected :DIA.Connector\r
- // - at least 1 route/branch points\r
- int connectedConnectors = cu.getConnectedConnectors(connection, null).size();\r
- int branchPoints = cu.getBranchPoints(connection, null).size();\r
- if (connectedConnectors > 0 && branchPoints > 0)\r
- continue;\r
-\r
- // Remove the whole connection.\r
- cu.removeConnection(connection);\r
- }\r
-\r
- cut("Cut Nodes", op.ea.nodeList, compose(nodeCutProcedure, registerNames));\r
- cut("Cut Others", op.ea.others, compose(nodeCutProcedure, registerNames));\r
- // Cut reference elements after nodes so that parent relationships can be restored\r
- // but before connections so that connections to the reference elements can be copied.\r
- cut("Cut References", op.ea.references, compose(referenceElementCutProcedure, registerNames));\r
- cut("Cut Flags", op.ea.flags, compose(flagCutProcedure, registerNames));\r
- cut("Cut Connections", op.ea.connections, compose(connectionCutProcedure, registerNames));\r
- cut("Cut Monitors", op.ea.monitors, compose(monitorCutProcedure, registerNames));\r
-\r
- // Make sure that all the pasted nodes have unique names in their new namespace.\r
- // Element names are only diagram-locally unique so this must be done after cut-paste.\r
- for (Resource element : cutElements)\r
- AddElement.claimFreshElementName(graph, targetDiagram, element);\r
-\r
- onFinish();\r
- }\r
-\r
- /**\r
- * @param description\r
- * @param nodes\r
- * @param affectedConnections\r
- * @return\r
- * @throws Exception\r
- */\r
- private Set<Resource> disconnectExcludedConnections(String description, Collection<Resource> nodes,\r
- final Set<Resource> selectedConnections, final Set<Resource> affectedConnections) throws Exception {\r
- final StructuralResource2 str = StructuralResource2.getInstance(graph);\r
-\r
- // Disconnect each connection that is not a part of selectedConnections\r
- // but is attached to the listed nodes.\r
- forEachResourceElement(description, nodes, new Procedure() {\r
- @Override\r
- public void execute(Resource resource) throws Exception {\r
- for (Resource connector : graph.getObjects(resource, str.IsConnectedTo)) {\r
- Resource connection = ConnectionUtil.tryGetConnection(graph, connector);\r
- if (connection == null) {\r
- // This is a stray connector that has no purpose and should be removed.\r
- cu.removeConnectionPart(connector);\r
- continue;\r
- }\r
- if (selectedConnections.contains(connection))\r
- continue;\r
-\r
- cu.removeConnectionPart(connector);\r
- affectedConnections.add(connection);\r
- }\r
- }\r
- });\r
-\r
- return affectedConnections;\r
- }\r
-\r
- /**\r
- * @param description\r
- * @param elements\r
- * @param cutProcedure custom pre- and post-cut processing, may be <code>null</code>\r
- * @throws Exception\r
- */\r
- private void cut(final String description, Collection<Resource> elements, final CutProcedure cutProcedure)\r
- throws Exception {\r
- final CopyAdvisor advisor = op.target.getHint(SynchronizationHints.COPY_ADVISOR);\r
-\r
- forEachResourceElement(description, elements, new Procedure() {\r
- @Override\r
- public void execute(Resource resource) throws Exception {\r
- if (DEBUG)\r
- System.out.println("[" + description + "] " + NameUtils.getSafeName(graph, resource, true));\r
-\r
- if (cutProcedure != null && !cutProcedure.preCut(resource)) {\r
- if (DEBUG)\r
- System.out.println("[" + description + "] ignoring element cut for " + NameUtils.getSafeName(graph, resource, true));\r
- return;\r
- }\r
-\r
- Object result = CopyAdvisorUtil.cut(targetContext, graph, advisor, resource, sourceDiagram, targetDiagram);\r
-\r
- if (DEBUG)\r
- System.out.println("[" + description + "] RESULT: " + result);\r
-\r
- if (cutProcedure != null)\r
- cutProcedure.postCut(resource, result);\r
- }\r
- });\r
- }\r
-\r
- static class CutProcedure {\r
- boolean preCut(Resource resource) throws Exception { return true; }\r
- void postCut(Resource resource, Object cutResult) throws Exception {}\r
- }\r
-\r
- static class ComposedCutProcedure extends CutProcedure {\r
- private final CutProcedure[] procedures;\r
-\r
- public static ComposedCutProcedure compose(CutProcedure... procedures) {\r
- return new ComposedCutProcedure(procedures);\r
- }\r
-\r
- public ComposedCutProcedure(CutProcedure... procedures) {\r
- this.procedures = procedures;\r
- }\r
-\r
- boolean preCut(Resource resource) throws Exception {\r
- for (CutProcedure proc : procedures)\r
- if (!proc.preCut(resource))\r
- return false;\r
- return true;\r
- }\r
- void postCut(Resource resource, Object cutResult) throws Exception {\r
- for (CutProcedure proc : procedures)\r
- proc.postCut(resource, cutResult);\r
- }\r
- }\r
-\r
- // ------------------------------------------------------------------------\r
- // COPY LOGIC SUPPORT CLASSES\r
- // ------------------------------------------------------------------------\r
-\r
- static class IdentifiedElement extends Tuple {\r
- public IdentifiedElement(Resource object, IElement element) {\r
- super(object, element);\r
- }\r
- public Resource getObject() {\r
- return (Resource) getField(0);\r
- }\r
- public IElement getElement() {\r
- return (IElement) getField(1);\r
- }\r
- }\r
-\r
- static public class NodeMap {\r
-\r
- Map<Resource, IdentifiedElement> resourceMap = new HashMap<Resource, IdentifiedElement>();\r
- Map<IElement, IdentifiedElement> elementMap = new HashMap<IElement, IdentifiedElement>();\r
-\r
- public void put(Resource sourceResource, IElement sourceElement, IdentifiedElement dst) {\r
- if (sourceResource == null)\r
- throw new NullPointerException("null source resource");\r
- resourceMap.put(sourceResource, dst);\r
- if (sourceElement != null)\r
- elementMap.put(sourceElement, dst);\r
- }\r
-\r
- public IdentifiedElement get(Resource source) {\r
- return resourceMap.get(source);\r
- }\r
-\r
- public IdentifiedElement get(IElement source) {\r
- return elementMap.get(source);\r
- }\r
- \r
- public Set<Resource> allResources() {\r
- return resourceMap.keySet();\r
- }\r
- \r
- public Resource getResource(Resource source) {\r
- IdentifiedElement ie = resourceMap.get(source);\r
- if(ie != null)\r
- return ie.getObject();\r
- else \r
- return null;\r
- }\r
-\r
- public Resource getResource(IElement source) {\r
- IdentifiedElement ie = elementMap.get(source);\r
- if(ie != null)\r
- return ie.getObject();\r
- else \r
- return null;\r
- }\r
- \r
- }\r
-\r
- static class ResourceMap extends HashMap<Resource, Resource> {\r
- private static final long serialVersionUID = 687528035082504835L;\r
- }\r
-\r
- static class StatementMap extends HashMap<Resource, Statement> {\r
- private static final long serialVersionUID = 8520092255776208395L;\r
- }\r
-\r
- static class MapQueue<K,V> {\r
- Map<K, Deque<V>> map = new HashMap<K, Deque<V>>();\r
- public void offer(K key, V value) {\r
- Deque<V> deque = map.get(key);\r
- if (deque == null)\r
- map.put(key, deque = new ArrayDeque<V>());\r
- deque.offer(value);\r
- }\r
- public V poll(K key) {\r
- Deque<V> deque = map.get(key);\r
- if (deque == null)\r
- return null;\r
- V value = deque.poll();\r
- if (deque.isEmpty())\r
- map.remove(key);\r
- return value;\r
- }\r
- }\r
-\r
- // ------------------------------------------------------------------------\r
- // COPY LOGIC\r
- // ------------------------------------------------------------------------\r
-\r
- /**\r
- * This is necessary to have DIA.Flag type copied over to the copied flag.\r
- * Diagram mapping will have problems and potentially break the\r
- * configuration if the type is not the same as in the source.\r
- */\r
- BinaryFunction<StatementEvaluation, ReadGraph, Statement> statementAdvisor =\r
- new BinaryFunction<StatementEvaluation, ReadGraph, Statement>() {\r
- @Override\r
- public StatementEvaluation call(ReadGraph graph, Statement stm) {\r
- if (DIA.HasFlagType.equals(stm.getPredicate()))\r
- return StatementEvaluation.INCLUDE;\r
- return StatementEvaluation.USE_DEFAULT;\r
- }\r
- };\r
-\r
- CopyProcedure nodeCopyProcedure = new CopyProcedure() {\r
- Resource copy(Resource source) throws Exception {\r
- Layer0 L0 = Layer0.getInstance(graph);\r
-\r
- Resource copy = null;\r
- final CopyAdvisor advisor = op.target.getHint(SynchronizationHints.COPY_ADVISOR);\r
- if (advisor != null) {\r
- Resource sourceComposite = graph.getPossibleObject(source, L0.PartOf);\r
- if (sourceComposite == null || !graph.isInstanceOf(source, DIA.Composite)) {\r
- DiagramResource DIA = DiagramResource.getInstance(graph);\r
- sourceComposite = OrderedSetUtils.getSingleOwnerList(graph, source, DIA.Composite);\r
- }\r
- copy = CopyAdvisorUtil.copy(targetContext, graph, advisor, source, sourceComposite, op.targetDiagram);\r
- }\r
-\r
- if (copy == null) {\r
- copy = CopyAdvisorUtil.copy2(graph, source, statementAdvisor);\r
- }\r
-\r
- graph.deny(copy, MOD.IsTemplatized, copy);\r
-\r
- // Add comment to change set.\r
- CommentMetadata cm = graph.getMetadata(CommentMetadata.class);\r
- graph.addMetadata(cm.add("Copied element " + source + " to " + copy));\r
-\r
- // Add the new element to the diagram composite\r
- OrderedSetUtils.add(graph, op.targetDiagram, copy);\r
-\r
- // Give running name to element and increment the counter attached to the diagram.\r
- AddElement.claimFreshElementName(graph, op.targetDiagram, copy);\r
-\r
- // Make the diagram consist of the new element\r
- graph.claim(op.targetDiagram, L0.ConsistsOf, copy);\r
-\r
- // Put the element on all the currently active layers if possible.\r
- GraphLayerManager glm = targetContext.get(GraphSynchronizationHints.GRAPH_LAYER_MANAGER);\r
- if (glm != null) {\r
- glm.removeFromAllLayers(graph, copy);\r
- glm.putElementOnVisibleLayers(op.target, graph, copy);\r
- }\r
-\r
- return copy;\r
- }\r
- @Override\r
- void postCopy(Resource source, Resource copy) throws Exception {\r
- CopyPasteUtil.copyElementPosition(graph, op.ctx, source, copy, op.offset);\r
- }\r
- };\r
-\r
- private void copy() throws Exception {\r
- nodeMap = new NodeMap();\r
- \r
- CommonDBUtils.selectClusterSet(graph, targetDiagram);\r
-\r
- // Fill nodeMap with initial Resource->Resource mappings\r
- if (op.initialNodeMap != null) {\r
- for (Map.Entry<Resource, Resource> entry : op.initialNodeMap.entrySet()) {\r
- nodeMap.put(entry.getKey(), null, new IdentifiedElement(entry.getValue(), null));\r
- }\r
- }\r
-\r
- // Perform copies in a suitable order\r
- copyNodes( nodeMap );\r
- // Copy reference elements after nodes so that parent relationships can be restored\r
- // but before connections so that connections to the reference elements can be copied.\r
- copyReferences( nodeMap );\r
- copyFlags( nodeMap );\r
- copyConnections( nodeMap );\r
- // Copy monitors last since their parents must have been copied already.\r
- copyMonitors( nodeMap );\r
-\r
- onFinish();\r
- }\r
- \r
-\r
- private NodeMap copyNodes(final NodeMap nodeMap) throws Exception {\r
- copy("Copy Others", op.ea.others, nodeMap, nodeCopyProcedure);\r
- copy("Copy Nodes", op.ea.nodeList, nodeMap, nodeCopyProcedure);\r
-\r
- return nodeMap;\r
- }\r
-\r
- private NodeMap copyReferences(final NodeMap nodeMap) throws Exception {\r
- final boolean forceCopyReferences = op.hasOption(ForceCopyReferences.class);\r
-\r
- copy("Copy References", op.ea.references, nodeMap, new CopyProcedure() {\r
- @Override\r
- Resource copy(Resource source) throws Exception {\r
- // Don't copy unless the parent component is copied too.\r
- Resource sourceParentComponent = graph.getPossibleObject(source, MOD.HasParentComponent);\r
- if (sourceParentComponent == null)\r
- return null;\r
- Resource sourceParentElement = graph.getPossibleObject(sourceParentComponent, MOD.ComponentToElement);\r
- if (sourceParentElement != null) {\r
- if (!forceCopyReferences && !op.ea.all.contains(sourceParentElement))\r
- return null;\r
- // Find copied component\r
- IdentifiedElement copiedParentElement = nodeMap.get(sourceParentElement);\r
- if (copiedParentElement == null)\r
- return null;\r
- Resource copiedParentComponent = graph.getPossibleObject(copiedParentElement.getObject(), MOD.ElementToComponent);\r
- if (copiedParentComponent == null)\r
- return null;\r
- return copyReference(source, copiedParentComponent);\r
- } else {\r
- // Check that the component is part of a diagramless composite before proceeding\r
- Resource partOf = graph.getPossibleObject(sourceParentComponent, L0.PartOf);\r
- if (partOf == null || graph.hasStatement(partOf, MOD.CompositeToDiagram))\r
- return null;\r
- // Resolve the matching parent component from the target context.\r
- Resource targetParentComponent = resolveTargetComponent(sourceParentComponent);\r
- if (targetParentComponent == null)\r
- return null;\r
- return copyReference(source, targetParentComponent);\r
- }\r
- }\r
-\r
- private Resource resolveTargetComponent(Resource sourceParentComponent) throws DatabaseException {\r
- if (operateWithinSameRoot)\r
- return sourceParentComponent;\r
- // Directly map relative source component URI into target root namespace.\r
- String sourceUri = graph.getURI(sourceParentComponent);\r
- String targetUri = sourceUri.replace(sourceRootUri, targetRootUri);\r
- Resource targetParentComponent = graph.getPossibleResource(targetUri);\r
- return targetParentComponent;\r
- }\r
-\r
- private Resource copyReference(Resource source, Resource parentComponent) throws Exception {\r
- Resource referenceRelation = graph.getPossibleObject(source, MOD.HasReferenceRelation);\r
- if (referenceRelation == null)\r
- return null;\r
-\r
- Resource relationCopy = CopyAdvisorUtil.copy4(graph, referenceRelation);\r
- if (relationCopy == null)\r
- return null;\r
-\r
- Resource copy = nodeCopyProcedure.copy(source);\r
-\r
- // WORKAROUND: The result consists of a badly copied reference relation.\r
- // Remove it. How the relation is copied depends on whether the copy target\r
- // is the same model or not. If it is, the relation is copied, but invalidly\r
- // and if the target is not the same model, the relation is simply referenced\r
- // with a uni-directional L0.ConsistsOf relation.\r
- for (Resource o : graph.getObjects(copy, L0.ConsistsOf)) {\r
- boolean ownedByCopy = graph.hasStatement(o, L0.PartOf, copy);\r
- if (ownedByCopy) {\r
- graph.deny(copy, L0.ConsistsOf, o);\r
- RemoverUtil.remove(graph, o);\r
- } else {\r
- graph.deny(copy, L0.ConsistsOf, o);\r
- }\r
- }\r
-\r
- // The element the copied reference is attached to was also copied.\r
- // This means that we must attach the copied reference to its\r
- // original component's copy.\r
- graph.deny(copy, MOD.HasParentComponent);\r
- if(parentComponent != null)\r
- graph.claim(copy, MOD.HasParentComponent, MOD.HasParentComponent_Inverse, parentComponent);\r
-\r
- // Attach reference relation\r
- graph.claim(copy, L0.ConsistsOf, L0.PartOf, relationCopy);\r
- graph.claim(copy, MOD.HasReferenceRelation, MOD.HasReferenceRelation_Inverse, relationCopy);\r
-\r
- return copy;\r
- }\r
-\r
- @Override\r
- void postCopy(Resource source, Resource copy) throws Exception {\r
- // Must fix element position if the copied reference element\r
- // doesn't have a visible parent element.\r
- Resource parentComponent = graph.getPossibleObject(source, MOD.HasParentComponent);\r
- if (parentComponent == null)\r
- return;\r
- Resource parentElement = graph.getPossibleObject(parentComponent, MOD.ComponentToElement);\r
- if (parentElement == null)\r
- CopyPasteUtil.copyElementPosition(graph, op.ctx, source, copy, op.offset);\r
- }\r
- });\r
-\r
- return nodeMap;\r
- }\r
-\r
- private NodeMap copyFlags(NodeMap nodeMap) throws Exception {\r
- final Layer0 l0 = Layer0.getInstance(graph);\r
- final DiagramResource dia = DiagramResource.getInstance(graph);\r
-\r
- class FlagCopy {\r
- private final Map<Resource, Resource> selectedFlags = new HashMap<Resource, Resource>();\r
- private final Map<Resource, Resource> flagSelectedCounterpart = new HashMap<Resource, Resource>();\r
-\r
- /**\r
- * Analyze which flag pairs are selected\r
- * \r
- * @throws DatabaseException\r
- */\r
- private void analyzeFlagSelection() throws DatabaseException {\r
- for (Resource flag : op.ea.flags) {\r
- selectedFlags.put(flag, flag);\r
- }\r
- for (Resource flag : selectedFlags.keySet()) {\r
- boolean external = FlagUtil.isExternal(graph, flag);\r
- boolean inSingleDiagram = FlagUtil.isJoinedInSingleDiagram(graph, flag);\r
- if (!external && inSingleDiagram) {\r
- // FIXME: this doesn't take into account local merged flags, which is a corner case but still possible\r
- Resource counterpart = FlagUtil.getPossibleCounterpart(graph, flag);\r
- if (selectedFlags.containsKey(counterpart)) {\r
- flagSelectedCounterpart.put(flag, counterpart);\r
- flagSelectedCounterpart.put(counterpart, flag);\r
- }\r
- }\r
- }\r
- }\r
-\r
- /**\r
- * Reconnect copied flag pairs.\r
- * @throws DatabaseException\r
- */\r
- private void reconnectLocalFlagPairs(NodeMap nodeMap) throws DatabaseException {\r
- FlagLabelingScheme scheme = DiagramFlagPreferences.getActiveFlagLabelingScheme(graph);\r
- Resource diagram = op.targetDiagram;\r
-\r
- Set<Resource> visited = new HashSet<Resource>();\r
- ArrayDeque<Resource> queue = new ArrayDeque<Resource>(flagSelectedCounterpart.values());\r
- while (!queue.isEmpty()) {\r
- Resource flag = queue.poll();\r
- Resource counterpart = flagSelectedCounterpart.get(flag);\r
- if (!visited.add(flag) || !visited.add(counterpart) || counterpart == null)\r
- continue;\r
-\r
- // Get copies\r
- Resource flagSourceElement = selectedFlags.get(flag);\r
- Resource counterpartSourceElement = selectedFlags.get(counterpart);\r
-\r
- IdentifiedElement flagCopy = nodeMap.get(flagSourceElement);\r
- IdentifiedElement counterpartCopy = nodeMap.get(counterpartSourceElement);\r
-\r
- FlagUtil.join(graph, flagCopy.getObject(), counterpartCopy.getObject());\r
-\r
- // Provide fresh labeling for connected flags if possible\r
- if (scheme != null) {\r
- String label = scheme.generateLabel(graph, diagram);\r
- if (label != null) {\r
- graph.claimLiteral(flagCopy.getObject(), l0.HasLabel, dia.FlagLabel, label, Bindings.STRING);\r
- graph.claimLiteral(counterpartCopy.getObject(), l0.HasLabel, dia.FlagLabel, label, Bindings.STRING);\r
- }\r
- }\r
- }\r
- }\r
-\r
- public void perform(NodeMap nodeMap) throws Exception {\r
- analyzeFlagSelection();\r
-\r
- copy("Copy Flags", op.ea.flags, nodeMap, new CopyProcedure() {\r
- @Override\r
- Resource copy(Resource source) throws Exception {\r
- return nodeCopyProcedure.copy(source);\r
- }\r
- @Override\r
- public void postCopy(Resource source, Resource copy) throws Exception {\r
- AffineTransform at = CopyPasteUtil.copyElementPosition(graph, op.ctx, source, copy, op.offset);\r
- \r
- // Update flag table binding\r
- IOTablesInfo ioTablesInfo = IOTableUtil.getIOTablesInfo(graph, op.targetDiagram);\r
- ioTablesInfo.updateBinding(graph, DIA, copy, at.getTranslateX(), at.getTranslateY());\r
- \r
- // All label properties must be removed from\r
- // the copied flags. Disconnected flags are\r
- // not supposed to have labels, and the right\r
- // place to reset the labels is when the flags\r
- // are reconnected to their respective\r
- // counterparts.\r
- graph.denyValue(copy, l0.HasLabel);\r
- }\r
- });\r
-\r
- reconnectLocalFlagPairs(nodeMap);\r
- }\r
-\r
- }\r
-\r
- new FlagCopy().perform( nodeMap );\r
- return nodeMap;\r
- }\r
-\r
- private NodeMap copyMonitors(final NodeMap nodeMap) throws Exception {\r
- copy("Copy Monitors", op.ea.monitors, nodeMap, new CopyProcedure() {\r
- @Override\r
- Resource copy(Resource source) throws Exception {\r
- // Don't copy monitors if they are copied without\r
- // their parent element into another root (model).\r
- if (!operateWithinSameRoot) {\r
- Resource monitorComponent = graph.getPossibleObject(source, DIA.HasMonitorComponent);\r
- if (monitorComponent != null) {\r
- Resource monitorElement = graph.getPossibleObject(monitorComponent, MOD.ComponentToElement);\r
- if (monitorElement == null || !op.ea.all.contains(monitorElement))\r
- return null;\r
- }\r
- }\r
- Resource copy = nodeCopyProcedure.copy(source);\r
- return copy;\r
- }\r
- @Override\r
- void postCopy(Resource source, Resource copy) throws Exception {\r
- // Find the component and diagram element the source monitor is\r
- // connected to.\r
- Resource monitorElement = null;\r
- Resource monitorComponent = graph.getPossibleObject(source, DIA.HasMonitorComponent);\r
- if (monitorComponent != null) {\r
- monitorElement = graph.getPossibleObject(monitorComponent, MOD.ComponentToElement);\r
- }\r
-\r
- if (monitorElement != null && op.ea.all.contains(monitorElement)) {\r
- // The element the copied monitor is attached was also copied.\r
- // This means that we must attach the copied monitor to its\r
- // original components copy.\r
-\r
- // Remove old association\r
- graph.deny(copy, DIA.HasMonitorComponent);\r
-\r
- // Associate to copied component\r
- IdentifiedElement parent = nodeMap.get(monitorElement);\r
- if (parent != null) {\r
- monitorComponent = graph.getPossibleObject(parent.getObject(), MOD.ElementToComponent);\r
- if (monitorComponent != null)\r
- graph.claim(copy, DIA.HasMonitorComponent, monitorComponent);\r
- } else {\r
- //throw new PasteException("no parent could be found for monitored element " + monitoredElement);\r
- }\r
- } else {\r
- // The element the copied monitor is attached was not copied\r
- // or there is no element for the monitored component.\r
- // This means that the copied monitor must be kept attached\r
- // to the same component no matter where it is in the model,\r
- // unless the copy is done into another model.\r
- if (operateWithinSameRoot && monitorComponent != null)\r
- graph.claim(copy, DIA.HasMonitorComponent, monitorComponent);\r
-\r
- Point2D offset = op.offset;\r
- if (!op.sameDiagram()) {\r
- if (monitorElement != null) {\r
- // Monitor doesn't have a diagram parent element any\r
- // more, must recalculate its offset.\r
- AffineTransform monitoredComponentTr = DiagramGraphUtil.getWorldTransform(graph, monitorElement);\r
- offset = new Point2D.Double(\r
- op.offset.getX() + monitoredComponentTr.getTranslateX(),\r
- op.offset.getY() + monitoredComponentTr.getTranslateY());\r
- }\r
- }\r
- CopyPasteUtil.copyElementPosition(graph, op.ctx, source, copy, offset);\r
- }\r
-\r
- // Copy monitor suffix from original to copy.\r
- String monitorSuffix = graph.getPossibleRelatedValue(source, DIA.HasMonitorSuffix, Bindings.STRING);\r
- if (monitorSuffix != null)\r
- graph.claimLiteral(copy, DIA.HasMonitorSuffix, monitorSuffix, Bindings.STRING);\r
-\r
- // Copy used property obtains for monitor template data.\r
- graph.deny(copy, L0X.ObtainsProperty);\r
- for (Statement stm : graph.getStatements(source, L0X.ObtainsProperty)) {\r
- graph.claim(copy, stm.getPredicate(), null, stm.getObject());\r
- }\r
- }\r
- });\r
-\r
- return nodeMap;\r
- }\r
-\r
- /**\r
- * @param description\r
- * @param elements\r
- * @param nodeMap\r
- * @param copyProcedure\r
- * @throws Exception\r
- */\r
- private void copy(final String description, Collection<Resource> elements, final NodeMap nodeMap,\r
- final CopyProcedure copyProcedure) throws Exception {\r
- if (copyProcedure == null)\r
- throw new IllegalArgumentException("null copy procedure");\r
-\r
- forEachResourceElement(description, elements, new Procedure() {\r
- @Override\r
- public void execute(Resource resource) throws Exception {\r
- if (DEBUG)\r
- System.out.println("[" + description + "] " + NameUtils.getSafeName(graph, resource, true));\r
- Resource copy = copyProcedure.copy(resource);\r
- if (copy != null) {\r
- if (DEBUG)\r
- System.out.println("[" + description + "] " + NameUtils.getSafeName(graph, resource, true) + " copied as " + NameUtils.getSafeName(graph, copy, true));\r
- nodeMap.put(resource, null, new IdentifiedElement(copy, null));\r
- if (op.copyMap != null)\r
- op.copyMap.put(resource, copy);\r
- copyProcedure.postCopy(resource, copy);\r
- }\r
- }\r
- });\r
- }\r
-\r
- public static class RouteLine extends Tuple2 {\r
- public RouteLine(Double position, Boolean horizontal) {\r
- super(position, horizontal);\r
- }\r
- public double getPosition() {\r
- Double pos = (Double) get(0);\r
- return pos != null ? pos : 0.0;\r
- }\r
- public boolean isHorizontal() {\r
- return Boolean.TRUE.equals(get(1));\r
- }\r
- }\r
-\r
- public static class BranchPoint extends Tuple3 {\r
- public BranchPoint(AffineTransform at, Boolean horizontal, Boolean vertical) {\r
- super(at, horizontal, vertical);\r
- }\r
- public AffineTransform getTransform() {\r
- return (AffineTransform) get(0);\r
- }\r
- }\r
-\r
- public static RouteLine readRouteLine(ReadGraph graph, Resource src) throws DatabaseException {\r
- DiagramResource DIA = DiagramResource.getInstance(graph);\r
- Double pos = graph.getPossibleRelatedValue(src, DIA.HasPosition, Bindings.DOUBLE);\r
- Boolean hor = graph.getPossibleRelatedValue(src, DIA.IsHorizontal, Bindings.BOOLEAN);\r
- return new RouteLine(pos, hor);\r
- }\r
-\r
- public static BranchPoint readBranchPoint(ReadGraph graph, Resource src) throws DatabaseException {\r
- DiagramResource DIA = DiagramResource.getInstance(graph);\r
- AffineTransform at = DiagramGraphUtil.getTransform(graph, src);\r
- boolean hor = graph.hasStatement(src, DIA.Horizontal);\r
- boolean ver = graph.hasStatement(src, DIA.Vertical);\r
- return new BranchPoint(at, hor, ver);\r
- }\r
-\r
- /**\r
- * @param nodeMap\r
- * @return\r
- * @throws Exception\r
- */\r
- private NodeMap copyConnections(final NodeMap nodeMap) throws Exception {\r
- final StructuralResource2 STR = StructuralResource2.getInstance(graph);\r
- final DiagramResource DIA = DiagramResource.getInstance(graph);\r
-\r
-// final IModelingRules rules = graph.syncRequest(DiagramRequests.getModelingRules(op.sourceDiagram, null));\r
-// if (rules == null)\r
-// throw new IllegalArgumentException("source diagram offers no modeling rules");\r
-\r
- final CopyAdvisor ca = op.target.getHint(SynchronizationHints.COPY_ADVISOR);\r
- if (ca == null)\r
- throw new UnsupportedOperationException("Cannot copy connections, no copy advisor available for diagram "\r
- + op.target);\r
-\r
- forEachResourceElement("Copy Connections", op.ea.connections, new Procedure() {\r
- @Override\r
- public void execute(Resource sourceObject) throws DatabaseException {\r
- copyConnection(sourceObject);\r
- }\r
-\r
- private void copyConnection(Resource sourceObject) throws DatabaseException {\r
- // For associating source<->destination connection parts\r
- final Map<Object, Object> resourceMap = new THashMap<Object, Object>();\r
- // For associating source connectors to source nodes \r
- final StatementMap connectorToNode = new StatementMap();\r
-\r
- // 1. copy connection\r
- // - This will also copy interior route nodes\r
- // - But will leave out the DIA.AreConnected relations between route nodes\r
- Resource sourceDiagram = graph.getPossibleObject(sourceObject, Layer0.getInstance(graph).PartOf);\r
- if (sourceDiagram == null)\r
- sourceDiagram = OrderedSetUtils.getSingleOwnerList(graph, sourceObject, DIA.Diagram);\r
- Resource copy = CopyAdvisorUtil.copy(targetContext, graph, ca, sourceObject, sourceDiagram, op.targetDiagram, resourceMap);\r
- if (copy == null)\r
- throw new UnsupportedOperationException("Could not copy connection " + sourceObject);\r
- OrderedSetUtils.addFirst(graph, op.targetDiagram, copy);\r
-\r
- graph.deny(copy, MOD.IsTemplatized, copy);\r
-\r
- AddElement.claimFreshElementName(graph, op.targetDiagram, copy);\r
-\r
- AddConnection.copyConnectionType(graph, sourceObject, copy);\r
-\r
- GraphLayerManager glm = targetContext.get(GraphSynchronizationHints.GRAPH_LAYER_MANAGER);\r
- if (glm != null) {\r
- glm.removeFromAllLayers(graph, copy);\r
- glm.putElementOnVisibleLayers(op.target, graph, copy);\r
- }\r
-\r
- nodeMap.put(sourceObject, null, new IdentifiedElement(copy, null));\r
- if (op.copyMap != null)\r
- op.copyMap.put(sourceObject, copy);\r
-\r
- // WORKAROUND: CopyAdvisorUtil.copy(..., resourceMap)\r
- // implementations do not all support filling the resource map.\r
- // Thus we resort to the old logic if resourceMap is empty at this point.\r
- final boolean mapResources = resourceMap.isEmpty();\r
-\r
- // 2. associate source connection parts to destination connection parts\r
-\r
- // Connectors\r
- Collection<Statement> sourceHasConnectors = graph.getStatements(sourceObject, DIA.HasConnector);\r
- MapQueue<Resource, Resource> connectorsByType = new MapQueue<Resource, Resource>();\r
- for (Statement hasConnector : sourceHasConnectors) {\r
- connectorsByType.offer(hasConnector.getPredicate(), hasConnector.getObject());\r
- for (Statement connects : graph.getStatements(hasConnector.getObject(), STR.Connects)) {\r
- if (!sourceObject.equals(connects.getObject())) {\r
- connectorToNode.put(hasConnector.getObject(), connects);\r
- break;\r
- }\r
- }\r
- }\r
- if (mapResources) {\r
- for (Statement hasConnector : graph.getStatements(copy, DIA.HasConnector)) {\r
- Resource srcConnector = connectorsByType.poll(hasConnector.getPredicate());\r
- resourceMap.put(srcConnector, hasConnector.getObject());\r
- }\r
- }\r
- // 2.2. Offset interior route nodes\r
- Collection<Resource> sourceInteriorRouteNodes = graph.getObjects(sourceObject, DIA.HasInteriorRouteNode);\r
- if (mapResources) {\r
- // WORKAROUND: for cases where resourceMap was not filled by\r
- // the copy operation. Still needed because TG copying does\r
- // not output this information.\r
- Queue<Resource> branchPoints = new ArrayDeque<Resource>(sourceInteriorRouteNodes.size());\r
- Queue<Resource> routeLines = new ArrayDeque<Resource>(sourceInteriorRouteNodes.size());\r
- for (Resource dst : graph.getObjects(copy, DIA.HasInteriorRouteNode)) {\r
- if (graph.isInstanceOf(dst, DIA.BranchPoint))\r
- branchPoints.offer(dst);\r
- else if (graph.isInstanceOf(dst, DIA.RouteLine))\r
- routeLines.offer(dst);\r
- }\r
- for (Resource src : sourceInteriorRouteNodes) {\r
- if (graph.isInstanceOf(src, DIA.BranchPoint)) {\r
- Resource dst = branchPoints.poll();\r
- resourceMap.put(src, dst);\r
- BranchPoint bp = readBranchPoint(graph, src);\r
- AffineTransform at = bp.getTransform();\r
- at.preConcatenate(offsetTransform);\r
- DiagramGraphUtil.setTransform(graph, dst, at);\r
- }\r
- else if (graph.isInstanceOf(src, DIA.RouteLine)) {\r
- Resource dst = routeLines.poll();\r
- resourceMap.put(src, dst);\r
- RouteLine rl = readRouteLine(graph, src);\r
- double newPos = rl.getPosition() + (rl.isHorizontal() ? op.offset.getY() : op.offset.getX());\r
- graph.claimLiteral(dst, DIA.HasPosition, newPos, Bindings.DOUBLE);\r
- }\r
- }\r
- } else {\r
- for (Resource src : sourceInteriorRouteNodes) {\r
- Resource dst = (Resource) resourceMap.get(src);\r
- if (dst != null) {\r
- if (graph.isInstanceOf(src, DIA.BranchPoint)) {\r
- BranchPoint bp = readBranchPoint(graph, src);\r
- AffineTransform at = bp.getTransform();\r
- at.preConcatenate(offsetTransform);\r
- DiagramGraphUtil.setTransform(graph, dst, at);\r
- } else if (graph.isInstanceOf(src, DIA.RouteLine)) {\r
- RouteLine rl = readRouteLine(graph, src);\r
- double newPos = rl.getPosition() + (rl.isHorizontal() ? op.offset.getY() : op.offset.getX());\r
- graph.claimLiteral(dst, DIA.HasPosition, newPos, Bindings.DOUBLE);\r
- }\r
- }\r
- }\r
- }\r
-\r
- // 3. Connect connection parts according to how the source is connected\r
- for (Resource src : sourceInteriorRouteNodes) {\r
- Resource dst = (Resource) resourceMap.get(src);\r
- for (Resource connectedToSrc : graph.getObjects(src, DIA.AreConnected)) {\r
- Resource connectedToDst = (Resource) resourceMap.get(connectedToSrc);\r
- if (connectedToDst != null) {\r
- graph.claim(dst, DIA.AreConnected, DIA.AreConnected, connectedToDst);\r
- } else {\r
- throw new DatabaseException("Connection copying failed due to an invalid DIA.AreConnected link between source resources " + src + " <-> " + connectedToSrc);\r
- }\r
- }\r
- }\r
- for (Statement hasConnector : sourceHasConnectors) {\r
- Resource srcConnector = hasConnector.getObject();\r
- Resource dstConnector = (Resource) resourceMap.get(srcConnector);\r
- Statement srcConnects = connectorToNode.get(srcConnector);\r
-\r
- // Connect to copied nodes\r
- IdentifiedElement dstNode = nodeMap.get(srcConnects.getObject());\r
- if (dstNode == null)\r
- throw new DatabaseException("Source element "\r
- + NameUtils.getURIOrSafeNameInternal(graph, srcConnects.getObject())\r
- + " not copied causing copying of connection "\r
- + NameUtils.getURIOrSafeNameInternal(graph, sourceObject) \r
- +" to fail.");\r
- graph.claim(dstConnector, srcConnects.getPredicate(), dstNode.getObject());\r
-\r
- // Connect to other copied route nodes\r
- for (Resource connectedToSrc : graph.getObjects(srcConnector, DIA.AreConnected)) {\r
- Resource connectedToDst = (Resource) resourceMap.get(connectedToSrc);\r
- graph.claim(dstConnector, DIA.AreConnected, DIA.AreConnected, connectedToDst);\r
- }\r
- }\r
-\r
- // 4. Make sure MOD.ConnectorToComponent relations are copied as well.\r
- // Otherwise diagram mapping will do bad things on the model.\r
- Resource sourceComponent = graph.getPossibleObject(sourceObject, MOD.ElementToComponent);\r
- if (sourceComponent != null) {\r
- for (Statement hasConnector : sourceHasConnectors) {\r
- Resource sourceConnector = hasConnector.getObject();\r
- Resource targetConnector = (Resource) resourceMap.get(sourceConnector);\r
- // Should have been defined back in steps 1-2.\r
- assert targetConnector != null;\r
- Statement sourceConnectorToComponent = graph.getPossibleStatement(sourceConnector, MOD.ConnectorToComponent);\r
- if (sourceConnectorToComponent == null)\r
- continue;\r
- if (!sourceConnectorToComponent.getObject().equals(sourceComponent))\r
- continue;\r
- Resource targetComponent = graph.getPossibleObject(copy, MOD.ElementToComponent);\r
- if (targetComponent == null)\r
- continue;\r
-\r
- graph.claim(targetConnector, sourceConnectorToComponent.getPredicate(), targetComponent);\r
-\r
- // #6190 & apros:#11435: Ensure that MOD.HasConnectionMappingSpecification is added to target\r
- for (Resource connectionMappingSpec : graph.getObjects(sourceConnector, MOD.HasConnectionMappingSpecification))\r
- graph.claim(targetConnector, MOD.HasConnectionMappingSpecification, connectionMappingSpec);\r
- }\r
- }\r
- }\r
- });\r
-\r
- return nodeMap;\r
- }\r
-\r
- class CopyProcedure {\r
- Resource copy(Resource source) throws Exception { throw new UnsupportedOperationException(); }\r
- void postCopy(Resource source, Resource copy) throws Exception {}\r
- }\r
-\r
- /**\r
- * @param judgment <code>null</code> if no judgement is available in which\r
- * case defaultValue is always returned\r
- * @param connectionPoint\r
- * @param defaultValue\r
- * @return\r
- * @throws DatabaseException\r
- */\r
- @SuppressWarnings("unused")\r
- private static Resource getAttachmentRelation(ReadGraph graph, ConnectionJudgement judgment,\r
- IConnectionPoint connectionPoint, Resource defaultValue) throws DatabaseException {\r
- if (judgment == null || !(connectionPoint instanceof CPTerminal) || judgment.attachmentRelations == null)\r
- return defaultValue;\r
- Resource attachment = judgment.attachmentRelations.get(graph, (CPTerminal) connectionPoint);\r
- return attachment != null ? attachment : defaultValue;\r
- }\r
- \r
- /**\r
- * Get node map of copied variables. Map contains original and new resources.\r
- * \r
- * @return NodeMap of copied resources or null if copy has not been performed\r
- */\r
- public NodeMap getNodeMap() {\r
- return nodeMap;\r
- }\r
-\r
-}\r
+package org.simantics.diagram.handler;
+
+import static org.simantics.diagram.handler.Paster.ComposedCutProcedure.compose;
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.function.BiFunction;
+
+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.WriteGraph;
+import org.simantics.db.common.CommentMetadata;
+import org.simantics.db.common.request.IndexRoot;
+import org.simantics.db.common.request.PossibleTypedParent;
+import org.simantics.db.common.request.WriteRequest;
+import org.simantics.db.common.utils.CommonDBUtils;
+import org.simantics.db.common.utils.NameUtils;
+import org.simantics.db.common.utils.OrderedSetUtils;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.layer0.util.Layer0Utils;
+import org.simantics.db.layer0.util.RemoverUtil;
+import org.simantics.db.request.Write;
+import org.simantics.diagram.content.ConnectionUtil;
+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.handler.PasteOperation.ForceCopyReferences;
+import org.simantics.diagram.internal.DebugPolicy;
+import org.simantics.diagram.stubs.DiagramResource;
+import org.simantics.diagram.synchronization.CopyAdvisor;
+import org.simantics.diagram.synchronization.ErrorHandler;
+import org.simantics.diagram.synchronization.IModifiableSynchronizationContext;
+import org.simantics.diagram.synchronization.StatementEvaluation;
+import org.simantics.diagram.synchronization.SynchronizationException;
+import org.simantics.diagram.synchronization.SynchronizationHints;
+import org.simantics.diagram.synchronization.graph.AddConnection;
+import org.simantics.diagram.synchronization.graph.AddElement;
+import org.simantics.diagram.synchronization.graph.CopyAdvisorUtil;
+import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;
+import org.simantics.diagram.synchronization.graph.GraphSynchronizationHints;
+import org.simantics.diagram.synchronization.graph.layer.GraphLayerManager;
+import org.simantics.g2d.element.IElement;
+import org.simantics.layer0.Layer0;
+import org.simantics.modeling.ModelingResources;
+import org.simantics.operation.Layer0X;
+import org.simantics.scl.runtime.tuple.Tuple2;
+import org.simantics.scl.runtime.tuple.Tuple3;
+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.utils.datastructures.map.Tuple;
+
+import gnu.trove.map.hash.THashMap;
+import gnu.trove.set.hash.THashSet;
+
+/**
+ * @author Tuukka Lehtonen
+ */
+public class Paster {
+
+ private final boolean DEBUG = DebugPolicy.DEBUG_COPY_PASTE | true;
+
+ private final Session session;
+
+ private final PasteOperation op;
+
+ private final Resource sourceDiagram;
+
+ private final Resource targetDiagram;
+
+ private final IModifiableSynchronizationContext targetContext;
+
+ /**
+ * Set for the duration of the write.
+ */
+ private WriteGraph graph;
+ private ConnectionUtil cu;
+
+ private Layer0 L0;
+ private Layer0X L0X;
+ private DiagramResource DIA;
+ private ModelingResources MOD;
+ private StructuralResource2 STR;
+
+ /**
+ * A translating affine transform that can be pre-concatenated to existing
+ * affine transforms to move them around according to the paste operation
+ * offset specification.
+ */
+ private AffineTransform offsetTransform;
+
+ /**
+ * A node map for post-processing copied resources
+ */
+ private NodeMap nodeMap;
+
+ private Resource sourceRoot;
+ private Resource targetRoot;
+ private String sourceRootUri;
+ private String targetRootUri;
+ private boolean operateWithinSameRoot;
+
+ /**
+ * @param session
+ * @param op
+ * @throws DatabaseException
+ */
+ public Paster(Session session, PasteOperation op) throws DatabaseException {
+ this.session = session;
+ this.op = op;
+
+ this.sourceDiagram = op.sourceDiagram;
+ this.targetDiagram = op.targetDiagram;
+ if (this.sourceDiagram == null)
+ throw new IllegalArgumentException("source diagram has no resource");
+ if (this.targetDiagram == null)
+ throw new IllegalArgumentException("target diagram has no resource");
+
+ this.targetContext = (IModifiableSynchronizationContext) op.target.getHint(SynchronizationHints.CONTEXT);
+ if (this.targetContext == null)
+ throw new IllegalArgumentException("target diagram has no synchronization context");
+
+ this.offsetTransform = AffineTransform.getTranslateInstance(op.offset.getX(), op.offset.getY());
+ }
+
+ private String toString(PasteOperation op) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Diagram paste ");
+ sb.append(op.ea.all.size());
+ sb.append(" element(s) ");
+ if (op.cut)
+ sb.append("cut");
+ else
+ sb.append("copied");
+ sb.append(" from ");
+ sb.append(op.sourceDiagram);
+ sb.append(" to ");
+ sb.append(op.targetDiagram);
+ return sb.toString();
+ }
+
+ public void perform() throws DatabaseException {
+ final String comment = toString(op);
+ Write request = new WriteRequest() {
+ @Override
+ public void perform(WriteGraph graph) throws DatabaseException {
+ graph.markUndoPoint();
+ Paster.this.perform(graph);
+ // Add comment to change set.
+ CommentMetadata cm = graph.getMetadata(CommentMetadata.class);
+ graph.addMetadata(cm.add(comment));
+ }
+ };
+ session.syncRequest(request);
+ }
+
+ public void perform(WriteGraph graph) throws DatabaseException {
+ L0 = Layer0.getInstance(graph);
+ L0X = Layer0X.getInstance(graph);
+ STR = StructuralResource2.getInstance(graph);
+ DIA = DiagramResource.getInstance(graph);
+ MOD = ModelingResources.getInstance(graph);
+ this.graph = graph;
+ this.cu = new ConnectionUtil(graph);
+ this.sourceRoot = graph.sync(new IndexRoot(sourceDiagram));
+ this.targetRoot = graph.sync(new IndexRoot(targetDiagram));
+ this.sourceRootUri = graph.getURI(sourceRoot);
+ this.targetRootUri = graph.getURI(targetRoot);
+ this.operateWithinSameRoot = sourceRoot.equals(targetRoot);
+ try {
+ if (op.cut)
+ cut();
+ else
+ copy();
+ } catch (DatabaseException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new DatabaseException(e);
+ } finally {
+ this.cu = null;
+ this.graph = null;
+ }
+ }
+
+ private void onFinish() {
+ final CopyAdvisor advisor = op.target.getHint(SynchronizationHints.COPY_ADVISOR);
+ if (advisor != null) {
+ try {
+ targetContext.set(GraphSynchronizationHints.READ_TRANSACTION, graph);
+ targetContext.set(GraphSynchronizationHints.WRITE_TRANSACTION, graph);
+ advisor.onFinish(targetContext);
+ } catch (SynchronizationException e) {
+ ErrorHandler eh = targetContext.get(SynchronizationHints.ERROR_HANDLER);
+ eh.error(e.getMessage(), e);
+ } finally {
+ targetContext.set(GraphSynchronizationHints.READ_TRANSACTION, null);
+ targetContext.set(GraphSynchronizationHints.WRITE_TRANSACTION, null);
+ }
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // SIMPLIFICATIONS
+ // ------------------------------------------------------------------------
+
+ interface Procedure {
+ void execute(Resource resource) throws Exception;
+ }
+
+ public void forEachResourceElement(String description, Collection<?> elements, Procedure procedure)
+ throws Exception {
+ for (Object object : elements) {
+ if (object instanceof Resource) {
+ procedure.execute((Resource) object);
+ } else {
+ if (DEBUG) {
+ System.out.println("[" + description + "] Skipping non-resource element: " + object);
+ }
+ }
+ }
+ }
+
+ private void applyPasteOffset(Resource forResource) throws DatabaseException {
+ applyOffset(forResource, op.offset);
+ }
+
+ private void applyOffset(Resource forResource, Point2D offset) throws DatabaseException {
+ AffineTransform at = DiagramGraphUtil.getTransform(graph, forResource);
+ at.preConcatenate(AffineTransform.getTranslateInstance(offset.getX(), offset.getY()));
+ DiagramGraphUtil.setTransform(graph, forResource, at);
+ }
+
+ private void applyPasteOffsetToRouteLine(Resource routeLine) throws DatabaseException {
+ Boolean isHorizontal = graph.getPossibleRelatedValue(routeLine, DIA.IsHorizontal, Bindings.BOOLEAN);
+ Double pos = graph.getPossibleRelatedValue(routeLine, DIA.HasPosition, Bindings.DOUBLE);
+ if (pos == null)
+ pos = 0.0;
+ if (Boolean.TRUE.equals(isHorizontal))
+ pos += op.offset.getY();
+ else
+ pos += op.offset.getX();
+ graph.claimLiteral(routeLine, DIA.HasPosition, pos, Bindings.DOUBLE);
+ }
+
+ // ------------------------------------------------------------------------
+ // CUT LOGIC
+ // ------------------------------------------------------------------------
+
+ Resource parentElement(Resource resource, Resource referenceRelation) throws DatabaseException {
+ // Only allow cutting if reference element has a parent and it is selected for cutting also.
+ Resource referencedParentComponent = graph.getPossibleObject(resource, referenceRelation);
+ if (referencedParentComponent == null)
+ return null;
+ return graph.getPossibleObject(referencedParentComponent, MOD.ComponentToElement);
+ }
+
+ boolean parentIsIncludedInCut(Resource resource, Resource referenceRelation, boolean noParentElementReturnValue) throws DatabaseException {
+ Resource referencedElement = parentElement(resource, referenceRelation);
+ if (referencedElement != null)
+ return op.ea.all.contains(referencedElement);
+ return noParentElementReturnValue;
+ }
+
+ protected void cut() throws Exception {
+ final GraphLayerManager glm = targetContext.get(GraphSynchronizationHints.GRAPH_LAYER_MANAGER);
+
+ final THashSet<Resource> cutElements = new THashSet<Resource>();
+ final CutProcedure registerNames = new CutProcedure() {
+ void postCut(Resource resource, Object cutResult) throws Exception {
+ String name = graph.getPossibleRelatedValue(resource, L0.HasName, Bindings.STRING);
+ if (name != null) {
+ cutElements.add(resource);
+ }
+ }
+ };
+
+ final CutProcedure nodeCutProcedure = new CutProcedure() {
+ @Override
+ void postCut(Resource resource, Object cutResult) throws Exception {
+ if (cutResult != null) {
+ applyPasteOffset(resource);
+
+ if (glm != null) {
+ glm.removeFromAllLayers(graph, resource);
+ glm.putElementOnVisibleLayers(op.target, graph, resource);
+ }
+ }
+ }
+ };
+
+ CutProcedure flagCutProcedure = new CutProcedure() {
+ @Override
+ boolean preCut(Resource resource) throws Exception {
+ return nodeCutProcedure.preCut(resource);
+ }
+ @Override
+ void postCut(Resource resource, Object cutResult) throws Exception {
+ nodeCutProcedure.postCut(resource, cutResult);
+
+ if (FlagUtil.isJoinedInSingleDiagram(graph, resource)) {
+ FlagLabelingScheme scheme = DiagramFlagPreferences.getActiveFlagLabelingScheme(graph);
+ String commonLabel = scheme.generateLabel(graph, targetDiagram);
+ graph.claimLiteral(resource, L0.HasLabel, DIA.FlagLabel, commonLabel);
+ for (Resource otherFlag : FlagUtil.getCounterparts(graph, resource))
+ graph.claimLiteral(otherFlag, L0.HasLabel, DIA.FlagLabel, commonLabel, Bindings.STRING);
+ }
+
+ IOTablesInfo ioTablesInfo = IOTableUtil.getIOTablesInfo(graph, op.targetDiagram);
+ double[] transform = graph.getRelatedValue(resource, DIA.HasTransform, Bindings.DOUBLE_ARRAY);
+ ioTablesInfo.updateBinding(graph, DIA, resource, transform[4], transform[5]);
+
+ // #11077: fix STR.JoinsComposite relations from joins related to moved flags.
+ // Otherwise the JoinsComposite relations will be wrong after the cut-operation.
+ for (Resource join : graph.getObjects(resource, DIA.FlagIsJoinedBy))
+ fixConnectionJoin(join);
+ }
+
+ Set<Resource> flagComposites = new HashSet<>();
+ Set<Resource> joinedComposites = new HashSet<>();
+ Set<Resource> invalidJoinedComposites = new HashSet<>();
+
+ void fixConnectionJoin(Resource join) throws DatabaseException {
+ Collection<Resource> flags = graph.getObjects(join, DIA.JoinsFlag);
+ if (flags.size() < 2) {
+ // Broken join, remove it. Joins that have
+ // < 2 flags attached to it shouldn't exist.
+ graph.deny(join);
+ } else {
+ flagComposites.clear();
+ possibleCompositesOfElements(flags, flagComposites);
+ joinedComposites.clear();
+ joinedComposites.addAll( graph.getObjects(join, STR.JoinsComposite) );
+
+ // Calculate which JoinsComposites need to be added and which removed.
+ invalidJoinedComposites.clear();
+ invalidJoinedComposites.addAll(joinedComposites);
+ invalidJoinedComposites.removeAll(flagComposites);
+ flagComposites.removeAll(joinedComposites);
+
+ if (!invalidJoinedComposites.isEmpty()) {
+ for (Resource invalidComposite : invalidJoinedComposites)
+ graph.deny(join, STR.JoinsComposite, invalidComposite);
+ }
+ if (!flagComposites.isEmpty()) {
+ for (Resource joinedComposite : flagComposites)
+ graph.claim(join, STR.JoinsComposite, joinedComposite);
+ }
+ }
+ }
+
+ Set<Resource> possibleCompositesOfElements(Collection<Resource> elements, Set<Resource> result) throws DatabaseException {
+ for (Resource e : elements) {
+ Resource composite = possibleCompositeOfElement(e);
+ if (composite != null)
+ result.add(composite);
+ }
+ return result;
+ }
+
+ Resource possibleCompositeOfElement(Resource element) throws DatabaseException {
+ Resource diagram = graph.getPossibleObject(element, L0.PartOf);
+ return diagram != null ? graph.getPossibleObject(diagram, MOD.DiagramToComposite) : null;
+ }
+ };
+
+ CutProcedure monitorCutProcedure = new CutProcedure() {
+ @Override
+ void postCut(Resource resource, Object cutResult) throws DatabaseException {
+ if (cutResult != null) {
+ Resource parentElement = parentElement(resource, DIA.HasMonitorComponent);
+ if (parentElement == null) {
+ applyPasteOffset(resource);
+ } else if (!op.ea.all.contains(parentElement)) {
+ Point2D offset = op.offset;
+ if (!op.sameDiagram()) {
+ Resource parentDiagram = graph.sync(new PossibleTypedParent(parentElement, DIA.Diagram));
+ AffineTransform monitoredComponentTr = DiagramGraphUtil.getWorldTransform(graph, parentElement);
+ if (op.targetDiagram.equals(parentDiagram)) {
+ // Monitor is moved back to the parent element diagram.
+ // Must make monitor position relative to the parent position.
+ offset = new Point2D.Double(
+ op.offset.getX() - monitoredComponentTr.getTranslateX(),
+ op.offset.getY() - monitoredComponentTr.getTranslateY());
+ } else {
+ // Monitor is moved to another diagram than the parent element diagram.
+ // Must make monitor position absolute.
+ offset = new Point2D.Double(
+ op.offset.getX() + monitoredComponentTr.getTranslateX(),
+ op.offset.getY() + monitoredComponentTr.getTranslateY());
+ }
+ }
+ applyOffset(resource, offset);
+ }
+
+ if (glm != null) {
+ glm.removeFromAllLayers(graph, resource);
+ glm.putElementOnVisibleLayers(op.target, graph, resource);
+ }
+ }
+ }
+ };
+
+ CutProcedure referenceElementCutProcedure = new CutProcedure() {
+ @Override
+ boolean preCut(Resource resource) throws DatabaseException {
+ // Only allow cutting if reference element has a parent and it is selected for cutting also.
+ return parentIsIncludedInCut(resource, MOD.HasParentComponent, true);
+ }
+ @Override
+ void postCut(Resource resource, Object cutResult) throws Exception {
+ if (cutResult != null) {
+ if (!parentIsIncludedInCut(resource, MOD.HasParentComponent, false)) {
+ applyPasteOffset(resource);
+ }
+
+ if (glm != null) {
+ glm.removeFromAllLayers(graph, resource);
+ glm.putElementOnVisibleLayers(op.target, graph, resource);
+ }
+ }
+ }
+ };
+
+ CutProcedure connectionCutProcedure = new CutProcedure() {
+ @Override
+ void postCut(Resource resource, Object cutResult) throws Exception {
+ if (cutResult != null) {
+ for (Resource rn : graph.getObjects(resource, DIA.HasInteriorRouteNode)) {
+ if (graph.isInstanceOf(rn, DIA.BranchPoint))
+ applyPasteOffset(rn);
+ else if (graph.isInstanceOf(rn, DIA.RouteLine))
+ applyPasteOffsetToRouteLine(rn);
+ }
+
+ if (glm != null) {
+ glm.removeFromAllLayers(graph, resource);
+ glm.putElementOnVisibleLayers(op.target, graph, resource);
+ }
+ }
+ }
+ };
+
+ // Before cutting, disconnect all nodes from connections that are not in
+ // the cut connection set.
+
+ final Set<Resource> selectedConnections = new HashSet<Resource>();
+ forEachResourceElement("Gather connections", op.ea.connections, new Procedure() {
+ @Override
+ public void execute(Resource resource) throws Exception {
+ selectedConnections.add(resource);
+ }
+ });
+
+ Set<Resource> affectedConnections = new HashSet<Resource>();
+ disconnectExcludedConnections("Disconnect Nodes", op.ea.nodeList, selectedConnections, affectedConnections);
+ disconnectExcludedConnections("Disconnect Flags", op.ea.flags, selectedConnections, affectedConnections);
+
+ for (Resource connection : affectedConnections) {
+ // Leave the connection only if it has:
+ // - at least one truly connected :DIA.Connector
+ // - at least 1 route/branch points
+ int connectedConnectors = cu.getConnectedConnectors(connection, null).size();
+ int branchPoints = cu.getBranchPoints(connection, null).size();
+ if (connectedConnectors > 0 && branchPoints > 0)
+ continue;
+
+ // Remove the whole connection.
+ cu.removeConnection(connection);
+ }
+
+ cut("Cut Nodes", op.ea.nodeList, compose(nodeCutProcedure, registerNames));
+ cut("Cut Others", op.ea.others, compose(nodeCutProcedure, registerNames));
+ // Cut reference elements after nodes so that parent relationships can be restored
+ // but before connections so that connections to the reference elements can be copied.
+ cut("Cut References", op.ea.references, compose(referenceElementCutProcedure, registerNames));
+ cut("Cut Flags", op.ea.flags, compose(flagCutProcedure, registerNames));
+ cut("Cut Connections", op.ea.connections, compose(connectionCutProcedure, registerNames));
+ cut("Cut Monitors", op.ea.monitors, compose(monitorCutProcedure, registerNames));
+
+ // Make sure that all the pasted nodes have unique names in their new namespace.
+ // Element names are only diagram-locally unique so this must be done after cut-paste.
+ for (Resource element : cutElements)
+ AddElement.claimFreshElementName(graph, targetDiagram, element);
+
+ onFinish();
+ }
+
+ /**
+ * @param description
+ * @param nodes
+ * @param affectedConnections
+ * @return
+ * @throws Exception
+ */
+ private Set<Resource> disconnectExcludedConnections(String description, Collection<Resource> nodes,
+ final Set<Resource> selectedConnections, final Set<Resource> affectedConnections) throws Exception {
+ final StructuralResource2 str = StructuralResource2.getInstance(graph);
+
+ // Disconnect each connection that is not a part of selectedConnections
+ // but is attached to the listed nodes.
+ forEachResourceElement(description, nodes, new Procedure() {
+ @Override
+ public void execute(Resource resource) throws Exception {
+ for (Resource connector : graph.getObjects(resource, str.IsConnectedTo)) {
+ Resource connection = ConnectionUtil.tryGetConnection(graph, connector);
+ if (connection == null) {
+ // This is a stray connector that has no purpose and should be removed.
+ cu.removeConnectionPart(connector);
+ continue;
+ }
+ if (selectedConnections.contains(connection))
+ continue;
+
+ cu.removeConnectionPart(connector);
+ affectedConnections.add(connection);
+ }
+ }
+ });
+
+ return affectedConnections;
+ }
+
+ /**
+ * @param description
+ * @param elements
+ * @param cutProcedure custom pre- and post-cut processing, may be <code>null</code>
+ * @throws Exception
+ */
+ private void cut(final String description, Collection<Resource> elements, final CutProcedure cutProcedure)
+ throws Exception {
+ final CopyAdvisor advisor = op.target.getHint(SynchronizationHints.COPY_ADVISOR);
+
+ forEachResourceElement(description, elements, new Procedure() {
+ @Override
+ public void execute(Resource resource) throws Exception {
+ if (DEBUG)
+ System.out.println("[" + description + "] " + NameUtils.getSafeName(graph, resource, true));
+
+ if (cutProcedure != null && !cutProcedure.preCut(resource)) {
+ if (DEBUG)
+ System.out.println("[" + description + "] ignoring element cut for " + NameUtils.getSafeName(graph, resource, true));
+ return;
+ }
+
+ Object result = CopyAdvisorUtil.cut(targetContext, graph, advisor, resource, sourceDiagram, targetDiagram);
+
+ if (DEBUG)
+ System.out.println("[" + description + "] RESULT: " + result);
+
+ if (cutProcedure != null)
+ cutProcedure.postCut(resource, result);
+ }
+ });
+ }
+
+ static class CutProcedure {
+ boolean preCut(Resource resource) throws Exception { return true; }
+ void postCut(Resource resource, Object cutResult) throws Exception {}
+ }
+
+ static class ComposedCutProcedure extends CutProcedure {
+ private final CutProcedure[] procedures;
+
+ public static ComposedCutProcedure compose(CutProcedure... procedures) {
+ return new ComposedCutProcedure(procedures);
+ }
+
+ public ComposedCutProcedure(CutProcedure... procedures) {
+ this.procedures = procedures;
+ }
+
+ boolean preCut(Resource resource) throws Exception {
+ for (CutProcedure proc : procedures)
+ if (!proc.preCut(resource))
+ return false;
+ return true;
+ }
+ void postCut(Resource resource, Object cutResult) throws Exception {
+ for (CutProcedure proc : procedures)
+ proc.postCut(resource, cutResult);
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // COPY LOGIC SUPPORT CLASSES
+ // ------------------------------------------------------------------------
+
+ static class IdentifiedElement extends Tuple {
+ public IdentifiedElement(Resource object, IElement element) {
+ super(object, element);
+ }
+ public Resource getObject() {
+ return (Resource) getField(0);
+ }
+ public IElement getElement() {
+ return (IElement) getField(1);
+ }
+ }
+
+ static public class NodeMap {
+
+ Map<Resource, IdentifiedElement> resourceMap = new HashMap<Resource, IdentifiedElement>();
+ Map<IElement, IdentifiedElement> elementMap = new HashMap<IElement, IdentifiedElement>();
+
+ public void put(Resource sourceResource, IElement sourceElement, IdentifiedElement dst) {
+ if (sourceResource == null)
+ throw new NullPointerException("null source resource");
+ resourceMap.put(sourceResource, dst);
+ if (sourceElement != null)
+ elementMap.put(sourceElement, dst);
+ }
+
+ public IdentifiedElement get(Resource source) {
+ return resourceMap.get(source);
+ }
+
+ public IdentifiedElement get(IElement source) {
+ return elementMap.get(source);
+ }
+
+ public Set<Resource> allResources() {
+ return resourceMap.keySet();
+ }
+
+ public Resource getResource(Resource source) {
+ IdentifiedElement ie = resourceMap.get(source);
+ if(ie != null)
+ return ie.getObject();
+ else
+ return null;
+ }
+
+ public Resource getResource(IElement source) {
+ IdentifiedElement ie = elementMap.get(source);
+ if(ie != null)
+ return ie.getObject();
+ else
+ return null;
+ }
+
+ }
+
+ static class ResourceMap extends HashMap<Resource, Resource> {
+ private static final long serialVersionUID = 687528035082504835L;
+ }
+
+ static class StatementMap extends HashMap<Resource, Statement> {
+ private static final long serialVersionUID = 8520092255776208395L;
+ }
+
+ static class MapQueue<K,V> {
+ Map<K, Deque<V>> map = new HashMap<K, Deque<V>>();
+ public void offer(K key, V value) {
+ Deque<V> deque = map.get(key);
+ if (deque == null)
+ map.put(key, deque = new ArrayDeque<V>());
+ deque.offer(value);
+ }
+ public V poll(K key) {
+ Deque<V> deque = map.get(key);
+ if (deque == null)
+ return null;
+ V value = deque.poll();
+ if (deque.isEmpty())
+ map.remove(key);
+ return value;
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // COPY LOGIC
+ // ------------------------------------------------------------------------
+
+ /**
+ * This is necessary to have DIA.Flag type copied over to the copied flag.
+ * Diagram mapping will have problems and potentially break the
+ * configuration if the type is not the same as in the source.
+ */
+ BiFunction<ReadGraph, Statement, StatementEvaluation> statementAdvisor =
+ new BiFunction<ReadGraph, Statement, StatementEvaluation>() {
+ @Override
+ public StatementEvaluation apply(ReadGraph graph, Statement stm) {
+ if (DIA.HasFlagType.equals(stm.getPredicate()))
+ return StatementEvaluation.INCLUDE;
+ return StatementEvaluation.USE_DEFAULT;
+ }
+ };
+
+ CopyProcedure nodeCopyProcedure = new CopyProcedure() {
+ Resource copy(Resource source) throws Exception {
+ Layer0 L0 = Layer0.getInstance(graph);
+
+ Resource copy = null;
+ final CopyAdvisor advisor = op.target.getHint(SynchronizationHints.COPY_ADVISOR);
+ if (advisor != null) {
+ Resource sourceComposite = graph.getPossibleObject(source, L0.PartOf);
+ if (sourceComposite == null || !graph.isInstanceOf(source, DIA.Composite)) {
+ DiagramResource DIA = DiagramResource.getInstance(graph);
+ sourceComposite = OrderedSetUtils.getSingleOwnerList(graph, source, DIA.Composite);
+ }
+ copy = CopyAdvisorUtil.copy(targetContext, graph, advisor, source, sourceComposite, op.targetDiagram);
+ }
+
+ if (copy == null) {
+ copy = CopyAdvisorUtil.copy2(graph, source, statementAdvisor);
+ }
+
+ graph.deny(copy, MOD.IsTemplatized, copy);
+
+ // Add comment to change set.
+ CommentMetadata cm = graph.getMetadata(CommentMetadata.class);
+ graph.addMetadata(cm.add("Copied element " + source + " to " + copy));
+
+ // Add the new element to the diagram composite
+ OrderedSetUtils.add(graph, op.targetDiagram, copy);
+
+ // Give running name to element and increment the counter attached to the diagram.
+ AddElement.claimFreshElementName(graph, op.targetDiagram, copy);
+
+ // Make the diagram consist of the new element
+ graph.claim(op.targetDiagram, L0.ConsistsOf, copy);
+
+ // Put the element on all the currently active layers if possible.
+ GraphLayerManager glm = targetContext.get(GraphSynchronizationHints.GRAPH_LAYER_MANAGER);
+ if (glm != null) {
+ glm.removeFromAllLayers(graph, copy);
+ glm.putElementOnVisibleLayers(op.target, graph, copy);
+ }
+
+ return copy;
+ }
+ @Override
+ void postCopy(Resource source, Resource copy) throws Exception {
+ CopyPasteUtil.copyElementPosition(graph, op.ctx, source, copy, op.offset);
+ }
+ };
+
+ protected void copy() throws Exception {
+ nodeMap = new NodeMap();
+
+ CommonDBUtils.selectClusterSet(graph, targetDiagram);
+
+ // Fill nodeMap with initial Resource->Resource mappings
+ if (op.initialNodeMap != null) {
+ for (Map.Entry<Resource, Resource> entry : op.initialNodeMap.entrySet()) {
+ nodeMap.put(entry.getKey(), null, new IdentifiedElement(entry.getValue(), null));
+ }
+ }
+
+ // Perform copies in a suitable order
+ copyNodes( nodeMap );
+ // Copy reference elements after nodes so that parent relationships can be restored
+ // but before connections so that connections to the reference elements can be copied.
+ copyReferences( nodeMap );
+ copyFlags( nodeMap );
+ copyConnections( nodeMap );
+ // Copy monitors last since their parents must have been copied already.
+ copyMonitors( nodeMap );
+
+ onFinish();
+ }
+
+
+ private NodeMap copyNodes(final NodeMap nodeMap) throws Exception {
+ copy("Copy Others", op.ea.others, nodeMap, nodeCopyProcedure);
+ copy("Copy Nodes", op.ea.nodeList, nodeMap, nodeCopyProcedure);
+
+ return nodeMap;
+ }
+
+ private NodeMap copyReferences(final NodeMap nodeMap) throws Exception {
+ final boolean forceCopyReferences = op.hasOption(ForceCopyReferences.class);
+
+ copy("Copy References", op.ea.references, nodeMap, new CopyProcedure() {
+ @Override
+ Resource copy(Resource source) throws Exception {
+ // Don't copy unless the parent component is copied too.
+ Resource sourceParentComponent = graph.getPossibleObject(source, MOD.HasParentComponent);
+ if (sourceParentComponent == null)
+ return null;
+ Resource sourceParentElement = graph.getPossibleObject(sourceParentComponent, MOD.ComponentToElement);
+ if (sourceParentElement != null) {
+ if (!forceCopyReferences && !op.ea.all.contains(sourceParentElement))
+ return null;
+ // Find copied component
+ IdentifiedElement copiedParentElement = nodeMap.get(sourceParentElement);
+ if (copiedParentElement == null)
+ return null;
+ Resource copiedParentComponent = graph.getPossibleObject(copiedParentElement.getObject(), MOD.ElementToComponent);
+ if (copiedParentComponent == null)
+ return null;
+ return copyReference(source, copiedParentComponent);
+ } else {
+ // Check that the component is part of a diagramless composite before proceeding
+ Resource partOf = graph.getPossibleObject(sourceParentComponent, L0.PartOf);
+ if (partOf == null || graph.hasStatement(partOf, MOD.CompositeToDiagram))
+ return null;
+ // Resolve the matching parent component from the target context.
+ Resource targetParentComponent = resolveTargetComponent(sourceParentComponent);
+ if (targetParentComponent == null)
+ return null;
+ return copyReference(source, targetParentComponent);
+ }
+ }
+
+ private Resource resolveTargetComponent(Resource sourceParentComponent) throws DatabaseException {
+ if (operateWithinSameRoot)
+ return sourceParentComponent;
+ // Directly map relative source component URI into target root namespace.
+ String sourceUri = graph.getURI(sourceParentComponent);
+ String targetUri = sourceUri.replace(sourceRootUri, targetRootUri);
+ Resource targetParentComponent = graph.getPossibleResource(targetUri);
+ return targetParentComponent;
+ }
+
+ private Resource copyReference(Resource source, Resource parentComponent) throws Exception {
+ Resource referenceRelation = graph.getPossibleObject(source, MOD.HasReferenceRelation);
+ if (referenceRelation == null)
+ return null;
+
+ Resource relationCopy = CopyAdvisorUtil.copy4(graph, referenceRelation);
+ if (relationCopy == null)
+ return null;
+
+ Resource copy = nodeCopyProcedure.copy(source);
+
+ // WORKAROUND: The result consists of a badly copied reference relation.
+ // Remove it. How the relation is copied depends on whether the copy target
+ // is the same model or not. If it is, the relation is copied, but invalidly
+ // and if the target is not the same model, the relation is simply referenced
+ // with a uni-directional L0.ConsistsOf relation.
+ for (Resource o : graph.getObjects(copy, L0.ConsistsOf)) {
+ boolean ownedByCopy = graph.hasStatement(o, L0.PartOf, copy);
+ if (ownedByCopy) {
+ graph.deny(copy, L0.ConsistsOf, o);
+ RemoverUtil.remove(graph, o);
+ } else {
+ graph.deny(copy, L0.ConsistsOf, o);
+ }
+ }
+
+ // The element the copied reference is attached to was also copied.
+ // This means that we must attach the copied reference to its
+ // original component's copy.
+ graph.deny(copy, MOD.HasParentComponent);
+ if(parentComponent != null)
+ graph.claim(copy, MOD.HasParentComponent, MOD.HasParentComponent_Inverse, parentComponent);
+
+ // Attach reference relation
+ graph.claim(copy, L0.ConsistsOf, L0.PartOf, relationCopy);
+ graph.claim(copy, MOD.HasReferenceRelation, MOD.HasReferenceRelation_Inverse, relationCopy);
+
+ // #7348: renew reference relation GUID identifiers properly
+ Layer0Utils.renewIdentifier(graph, relationCopy);
+ for (Resource invRel : graph.getObjects(relationCopy, L0.ConsistsOf))
+ Layer0Utils.renewIdentifier(graph, invRel);
+
+ return copy;
+ }
+
+ @Override
+ void postCopy(Resource source, Resource copy) throws Exception {
+ // Must fix element position if the copied reference element
+ // doesn't have a visible parent element.
+ Resource parentComponent = graph.getPossibleObject(source, MOD.HasParentComponent);
+ if (parentComponent == null)
+ return;
+ Resource parentElement = graph.getPossibleObject(parentComponent, MOD.ComponentToElement);
+ if (parentElement == null)
+ CopyPasteUtil.copyElementPosition(graph, op.ctx, source, copy, op.offset);
+ }
+ });
+
+ return nodeMap;
+ }
+
+ private NodeMap copyFlags(NodeMap nodeMap) throws Exception {
+ final Layer0 l0 = Layer0.getInstance(graph);
+ final DiagramResource dia = DiagramResource.getInstance(graph);
+
+ class FlagCopy {
+ private final Map<Resource, Resource> selectedFlags = new HashMap<Resource, Resource>();
+ private final Map<Resource, Resource> flagSelectedCounterpart = new HashMap<Resource, Resource>();
+
+ /**
+ * Analyze which flag pairs are selected
+ *
+ * @throws DatabaseException
+ */
+ private void analyzeFlagSelection() throws DatabaseException {
+ for (Resource flag : op.ea.flags) {
+ selectedFlags.put(flag, flag);
+ }
+ for (Resource flag : selectedFlags.keySet()) {
+ boolean external = FlagUtil.isExternal(graph, flag);
+ boolean inSingleDiagram = FlagUtil.isJoinedInSingleDiagram(graph, flag);
+ if (!external && inSingleDiagram) {
+ // FIXME: this doesn't take into account local merged flags, which is a corner case but still possible
+ Resource counterpart = FlagUtil.getPossibleCounterpart(graph, flag);
+ if (selectedFlags.containsKey(counterpart)) {
+ flagSelectedCounterpart.put(flag, counterpart);
+ flagSelectedCounterpart.put(counterpart, flag);
+ }
+ }
+ }
+ }
+
+ /**
+ * Reconnect copied flag pairs.
+ * @throws DatabaseException
+ */
+ private void reconnectLocalFlagPairs(NodeMap nodeMap) throws DatabaseException {
+ FlagLabelingScheme scheme = DiagramFlagPreferences.getActiveFlagLabelingScheme(graph);
+ Resource diagram = op.targetDiagram;
+
+ Set<Resource> visited = new HashSet<Resource>();
+ ArrayDeque<Resource> queue = new ArrayDeque<Resource>(flagSelectedCounterpart.values());
+ while (!queue.isEmpty()) {
+ Resource flag = queue.poll();
+ Resource counterpart = flagSelectedCounterpart.get(flag);
+ if (!visited.add(flag) || !visited.add(counterpart) || counterpart == null)
+ continue;
+
+ // Get copies
+ Resource flagSourceElement = selectedFlags.get(flag);
+ Resource counterpartSourceElement = selectedFlags.get(counterpart);
+
+ IdentifiedElement flagCopy = nodeMap.get(flagSourceElement);
+ IdentifiedElement counterpartCopy = nodeMap.get(counterpartSourceElement);
+
+ FlagUtil.join(graph, flagCopy.getObject(), counterpartCopy.getObject());
+
+ // Provide fresh labeling for connected flags if possible
+ if (scheme != null) {
+ String label = scheme.generateLabel(graph, diagram);
+ if (label != null) {
+ graph.claimLiteral(flagCopy.getObject(), l0.HasLabel, dia.FlagLabel, label, Bindings.STRING);
+ graph.claimLiteral(counterpartCopy.getObject(), l0.HasLabel, dia.FlagLabel, label, Bindings.STRING);
+ }
+ }
+ }
+ }
+
+ public void perform(NodeMap nodeMap) throws Exception {
+ analyzeFlagSelection();
+
+ copy("Copy Flags", op.ea.flags, nodeMap, new CopyProcedure() {
+ @Override
+ Resource copy(Resource source) throws Exception {
+ return nodeCopyProcedure.copy(source);
+ }
+ @Override
+ public void postCopy(Resource source, Resource copy) throws Exception {
+ AffineTransform at = CopyPasteUtil.copyElementPosition(graph, op.ctx, source, copy, op.offset);
+
+ // Update flag table binding
+ IOTablesInfo ioTablesInfo = IOTableUtil.getIOTablesInfo(graph, op.targetDiagram);
+ ioTablesInfo.updateBinding(graph, DIA, copy, at.getTranslateX(), at.getTranslateY());
+
+ // All label properties must be removed from
+ // the copied flags. Disconnected flags are
+ // not supposed to have labels, and the right
+ // place to reset the labels is when the flags
+ // are reconnected to their respective
+ // counterparts.
+ graph.denyValue(copy, l0.HasLabel);
+ }
+ });
+
+ reconnectLocalFlagPairs(nodeMap);
+ }
+
+ }
+
+ new FlagCopy().perform( nodeMap );
+ return nodeMap;
+ }
+
+ private NodeMap copyMonitors(final NodeMap nodeMap) throws Exception {
+ copy("Copy Monitors", op.ea.monitors, nodeMap, new CopyProcedure() {
+ @Override
+ Resource copy(Resource source) throws Exception {
+ // Don't copy monitors if they are copied without
+ // their parent element into another root (model).
+ if (!operateWithinSameRoot) {
+ Resource monitorComponent = graph.getPossibleObject(source, DIA.HasMonitorComponent);
+ if (monitorComponent != null) {
+ Resource monitorElement = graph.getPossibleObject(monitorComponent, MOD.ComponentToElement);
+ if (monitorElement == null || !op.ea.all.contains(monitorElement))
+ return null;
+ }
+ }
+ Resource copy = nodeCopyProcedure.copy(source);
+ return copy;
+ }
+ @Override
+ void postCopy(Resource source, Resource copy) throws Exception {
+ // Find the component and diagram element the source monitor is
+ // connected to.
+ Resource monitorElement = null;
+ Resource monitorComponent = graph.getPossibleObject(source, DIA.HasMonitorComponent);
+ if (monitorComponent != null) {
+ monitorElement = graph.getPossibleObject(monitorComponent, MOD.ComponentToElement);
+ }
+
+ if (monitorElement != null && op.ea.all.contains(monitorElement)) {
+ // The element the copied monitor is attached was also copied.
+ // This means that we must attach the copied monitor to its
+ // original components copy.
+
+ // Remove old association
+ graph.deny(copy, DIA.HasMonitorComponent);
+
+ // Associate to copied component
+ IdentifiedElement parent = nodeMap.get(monitorElement);
+ if (parent != null) {
+ monitorComponent = graph.getPossibleObject(parent.getObject(), MOD.ElementToComponent);
+ if (monitorComponent != null)
+ graph.claim(copy, DIA.HasMonitorComponent, monitorComponent);
+ } else {
+ //throw new PasteException("no parent could be found for monitored element " + monitoredElement);
+ }
+ } else {
+ // The element the copied monitor is attached was not copied
+ // or there is no element for the monitored component.
+ // This means that the copied monitor must be kept attached
+ // to the same component no matter where it is in the model,
+ // unless the copy is done into another model.
+ if (operateWithinSameRoot && monitorComponent != null)
+ graph.claim(copy, DIA.HasMonitorComponent, monitorComponent);
+
+ Point2D offset = op.offset;
+ if (!op.sameDiagram()) {
+ if (monitorElement != null) {
+ // Monitor doesn't have a diagram parent element any
+ // more, must recalculate its offset.
+ AffineTransform monitoredComponentTr = DiagramGraphUtil.getWorldTransform(graph, monitorElement);
+ offset = new Point2D.Double(
+ op.offset.getX() + monitoredComponentTr.getTranslateX(),
+ op.offset.getY() + monitoredComponentTr.getTranslateY());
+ }
+ }
+ CopyPasteUtil.copyElementPosition(graph, op.ctx, source, copy, offset);
+ }
+
+ // Copy monitor suffix from original to copy.
+ String monitorSuffix = graph.getPossibleRelatedValue(source, DIA.HasMonitorSuffix, Bindings.STRING);
+ if (monitorSuffix != null)
+ graph.claimLiteral(copy, DIA.HasMonitorSuffix, monitorSuffix, Bindings.STRING);
+
+ // Copy used property obtains for monitor template data.
+ graph.deny(copy, L0X.ObtainsProperty);
+ for (Statement stm : graph.getStatements(source, L0X.ObtainsProperty)) {
+ graph.claim(copy, stm.getPredicate(), null, stm.getObject());
+ }
+ }
+ });
+
+ return nodeMap;
+ }
+
+ /**
+ * @param description
+ * @param elements
+ * @param nodeMap
+ * @param copyProcedure
+ * @throws Exception
+ */
+ private void copy(final String description, Collection<Resource> elements, final NodeMap nodeMap,
+ final CopyProcedure copyProcedure) throws Exception {
+ if (copyProcedure == null)
+ throw new IllegalArgumentException("null copy procedure");
+
+ forEachResourceElement(description, elements, new Procedure() {
+ @Override
+ public void execute(Resource resource) throws Exception {
+ if (DEBUG)
+ System.out.println("[" + description + "] " + NameUtils.getSafeName(graph, resource, true));
+ Resource copy = copyProcedure.copy(resource);
+ if (copy != null) {
+ if (DEBUG)
+ System.out.println("[" + description + "] " + NameUtils.getSafeName(graph, resource, true) + " copied as " + NameUtils.getSafeName(graph, copy, true));
+ nodeMap.put(resource, null, new IdentifiedElement(copy, null));
+ if (op.copyMap != null)
+ op.copyMap.put(resource, copy);
+ copyProcedure.postCopy(resource, copy);
+ }
+ }
+ });
+ }
+
+ public static class RouteLine extends Tuple2 {
+ public RouteLine(Double position, Boolean horizontal) {
+ super(position, horizontal);
+ }
+ public double getPosition() {
+ Double pos = (Double) get(0);
+ return pos != null ? pos : 0.0;
+ }
+ public boolean isHorizontal() {
+ return Boolean.TRUE.equals(get(1));
+ }
+ }
+
+ public static class BranchPoint extends Tuple3 {
+ public BranchPoint(AffineTransform at, Boolean horizontal, Boolean vertical) {
+ super(at, horizontal, vertical);
+ }
+ public AffineTransform getTransform() {
+ return (AffineTransform) get(0);
+ }
+ }
+
+ public static RouteLine readRouteLine(ReadGraph graph, Resource src) throws DatabaseException {
+ DiagramResource DIA = DiagramResource.getInstance(graph);
+ Double pos = graph.getPossibleRelatedValue(src, DIA.HasPosition, Bindings.DOUBLE);
+ Boolean hor = graph.getPossibleRelatedValue(src, DIA.IsHorizontal, Bindings.BOOLEAN);
+ return new RouteLine(pos, hor);
+ }
+
+ public static BranchPoint readBranchPoint(ReadGraph graph, Resource src) throws DatabaseException {
+ DiagramResource DIA = DiagramResource.getInstance(graph);
+ AffineTransform at = DiagramGraphUtil.getTransform(graph, src);
+ boolean hor = graph.hasStatement(src, DIA.Horizontal);
+ boolean ver = graph.hasStatement(src, DIA.Vertical);
+ return new BranchPoint(at, hor, ver);
+ }
+
+ /**
+ * @param nodeMap
+ * @return
+ * @throws Exception
+ */
+ private NodeMap copyConnections(final NodeMap nodeMap) throws Exception {
+ final StructuralResource2 STR = StructuralResource2.getInstance(graph);
+ final DiagramResource DIA = DiagramResource.getInstance(graph);
+
+// final IModelingRules rules = graph.syncRequest(DiagramRequests.getModelingRules(op.sourceDiagram, null));
+// if (rules == null)
+// throw new IllegalArgumentException("source diagram offers no modeling rules");
+
+ final CopyAdvisor ca = op.target.getHint(SynchronizationHints.COPY_ADVISOR);
+ if (ca == null)
+ throw new UnsupportedOperationException("Cannot copy connections, no copy advisor available for diagram "
+ + op.target);
+
+ forEachResourceElement("Copy Connections", op.ea.connections, new Procedure() {
+ @Override
+ public void execute(Resource sourceObject) throws DatabaseException {
+ copyConnection(sourceObject);
+ }
+
+ private void copyConnection(Resource sourceObject) throws DatabaseException {
+ // For associating source<->destination connection parts
+ final Map<Object, Object> resourceMap = new THashMap<Object, Object>();
+ // For associating source connectors to source nodes
+ final StatementMap connectorToNode = new StatementMap();
+
+ // 1. copy connection
+ // - This will also copy interior route nodes
+ // - But will leave out the DIA.AreConnected relations between route nodes
+ Resource sourceDiagram = graph.getPossibleObject(sourceObject, Layer0.getInstance(graph).PartOf);
+ if (sourceDiagram == null)
+ sourceDiagram = OrderedSetUtils.getSingleOwnerList(graph, sourceObject, DIA.Diagram);
+ Resource copy = CopyAdvisorUtil.copy(targetContext, graph, ca, sourceObject, sourceDiagram, op.targetDiagram, resourceMap);
+ if (copy == null)
+ throw new UnsupportedOperationException("Could not copy connection " + sourceObject);
+ OrderedSetUtils.addFirst(graph, op.targetDiagram, copy);
+
+ graph.deny(copy, MOD.IsTemplatized, copy);
+
+ AddElement.claimFreshElementName(graph, op.targetDiagram, copy);
+
+ AddConnection.copyConnectionType(graph, sourceObject, copy);
+
+ GraphLayerManager glm = targetContext.get(GraphSynchronizationHints.GRAPH_LAYER_MANAGER);
+ if (glm != null) {
+ glm.removeFromAllLayers(graph, copy);
+ glm.putElementOnVisibleLayers(op.target, graph, copy);
+ }
+
+ nodeMap.put(sourceObject, null, new IdentifiedElement(copy, null));
+ if (op.copyMap != null)
+ op.copyMap.put(sourceObject, copy);
+
+ // WORKAROUND: CopyAdvisorUtil.copy(..., resourceMap)
+ // implementations do not all support filling the resource map.
+ // Thus we resort to the old logic if resourceMap is empty at this point.
+ final boolean mapResources = resourceMap.isEmpty();
+
+ // 2. associate source connection parts to destination connection parts
+
+ // Connectors
+ Collection<Statement> sourceHasConnectors = graph.getStatements(sourceObject, DIA.HasConnector);
+ MapQueue<Resource, Resource> connectorsByType = new MapQueue<Resource, Resource>();
+ for (Statement hasConnector : sourceHasConnectors) {
+ connectorsByType.offer(hasConnector.getPredicate(), hasConnector.getObject());
+ for (Statement connects : graph.getStatements(hasConnector.getObject(), STR.Connects)) {
+ if (!sourceObject.equals(connects.getObject())) {
+ connectorToNode.put(hasConnector.getObject(), connects);
+ break;
+ }
+ }
+ }
+ if (mapResources) {
+ for (Statement hasConnector : graph.getStatements(copy, DIA.HasConnector)) {
+ Resource srcConnector = connectorsByType.poll(hasConnector.getPredicate());
+ resourceMap.put(srcConnector, hasConnector.getObject());
+ }
+ }
+ // 2.2. Offset interior route nodes
+ Collection<Resource> sourceInteriorRouteNodes = graph.getObjects(sourceObject, DIA.HasInteriorRouteNode);
+ if (mapResources) {
+ // WORKAROUND: for cases where resourceMap was not filled by
+ // the copy operation. Still needed because TG copying does
+ // not output this information.
+ Queue<Resource> branchPoints = new ArrayDeque<Resource>(sourceInteriorRouteNodes.size());
+ Queue<Resource> routeLines = new ArrayDeque<Resource>(sourceInteriorRouteNodes.size());
+ for (Resource dst : graph.getObjects(copy, DIA.HasInteriorRouteNode)) {
+ if (graph.isInstanceOf(dst, DIA.BranchPoint))
+ branchPoints.offer(dst);
+ else if (graph.isInstanceOf(dst, DIA.RouteLine))
+ routeLines.offer(dst);
+ }
+ for (Resource src : sourceInteriorRouteNodes) {
+ if (graph.isInstanceOf(src, DIA.BranchPoint)) {
+ Resource dst = branchPoints.poll();
+ resourceMap.put(src, dst);
+ BranchPoint bp = readBranchPoint(graph, src);
+ AffineTransform at = bp.getTransform();
+ at.preConcatenate(offsetTransform);
+ DiagramGraphUtil.setTransform(graph, dst, at);
+ }
+ else if (graph.isInstanceOf(src, DIA.RouteLine)) {
+ Resource dst = routeLines.poll();
+ resourceMap.put(src, dst);
+ RouteLine rl = readRouteLine(graph, src);
+ double newPos = rl.getPosition() + (rl.isHorizontal() ? op.offset.getY() : op.offset.getX());
+ graph.claimLiteral(dst, DIA.HasPosition, newPos, Bindings.DOUBLE);
+ }
+ }
+ } else {
+ for (Resource src : sourceInteriorRouteNodes) {
+ Resource dst = (Resource) resourceMap.get(src);
+ if (dst != null) {
+ if (graph.isInstanceOf(src, DIA.BranchPoint)) {
+ BranchPoint bp = readBranchPoint(graph, src);
+ AffineTransform at = bp.getTransform();
+ at.preConcatenate(offsetTransform);
+ DiagramGraphUtil.setTransform(graph, dst, at);
+ } else if (graph.isInstanceOf(src, DIA.RouteLine)) {
+ RouteLine rl = readRouteLine(graph, src);
+ double newPos = rl.getPosition() + (rl.isHorizontal() ? op.offset.getY() : op.offset.getX());
+ graph.claimLiteral(dst, DIA.HasPosition, newPos, Bindings.DOUBLE);
+ }
+ }
+ }
+ }
+
+ // 3. Connect connection parts according to how the source is connected
+ for (Resource src : sourceInteriorRouteNodes) {
+ Resource dst = (Resource) resourceMap.get(src);
+ for (Resource connectedToSrc : graph.getObjects(src, DIA.AreConnected)) {
+ Resource connectedToDst = (Resource) resourceMap.get(connectedToSrc);
+ if (connectedToDst != null) {
+ graph.claim(dst, DIA.AreConnected, DIA.AreConnected, connectedToDst);
+ } else {
+ throw new DatabaseException("Connection copying failed due to an invalid DIA.AreConnected link between source resources " + src + " <-> " + connectedToSrc);
+ }
+ }
+ }
+ for (Statement hasConnector : sourceHasConnectors) {
+ Resource srcConnector = hasConnector.getObject();
+ Resource dstConnector = (Resource) resourceMap.get(srcConnector);
+ Statement srcConnects = connectorToNode.get(srcConnector);
+
+ // Connect to copied nodes
+ IdentifiedElement dstNode = nodeMap.get(srcConnects.getObject());
+ if (dstNode == null)
+ throw new DatabaseException("Source element "
+ + NameUtils.getURIOrSafeNameInternal(graph, srcConnects.getObject())
+ + " not copied causing copying of connection "
+ + NameUtils.getURIOrSafeNameInternal(graph, sourceObject)
+ +" to fail.");
+ graph.claim(dstConnector, srcConnects.getPredicate(), dstNode.getObject());
+
+ // Connect to other copied route nodes
+ for (Resource connectedToSrc : graph.getObjects(srcConnector, DIA.AreConnected)) {
+ Resource connectedToDst = (Resource) resourceMap.get(connectedToSrc);
+ graph.claim(dstConnector, DIA.AreConnected, DIA.AreConnected, connectedToDst);
+ }
+ }
+
+ // 4. Make sure MOD.ConnectorToComponent relations are copied as well.
+ // Otherwise diagram mapping will do bad things on the model.
+ Resource sourceComponent = graph.getPossibleObject(sourceObject, MOD.ElementToComponent);
+ if (sourceComponent != null) {
+ for (Statement hasConnector : sourceHasConnectors) {
+ Resource sourceConnector = hasConnector.getObject();
+ Resource targetConnector = (Resource) resourceMap.get(sourceConnector);
+ // Should have been defined back in steps 1-2.
+ assert targetConnector != null;
+ Statement sourceConnectorToComponent = graph.getPossibleStatement(sourceConnector, MOD.ConnectorToComponent);
+ if (sourceConnectorToComponent == null)
+ continue;
+ if (!sourceConnectorToComponent.getObject().equals(sourceComponent))
+ continue;
+ Resource targetComponent = graph.getPossibleObject(copy, MOD.ElementToComponent);
+ if (targetComponent == null)
+ continue;
+
+ graph.claim(targetConnector, sourceConnectorToComponent.getPredicate(), targetComponent);
+
+ // #6190 & apros:#11435: Ensure that MOD.HasConnectionMappingSpecification is added to target
+ for (Resource connectionMappingSpec : graph.getObjects(sourceConnector, MOD.HasConnectionMappingSpecification))
+ graph.claim(targetConnector, MOD.HasConnectionMappingSpecification, connectionMappingSpec);
+ }
+ }
+ }
+ });
+
+ return nodeMap;
+ }
+
+ class CopyProcedure {
+ Resource copy(Resource source) throws Exception { throw new UnsupportedOperationException(); }
+ void postCopy(Resource source, Resource copy) throws Exception {}
+ }
+
+ /**
+ * @param judgment <code>null</code> if no judgement is available in which
+ * case defaultValue is always returned
+ * @param connectionPoint
+ * @param defaultValue
+ * @return
+ * @throws DatabaseException
+ */
+ @SuppressWarnings("unused")
+ private static Resource getAttachmentRelation(ReadGraph graph, ConnectionJudgement judgment,
+ IConnectionPoint connectionPoint, Resource defaultValue) throws DatabaseException {
+ if (judgment == null || !(connectionPoint instanceof CPTerminal) || judgment.attachmentRelations == null)
+ return defaultValue;
+ Resource attachment = judgment.attachmentRelations.get(graph, (CPTerminal) connectionPoint);
+ return attachment != null ? attachment : defaultValue;
+ }
+
+ /**
+ * Get node map of copied variables. Map contains original and new resources.
+ *
+ * @return NodeMap of copied resources or null if copy has not been performed
+ */
+ public NodeMap getNodeMap() {
+ return nodeMap;
+ }
+
+ protected PasteOperation getOperation() {
+ return op;
+ }
+
+ public WriteGraph getGraph() {
+ return graph;
+ }
+}