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