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