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