]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram/src/org/simantics/diagram/handler/Paster.java
6672599073b1b18e717bd1f79c36f6e3ac0bdb78
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / handler / Paster.java
1 package org.simantics.diagram.handler;
2
3 import static org.simantics.diagram.handler.Paster.ComposedCutProcedure.compose;
4
5 import java.awt.geom.AffineTransform;
6 import java.awt.geom.Point2D;
7 import java.util.ArrayDeque;
8 import java.util.Collection;
9 import java.util.Deque;
10 import java.util.HashMap;
11 import java.util.HashSet;
12 import java.util.Map;
13 import java.util.Queue;
14 import java.util.Set;
15 import java.util.function.BiFunction;
16
17 import org.simantics.databoard.Bindings;
18 import org.simantics.db.ReadGraph;
19 import org.simantics.db.Resource;
20 import org.simantics.db.Session;
21 import org.simantics.db.Statement;
22 import org.simantics.db.WriteGraph;
23 import org.simantics.db.common.CommentMetadata;
24 import org.simantics.db.common.request.IndexRoot;
25 import org.simantics.db.common.request.PossibleTypedParent;
26 import org.simantics.db.common.request.WriteRequest;
27 import org.simantics.db.common.utils.CommonDBUtils;
28 import org.simantics.db.common.utils.NameUtils;
29 import org.simantics.db.common.utils.OrderedSetUtils;
30 import org.simantics.db.exception.DatabaseException;
31 import org.simantics.db.layer0.util.RemoverUtil;
32 import org.simantics.db.request.Write;
33 import org.simantics.diagram.content.ConnectionUtil;
34 import org.simantics.diagram.flag.DiagramFlagPreferences;
35 import org.simantics.diagram.flag.FlagLabelingScheme;
36 import org.simantics.diagram.flag.FlagUtil;
37 import org.simantics.diagram.flag.IOTableUtil;
38 import org.simantics.diagram.flag.IOTablesInfo;
39 import org.simantics.diagram.handler.PasteOperation.ForceCopyReferences;
40 import org.simantics.diagram.internal.DebugPolicy;
41 import org.simantics.diagram.stubs.DiagramResource;
42 import org.simantics.diagram.synchronization.CopyAdvisor;
43 import org.simantics.diagram.synchronization.ErrorHandler;
44 import org.simantics.diagram.synchronization.IModifiableSynchronizationContext;
45 import org.simantics.diagram.synchronization.StatementEvaluation;
46 import org.simantics.diagram.synchronization.SynchronizationException;
47 import org.simantics.diagram.synchronization.SynchronizationHints;
48 import org.simantics.diagram.synchronization.graph.AddConnection;
49 import org.simantics.diagram.synchronization.graph.AddElement;
50 import org.simantics.diagram.synchronization.graph.CopyAdvisorUtil;
51 import org.simantics.diagram.synchronization.graph.DiagramGraphUtil;
52 import org.simantics.diagram.synchronization.graph.GraphSynchronizationHints;
53 import org.simantics.diagram.synchronization.graph.layer.GraphLayerManager;
54 import org.simantics.g2d.element.IElement;
55 import org.simantics.layer0.Layer0;
56 import org.simantics.modeling.ModelingResources;
57 import org.simantics.operation.Layer0X;
58 import org.simantics.scl.runtime.tuple.Tuple2;
59 import org.simantics.scl.runtime.tuple.Tuple3;
60 import org.simantics.structural.stubs.StructuralResource2;
61 import org.simantics.structural2.modelingRules.CPTerminal;
62 import org.simantics.structural2.modelingRules.ConnectionJudgement;
63 import org.simantics.structural2.modelingRules.IConnectionPoint;
64 import org.simantics.utils.datastructures.map.Tuple;
65
66 import gnu.trove.map.hash.THashMap;
67 import gnu.trove.set.hash.THashSet;
68
69 /**
70  * @author Tuukka Lehtonen
71  */
72 public class Paster {
73
74     private final boolean                           DEBUG = DebugPolicy.DEBUG_COPY_PASTE | true;
75
76     private final Session                           session;
77
78     private final PasteOperation                    op;
79
80     private final Resource                          sourceDiagram;
81
82     private final Resource                          targetDiagram;
83
84     private final IModifiableSynchronizationContext targetContext;
85
86     /**
87      * Set for the duration of the write.
88      */
89     private WriteGraph                              graph;
90     private ConnectionUtil                          cu;
91
92     private Layer0                                  L0;
93     private Layer0X                                 L0X;
94     private DiagramResource                         DIA;
95     private ModelingResources                       MOD;
96     private StructuralResource2                     STR;
97
98     /**
99      * A translating affine transform that can be pre-concatenated to existing
100      * affine transforms to move them around according to the paste operation
101      * offset specification.
102      */
103     private AffineTransform                         offsetTransform;
104     
105     /**
106      * A node map for post-processing copied resources
107      */
108     private NodeMap                                 nodeMap;
109
110     private Resource                                sourceRoot;
111     private Resource                                targetRoot;
112     private String                                  sourceRootUri;
113     private String                                  targetRootUri;
114     private boolean                                 operateWithinSameRoot;
115
116     /**
117      * @param session
118      * @param op
119      * @throws DatabaseException
120      */
121     public Paster(Session session, PasteOperation op) throws DatabaseException {
122         this.session = session;
123         this.op = op;
124
125         this.sourceDiagram = op.sourceDiagram;
126         this.targetDiagram = op.targetDiagram;
127         if (this.sourceDiagram == null)
128             throw new IllegalArgumentException("source diagram has no resource");
129         if (this.targetDiagram == null)
130             throw new IllegalArgumentException("target diagram has no resource");
131
132         this.targetContext = (IModifiableSynchronizationContext) op.target.getHint(SynchronizationHints.CONTEXT);
133         if (this.targetContext == null)
134             throw new IllegalArgumentException("target diagram has no synchronization context");
135
136         this.offsetTransform = AffineTransform.getTranslateInstance(op.offset.getX(), op.offset.getY());
137     }
138
139     private String toString(PasteOperation op) {
140         StringBuilder sb = new StringBuilder();
141         sb.append("Diagram paste ");
142         sb.append(op.ea.all.size());
143         sb.append(" element(s) ");
144         if (op.cut)
145             sb.append("cut");
146         else
147             sb.append("copied");
148         sb.append(" from ");
149         sb.append(op.sourceDiagram);
150         sb.append(" to ");
151         sb.append(op.targetDiagram);
152         return sb.toString();
153     }
154
155     public void perform() throws DatabaseException {
156         final String comment = toString(op);
157         Write request = new WriteRequest() {
158             @Override
159             public void perform(WriteGraph graph) throws DatabaseException {
160                 graph.markUndoPoint();
161                 Paster.this.perform(graph);
162                 // Add comment to change set.
163                 CommentMetadata cm = graph.getMetadata(CommentMetadata.class);
164                 graph.addMetadata(cm.add(comment));
165             }
166         };
167         session.syncRequest(request);
168     }
169
170     public void perform(WriteGraph graph) throws DatabaseException {
171         L0 = Layer0.getInstance(graph);
172         L0X = Layer0X.getInstance(graph);
173         STR = StructuralResource2.getInstance(graph);
174         DIA = DiagramResource.getInstance(graph);
175         MOD = ModelingResources.getInstance(graph);
176         this.graph = graph;
177         this.cu = new ConnectionUtil(graph);
178         this.sourceRoot = graph.sync(new IndexRoot(sourceDiagram));
179         this.targetRoot = graph.sync(new IndexRoot(targetDiagram));
180         this.sourceRootUri = graph.getURI(sourceRoot);
181         this.targetRootUri = graph.getURI(targetRoot);
182         this.operateWithinSameRoot = sourceRoot.equals(targetRoot);
183         try {
184             if (op.cut)
185                 cut();
186             else
187                 copy();
188         } catch (DatabaseException e) {
189             throw e;
190         } catch (Exception e) {
191             throw new DatabaseException(e);
192         } finally {
193             this.cu = null;
194             this.graph = null;
195         }
196     }
197
198     private void onFinish() {
199         final CopyAdvisor advisor = op.target.getHint(SynchronizationHints.COPY_ADVISOR);
200         if (advisor != null) {
201             try {
202                 targetContext.set(GraphSynchronizationHints.READ_TRANSACTION, graph);
203                 targetContext.set(GraphSynchronizationHints.WRITE_TRANSACTION, graph);
204                 advisor.onFinish(targetContext);
205             } catch (SynchronizationException e) {
206                 ErrorHandler eh = targetContext.get(SynchronizationHints.ERROR_HANDLER);
207                 eh.error(e.getMessage(), e);
208             } finally {
209                 targetContext.set(GraphSynchronizationHints.READ_TRANSACTION, null);
210                 targetContext.set(GraphSynchronizationHints.WRITE_TRANSACTION, null);
211             }
212         }
213     }
214
215     // ------------------------------------------------------------------------
216     // SIMPLIFICATIONS
217     // ------------------------------------------------------------------------
218
219     interface Procedure {
220         void execute(Resource resource) throws Exception;
221     }
222
223     public void forEachResourceElement(String description, Collection<?> elements, Procedure procedure)
224     throws Exception {
225         for (Object object : elements) {
226             if (object instanceof Resource) {
227                 procedure.execute((Resource) object);
228             } else {
229                 if (DEBUG) {
230                     System.out.println("[" + description + "] Skipping non-resource element: " + object);
231                 }
232             }
233         }
234     }
235
236     private void applyPasteOffset(Resource forResource) throws DatabaseException {
237         applyOffset(forResource, op.offset);
238     }
239
240     private void applyOffset(Resource forResource, Point2D offset) throws DatabaseException {
241         AffineTransform at = DiagramGraphUtil.getTransform(graph, forResource);
242         at.preConcatenate(AffineTransform.getTranslateInstance(offset.getX(), offset.getY()));
243         DiagramGraphUtil.setTransform(graph, forResource, at);
244     }
245
246     private void applyPasteOffsetToRouteLine(Resource routeLine) throws DatabaseException {
247         Boolean isHorizontal = graph.getPossibleRelatedValue(routeLine, DIA.IsHorizontal, Bindings.BOOLEAN);
248         Double pos = graph.getPossibleRelatedValue(routeLine, DIA.HasPosition, Bindings.DOUBLE);
249         if (pos == null)
250             pos = 0.0;
251         if (Boolean.TRUE.equals(isHorizontal))
252             pos += op.offset.getY();
253         else
254             pos += op.offset.getX();
255         graph.claimLiteral(routeLine, DIA.HasPosition, pos, Bindings.DOUBLE);
256     }
257
258     // ------------------------------------------------------------------------
259     // CUT LOGIC
260     // ------------------------------------------------------------------------
261
262     Resource parentElement(Resource resource, Resource referenceRelation) throws DatabaseException {
263         // Only allow cutting if reference element has a parent and it is selected for cutting also.
264         Resource referencedParentComponent = graph.getPossibleObject(resource, referenceRelation);
265         if (referencedParentComponent == null)
266             return null;
267         return graph.getPossibleObject(referencedParentComponent, MOD.ComponentToElement);
268     }
269
270     boolean parentIsIncludedInCut(Resource resource, Resource referenceRelation, boolean noParentElementReturnValue) throws DatabaseException {
271         Resource referencedElement = parentElement(resource, referenceRelation);
272         if (referencedElement != null)
273             return op.ea.all.contains(referencedElement);
274         return noParentElementReturnValue;
275     }
276
277     protected void cut() throws Exception {
278         final GraphLayerManager glm = targetContext.get(GraphSynchronizationHints.GRAPH_LAYER_MANAGER);
279
280         final THashSet<Resource> cutElements = new THashSet<Resource>();
281         final CutProcedure registerNames = new CutProcedure() {
282             void postCut(Resource resource, Object cutResult) throws Exception {
283                 String name = graph.getPossibleRelatedValue(resource, L0.HasName, Bindings.STRING);
284                 if (name != null) {
285                     cutElements.add(resource);
286                 }
287             }
288         };
289
290         final CutProcedure nodeCutProcedure = new CutProcedure() {
291             @Override
292             void postCut(Resource resource, Object cutResult) throws Exception {
293                 if (cutResult != null) {
294                     applyPasteOffset(resource);
295
296                     if (glm != null) {
297                         glm.removeFromAllLayers(graph, resource);
298                         glm.putElementOnVisibleLayers(op.target, graph, resource);
299                     }
300                 }
301             }
302         };
303
304         CutProcedure flagCutProcedure = new CutProcedure() {
305             @Override
306             boolean preCut(Resource resource) throws Exception {
307                 return nodeCutProcedure.preCut(resource);
308             }
309             @Override
310             void postCut(Resource resource, Object cutResult) throws Exception {
311                 nodeCutProcedure.postCut(resource, cutResult);
312
313                 if (FlagUtil.isJoinedInSingleDiagram(graph, resource)) {
314                     FlagLabelingScheme scheme = DiagramFlagPreferences.getActiveFlagLabelingScheme(graph);
315                     String commonLabel = scheme.generateLabel(graph, targetDiagram);
316                     graph.claimLiteral(resource, L0.HasLabel, DIA.FlagLabel, commonLabel);
317                     for (Resource otherFlag : FlagUtil.getCounterparts(graph, resource))
318                         graph.claimLiteral(otherFlag, L0.HasLabel, DIA.FlagLabel, commonLabel, Bindings.STRING);
319                 }
320
321                 IOTablesInfo ioTablesInfo = IOTableUtil.getIOTablesInfo(graph, op.targetDiagram);
322                 double[] transform = graph.getRelatedValue(resource, DIA.HasTransform, Bindings.DOUBLE_ARRAY);
323                 ioTablesInfo.updateBinding(graph, DIA, resource, transform[4], transform[5]);
324
325                 // #11077: fix STR.JoinsComposite relations from joins related to moved flags.
326                 // Otherwise the JoinsComposite relations will be wrong after the cut-operation.
327                 for (Resource join : graph.getObjects(resource, DIA.FlagIsJoinedBy))
328                     fixConnectionJoin(join);
329             }
330
331             Set<Resource> flagComposites = new HashSet<>();
332             Set<Resource> joinedComposites = new HashSet<>();
333             Set<Resource> invalidJoinedComposites = new HashSet<>();
334
335             void fixConnectionJoin(Resource join) throws DatabaseException {
336                 Collection<Resource> flags = graph.getObjects(join, DIA.JoinsFlag);
337                 if (flags.size() < 2) {
338                     // Broken join, remove it. Joins that have
339                     // < 2 flags attached to it shouldn't exist.
340                     graph.deny(join);
341                 } else {
342                     flagComposites.clear();
343                     possibleCompositesOfElements(flags, flagComposites);
344                     joinedComposites.clear();
345                     joinedComposites.addAll( graph.getObjects(join, STR.JoinsComposite) );
346
347                     // Calculate which JoinsComposites need to be added and which removed.
348                     invalidJoinedComposites.clear();
349                     invalidJoinedComposites.addAll(joinedComposites);
350                     invalidJoinedComposites.removeAll(flagComposites);
351                     flagComposites.removeAll(joinedComposites);
352
353                     if (!invalidJoinedComposites.isEmpty()) {
354                         for (Resource invalidComposite : invalidJoinedComposites)
355                             graph.deny(join, STR.JoinsComposite, invalidComposite);
356                     }
357                     if (!flagComposites.isEmpty()) {
358                         for (Resource joinedComposite : flagComposites)
359                             graph.claim(join, STR.JoinsComposite, joinedComposite);
360                     }
361                 }
362             }
363
364             Set<Resource> possibleCompositesOfElements(Collection<Resource> elements, Set<Resource> result) throws DatabaseException {
365                 for (Resource e : elements) {
366                     Resource composite = possibleCompositeOfElement(e);
367                     if (composite != null)
368                         result.add(composite);
369                 }
370                 return result;
371             }
372
373             Resource possibleCompositeOfElement(Resource element) throws DatabaseException {
374                 Resource diagram = graph.getPossibleObject(element, L0.PartOf);
375                 return diagram != null ? graph.getPossibleObject(diagram, MOD.DiagramToComposite) : null;
376             }
377         };
378
379         CutProcedure monitorCutProcedure = new CutProcedure() {
380             @Override
381             void postCut(Resource resource, Object cutResult) throws DatabaseException {
382                 if (cutResult != null) {
383                     Resource parentElement = parentElement(resource, DIA.HasMonitorComponent);
384                     if (parentElement == null) {
385                         applyPasteOffset(resource);
386                     } else if (!op.ea.all.contains(parentElement)) {
387                         Point2D offset = op.offset;
388                         if (!op.sameDiagram()) {
389                             Resource parentDiagram = graph.sync(new PossibleTypedParent(parentElement, DIA.Diagram));
390                             AffineTransform monitoredComponentTr = DiagramGraphUtil.getWorldTransform(graph, parentElement);
391                             if (op.targetDiagram.equals(parentDiagram)) {
392                                 // Monitor is moved back to the parent element diagram.
393                                 // Must make monitor position relative to the parent position.
394                                 offset = new Point2D.Double(
395                                         op.offset.getX() - monitoredComponentTr.getTranslateX(),
396                                         op.offset.getY() - monitoredComponentTr.getTranslateY());
397                             } else {
398                                 // Monitor is moved to another diagram than the parent element diagram.
399                                 // Must make monitor position absolute.
400                                 offset = new Point2D.Double(
401                                         op.offset.getX() + monitoredComponentTr.getTranslateX(),
402                                         op.offset.getY() + monitoredComponentTr.getTranslateY());
403                             }
404                         }
405                         applyOffset(resource, offset);
406                     }
407
408                     if (glm != null) {
409                         glm.removeFromAllLayers(graph, resource);
410                         glm.putElementOnVisibleLayers(op.target, graph, resource);
411                     }
412                 }
413             }
414         };
415
416         CutProcedure referenceElementCutProcedure = new CutProcedure() {
417             @Override
418             boolean preCut(Resource resource) throws DatabaseException {
419                 // Only allow cutting if reference element has a parent and it is selected for cutting also.
420                 return parentIsIncludedInCut(resource, MOD.HasParentComponent, true);
421             }
422             @Override
423             void postCut(Resource resource, Object cutResult) throws Exception {
424                 if (cutResult != null) {
425                     if (!parentIsIncludedInCut(resource, MOD.HasParentComponent, false)) {
426                         applyPasteOffset(resource);
427                     }
428
429                     if (glm != null) {
430                         glm.removeFromAllLayers(graph, resource);
431                         glm.putElementOnVisibleLayers(op.target, graph, resource);
432                     }
433                 }
434             }
435         };
436
437         CutProcedure connectionCutProcedure = new CutProcedure() {
438             @Override
439             void postCut(Resource resource, Object cutResult) throws Exception {
440                 if (cutResult != null) {
441                     for (Resource rn : graph.getObjects(resource, DIA.HasInteriorRouteNode)) {
442                         if (graph.isInstanceOf(rn, DIA.BranchPoint))
443                             applyPasteOffset(rn);
444                         else if (graph.isInstanceOf(rn, DIA.RouteLine))
445                             applyPasteOffsetToRouteLine(rn);
446                     }
447
448                     if (glm != null) {
449                         glm.removeFromAllLayers(graph, resource);
450                         glm.putElementOnVisibleLayers(op.target, graph, resource);
451                     }
452                 }
453             }
454         };
455
456         // Before cutting, disconnect all nodes from connections that are not in
457         // the cut connection set.
458
459         final Set<Resource> selectedConnections = new HashSet<Resource>();
460         forEachResourceElement("Gather connections", op.ea.connections, new Procedure() {
461             @Override
462             public void execute(Resource resource) throws Exception {
463                 selectedConnections.add(resource);
464             }
465         });
466
467         Set<Resource> affectedConnections = new HashSet<Resource>();
468         disconnectExcludedConnections("Disconnect Nodes", op.ea.nodeList, selectedConnections, affectedConnections);
469         disconnectExcludedConnections("Disconnect Flags", op.ea.flags, selectedConnections, affectedConnections);
470
471         for (Resource connection : affectedConnections) {
472             // Leave the connection only if it has:
473             //  - at least one truly connected :DIA.Connector
474             //  - at least 1 route/branch points
475             int connectedConnectors = cu.getConnectedConnectors(connection, null).size();
476             int branchPoints = cu.getBranchPoints(connection, null).size();
477             if (connectedConnectors > 0 && branchPoints > 0)
478                 continue;
479
480             // Remove the whole connection.
481             cu.removeConnection(connection);
482         }
483
484         cut("Cut Nodes", op.ea.nodeList, compose(nodeCutProcedure, registerNames));
485         cut("Cut Others", op.ea.others, compose(nodeCutProcedure, registerNames));
486         // Cut reference elements after nodes so that parent relationships can be restored
487         // but before connections so that connections to the reference elements can be copied.
488         cut("Cut References", op.ea.references, compose(referenceElementCutProcedure, registerNames));
489         cut("Cut Flags", op.ea.flags, compose(flagCutProcedure, registerNames));
490         cut("Cut Connections", op.ea.connections, compose(connectionCutProcedure, registerNames));
491         cut("Cut Monitors", op.ea.monitors, compose(monitorCutProcedure, registerNames));
492
493         // Make sure that all the pasted nodes have unique names in their new namespace.
494         // Element names are only diagram-locally unique so this must be done after cut-paste.
495         for (Resource element : cutElements)
496             AddElement.claimFreshElementName(graph, targetDiagram, element);
497
498         onFinish();
499     }
500
501     /**
502      * @param description
503      * @param nodes
504      * @param affectedConnections
505      * @return
506      * @throws Exception
507      */
508     private Set<Resource> disconnectExcludedConnections(String description, Collection<Resource> nodes,
509             final Set<Resource> selectedConnections, final Set<Resource> affectedConnections) throws Exception {
510         final StructuralResource2 str = StructuralResource2.getInstance(graph);
511
512         // Disconnect each connection that is not a part of selectedConnections
513         // but is attached to the listed nodes.
514         forEachResourceElement(description, nodes, new Procedure() {
515             @Override
516             public void execute(Resource resource) throws Exception {
517                 for (Resource connector : graph.getObjects(resource, str.IsConnectedTo)) {
518                     Resource connection = ConnectionUtil.tryGetConnection(graph, connector);
519                     if (connection == null) {
520                         // This is a stray connector that has no purpose and should be removed.
521                         cu.removeConnectionPart(connector);
522                         continue;
523                     }
524                     if (selectedConnections.contains(connection))
525                         continue;
526
527                     cu.removeConnectionPart(connector);
528                     affectedConnections.add(connection);
529                 }
530             }
531         });
532
533         return affectedConnections;
534     }
535
536     /**
537      * @param description
538      * @param elements
539      * @param cutProcedure custom pre- and post-cut processing, may be <code>null</code>
540      * @throws Exception
541      */
542     private void cut(final String description, Collection<Resource> elements, final CutProcedure cutProcedure)
543     throws Exception {
544         final CopyAdvisor advisor = op.target.getHint(SynchronizationHints.COPY_ADVISOR);
545
546         forEachResourceElement(description, elements, new Procedure() {
547             @Override
548             public void execute(Resource resource) throws Exception {
549                 if (DEBUG)
550                     System.out.println("[" + description + "] " + NameUtils.getSafeName(graph, resource, true));
551
552                 if (cutProcedure != null && !cutProcedure.preCut(resource)) {
553                     if (DEBUG)
554                         System.out.println("[" + description + "] ignoring element cut for " + NameUtils.getSafeName(graph, resource, true));
555                     return;
556                 }
557
558                 Object result = CopyAdvisorUtil.cut(targetContext, graph, advisor, resource, sourceDiagram, targetDiagram);
559
560                 if (DEBUG)
561                     System.out.println("[" + description + "] RESULT: " + result);
562
563                 if (cutProcedure != null)
564                     cutProcedure.postCut(resource, result);
565             }
566         });
567     }
568
569     static class CutProcedure {
570         boolean preCut(Resource resource) throws Exception { return true; }
571         void postCut(Resource resource, Object cutResult) throws Exception {}
572     }
573
574     static class ComposedCutProcedure extends CutProcedure {
575         private final CutProcedure[] procedures;
576
577         public static ComposedCutProcedure compose(CutProcedure... procedures) {
578             return new ComposedCutProcedure(procedures);
579         }
580
581         public ComposedCutProcedure(CutProcedure... procedures) {
582             this.procedures = procedures;
583         }
584
585         boolean preCut(Resource resource) throws Exception {
586             for (CutProcedure proc : procedures)
587                 if (!proc.preCut(resource))
588                     return false;
589             return true;
590         }
591         void postCut(Resource resource, Object cutResult) throws Exception {
592             for (CutProcedure proc : procedures)
593                 proc.postCut(resource, cutResult);
594         }
595     }
596
597     // ------------------------------------------------------------------------
598     // COPY LOGIC SUPPORT CLASSES
599     // ------------------------------------------------------------------------
600
601     static class IdentifiedElement extends Tuple {
602         public IdentifiedElement(Resource object, IElement element) {
603             super(object, element);
604         }
605         public Resource getObject() {
606             return (Resource) getField(0);
607         }
608         public IElement getElement() {
609             return (IElement) getField(1);
610         }
611     }
612
613     static public class NodeMap {
614
615         Map<Resource, IdentifiedElement> resourceMap = new HashMap<Resource, IdentifiedElement>();
616         Map<IElement, IdentifiedElement> elementMap  = new HashMap<IElement, IdentifiedElement>();
617
618         public void put(Resource sourceResource, IElement sourceElement, IdentifiedElement dst) {
619             if (sourceResource == null)
620                 throw new NullPointerException("null source resource");
621             resourceMap.put(sourceResource, dst);
622             if (sourceElement != null)
623                 elementMap.put(sourceElement, dst);
624         }
625
626         public IdentifiedElement get(Resource source) {
627             return resourceMap.get(source);
628         }
629
630         public IdentifiedElement get(IElement source) {
631             return elementMap.get(source);
632         }
633         
634         public Set<Resource> allResources() {
635             return resourceMap.keySet();
636         }
637         
638         public Resource getResource(Resource source) {
639             IdentifiedElement ie = resourceMap.get(source);
640             if(ie != null)
641                 return ie.getObject();
642             else 
643                 return null;
644         }
645
646         public Resource getResource(IElement source) {
647             IdentifiedElement ie =  elementMap.get(source);
648             if(ie != null)
649                 return ie.getObject();
650             else 
651                 return null;
652         }
653         
654     }
655
656     static class ResourceMap extends HashMap<Resource, Resource> {
657         private static final long serialVersionUID = 687528035082504835L;
658     }
659
660     static class StatementMap extends HashMap<Resource, Statement> {
661         private static final long serialVersionUID = 8520092255776208395L;
662     }
663
664     static class MapQueue<K,V> {
665         Map<K, Deque<V>> map = new HashMap<K, Deque<V>>();
666         public void offer(K key, V value) {
667             Deque<V> deque = map.get(key);
668             if (deque == null)
669                 map.put(key, deque = new ArrayDeque<V>());
670             deque.offer(value);
671         }
672         public V poll(K key) {
673             Deque<V> deque = map.get(key);
674             if (deque == null)
675                 return null;
676             V value = deque.poll();
677             if (deque.isEmpty())
678                 map.remove(key);
679             return value;
680         }
681     }
682
683     // ------------------------------------------------------------------------
684     // COPY LOGIC
685     // ------------------------------------------------------------------------
686
687     /**
688      * This is necessary to have DIA.Flag type copied over to the copied flag.
689      * Diagram mapping will have problems and potentially break the
690      * configuration if the type is not the same as in the source.
691      */
692     BiFunction<ReadGraph, Statement, StatementEvaluation> statementAdvisor =
693             new BiFunction<ReadGraph, Statement, StatementEvaluation>() {
694         @Override
695         public StatementEvaluation apply(ReadGraph graph, Statement stm) {
696             if (DIA.HasFlagType.equals(stm.getPredicate()))
697                 return StatementEvaluation.INCLUDE;
698             return StatementEvaluation.USE_DEFAULT;
699         }
700     };
701
702     CopyProcedure nodeCopyProcedure = new CopyProcedure() {
703         Resource copy(Resource source) throws Exception {
704             Layer0 L0 = Layer0.getInstance(graph);
705
706             Resource copy = null;
707             final CopyAdvisor advisor = op.target.getHint(SynchronizationHints.COPY_ADVISOR);
708             if (advisor != null) {
709                 Resource sourceComposite = graph.getPossibleObject(source, L0.PartOf);
710                 if (sourceComposite == null || !graph.isInstanceOf(source, DIA.Composite)) {
711                     DiagramResource DIA = DiagramResource.getInstance(graph);
712                     sourceComposite = OrderedSetUtils.getSingleOwnerList(graph, source, DIA.Composite);
713                 }
714                 copy = CopyAdvisorUtil.copy(targetContext, graph, advisor, source, sourceComposite, op.targetDiagram);
715             }
716
717             if (copy == null) {
718                 copy = CopyAdvisorUtil.copy2(graph, source, statementAdvisor);
719             }
720
721             graph.deny(copy, MOD.IsTemplatized, copy);
722
723             // Add comment to change set.
724             CommentMetadata cm = graph.getMetadata(CommentMetadata.class);
725             graph.addMetadata(cm.add("Copied element " + source + " to " + copy));
726
727             // Add the new element to the diagram composite
728             OrderedSetUtils.add(graph, op.targetDiagram, copy);
729
730             // Give running name to element and increment the counter attached to the diagram.
731             AddElement.claimFreshElementName(graph, op.targetDiagram, copy);
732
733             // Make the diagram consist of the new element
734             graph.claim(op.targetDiagram, L0.ConsistsOf, copy);
735
736             // Put the element on all the currently active layers if possible.
737             GraphLayerManager glm = targetContext.get(GraphSynchronizationHints.GRAPH_LAYER_MANAGER);
738             if (glm != null) {
739                 glm.removeFromAllLayers(graph, copy);
740                 glm.putElementOnVisibleLayers(op.target, graph, copy);
741             }
742
743             return copy;
744         }
745         @Override
746         void postCopy(Resource source, Resource copy) throws Exception {
747             CopyPasteUtil.copyElementPosition(graph, op.ctx, source, copy, op.offset);
748         }
749     };
750
751     protected void copy() throws Exception {
752         nodeMap = new NodeMap();
753         
754         CommonDBUtils.selectClusterSet(graph, targetDiagram);
755
756         // Fill nodeMap with initial Resource->Resource mappings
757         if (op.initialNodeMap != null) {
758             for (Map.Entry<Resource, Resource> entry : op.initialNodeMap.entrySet()) {
759                 nodeMap.put(entry.getKey(), null, new IdentifiedElement(entry.getValue(), null));
760             }
761         }
762
763         // Perform copies in a suitable order
764         copyNodes( nodeMap );
765         // Copy reference elements after nodes so that parent relationships can be restored
766         // but before connections so that connections to the reference elements can be copied.
767         copyReferences( nodeMap );
768         copyFlags( nodeMap );
769         copyConnections( nodeMap );
770         // Copy monitors last since their parents must have been copied already.
771         copyMonitors( nodeMap );
772
773         onFinish();
774     }
775     
776
777     private NodeMap copyNodes(final NodeMap nodeMap) throws Exception {
778         copy("Copy Others", op.ea.others, nodeMap, nodeCopyProcedure);
779         copy("Copy Nodes", op.ea.nodeList, nodeMap, nodeCopyProcedure);
780
781         return nodeMap;
782     }
783
784     private NodeMap copyReferences(final NodeMap nodeMap) throws Exception {
785         final boolean forceCopyReferences = op.hasOption(ForceCopyReferences.class);
786
787         copy("Copy References", op.ea.references, nodeMap, new CopyProcedure() {
788             @Override
789             Resource copy(Resource source) throws Exception {
790                 // Don't copy unless the parent component is copied too.
791                 Resource sourceParentComponent = graph.getPossibleObject(source, MOD.HasParentComponent);
792                 if (sourceParentComponent == null)
793                     return null;
794                 Resource sourceParentElement = graph.getPossibleObject(sourceParentComponent, MOD.ComponentToElement);
795                 if (sourceParentElement != null) {
796                     if (!forceCopyReferences && !op.ea.all.contains(sourceParentElement))
797                         return null;
798                     // Find copied component
799                     IdentifiedElement copiedParentElement = nodeMap.get(sourceParentElement);
800                     if (copiedParentElement == null)
801                         return null;
802                     Resource copiedParentComponent = graph.getPossibleObject(copiedParentElement.getObject(), MOD.ElementToComponent);
803                     if (copiedParentComponent == null)
804                         return null;
805                     return copyReference(source, copiedParentComponent);
806                 } else {
807                     // Check that the component is part of a diagramless composite before proceeding
808                     Resource partOf = graph.getPossibleObject(sourceParentComponent, L0.PartOf);
809                     if (partOf == null || graph.hasStatement(partOf, MOD.CompositeToDiagram))
810                         return null;
811                     // Resolve the matching parent component from the target context.
812                     Resource targetParentComponent = resolveTargetComponent(sourceParentComponent);
813                     if (targetParentComponent == null)
814                         return null;
815                     return copyReference(source, targetParentComponent);
816                 }
817             }
818
819             private Resource resolveTargetComponent(Resource sourceParentComponent) throws DatabaseException {
820                 if (operateWithinSameRoot)
821                     return sourceParentComponent;
822                 // Directly map relative source component URI into target root namespace.
823                 String sourceUri = graph.getURI(sourceParentComponent);
824                 String targetUri = sourceUri.replace(sourceRootUri, targetRootUri);
825                 Resource targetParentComponent = graph.getPossibleResource(targetUri);
826                 return targetParentComponent;
827             }
828
829             private Resource copyReference(Resource source, Resource parentComponent) throws Exception {
830                 Resource referenceRelation = graph.getPossibleObject(source, MOD.HasReferenceRelation);
831                 if (referenceRelation == null)
832                     return null;
833
834                 Resource relationCopy = CopyAdvisorUtil.copy4(graph, referenceRelation);
835                 if (relationCopy == null)
836                     return null;
837
838                 Resource copy = nodeCopyProcedure.copy(source);
839
840                 // WORKAROUND: The result consists of a badly copied reference relation.
841                 // Remove it. How the relation is copied depends on whether the copy target
842                 // is the same model or not. If it is, the relation is copied, but invalidly
843                 // and if the target is not the same model, the relation is simply referenced
844                 // with a uni-directional L0.ConsistsOf relation.
845                 for (Resource o : graph.getObjects(copy, L0.ConsistsOf)) {
846                     boolean ownedByCopy = graph.hasStatement(o, L0.PartOf, copy);
847                     if (ownedByCopy) {
848                         graph.deny(copy, L0.ConsistsOf, o);
849                         RemoverUtil.remove(graph, o);
850                     } else {
851                         graph.deny(copy, L0.ConsistsOf, o);
852                     }
853                 }
854
855                 // The element the copied reference is attached to was also copied.
856                 // This means that we must attach the copied reference to its
857                 // original component's copy.
858                 graph.deny(copy, MOD.HasParentComponent);
859                 if(parentComponent != null)
860                     graph.claim(copy, MOD.HasParentComponent, MOD.HasParentComponent_Inverse, parentComponent);
861
862                 // Attach reference relation
863                 graph.claim(copy, L0.ConsistsOf, L0.PartOf, relationCopy);
864                 graph.claim(copy, MOD.HasReferenceRelation, MOD.HasReferenceRelation_Inverse, relationCopy);
865
866                 return copy;
867             }
868
869             @Override
870             void postCopy(Resource source, Resource copy) throws Exception {
871                 // Must fix element position if the copied reference element
872                 // doesn't have a visible parent element.
873                 Resource parentComponent = graph.getPossibleObject(source, MOD.HasParentComponent);
874                 if (parentComponent == null)
875                     return;
876                 Resource parentElement = graph.getPossibleObject(parentComponent, MOD.ComponentToElement);
877                 if (parentElement == null)
878                     CopyPasteUtil.copyElementPosition(graph, op.ctx, source, copy, op.offset);
879             }
880         });
881
882         return nodeMap;
883     }
884
885     private NodeMap copyFlags(NodeMap nodeMap) throws Exception {
886         final Layer0 l0 = Layer0.getInstance(graph);
887         final DiagramResource dia = DiagramResource.getInstance(graph);
888
889         class FlagCopy {
890             private final Map<Resource, Resource> selectedFlags           = new HashMap<Resource, Resource>();
891             private final Map<Resource, Resource> flagSelectedCounterpart = new HashMap<Resource, Resource>();
892
893             /**
894              * Analyze which flag pairs are selected
895              * 
896              * @throws DatabaseException
897              */
898             private void analyzeFlagSelection() throws DatabaseException {
899                 for (Resource flag : op.ea.flags) {
900                     selectedFlags.put(flag, flag);
901                 }
902                 for (Resource flag : selectedFlags.keySet()) {
903                     boolean external = FlagUtil.isExternal(graph, flag);
904                     boolean inSingleDiagram = FlagUtil.isJoinedInSingleDiagram(graph, flag);
905                     if (!external && inSingleDiagram) {
906                         // FIXME: this doesn't take into account local merged flags, which is a corner case but still possible
907                         Resource counterpart = FlagUtil.getPossibleCounterpart(graph, flag);
908                         if (selectedFlags.containsKey(counterpart)) {
909                             flagSelectedCounterpart.put(flag, counterpart);
910                             flagSelectedCounterpart.put(counterpart, flag);
911                         }
912                     }
913                 }
914             }
915
916             /**
917              * Reconnect copied flag pairs.
918              * @throws DatabaseException
919              */
920             private void reconnectLocalFlagPairs(NodeMap nodeMap) throws DatabaseException {
921                 FlagLabelingScheme scheme = DiagramFlagPreferences.getActiveFlagLabelingScheme(graph);
922                 Resource diagram = op.targetDiagram;
923
924                 Set<Resource> visited = new HashSet<Resource>();
925                 ArrayDeque<Resource> queue = new ArrayDeque<Resource>(flagSelectedCounterpart.values());
926                 while (!queue.isEmpty()) {
927                     Resource flag = queue.poll();
928                     Resource counterpart = flagSelectedCounterpart.get(flag);
929                     if (!visited.add(flag) || !visited.add(counterpart) || counterpart == null)
930                         continue;
931
932                     // Get copies
933                     Resource flagSourceElement = selectedFlags.get(flag);
934                     Resource counterpartSourceElement = selectedFlags.get(counterpart);
935
936                     IdentifiedElement flagCopy = nodeMap.get(flagSourceElement);
937                     IdentifiedElement counterpartCopy = nodeMap.get(counterpartSourceElement);
938
939                     FlagUtil.join(graph, flagCopy.getObject(), counterpartCopy.getObject());
940
941                     // Provide fresh labeling for connected flags if possible
942                     if (scheme != null) {
943                         String label = scheme.generateLabel(graph, diagram);
944                         if (label != null) {
945                             graph.claimLiteral(flagCopy.getObject(), l0.HasLabel, dia.FlagLabel, label, Bindings.STRING);
946                             graph.claimLiteral(counterpartCopy.getObject(), l0.HasLabel, dia.FlagLabel, label, Bindings.STRING);
947                         }
948                     }
949                 }
950             }
951
952             public void perform(NodeMap nodeMap) throws Exception {
953                 analyzeFlagSelection();
954
955                 copy("Copy Flags", op.ea.flags, nodeMap, new CopyProcedure() {
956                     @Override
957                     Resource copy(Resource source) throws Exception {
958                         return nodeCopyProcedure.copy(source);
959                     }
960                     @Override
961                     public void postCopy(Resource source, Resource copy) throws Exception {
962                         AffineTransform at = CopyPasteUtil.copyElementPosition(graph, op.ctx, source, copy, op.offset);
963                         
964                         // Update flag table binding
965                         IOTablesInfo ioTablesInfo = IOTableUtil.getIOTablesInfo(graph, op.targetDiagram);
966                         ioTablesInfo.updateBinding(graph, DIA, copy, at.getTranslateX(), at.getTranslateY());
967                         
968                         // All label properties must be removed from
969                         // the copied flags. Disconnected flags are
970                         // not supposed to have labels, and the right
971                         // place to reset the labels is when the flags
972                         // are reconnected to their respective
973                         // counterparts.
974                         graph.denyValue(copy, l0.HasLabel);
975                     }
976                 });
977
978                 reconnectLocalFlagPairs(nodeMap);
979             }
980
981         }
982
983         new FlagCopy().perform( nodeMap );
984         return nodeMap;
985     }
986
987     private NodeMap copyMonitors(final NodeMap nodeMap) throws Exception {
988         copy("Copy Monitors", op.ea.monitors, nodeMap, new CopyProcedure() {
989             @Override
990             Resource copy(Resource source) throws Exception {
991                 // Don't copy monitors if they are copied without
992                 // their parent element into another root (model).
993                 if (!operateWithinSameRoot) {
994                     Resource monitorComponent = graph.getPossibleObject(source, DIA.HasMonitorComponent);
995                     if (monitorComponent != null) {
996                         Resource monitorElement = graph.getPossibleObject(monitorComponent, MOD.ComponentToElement);
997                         if (monitorElement == null || !op.ea.all.contains(monitorElement))
998                             return null;
999                     }
1000                 }
1001                 Resource copy = nodeCopyProcedure.copy(source);
1002                 return copy;
1003             }
1004             @Override
1005             void postCopy(Resource source, Resource copy) throws Exception {
1006                 // Find the component and diagram element the source monitor is
1007                 // connected to.
1008                 Resource monitorElement = null;
1009                 Resource monitorComponent = graph.getPossibleObject(source, DIA.HasMonitorComponent);
1010                 if (monitorComponent != null) {
1011                     monitorElement = graph.getPossibleObject(monitorComponent, MOD.ComponentToElement);
1012                 }
1013
1014                 if (monitorElement != null && op.ea.all.contains(monitorElement)) {
1015                     // The element the copied monitor is attached was also copied.
1016                     // This means that we must attach the copied monitor to its
1017                     // original components copy.
1018
1019                     // Remove old association
1020                     graph.deny(copy, DIA.HasMonitorComponent);
1021
1022                     // Associate to copied component
1023                     IdentifiedElement parent = nodeMap.get(monitorElement);
1024                     if (parent != null) {
1025                         monitorComponent = graph.getPossibleObject(parent.getObject(), MOD.ElementToComponent);
1026                         if (monitorComponent != null)
1027                             graph.claim(copy, DIA.HasMonitorComponent, monitorComponent);
1028                     } else {
1029                         //throw new PasteException("no parent could be found for monitored element " + monitoredElement);
1030                     }
1031                 } else {
1032                     // The element the copied monitor is attached was not copied
1033                     // or there is no element for the monitored component.
1034                     // This means that the copied monitor must be kept attached
1035                     // to the same component no matter where it is in the model,
1036                     // unless the copy is done into another model.
1037                     if (operateWithinSameRoot && monitorComponent != null)
1038                         graph.claim(copy, DIA.HasMonitorComponent, monitorComponent);
1039
1040                     Point2D offset = op.offset;
1041                     if (!op.sameDiagram()) {
1042                         if (monitorElement != null) {
1043                             // Monitor doesn't have a diagram parent element any
1044                             // more, must recalculate its offset.
1045                             AffineTransform monitoredComponentTr = DiagramGraphUtil.getWorldTransform(graph, monitorElement);
1046                             offset = new Point2D.Double(
1047                                     op.offset.getX() + monitoredComponentTr.getTranslateX(),
1048                                     op.offset.getY() + monitoredComponentTr.getTranslateY());
1049                         }
1050                     }
1051                     CopyPasteUtil.copyElementPosition(graph, op.ctx, source, copy, offset);
1052                 }
1053
1054                 // Copy monitor suffix from original to copy.
1055                 String monitorSuffix = graph.getPossibleRelatedValue(source, DIA.HasMonitorSuffix, Bindings.STRING);
1056                 if (monitorSuffix != null)
1057                     graph.claimLiteral(copy, DIA.HasMonitorSuffix, monitorSuffix, Bindings.STRING);
1058
1059                 // Copy used property obtains for monitor template data.
1060                 graph.deny(copy, L0X.ObtainsProperty);
1061                 for (Statement stm : graph.getStatements(source, L0X.ObtainsProperty)) {
1062                     graph.claim(copy, stm.getPredicate(), null, stm.getObject());
1063                 }
1064             }
1065         });
1066
1067         return nodeMap;
1068     }
1069
1070     /**
1071      * @param description
1072      * @param elements
1073      * @param nodeMap
1074      * @param copyProcedure
1075      * @throws Exception
1076      */
1077     private void copy(final String description, Collection<Resource> elements, final NodeMap nodeMap,
1078             final CopyProcedure copyProcedure) throws Exception {
1079         if (copyProcedure == null)
1080             throw new IllegalArgumentException("null copy procedure");
1081
1082         forEachResourceElement(description, elements, new Procedure() {
1083             @Override
1084             public void execute(Resource resource) throws Exception {
1085                 if (DEBUG)
1086                     System.out.println("[" + description + "] " + NameUtils.getSafeName(graph, resource, true));
1087                 Resource copy = copyProcedure.copy(resource);
1088                 if (copy != null) {
1089                     if (DEBUG)
1090                         System.out.println("[" + description + "] " + NameUtils.getSafeName(graph, resource, true) + " copied as " + NameUtils.getSafeName(graph, copy, true));
1091                     nodeMap.put(resource, null, new IdentifiedElement(copy, null));
1092                     if (op.copyMap != null)
1093                         op.copyMap.put(resource, copy);
1094                     copyProcedure.postCopy(resource, copy);
1095                 }
1096             }
1097         });
1098     }
1099
1100     public static class RouteLine extends Tuple2 {
1101         public RouteLine(Double position, Boolean horizontal) {
1102             super(position, horizontal);
1103         }
1104         public double getPosition() {
1105             Double pos = (Double) get(0);
1106             return pos != null ? pos : 0.0;
1107         }
1108         public boolean isHorizontal() {
1109             return Boolean.TRUE.equals(get(1));
1110         }
1111     }
1112
1113     public static class BranchPoint extends Tuple3 {
1114         public BranchPoint(AffineTransform at, Boolean horizontal, Boolean vertical) {
1115             super(at, horizontal, vertical);
1116         }
1117         public AffineTransform getTransform() {
1118             return (AffineTransform) get(0);
1119         }
1120     }
1121
1122     public static RouteLine readRouteLine(ReadGraph graph, Resource src) throws DatabaseException {
1123         DiagramResource DIA = DiagramResource.getInstance(graph);
1124         Double pos = graph.getPossibleRelatedValue(src, DIA.HasPosition, Bindings.DOUBLE);
1125         Boolean hor = graph.getPossibleRelatedValue(src, DIA.IsHorizontal, Bindings.BOOLEAN);
1126         return new RouteLine(pos, hor);
1127     }
1128
1129     public static BranchPoint readBranchPoint(ReadGraph graph, Resource src) throws DatabaseException {
1130         DiagramResource DIA = DiagramResource.getInstance(graph);
1131         AffineTransform at = DiagramGraphUtil.getTransform(graph, src);
1132         boolean hor = graph.hasStatement(src, DIA.Horizontal);
1133         boolean ver = graph.hasStatement(src, DIA.Vertical);
1134         return new BranchPoint(at, hor, ver);
1135     }
1136
1137     /**
1138      * @param nodeMap
1139      * @return
1140      * @throws Exception
1141      */
1142     private NodeMap copyConnections(final NodeMap nodeMap) throws Exception {
1143         final StructuralResource2 STR = StructuralResource2.getInstance(graph);
1144         final DiagramResource DIA = DiagramResource.getInstance(graph);
1145
1146 //        final IModelingRules rules = graph.syncRequest(DiagramRequests.getModelingRules(op.sourceDiagram, null));
1147 //        if (rules == null)
1148 //            throw new IllegalArgumentException("source diagram offers no modeling rules");
1149
1150         final CopyAdvisor ca = op.target.getHint(SynchronizationHints.COPY_ADVISOR);
1151         if (ca == null)
1152             throw new UnsupportedOperationException("Cannot copy connections, no copy advisor available for diagram "
1153                     + op.target);
1154
1155         forEachResourceElement("Copy Connections", op.ea.connections, new Procedure() {
1156             @Override
1157             public void execute(Resource sourceObject) throws DatabaseException {
1158                 copyConnection(sourceObject);
1159             }
1160
1161             private void copyConnection(Resource sourceObject) throws DatabaseException {
1162                 // For associating source<->destination connection parts
1163                 final Map<Object, Object> resourceMap = new THashMap<Object, Object>();
1164                 // For associating source connectors to source nodes 
1165                 final StatementMap connectorToNode = new StatementMap();
1166
1167                 // 1. copy connection
1168                 // - This will also copy interior route nodes
1169                 // - But will leave out the DIA.AreConnected relations between route nodes
1170                 Resource sourceDiagram = graph.getPossibleObject(sourceObject, Layer0.getInstance(graph).PartOf);
1171                 if (sourceDiagram == null)
1172                     sourceDiagram = OrderedSetUtils.getSingleOwnerList(graph, sourceObject, DIA.Diagram);
1173                 Resource copy = CopyAdvisorUtil.copy(targetContext, graph, ca, sourceObject, sourceDiagram, op.targetDiagram, resourceMap);
1174                 if (copy == null)
1175                     throw new UnsupportedOperationException("Could not copy connection " + sourceObject);
1176                 OrderedSetUtils.addFirst(graph, op.targetDiagram, copy);
1177
1178                 graph.deny(copy, MOD.IsTemplatized, copy);
1179
1180                 AddElement.claimFreshElementName(graph, op.targetDiagram, copy);
1181
1182                 AddConnection.copyConnectionType(graph, sourceObject, copy);
1183
1184                 GraphLayerManager glm = targetContext.get(GraphSynchronizationHints.GRAPH_LAYER_MANAGER);
1185                 if (glm != null) {
1186                     glm.removeFromAllLayers(graph, copy);
1187                     glm.putElementOnVisibleLayers(op.target, graph, copy);
1188                 }
1189
1190                 nodeMap.put(sourceObject, null, new IdentifiedElement(copy, null));
1191                 if (op.copyMap != null)
1192                     op.copyMap.put(sourceObject, copy);
1193
1194                 // WORKAROUND: CopyAdvisorUtil.copy(..., resourceMap)
1195                 // implementations do not all support filling the resource map.
1196                 // Thus we resort to the old logic if resourceMap is empty at this point.
1197                 final boolean mapResources = resourceMap.isEmpty();
1198
1199                 // 2. associate source connection parts to destination connection parts
1200
1201                 // Connectors
1202                 Collection<Statement> sourceHasConnectors = graph.getStatements(sourceObject, DIA.HasConnector);
1203                 MapQueue<Resource, Resource> connectorsByType = new MapQueue<Resource, Resource>();
1204                 for (Statement hasConnector : sourceHasConnectors) {
1205                     connectorsByType.offer(hasConnector.getPredicate(), hasConnector.getObject());
1206                     for (Statement connects : graph.getStatements(hasConnector.getObject(), STR.Connects)) {
1207                         if (!sourceObject.equals(connects.getObject())) {
1208                             connectorToNode.put(hasConnector.getObject(), connects);
1209                             break;
1210                         }
1211                     }
1212                 }
1213                 if (mapResources) {
1214                     for (Statement hasConnector : graph.getStatements(copy, DIA.HasConnector)) {
1215                         Resource srcConnector = connectorsByType.poll(hasConnector.getPredicate());
1216                         resourceMap.put(srcConnector, hasConnector.getObject());
1217                     }
1218                 }
1219                 // 2.2. Offset interior route nodes
1220                 Collection<Resource> sourceInteriorRouteNodes = graph.getObjects(sourceObject, DIA.HasInteriorRouteNode);
1221                 if (mapResources) {
1222                     // WORKAROUND: for cases where resourceMap was not filled by
1223                     // the copy operation. Still needed because TG copying does
1224                     // not output this information.
1225                     Queue<Resource> branchPoints = new ArrayDeque<Resource>(sourceInteriorRouteNodes.size());
1226                     Queue<Resource> routeLines = new ArrayDeque<Resource>(sourceInteriorRouteNodes.size());
1227                     for (Resource dst : graph.getObjects(copy, DIA.HasInteriorRouteNode)) {
1228                         if (graph.isInstanceOf(dst, DIA.BranchPoint))
1229                             branchPoints.offer(dst);
1230                         else if (graph.isInstanceOf(dst, DIA.RouteLine))
1231                             routeLines.offer(dst);
1232                     }
1233                     for (Resource src : sourceInteriorRouteNodes) {
1234                         if (graph.isInstanceOf(src, DIA.BranchPoint)) {
1235                             Resource dst = branchPoints.poll();
1236                             resourceMap.put(src, dst);
1237                             BranchPoint bp = readBranchPoint(graph, src);
1238                             AffineTransform at = bp.getTransform();
1239                             at.preConcatenate(offsetTransform);
1240                             DiagramGraphUtil.setTransform(graph, dst, at);
1241                         }
1242                         else if (graph.isInstanceOf(src, DIA.RouteLine)) {
1243                             Resource dst = routeLines.poll();
1244                             resourceMap.put(src, dst);
1245                             RouteLine rl = readRouteLine(graph, src);
1246                             double newPos = rl.getPosition() + (rl.isHorizontal() ? op.offset.getY() : op.offset.getX());
1247                             graph.claimLiteral(dst, DIA.HasPosition, newPos, Bindings.DOUBLE);
1248                         }
1249                     }
1250                 } else {
1251                     for (Resource src : sourceInteriorRouteNodes) {
1252                         Resource dst = (Resource) resourceMap.get(src);
1253                         if (dst != null) {
1254                             if (graph.isInstanceOf(src, DIA.BranchPoint)) {
1255                                 BranchPoint bp = readBranchPoint(graph, src);
1256                                 AffineTransform at = bp.getTransform();
1257                                 at.preConcatenate(offsetTransform);
1258                                 DiagramGraphUtil.setTransform(graph, dst, at);
1259                             } else if (graph.isInstanceOf(src, DIA.RouteLine)) {
1260                                 RouteLine rl = readRouteLine(graph, src);
1261                                 double newPos = rl.getPosition() + (rl.isHorizontal() ? op.offset.getY() : op.offset.getX());
1262                                 graph.claimLiteral(dst, DIA.HasPosition, newPos, Bindings.DOUBLE);
1263                             }
1264                         }
1265                     }
1266                 }
1267
1268                 // 3. Connect connection parts according to how the source is connected
1269                 for (Resource src : sourceInteriorRouteNodes) {
1270                     Resource dst = (Resource) resourceMap.get(src);
1271                     for (Resource connectedToSrc : graph.getObjects(src, DIA.AreConnected)) {
1272                         Resource connectedToDst = (Resource) resourceMap.get(connectedToSrc);
1273                         if (connectedToDst != null) {
1274                             graph.claim(dst, DIA.AreConnected, DIA.AreConnected, connectedToDst);
1275                         } else {
1276                             throw new DatabaseException("Connection copying failed due to an invalid DIA.AreConnected link between source resources " + src + " <-> " + connectedToSrc);
1277                         }
1278                     }
1279                 }
1280                 for (Statement hasConnector : sourceHasConnectors) {
1281                     Resource srcConnector = hasConnector.getObject();
1282                     Resource dstConnector = (Resource) resourceMap.get(srcConnector);
1283                     Statement srcConnects = connectorToNode.get(srcConnector);
1284
1285                     // Connect to copied nodes
1286                     IdentifiedElement dstNode = nodeMap.get(srcConnects.getObject());
1287                     if (dstNode == null)
1288                         throw new DatabaseException("Source element "
1289                                 + NameUtils.getURIOrSafeNameInternal(graph, srcConnects.getObject())
1290                                 + " not copied causing copying of connection "
1291                                 + NameUtils.getURIOrSafeNameInternal(graph, sourceObject) 
1292                                 +" to fail.");
1293                     graph.claim(dstConnector, srcConnects.getPredicate(), dstNode.getObject());
1294
1295                     // Connect to other copied route nodes
1296                     for (Resource connectedToSrc : graph.getObjects(srcConnector, DIA.AreConnected)) {
1297                         Resource connectedToDst = (Resource) resourceMap.get(connectedToSrc);
1298                         graph.claim(dstConnector, DIA.AreConnected, DIA.AreConnected, connectedToDst);
1299                     }
1300                 }
1301
1302                 // 4. Make sure MOD.ConnectorToComponent relations are copied as well.
1303                 // Otherwise diagram mapping will do bad things on the model.
1304                 Resource sourceComponent = graph.getPossibleObject(sourceObject, MOD.ElementToComponent);
1305                 if (sourceComponent != null) {
1306                     for (Statement hasConnector : sourceHasConnectors) {
1307                         Resource sourceConnector = hasConnector.getObject();
1308                         Resource targetConnector = (Resource) resourceMap.get(sourceConnector);
1309                         // Should have been defined back in steps 1-2.
1310                         assert targetConnector != null;
1311                         Statement sourceConnectorToComponent = graph.getPossibleStatement(sourceConnector, MOD.ConnectorToComponent);
1312                         if (sourceConnectorToComponent == null)
1313                             continue;
1314                         if (!sourceConnectorToComponent.getObject().equals(sourceComponent))
1315                             continue;
1316                         Resource targetComponent = graph.getPossibleObject(copy, MOD.ElementToComponent);
1317                         if (targetComponent == null)
1318                             continue;
1319
1320                         graph.claim(targetConnector, sourceConnectorToComponent.getPredicate(), targetComponent);
1321
1322                         // #6190 & apros:#11435: Ensure that MOD.HasConnectionMappingSpecification is added to target
1323                         for (Resource connectionMappingSpec : graph.getObjects(sourceConnector, MOD.HasConnectionMappingSpecification))
1324                             graph.claim(targetConnector, MOD.HasConnectionMappingSpecification, connectionMappingSpec);
1325                     }
1326                 }
1327             }
1328         });
1329
1330         return nodeMap;
1331     }
1332
1333     class CopyProcedure {
1334         Resource copy(Resource source) throws Exception { throw new UnsupportedOperationException(); }
1335         void postCopy(Resource source, Resource copy) throws Exception {}
1336     }
1337
1338     /**
1339      * @param judgment <code>null</code> if no judgement is available in which
1340      *        case defaultValue is always returned
1341      * @param connectionPoint
1342      * @param defaultValue
1343      * @return
1344      * @throws DatabaseException
1345      */
1346     @SuppressWarnings("unused")
1347     private static Resource getAttachmentRelation(ReadGraph graph, ConnectionJudgement judgment,
1348             IConnectionPoint connectionPoint, Resource defaultValue) throws DatabaseException {
1349         if (judgment == null || !(connectionPoint instanceof CPTerminal) || judgment.attachmentRelations == null)
1350             return defaultValue;
1351         Resource attachment = judgment.attachmentRelations.get(graph, (CPTerminal) connectionPoint);
1352         return attachment != null ? attachment : defaultValue;
1353     }
1354     
1355     /**
1356      * Get node map of copied variables. Map contains original and new resources.
1357      * 
1358      * @return NodeMap of copied resources or null if copy has not been performed
1359      */
1360     public NodeMap getNodeMap() {
1361         return nodeMap;
1362     }
1363     
1364     protected PasteOperation getOperation() {
1365         return op;
1366     }
1367     
1368     public WriteGraph getGraph() {
1369                 return graph;
1370         }
1371 }