]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.diagram/src/org/simantics/diagram/handler/Paster.java
Paster.getGraph() method for overriding classes.
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / handler / Paster.java
index 153199d6cfae6e81d47a095b9bd460e22c3a0a08..6672599073b1b18e717bd1f79c36f6e3ac0bdb78 100644 (file)
-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.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);
+
+                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;
+       }
+}