X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.diagram%2Fsrc%2Forg%2Fsimantics%2Fdiagram%2Fhandler%2FPaster.java;fp=bundles%2Forg.simantics.diagram%2Fsrc%2Forg%2Fsimantics%2Fdiagram%2Fhandler%2FPaster.java;h=a103ab8bc9fdca3f529d7534719937b194e9d90a;hb=969bd23cab98a79ca9101af33334000879fb60c5;hp=0000000000000000000000000000000000000000;hpb=866dba5cd5a3929bbeae85991796acb212338a08;p=simantics%2Fplatform.git 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 new file mode 100644 index 000000000..a103ab8bc --- /dev/null +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/handler/Paster.java @@ -0,0 +1,1358 @@ +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; + } + +}