]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram/src/org/simantics/diagram/synchronization/graph/CopyAdvisorUtil.java
Fix CopyAdvisorUtil.copy2 to copy IsRelatedTo-statements left out before
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / synchronization / graph / CopyAdvisorUtil.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.diagram.synchronization.graph;
13
14 import java.util.Map;
15 import java.util.Set;
16 import java.util.function.BiFunction;
17
18 import org.eclipse.core.runtime.NullProgressMonitor;
19 import org.simantics.databoard.Bindings;
20 import org.simantics.databoard.binding.Binding;
21 import org.simantics.databoard.type.Datatype;
22 import org.simantics.db.ReadGraph;
23 import org.simantics.db.Resource;
24 import org.simantics.db.Statement;
25 import org.simantics.db.WriteGraph;
26 import org.simantics.db.common.utils.NameUtils;
27 import org.simantics.db.exception.DatabaseException;
28 import org.simantics.db.layer0.adapter.CopyHandler;
29 import org.simantics.db.layer0.adapter.impl.FixedRootImportAdvisor;
30 import org.simantics.db.layer0.util.ClipboardUtils;
31 import org.simantics.db.layer0.util.SimanticsClipboard;
32 import org.simantics.db.layer0.util.SimanticsClipboard.Representation;
33 import org.simantics.db.layer0.util.SimanticsClipboardImpl;
34 import org.simantics.db.layer0.util.SimanticsKeys;
35 import org.simantics.diagram.adapter.GraphToDiagramSynchronizer;
36 import org.simantics.diagram.internal.DebugPolicy;
37 import org.simantics.diagram.synchronization.CopyAdvisor;
38 import org.simantics.diagram.synchronization.CopyAdvisor.Evaluation;
39 import org.simantics.diagram.synchronization.ErrorHandler;
40 import org.simantics.diagram.synchronization.IModifiableSynchronizationContext;
41 import org.simantics.diagram.synchronization.StatementEvaluation;
42 import org.simantics.diagram.synchronization.SynchronizationException;
43 import org.simantics.diagram.synchronization.SynchronizationHints;
44 import org.simantics.graph.db.TransferableGraphs;
45 import org.simantics.graph.representation.TransferableGraph1;
46 import org.simantics.layer0.Layer0;
47 import org.simantics.utils.datastructures.BinaryFunction;
48
49 import gnu.trove.map.hash.THashMap;
50 import gnu.trove.set.hash.THashSet;
51
52 /**
53  * This class contains utility methods for the basic cut/copy operations
54  * performed related to cut-copy-pasting diagram/configuration component and
55  * composites.
56  * 
57  * <p>
58  * Methods
59  * {@link #cut(IModifiableSynchronizationContext, WriteGraph, CopyAdvisor, Resource, Resource, Resource)}
60  * and
61  * {@link #copy(IModifiableSynchronizationContext, WriteGraph, CopyAdvisor, Resource, Resource, Resource)}
62  * are available for making it easier to properly invoke
63  * {@link CopyAdvisor#cut(org.simantics.diagram.synchronization.ISynchronizationContext, Object, Object, Object)}
64  * and
65  * {@link CopyAdvisor#copy(org.simantics.diagram.synchronization.ISynchronizationContext, Object, Object, Object)}
66  * operations in a diagram/graph transaction context.
67  * 
68  * <p>
69  * Methods {@link #copy(WriteGraph, Resource, BinaryFunction)},
70  * {@link #copy2(WriteGraph, Resource, BinaryFunction)},
71  * {@link #copy3(WriteGraph, Resource, Resource, BinaryFunction)},
72  * {@link #copy4(WriteGraph, Resource)} and
73  * {@link #copy4(WriteGraph, Resource, CopyHandler)} offer differently
74  * functioning versions of copying a single resource in the graph that are
75  * mainly tuned for copying diagram elements and configuration components.
76  * 
77  * <p>
78  * <b>IMPORTANT:</b> Note that copy, copy2 and copy3 cannot handle copying of ordered sets
79  * properly.
80  * 
81  * @author Tuukka Lehtonen
82  */
83 public class CopyAdvisorUtil {
84
85     public static final boolean DEBUG_COPY = DebugPolicy.DEBUG_COPY_PASTE;
86
87     private static class Statement4 {
88         public final Statement stm;
89         public final Resource inverse;
90         public Statement4(Statement stm, Resource inverse) {
91             this.stm = stm;
92             this.inverse = inverse;
93         }
94     }
95
96     /**
97      * @param context a synchronization context instance, such as
98      *        {@link GraphToDiagramSynchronizer}
99      * @param g handle for graph writing
100      * @param ca the advisor for the copy operation
101      * @param cut the resource that is about to be cut
102      * @param sourceContainer the container from which the cut argument is about
103      *        to be removed
104      * @param targetContainer the container into which the cut argument is about
105      *        to be moved
106      * @return the result of
107      *         {@link CopyAdvisor#cut(org.simantics.diagram.synchronization.ISynchronizationContext, Object, Object, Object)}
108      * @throws DatabaseException
109      */
110     public static Object cut(IModifiableSynchronizationContext context, WriteGraph g, CopyAdvisor ca, Resource cut, Resource sourceContainer, Resource targetContainer) throws DatabaseException {
111         if (DEBUG_COPY)
112             System.out.println("Attempting to cut component " + NameUtils.getSafeName(g, cut, true));
113         try {
114             context.set(GraphSynchronizationHints.READ_TRANSACTION, g);
115             context.set(GraphSynchronizationHints.WRITE_TRANSACTION, g);
116             return ca.cut(context, cut, sourceContainer, targetContainer);
117         } catch (SynchronizationException e) {
118             ErrorHandler eh = context.get(SynchronizationHints.ERROR_HANDLER);
119             eh.error(e.getMessage(), e);
120         } finally {
121             context.set(GraphSynchronizationHints.READ_TRANSACTION, null);
122             context.set(GraphSynchronizationHints.WRITE_TRANSACTION, null);
123         }
124         return null;
125     }
126
127     /**
128      * @param context a synchronization context instance, such as
129      *        {@link GraphToDiagramSynchronizer}
130      * @param g handle for graph writing
131      * @param ca the advisor for the copy operation
132      * @param copyOf the resource that is about to be copied
133      * @param sourceContainer the container of the resource that will be copied
134      * @param targetContainer the to-be container of the copied resource instance
135      * @return the copied resource
136      * @throws DatabaseException
137      */
138     public static Resource copy(IModifiableSynchronizationContext context, WriteGraph g, CopyAdvisor ca,
139             Resource copyOf, Resource sourceContainer, Resource targetContainer) throws DatabaseException {
140         Resource resource = null;
141         if (DEBUG_COPY)
142             System.out.println("Attempting to copy component " + NameUtils.getSafeName(g, copyOf, true));
143         try {
144             context.set(GraphSynchronizationHints.READ_TRANSACTION, g);
145             context.set(GraphSynchronizationHints.WRITE_TRANSACTION, g);
146             Evaluation eval = ca.canCopy(context, copyOf, sourceContainer, targetContainer);
147             if (DEBUG_COPY)
148                 System.out.println("  CopyAdvisor(" + ca + ").canCopy evaluation result: " + eval);
149             if (CopyAdvisor.SUPPORTED.contains(eval)) {
150                 Object copy = ca.copy(context, copyOf, sourceContainer, targetContainer);
151                 if (DEBUG_COPY)
152                     System.out.println("  CopyAdvisor(" + ca + ").copy result: " + copy);
153                 if (copy instanceof Resource) {
154                     resource = (Resource) copy;
155                 } else {
156                     throw new UnsupportedOperationException("Cannot copy element " + copyOf);
157                 }
158             }
159         } catch (SynchronizationException e) {
160             ErrorHandler eh = context.get(SynchronizationHints.ERROR_HANDLER);
161             eh.error(e.getMessage(), e);
162             // throwing exception allows canceling failed copy!
163             throw new DatabaseException(e);
164         } finally {
165             context.set(GraphSynchronizationHints.READ_TRANSACTION, null);
166             context.set(GraphSynchronizationHints.WRITE_TRANSACTION, null);
167         }
168         return resource;
169     }
170
171     /**
172      * @param context a synchronization context instance, such as
173      *        {@link GraphToDiagramSynchronizer}
174      * @param g handle for graph writing
175      * @param ca the advisor for the copy operation
176      * @param copyOf the resource that is about to be copied
177      * @param sourceContainer the container of the resource that will be copied
178      * @param targetContainer the to-be container of the copied resource instance
179      * @param map a map for storing the correspondences between original and
180      *        copied objects. This is used to output data from the copy process.
181      * @return the copied resource
182      * @throws DatabaseException
183      */
184     public static Resource copy(IModifiableSynchronizationContext context, WriteGraph g, CopyAdvisor ca,
185             Resource copyOf, Resource sourceContainer, Resource targetContainer, Map<Object, Object> map)
186             throws DatabaseException {
187         Resource resource = null;
188         if (DEBUG_COPY)
189             System.out.println("Attempting to copy component " + NameUtils.getSafeName(g, copyOf, true));
190         try {
191             context.set(GraphSynchronizationHints.READ_TRANSACTION, g);
192             context.set(GraphSynchronizationHints.WRITE_TRANSACTION, g);
193             Evaluation eval = ca.canCopy(context, copyOf, sourceContainer, targetContainer);
194             if (DEBUG_COPY)
195                 System.out.println("  CopyAdvisor(" + ca + ").canCopy evaluation result: " + eval);
196             if (CopyAdvisor.SUPPORTED.contains(eval)) {
197                 Object copy = ca.copy(context, copyOf, sourceContainer, targetContainer, map);
198                 if (DEBUG_COPY) {
199                     System.out.println("  CopyAdvisor(" + ca + ").copy result: " + copy);
200                     System.out.println("  CopyAdvisor(" + ca + ").copy result map: " + map);
201                 }
202                 if (copy instanceof Resource) {
203                     resource = (Resource) copy;
204                 } else {
205                     throw new UnsupportedOperationException("Cannot copy element " + copyOf);
206                 }
207             }
208         } catch (SynchronizationException e) {
209             ErrorHandler eh = context.get(SynchronizationHints.ERROR_HANDLER);
210             eh.error(e.getMessage(), e);
211             // throwing exception allows canceling failed copy!
212             throw new DatabaseException(e);
213         } finally {
214             context.set(GraphSynchronizationHints.READ_TRANSACTION, null);
215             context.set(GraphSynchronizationHints.WRITE_TRANSACTION, null);
216         }
217         return resource;
218     }
219
220     /**
221      * Creates and returns a copy of the specified source resource based on the
222      * standard Layer0 relation hierarchy by recursively including all resources
223      * in the copy that the source and is composed of (see
224      * {@link Layer0#IsComposedOf}). The routine will always copy at least all
225      * L0.InstanceOf statements and tags related to its input resource. If the
226      * copied resource has a value attached, it will also be copied.
227      * 
228      * @param graph database write access
229      * @param source the resource to start the copy from
230      * @param advisor <code>null</code> or a custom advisor to guide whether or
231      *        not to copy relations that are not inherited from L0.IsComposedOf.
232      *        This advisor cannot be used to say that non-composing relations
233      *        should perform recursive copy, only whether to copy the tested
234      *        statement of or not.
235      * @return the copied resource
236      * @throws DatabaseException
237      */
238     public static Resource copy(WriteGraph graph, Resource source, BiFunction<ReadGraph, Statement, Boolean> advisor) throws DatabaseException {
239         return copy(graph, source, 0, advisor, new THashMap<Object, Object>());
240     }
241
242     /**
243      * See {@link #copy(WriteGraph, Resource, BinaryFunction)}.
244      * 
245      * @param graph
246      * @param source
247      * @param advisor
248      * @param copyMap a map for storing the correspondences between original and
249      *        copied objects. This is used to output data from the copy process.
250      * @return
251      * @throws DatabaseException
252      */
253     public static Resource copy(WriteGraph graph, Resource source, BiFunction<ReadGraph, Statement, Boolean> advisor, Map<Object, Object> copyMap) throws DatabaseException {
254         return copy(graph, source, 0, advisor, copyMap);
255     }
256
257     private static Resource copy(WriteGraph graph, Resource source, int level, BiFunction<ReadGraph, Statement, Boolean> advisor, Map<Object, Object> copyMap) throws DatabaseException {
258         if (DEBUG_COPY)
259             System.out.println("[" + level + "] CopyAdvisorUtil.copy(" + NameUtils.getSafeName(graph, source) + ", advisor=" + advisor + ")");
260
261         Resource copy = (Resource) copyMap.get(source);
262         if (copy != null) {
263             if (DEBUG_COPY)
264                 System.out.println("[" + level + "] already copied: " + NameUtils.getSafeName(graph, source) + " -> " + NameUtils.getSafeName(graph, copy));
265             return copy;
266         }
267
268         Layer0 L0 = Layer0.getInstance(graph);
269         copy = graph.newResource();
270         copyMap.put(source, copy);
271         for (Resource type : graph.getObjects(source, L0.InstanceOf))
272             graph.claim(copy, L0.InstanceOf, null, type);
273
274         if (graph.hasValue(source)) {
275             Datatype dt = graph.getRelatedValue(source, L0.HasDataType, Bindings.getBindingUnchecked(Datatype.class));
276             Binding b = Bindings.getBinding(dt);
277             graph.claimValue(copy, graph.<Object>getValue(source, b), b);
278         }
279
280         // Copy tags
281         for (Statement stm : graph.getStatements(source, L0.IsWeaklyRelatedTo)) {
282             if (stm.isAsserted(source))
283                 continue;
284             if (graph.isInstanceOf(stm.getPredicate(), L0.Tag)) {
285                 if (DEBUG_COPY)
286                     System.out.println("[" + level + "]\tcopying tag ("
287                             + NameUtils.getSafeName(graph, stm.getSubject()) + ", "
288                             + NameUtils.getSafeName(graph, stm.getPredicate()) + ")");
289                 graph.claim(copy, stm.getPredicate(), stm.getPredicate(), copy);
290             }
291         }
292
293         for (Statement stm : graph.getStatements(source, L0.IsRelatedTo)) {
294             Resource relation = stm.getPredicate();
295
296             // InstanceOf statements are handled separately, silently ignore them here.
297             if (L0.InstanceOf.equals(relation))
298                 continue;
299
300             // Don't copy asserted relations!
301             if (stm.isAsserted(source)) {
302                 if (DEBUG_COPY)
303                     System.out.println("[" + level + "]\t\tSkipping asserted statement");
304                 continue;
305             }
306
307             if (DEBUG_COPY)
308                 System.out.println("[" + level + "]\tprocessing statement (" + NameUtils.toString(graph, stm) + ")");
309
310             Resource subject = stm.getSubject();
311             Resource inverse = graph.getPossibleInverse(relation);
312             boolean addInverse = false;
313             Resource obj = stm.getObject();
314             Resource propType = graph.getPossibleType(obj, L0.Literal);
315
316             // Â§1 only L0.IsComposedOf and its subrelations can be considered to be copied automatically
317
318             if (propType != null || graph.isSubrelationOf(relation, L0.IsComposedOf)) {
319                 if (propType != null && graph.hasStatement(propType, L0.Enumeration, propType)) {
320                     if (DEBUG_COPY)
321                         System.out.println("[" + level + "]\t\tclaim enumeration statement");
322                     graph.claim(copy, relation, null, obj);
323                 } else {
324                     if (inverse != null)
325                         addInverse = graph.hasStatement(obj, inverse, subject);
326
327                     // Copy instantiated properties, not asserted ones.
328                     if (DEBUG_COPY)
329                         System.out.println("[" + level + "]\t\tcopy whole object");
330                     Resource clone = copy(graph, obj, level + 1, advisor, copyMap);
331                     graph.claim(copy, relation, clone);
332                 }
333             } else {
334                 if (advisor != null) {
335                     Boolean result = advisor.apply(graph, stm);
336                     if (Boolean.TRUE.equals(result)) {
337                         // Don't clone the object, just add relation to the same object.
338                         if (inverse != null)
339                             addInverse = graph.hasStatement(obj, inverse, subject);
340
341                         if (DEBUG_COPY) {
342                             System.out.println("[" + level + "]\t\tCopyAdvisorUtil.copy.claim(" + NameUtils.getSafeName(graph, copy) + ", "
343                                     + NameUtils.getSafeName(graph, relation) + ", "
344                                     + (addInverse ? NameUtils.getSafeName(graph, inverse) : null) + ", "
345                                     + NameUtils.getSafeName(graph, obj));
346                         }
347
348                         graph.claim(copy, relation, addInverse ? inverse : null, obj);
349                     }
350                 } else {
351                     if (DEBUG_COPY)
352                         System.out.println("[" + level + "]\t\tskipping statement");
353                 }
354             }
355         }
356         return copy;
357     }
358
359     /**
360      * Creates and returns a copy of the specified source resource based on the
361      * standard Layer0 relation hierarchy by recursively including all resources
362      * in the copy that the source and is composed of (see
363      * {@link Layer0#IsComposedOf}). A customizable advisor function can be used
364      * to guide the copy process. The routine will always copy at least all
365      * L0.InstanceOf statements and tags related to its input resource. If the
366      * copied resource has a value attached, it will also be copied.
367      * 
368      * @param graph database write access
369      * @param source the resource to start the copy from
370      * @param advisor <code>null</code> or a custom advisor to guide the copy
371      *        process according to the specifications of
372      *        {@link StatementEvaluation}. Every copied statement besides
373      *        L0.InstanceOf and tags will be evaluated by this advisor.
374      * @return the copied resource
375      * @throws DatabaseException
376      */
377     public static Resource copy2(WriteGraph graph, Resource source,
378             BiFunction<ReadGraph, Statement, StatementEvaluation> advisor)
379                     throws DatabaseException
380     {
381         return copy2(graph, source, advisor, new THashMap<>());
382     }
383
384     /**
385      * See {@link #copy2(WriteGraph, Resource, BinaryFunction)}.
386      * 
387      * @param graph
388      * @param source
389      * @param advisor
390      * @param copyMap a map for storing the correspondences between original and
391      *        copied objects. This is used to output data from the copy process.
392      * @return
393      * @throws DatabaseException 
394      */
395     public static Resource copy2(WriteGraph graph, Resource source,
396             BiFunction<ReadGraph, Statement, StatementEvaluation> advisor,
397             Map<Object, Object> copyMap)
398                     throws DatabaseException
399     {
400         Set<Statement4> pendingStatements = new THashSet<>();
401         Resource result = copy2(graph, source, 0, advisor, copyMap, pendingStatements);
402         postProcessStatements(graph, copyMap, pendingStatements);
403         return result;
404     }
405
406     /**
407      * Post-process pending statement
408      *  
409      * Rule: If both the subject and object of a pending source statement have
410      *       been copied, then the pending statement should also be copied.
411      */
412     private static void postProcessStatements(
413             WriteGraph graph,
414             Map<Object, Object> copyMap,
415             Set<Statement4> pendingStatements)
416                     throws DatabaseException
417     {
418         if (pendingStatements.isEmpty())
419             return;
420
421         if (DEBUG_COPY)
422             System.out.println("post processing " + pendingStatements.size() + " pending statements");
423         for (Statement4 srcStm : pendingStatements) {
424             // At this point, it is certain that srcStm subject has been copied
425             // but test it anyway.
426             Resource subjectCopy = (Resource) copyMap.get(srcStm.stm.getSubject());
427             Resource objectCopy = (Resource) copyMap.get(srcStm.stm.getObject());
428             if (subjectCopy == null || objectCopy == null) {
429                 if (DEBUG_COPY)
430                     System.out.println("skipping pending statement: " + NameUtils.toString(graph, srcStm.stm));
431                 continue;
432             }
433             if (DEBUG_COPY)
434                 System.out.println("copying pending statement: " + NameUtils.toString(graph, srcStm.stm));
435             graph.claim(subjectCopy, srcStm.stm.getPredicate(), srcStm.inverse, objectCopy);
436         }
437     }
438
439     private static Resource copy2(final WriteGraph graph, final Resource source, final int level,
440             BiFunction<ReadGraph, Statement, StatementEvaluation> advisor, Map<Object, Object> copyMap,
441             Set<Statement4> pendingSourceStatements)
442     throws DatabaseException {
443         if (DEBUG_COPY)
444             System.out.println("[" + level + "] CopyAdvisorUtil.copy(" + NameUtils.getSafeName(graph, source) + ", advisor=" + advisor + ")");
445
446         Resource copy = (Resource) copyMap.get(source);
447         if (copy != null) {
448             if (DEBUG_COPY)
449                 System.out.println("[" + level + "] already copied: " + NameUtils.getSafeName(graph, source) + " -> " + NameUtils.getSafeName(graph, copy));
450             return copy;
451         }
452
453         Layer0 L0 = Layer0.getInstance(graph);
454         copy = graph.newResource();
455         copyMap.put(source, copy);
456         for (Resource type : graph.getObjects(source, L0.InstanceOf))
457             graph.claim(copy, L0.InstanceOf, null, type);
458
459         if (graph.hasValue(source)) {
460             Datatype dt = graph.getRelatedValue(source, L0.HasDataType, Bindings.getBindingUnchecked(Datatype.class));
461             Binding b = Bindings.getBinding(dt);
462             graph.claimValue(copy, graph.<Object>getValue(source, b), b);
463         }
464
465         // Copy tags
466         for (Statement stm : graph.getStatements(source, L0.IsWeaklyRelatedTo)) {
467             if (stm.isAsserted(source))
468                 continue;
469             if (graph.isInstanceOf(stm.getPredicate(), L0.Tag)) {
470                 if (DEBUG_COPY)
471                     System.out.println("[" + level + "]\tcopying tag ("
472                             + NameUtils.getSafeName(graph, stm.getSubject()) + ", "
473                             + NameUtils.getSafeName(graph, stm.getPredicate()) + ")");
474                 graph.claim(copy, stm.getPredicate(), stm.getPredicate(), copy);
475             }
476         }
477
478         for (Statement stm : graph.getStatements(source, L0.IsRelatedTo)) {
479             Resource relation = stm.getPredicate();
480
481             // InstanceOf statements are handled separately, silently ignore them here.
482             if (L0.InstanceOf.equals(relation))
483                 continue;
484
485             // Don't copy asserted relations!
486             if (stm.isAsserted(source)) {
487                 if (DEBUG_COPY)
488                     System.out.println("[" + level + "]\tskipping asserted statement (" + NameUtils.toString(graph, stm) + ")");
489                 continue;
490             }
491
492             if (DEBUG_COPY)
493                 System.out.println("[" + level + "]\tprocessing statement (" + NameUtils.toString(graph, stm) + ")");
494
495             Resource subject = stm.getSubject();
496             Resource inverse = graph.getPossibleInverse(relation);
497             Resource obj = stm.getObject();
498             Resource propType = graph.getPossibleType(obj, L0.Literal);
499             boolean addInverse = false;
500             boolean forceIncludeAndFollow = false;
501
502             switch (evaluate(graph, stm, advisor)) {
503                 case SKIP:
504                     if (DEBUG_COPY)
505                         System.out.println("[" + level + "]\t\tskipping statement");
506                     break;
507
508                 case INCLUDE:
509                 {
510                     // Don't clone the object, just add relation to the same object.
511                     if (inverse != null)
512                         addInverse = graph.hasStatement(obj, inverse, subject);
513
514                     if (DEBUG_COPY) {
515                         System.out.println("[" + level + "]\t\tCopyAdvisorUtil.copy2.claim(" + NameUtils.getSafeName(graph, copy) + ", "
516                                 + NameUtils.getSafeName(graph, relation) + ", "
517                                 + (addInverse ? NameUtils.getSafeName(graph, inverse) : null) + ", "
518                                 + NameUtils.getSafeName(graph, obj));
519                     }
520
521                     graph.claim(copy, relation, addInverse ? inverse : null, obj);
522                     break;
523                 }
524
525                 case INCLUDE_AND_FOLLOW:
526                     // Force follow-through in the default copy logic
527                     forceIncludeAndFollow = true;
528                     // NOTE: intentional fall-through here
529
530                 case USE_DEFAULT:
531                 {
532                     if (forceIncludeAndFollow || propType != null || graph.isSubrelationOf(relation, L0.IsComposedOf)) {
533                         if (propType != null && graph.hasStatement(propType, L0.Enumeration, propType)) {
534                             // This logic is applied only for enumeration property
535                             // statements that should not have an inverse in any case.
536                             if (DEBUG_COPY) {
537                                 System.out.println("[" + level + "]\t\tclaim enumeration statement("
538                                         + NameUtils.getSafeName(graph, copy) + ", "
539                                         + NameUtils.getSafeName(graph, relation)+ ", null, "
540                                         + NameUtils.getSafeName(graph, obj));
541                             }
542                             graph.claim(copy, relation, null, obj);
543                         } else {
544                             // This logic is applied for other properties besides enumerations
545                             if (inverse != null)
546                                 addInverse = graph.hasStatement(obj, inverse, subject);
547
548                             // Copy instantiated properties, not asserted ones.
549                             if (DEBUG_COPY)
550                                 System.out.println("[" + level + "]\t\tcopy whole object");
551
552                             Resource clone = copy2(graph, obj, level + 1, advisor, copyMap, pendingSourceStatements);
553                             graph.claim(copy, relation, inverse, clone);
554                         }
555                     } else {
556                         if (graph.isSubrelationOf(relation, L0.IsRelatedTo)) {
557                             if (DEBUG_COPY)
558                                 System.out.println("[" + level + "]\t\tmarking statement as pending for post-processing");
559                             pendingSourceStatements.add(new Statement4(stm, inverse));
560                         } else {
561                             if (DEBUG_COPY)
562                                 System.out.println("[" + level + "]\t\tskipping weak statement");
563                         }
564                     }
565                 }
566             }
567         }
568         return copy;
569     }
570
571     /**
572      * Creates and returns a copy of the specified source resource based on the
573      * standard Layer0 relation hierarchy by recursively including all resources
574      * in the copy that the source and is composed of (see
575      * {@link Layer0#IsComposedOf}). A customizable advisor function can be used
576      * to guide the copy process. The routine will always copy at least all
577      * L0.InstanceOf statements and tags related to its input resource. If the
578      * copied resource has a value attached, it will also be copied.
579      * 
580      * Works exactly like {@link #copy2(WriteGraph, Resource, BinaryFunction)}
581      * but uses the <code>model</code> argument to make sure that the copy
582      * process does not propagate outside of the model. Any references that go
583      * to resources with URIs that are not in the model's namespace are copied
584      * as unidirectional.
585      * 
586      * @param graph database write access
587      * @param source the resource to start the copy from
588      * @param model the model containing the source object, used to keep the
589      *        copy process model-local
590      * @param advisor <code>null</code> or a custom advisor to guide the copy
591      *        process according to the specifications of
592      *        {@link StatementEvaluation}. Every copied statement besides
593      *        L0.InstanceOf and tags will be evaluated by this advisor.
594      * @return the copied resource
595      * @throws DatabaseException
596      */
597     public static Resource copy3(WriteGraph graph, Resource source, Resource model,
598             BiFunction<ReadGraph, Statement, StatementEvaluation> advisor) throws DatabaseException {
599         String modelURI = graph.getURI(model);
600         return copy3(graph, modelURI, source, 0, advisor, new THashMap<Object, Object>());
601     }
602
603     /**
604      * See {@link #copy3(WriteGraph, Resource, Resource, BinaryFunction)}.
605      * 
606      * @param graph
607      * @param source
608      * @param model
609      * @param advisor
610      * @param copyMap a map for storing the correspondences between original and
611      *        copied objects. This is used to output data from the copy process.
612      * @return
613      * @throws DatabaseException
614      */
615     public static Resource copy3(WriteGraph graph, Resource source, Resource model,
616             BiFunction<ReadGraph, Statement, StatementEvaluation> advisor, Map<Object, Object> copyMap) throws DatabaseException {
617         String modelURI = graph.getURI(model);
618         return copy3(graph, modelURI, source, 0, advisor, copyMap);
619     }
620
621     private static Resource copy3(WriteGraph graph, String modelURI, Resource source, int level,
622             BiFunction<ReadGraph, Statement, StatementEvaluation> advisor, Map<Object, Object> copyMap)
623     throws DatabaseException {
624         if (DEBUG_COPY)
625             System.out.println("[" + level + "] CopyAdvisorUtil.copy(" + NameUtils.getSafeName(graph, source) + ", advisor=" + advisor + ")");
626
627         Resource copy = (Resource) copyMap.get(source);
628         if (copy != null) {
629             if (DEBUG_COPY)
630                 System.out.println("[" + level + "] already copied: " + NameUtils.getSafeName(graph, source) + " -> " + NameUtils.getSafeName(graph, copy));
631             return copy;
632         }
633
634         Layer0 L0 = Layer0.getInstance(graph);
635         copy = graph.newResource();
636         copyMap.put(source, copy);
637         for (Resource type : graph.getObjects(source, L0.InstanceOf))
638             graph.claim(copy, L0.InstanceOf, null, type);
639
640         if (graph.hasValue(source)) {
641             Datatype dt = graph.getRelatedValue(source, L0.HasDataType, Bindings.getBindingUnchecked(Datatype.class));
642             Binding b = Bindings.getBinding(dt);
643             graph.claimValue(copy, graph.<Object>getValue(source, b), b);
644         }
645
646         // Copy tags
647         for (Statement stm : graph.getStatements(source, L0.IsWeaklyRelatedTo)) {
648             if (stm.isAsserted(source))
649                 continue;
650             if (graph.isInstanceOf(stm.getPredicate(), L0.Tag)) {
651                 if (DEBUG_COPY)
652                     System.out.println("[" + level + "]\tcopying tag ("
653                             + NameUtils.getSafeName(graph, stm.getSubject()) + ", "
654                             + NameUtils.getSafeName(graph, stm.getPredicate()) + ")");
655                 graph.claim(copy, stm.getPredicate(), stm.getPredicate(), copy);
656             }
657         }
658
659         for (Statement stm : graph.getStatements(source, L0.IsRelatedTo)) {
660             Resource relation = stm.getPredicate();
661
662             // InstanceOf statements are handled separately, silently ignore them here.
663             if (L0.InstanceOf.equals(relation))
664                 continue;
665
666             // Don't copy asserted relations!
667             if (stm.isAsserted(source)) {
668                 if (DEBUG_COPY)
669                     System.out.println("[" + level + "]\tskipping asserted statement (" + NameUtils.toString(graph, stm) + ")");
670                 continue;
671             }
672
673             if (DEBUG_COPY)
674                 System.out.println("[" + level + "]\tprocessing statement (" + NameUtils.toString(graph, stm) + ")");
675
676             Resource subject = stm.getSubject();
677             Resource inverse = graph.getPossibleInverse(relation);
678             Resource obj = stm.getObject();
679             Resource propType = graph.getPossibleType(obj, L0.Literal);
680             boolean addInverse = false;
681             boolean forceIncludeAndFollow = false;
682
683             switch (evaluate(graph, stm, advisor)) {
684                 case SKIP:
685                     if (DEBUG_COPY)
686                         System.out.println("[" + level + "]\t\tskipping statement");
687                     break;
688
689                 case INCLUDE:
690                 {
691                     // Don't clone the object, just add relation to the same object.
692                     if (inverse != null)
693                         addInverse = graph.hasStatement(obj, inverse, subject);
694
695                     if (DEBUG_COPY) {
696                         System.out.println("[" + level + "]\t\tCopyAdvisorUtil.copy2.claim(" + NameUtils.getSafeName(graph, copy) + ", "
697                                 + NameUtils.getSafeName(graph, relation) + ", "
698                                 + (addInverse ? NameUtils.getSafeName(graph, inverse) : null) + ", "
699                                 + NameUtils.getSafeName(graph, obj));
700                     }
701
702                     graph.claim(copy, relation, addInverse ? inverse : null, obj);
703                     break;
704                 }
705
706                 case INCLUDE_AND_FOLLOW:
707                     // Force follow-through in the default copy logic
708                     forceIncludeAndFollow = true;
709                     // NOTE: intentional fall-through here
710
711                 case USE_DEFAULT:
712                 {
713                     if (forceIncludeAndFollow || propType != null || graph.isSubrelationOf(relation, L0.IsComposedOf)) {
714                         if (propType != null && graph.hasStatement(propType, L0.Enumeration, propType)) {
715                             // This logic is applied only for enumeration property
716                             // statements that should not have an inverse in any case.
717                             if (DEBUG_COPY) {
718                                 System.out.println("[" + level + "]\t\tclaim enumeration statement("
719                                         + NameUtils.getSafeName(graph, copy) + ", "
720                                         + NameUtils.getSafeName(graph, relation)+ ", null, "
721                                         + NameUtils.getSafeName(graph, obj));
722                             }
723                             graph.claim(copy, relation, null, obj);
724                         } else {
725                             // This logic is applied for other properties besides enumerations
726                             if (inverse != null)
727                                 addInverse = graph.hasStatement(obj, inverse, subject);
728
729                             String objectURI = graph.getPossibleURI(obj);
730
731                             if(objectURI != null && !objectURI.startsWith(modelURI)) {
732
733                                 if (DEBUG_COPY)
734                                     System.out.println("[" + level + "]\t\tclaim ontological reference");
735
736                                 graph.claim(copy, relation, null, obj);
737
738                             } else {
739
740                                 // Copy instantiated properties, not asserted ones.
741                                 if (DEBUG_COPY)
742                                     System.out.println("[" + level + "]\t\tcopy whole object");
743
744                                 Resource clone = copy3(graph, modelURI, obj, level + 1, advisor, copyMap);
745                                 graph.claim(copy, relation, inverse, clone);
746
747                             }
748
749                         }
750                     } else {
751                         if (DEBUG_COPY)
752                             System.out.println("[" + level + "]\t\tskipping statement");
753                     }
754                 }
755             }
756         }
757         return copy;
758     }
759
760     protected static StatementEvaluation evaluate(ReadGraph graph, Statement stm, BiFunction<ReadGraph, Statement, StatementEvaluation> tester) {
761         if (tester == null)
762             return StatementEvaluation.USE_DEFAULT;
763         return tester.apply(graph, stm);
764     }
765
766     /**
767      * Equal to
768      * <code>copy4(graph, source, graph.adapt(source, CopyHandler.class)))</code>
769      * .
770      * 
771      * @param graph database write access
772      * @param source the resource to start the copy from
773      * @return the copied resource
774      * @throws DatabaseException
775      */
776     public static Resource copy4(WriteGraph graph, Resource source) throws DatabaseException {
777         CopyHandler handler = graph.adapt(source, CopyHandler.class);
778         return copy4(graph, source, handler);
779     }
780
781     /**
782      * Creates and returns a copy of the specified source resource based on
783      * transferable graph export and import. The TG representation shall be
784      * generated by the specified {@link CopyHandler} into a
785      * {@link SimanticsClipboard} instance from where it is read back as
786      * {@link TransferableGraph1} and imported into the database through
787      * {@link TransferableGraphs#importGraph1(WriteGraph, TransferableGraph1, org.simantics.graph.db.IImportAdvisor)}.
788      * 
789      * @param graph database write access
790      * @param source the resource to start the copy from
791      * @param copyHandler the handler to use for generating the transferable
792      *        graph representation from the copied resource
793      * @return the copied resource
794      * @throws DatabaseException
795      */
796     public static Resource copy4(WriteGraph graph, Resource source, CopyHandler copyHandler) throws DatabaseException {
797         SimanticsClipboardImpl builder = new SimanticsClipboardImpl();
798         copyHandler.copyToClipboard(graph, builder, new NullProgressMonitor());
799
800         for(Set<Representation> object : builder.getContents()) {
801             TransferableGraph1 tg = ClipboardUtils.accept(graph, object, SimanticsKeys.KEY_TRANSFERABLE_GRAPH);
802             if(tg != null) {
803                 FixedRootImportAdvisor advisor = new FixedRootImportAdvisor();
804                 TransferableGraphs.importGraph1(graph, tg, advisor);
805                 return advisor.getRoot();
806             }
807         }
808
809         String uri = graph.getPossibleURI(source);
810         throw new DatabaseException("Failed to copy resource " + NameUtils.getSafeName(graph, source, true)
811                 + (uri != null ? " with URI " + uri : ""));
812     }
813
814 }