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