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