X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.diagram%2Fsrc%2Forg%2Fsimantics%2Fdiagram%2Fhandler%2FPaster.java;h=6672599073b1b18e717bd1f79c36f6e3ac0bdb78;hp=a103ab8bc9fdca3f529d7534719937b194e9d90a;hb=c312f895bb8767ddd23d5d7a64b0b9d2105d41f0;hpb=969bd23cab98a79ca9101af33334000879fb60c5 diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/handler/Paster.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/handler/Paster.java index a103ab8bc..667259907 100644 --- a/bundles/org.simantics.diagram/src/org/simantics/diagram/handler/Paster.java +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/handler/Paster.java @@ -1,1358 +1,1371 @@ -package org.simantics.diagram.handler; - -import static org.simantics.diagram.handler.Paster.ComposedCutProcedure.compose; -import gnu.trove.map.hash.THashMap; -import gnu.trove.set.hash.THashSet; - -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 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.BinaryFunction; -import org.simantics.utils.datastructures.map.Tuple; - -/** - * @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; - } - - private void cut() throws Exception { - final GraphLayerManager glm = targetContext.get(GraphSynchronizationHints.GRAPH_LAYER_MANAGER); - - final THashSet cutElements = new THashSet(); - 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 flagComposites = new HashSet<>(); - Set joinedComposites = new HashSet<>(); - Set invalidJoinedComposites = new HashSet<>(); - - void fixConnectionJoin(Resource join) throws DatabaseException { - Collection 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 possibleCompositesOfElements(Collection elements, Set 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 selectedConnections = new HashSet(); - forEachResourceElement("Gather connections", op.ea.connections, new Procedure() { - @Override - public void execute(Resource resource) throws Exception { - selectedConnections.add(resource); - } - }); - - Set affectedConnections = new HashSet(); - 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 disconnectExcludedConnections(String description, Collection nodes, - final Set selectedConnections, final Set 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 null - * @throws Exception - */ - private void cut(final String description, Collection 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 resourceMap = new HashMap(); - Map elementMap = new HashMap(); - - 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 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 { - private static final long serialVersionUID = 687528035082504835L; - } - - static class StatementMap extends HashMap { - private static final long serialVersionUID = 8520092255776208395L; - } - - static class MapQueue { - Map> map = new HashMap>(); - public void offer(K key, V value) { - Deque deque = map.get(key); - if (deque == null) - map.put(key, deque = new ArrayDeque()); - deque.offer(value); - } - public V poll(K key) { - Deque 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. - */ - BinaryFunction statementAdvisor = - new BinaryFunction() { - @Override - public StatementEvaluation call(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); - } - }; - - private 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 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 selectedFlags = new HashMap(); - private final Map flagSelectedCounterpart = new HashMap(); - - /** - * 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) { - 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 visited = new HashSet(); - ArrayDeque queue = new ArrayDeque(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 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 resourceMap = new THashMap(); - // 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 sourceHasConnectors = graph.getStatements(sourceObject, DIA.HasConnector); - MapQueue connectorsByType = new MapQueue(); - 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 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 branchPoints = new ArrayDeque(sourceInteriorRouteNodes.size()); - Queue routeLines = new ArrayDeque(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); - graph.claim(dst, DIA.AreConnected, DIA.AreConnected, connectedToDst); - } - } - 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 null 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; - } - -} +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 cutElements = new THashSet(); + 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 flagComposites = new HashSet<>(); + Set joinedComposites = new HashSet<>(); + Set invalidJoinedComposites = new HashSet<>(); + + void fixConnectionJoin(Resource join) throws DatabaseException { + Collection 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 possibleCompositesOfElements(Collection elements, Set 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 selectedConnections = new HashSet(); + forEachResourceElement("Gather connections", op.ea.connections, new Procedure() { + @Override + public void execute(Resource resource) throws Exception { + selectedConnections.add(resource); + } + }); + + Set affectedConnections = new HashSet(); + 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 disconnectExcludedConnections(String description, Collection nodes, + final Set selectedConnections, final Set 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 null + * @throws Exception + */ + private void cut(final String description, Collection 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 resourceMap = new HashMap(); + Map elementMap = new HashMap(); + + 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 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 { + private static final long serialVersionUID = 687528035082504835L; + } + + static class StatementMap extends HashMap { + private static final long serialVersionUID = 8520092255776208395L; + } + + static class MapQueue { + Map> map = new HashMap>(); + public void offer(K key, V value) { + Deque deque = map.get(key); + if (deque == null) + map.put(key, deque = new ArrayDeque()); + deque.offer(value); + } + public V poll(K key) { + Deque 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 statementAdvisor = + new BiFunction() { + @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 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 selectedFlags = new HashMap(); + private final Map flagSelectedCounterpart = new HashMap(); + + /** + * 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 visited = new HashSet(); + ArrayDeque queue = new ArrayDeque(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 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 resourceMap = new THashMap(); + // 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 sourceHasConnectors = graph.getStatements(sourceObject, DIA.HasConnector); + MapQueue connectorsByType = new MapQueue(); + 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 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 branchPoints = new ArrayDeque(sourceInteriorRouteNodes.size()); + Queue routeLines = new ArrayDeque(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 null 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; + } +}