]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.diagram/src/org/simantics/diagram/synchronization/graph/CopyAdvisorUtil.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / synchronization / graph / CopyAdvisorUtil.java
diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/synchronization/graph/CopyAdvisorUtil.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/synchronization/graph/CopyAdvisorUtil.java
new file mode 100644 (file)
index 0000000..86a6739
--- /dev/null
@@ -0,0 +1,755 @@
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ *     VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.diagram.synchronization.graph;\r
+\r
+import gnu.trove.map.hash.THashMap;\r
+\r
+import java.util.Map;\r
+import java.util.Set;\r
+\r
+import org.simantics.databoard.Bindings;\r
+import org.simantics.databoard.binding.Binding;\r
+import org.simantics.databoard.type.Datatype;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.Statement;\r
+import org.simantics.db.WriteGraph;\r
+import org.simantics.db.common.utils.NameUtils;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.db.layer0.adapter.CopyHandler;\r
+import org.simantics.db.layer0.adapter.impl.FixedRootImportAdvisor;\r
+import org.simantics.db.layer0.util.ClipboardUtils;\r
+import org.simantics.db.layer0.util.SimanticsClipboard;\r
+import org.simantics.db.layer0.util.SimanticsClipboard.Representation;\r
+import org.simantics.db.layer0.util.SimanticsClipboardImpl;\r
+import org.simantics.db.layer0.util.SimanticsKeys;\r
+import org.simantics.diagram.adapter.GraphToDiagramSynchronizer;\r
+import org.simantics.diagram.internal.DebugPolicy;\r
+import org.simantics.diagram.synchronization.CopyAdvisor;\r
+import org.simantics.diagram.synchronization.CopyAdvisor.Evaluation;\r
+import org.simantics.diagram.synchronization.ErrorHandler;\r
+import org.simantics.diagram.synchronization.IModifiableSynchronizationContext;\r
+import org.simantics.diagram.synchronization.StatementEvaluation;\r
+import org.simantics.diagram.synchronization.SynchronizationException;\r
+import org.simantics.diagram.synchronization.SynchronizationHints;\r
+import org.simantics.graph.db.TransferableGraphs;\r
+import org.simantics.graph.representation.TransferableGraph1;\r
+import org.simantics.layer0.Layer0;\r
+import org.simantics.utils.datastructures.BinaryFunction;\r
+\r
+/**\r
+ * This class contains utility methods for the basic cut/copy operations\r
+ * performed related to cut-copy-pasting diagram/configuration component and\r
+ * composites.\r
+ * \r
+ * <p>\r
+ * Methods\r
+ * {@link #cut(IModifiableSynchronizationContext, WriteGraph, CopyAdvisor, Resource, Resource, Resource)}\r
+ * and\r
+ * {@link #copy(IModifiableSynchronizationContext, WriteGraph, CopyAdvisor, Resource, Resource, Resource)}\r
+ * are available for making it easier to properly invoke\r
+ * {@link CopyAdvisor#cut(org.simantics.diagram.synchronization.ISynchronizationContext, Object, Object, Object)}\r
+ * and\r
+ * {@link CopyAdvisor#copy(org.simantics.diagram.synchronization.ISynchronizationContext, Object, Object, Object)}\r
+ * operations in a diagram/graph transaction context.\r
+ * \r
+ * <p>\r
+ * Methods {@link #copy(WriteGraph, Resource, BinaryFunction)},\r
+ * {@link #copy2(WriteGraph, Resource, BinaryFunction)},\r
+ * {@link #copy3(WriteGraph, Resource, Resource, BinaryFunction)},\r
+ * {@link #copy4(WriteGraph, Resource)} and\r
+ * {@link #copy4(WriteGraph, Resource, CopyHandler)} offer differently\r
+ * functioning versions of copying a single resource in the graph that are\r
+ * mainly tuned for copying diagram elements and configuration components.\r
+ * \r
+ * <p>\r
+ * <b>IMPORTANT:</b> Note that copy, copy2 and copy3 cannot handle copying of ordered sets\r
+ * properly.\r
+ * \r
+ * @author Tuukka Lehtonen\r
+ */\r
+public class CopyAdvisorUtil {\r
+\r
+    public static final boolean DEBUG_COPY = DebugPolicy.DEBUG_COPY_PASTE;\r
+\r
+    /**\r
+     * @param context a synchronization context instance, such as\r
+     *        {@link GraphToDiagramSynchronizer}\r
+     * @param g handle for graph writing\r
+     * @param ca the advisor for the copy operation\r
+     * @param cut the resource that is about to be cut\r
+     * @param sourceContainer the container from which the cut argument is about\r
+     *        to be removed\r
+     * @param targetContainer the container into which the cut argument is about\r
+     *        to be moved\r
+     * @return the result of\r
+     *         {@link CopyAdvisor#cut(org.simantics.diagram.synchronization.ISynchronizationContext, Object, Object, Object)}\r
+     * @throws DatabaseException\r
+     */\r
+    public static Object cut(IModifiableSynchronizationContext context, WriteGraph g, CopyAdvisor ca, Resource cut, Resource sourceContainer, Resource targetContainer) throws DatabaseException {\r
+        if (DEBUG_COPY)\r
+            System.out.println("Attempting to cut component " + NameUtils.getSafeName(g, cut, true));\r
+        try {\r
+            context.set(GraphSynchronizationHints.READ_TRANSACTION, g);\r
+            context.set(GraphSynchronizationHints.WRITE_TRANSACTION, g);\r
+            return ca.cut(context, cut, sourceContainer, targetContainer);\r
+        } catch (SynchronizationException e) {\r
+            ErrorHandler eh = context.get(SynchronizationHints.ERROR_HANDLER);\r
+            eh.error(e.getMessage(), e);\r
+        } finally {\r
+            context.set(GraphSynchronizationHints.READ_TRANSACTION, null);\r
+            context.set(GraphSynchronizationHints.WRITE_TRANSACTION, null);\r
+        }\r
+        return null;\r
+    }\r
+\r
+    /**\r
+     * @param context a synchronization context instance, such as\r
+     *        {@link GraphToDiagramSynchronizer}\r
+     * @param g handle for graph writing\r
+     * @param ca the advisor for the copy operation\r
+     * @param copyOf the resource that is about to be copied\r
+     * @param sourceContainer the container of the resource that will be copied\r
+     * @param targetContainer the to-be container of the copied resource instance\r
+     * @return the copied resource\r
+     * @throws DatabaseException\r
+     */\r
+    public static Resource copy(IModifiableSynchronizationContext context, WriteGraph g, CopyAdvisor ca,\r
+            Resource copyOf, Resource sourceContainer, Resource targetContainer) throws DatabaseException {\r
+        Resource resource = null;\r
+        if (DEBUG_COPY)\r
+            System.out.println("Attempting to copy component " + NameUtils.getSafeName(g, copyOf, true));\r
+        try {\r
+            context.set(GraphSynchronizationHints.READ_TRANSACTION, g);\r
+            context.set(GraphSynchronizationHints.WRITE_TRANSACTION, g);\r
+            Evaluation eval = ca.canCopy(context, copyOf, sourceContainer, targetContainer);\r
+            if (DEBUG_COPY)\r
+                System.out.println("  CopyAdvisor(" + ca + ").canCopy evaluation result: " + eval);\r
+            if (CopyAdvisor.SUPPORTED.contains(eval)) {\r
+                Object copy = ca.copy(context, copyOf, sourceContainer, targetContainer);\r
+                if (DEBUG_COPY)\r
+                    System.out.println("  CopyAdvisor(" + ca + ").copy result: " + copy);\r
+                if (copy instanceof Resource) {\r
+                    resource = (Resource) copy;\r
+                } else {\r
+                    throw new UnsupportedOperationException("Cannot copy element " + copyOf);\r
+                }\r
+            }\r
+        } catch (SynchronizationException e) {\r
+            ErrorHandler eh = context.get(SynchronizationHints.ERROR_HANDLER);\r
+            eh.error(e.getMessage(), e);\r
+            // throwing exception allows canceling failed copy!\r
+            throw new DatabaseException(e);\r
+        } finally {\r
+            context.set(GraphSynchronizationHints.READ_TRANSACTION, null);\r
+            context.set(GraphSynchronizationHints.WRITE_TRANSACTION, null);\r
+        }\r
+        return resource;\r
+    }\r
+\r
+    /**\r
+     * @param context a synchronization context instance, such as\r
+     *        {@link GraphToDiagramSynchronizer}\r
+     * @param g handle for graph writing\r
+     * @param ca the advisor for the copy operation\r
+     * @param copyOf the resource that is about to be copied\r
+     * @param sourceContainer the container of the resource that will be copied\r
+     * @param targetContainer the to-be container of the copied resource instance\r
+     * @param map a map for storing the correspondences between original and\r
+     *        copied objects. This is used to output data from the copy process.\r
+     * @return the copied resource\r
+     * @throws DatabaseException\r
+     */\r
+    public static Resource copy(IModifiableSynchronizationContext context, WriteGraph g, CopyAdvisor ca,\r
+            Resource copyOf, Resource sourceContainer, Resource targetContainer, Map<Object, Object> map)\r
+            throws DatabaseException {\r
+        Resource resource = null;\r
+        if (DEBUG_COPY)\r
+            System.out.println("Attempting to copy component " + NameUtils.getSafeName(g, copyOf, true));\r
+        try {\r
+            context.set(GraphSynchronizationHints.READ_TRANSACTION, g);\r
+            context.set(GraphSynchronizationHints.WRITE_TRANSACTION, g);\r
+            Evaluation eval = ca.canCopy(context, copyOf, sourceContainer, targetContainer);\r
+            if (DEBUG_COPY)\r
+                System.out.println("  CopyAdvisor(" + ca + ").canCopy evaluation result: " + eval);\r
+            if (CopyAdvisor.SUPPORTED.contains(eval)) {\r
+                Object copy = ca.copy(context, copyOf, sourceContainer, targetContainer, map);\r
+                if (DEBUG_COPY) {\r
+                    System.out.println("  CopyAdvisor(" + ca + ").copy result: " + copy);\r
+                    System.out.println("  CopyAdvisor(" + ca + ").copy result map: " + map);\r
+                }\r
+                if (copy instanceof Resource) {\r
+                    resource = (Resource) copy;\r
+                } else {\r
+                    throw new UnsupportedOperationException("Cannot copy element " + copyOf);\r
+                }\r
+            }\r
+        } catch (SynchronizationException e) {\r
+            ErrorHandler eh = context.get(SynchronizationHints.ERROR_HANDLER);\r
+            eh.error(e.getMessage(), e);\r
+            // throwing exception allows canceling failed copy!\r
+            throw new DatabaseException(e);\r
+        } finally {\r
+            context.set(GraphSynchronizationHints.READ_TRANSACTION, null);\r
+            context.set(GraphSynchronizationHints.WRITE_TRANSACTION, null);\r
+        }\r
+        return resource;\r
+    }\r
+\r
+    /**\r
+     * Creates and returns a copy of the specified source resource based on the\r
+     * standard Layer0 relation hierarchy by recursively including all resources\r
+     * in the copy that the source and is composed of (see\r
+     * {@link Layer0#IsComposedOf}). The routine will always copy at least all\r
+     * L0.InstanceOf statements and tags related to its input resource. If the\r
+     * copied resource has a value attached, it will also be copied.\r
+     * \r
+     * @param graph database write access\r
+     * @param source the resource to start the copy from\r
+     * @param advisor <code>null</code> or a custom advisor to guide whether or\r
+     *        not to copy relations that are not inherited from L0.IsComposedOf.\r
+     *        This advisor cannot be used to say that non-composing relations\r
+     *        should perform recursive copy, only whether to copy the tested\r
+     *        statement of or not.\r
+     * @return the copied resource\r
+     * @throws DatabaseException\r
+     */\r
+    public static Resource copy(WriteGraph graph, Resource source, BinaryFunction<Boolean, ReadGraph, Statement> advisor) throws DatabaseException {\r
+        return copy(graph, source, 0, advisor, new THashMap<Object, Object>());\r
+    }\r
+\r
+    /**\r
+     * See {@link #copy(WriteGraph, Resource, BinaryFunction)}.\r
+     * \r
+     * @param graph\r
+     * @param source\r
+     * @param advisor\r
+     * @param copyMap a map for storing the correspondences between original and\r
+     *        copied objects. This is used to output data from the copy process.\r
+     * @return\r
+     * @throws DatabaseException\r
+     */\r
+    public static Resource copy(WriteGraph graph, Resource source, BinaryFunction<Boolean, ReadGraph, Statement> advisor, Map<Object, Object> copyMap) throws DatabaseException {\r
+        return copy(graph, source, 0, advisor, copyMap);\r
+    }\r
+\r
+    private static Resource copy(WriteGraph graph, Resource source, int level, BinaryFunction<Boolean, ReadGraph, Statement> advisor, Map<Object, Object> copyMap) throws DatabaseException {\r
+        if (DEBUG_COPY)\r
+            System.out.println("[" + level + "] CopyAdvisorUtil.copy(" + NameUtils.getSafeName(graph, source) + ", advisor=" + advisor + ")");\r
+\r
+        Resource copy = (Resource) copyMap.get(source);\r
+        if (copy != null) {\r
+            if (DEBUG_COPY)\r
+                System.out.println("[" + level + "] already copied: " + NameUtils.getSafeName(graph, source) + " -> " + NameUtils.getSafeName(graph, copy));\r
+            return copy;\r
+        }\r
+\r
+        Layer0 L0 = Layer0.getInstance(graph);\r
+        copy = graph.newResource();\r
+        copyMap.put(source, copy);\r
+        for (Resource type : graph.getObjects(source, L0.InstanceOf))\r
+            graph.claim(copy, L0.InstanceOf, null, type);\r
+\r
+        if (graph.hasValue(source)) {\r
+            Datatype dt = graph.getRelatedValue(source, L0.HasDataType, Bindings.getBindingUnchecked(Datatype.class));\r
+            Binding b = Bindings.getBinding(dt);\r
+            graph.claimValue(copy, graph.<Object>getValue(source, b), b);\r
+        }\r
+\r
+        // Copy tags\r
+        for (Statement stm : graph.getStatements(source, L0.IsWeaklyRelatedTo)) {\r
+            if (stm.isAsserted(source))\r
+                continue;\r
+            if (graph.isInstanceOf(stm.getPredicate(), L0.Tag)) {\r
+                if (DEBUG_COPY)\r
+                    System.out.println("[" + level + "]\tcopying tag ("\r
+                            + NameUtils.getSafeName(graph, stm.getSubject()) + ", "\r
+                            + NameUtils.getSafeName(graph, stm.getPredicate()) + ")");\r
+                graph.claim(copy, stm.getPredicate(), stm.getPredicate(), copy);\r
+            }\r
+        }\r
+\r
+        for (Statement stm : graph.getStatements(source, L0.IsRelatedTo)) {\r
+            Resource relation = stm.getPredicate();\r
+\r
+            // InstanceOf statements are handled separately, silently ignore them here.\r
+            if (L0.InstanceOf.equals(relation))\r
+                continue;\r
+\r
+            // Don't copy asserted relations!\r
+            if (stm.isAsserted(source)) {\r
+                if (DEBUG_COPY)\r
+                    System.out.println("[" + level + "]\t\tSkipping asserted statement");\r
+                continue;\r
+            }\r
+\r
+            if (DEBUG_COPY)\r
+                System.out.println("[" + level + "]\tprocessing statement (" + NameUtils.toString(graph, stm) + ")");\r
+\r
+            Resource subject = stm.getSubject();\r
+            Resource inverse = graph.getPossibleInverse(relation);\r
+            boolean addInverse = false;\r
+            Resource obj = stm.getObject();\r
+            Resource propType = graph.getPossibleType(obj, L0.Literal);\r
+\r
+            // ยง1 only L0.IsComposedOf and its subrelations can be considered to be copied automatically\r
+\r
+            if (propType != null || graph.isSubrelationOf(relation, L0.IsComposedOf)) {\r
+                if (propType != null && graph.hasStatement(propType, L0.Enumeration, propType)) {\r
+                    if (DEBUG_COPY)\r
+                        System.out.println("[" + level + "]\t\tclaim enumeration statement");\r
+                    graph.claim(copy, relation, null, obj);\r
+                } else {\r
+                    if (inverse != null)\r
+                        addInverse = graph.hasStatement(obj, inverse, subject);\r
+\r
+                    // Copy instantiated properties, not asserted ones.\r
+                    if (DEBUG_COPY)\r
+                        System.out.println("[" + level + "]\t\tcopy whole object");\r
+                    Resource clone = copy(graph, obj, level + 1, advisor, copyMap);\r
+                    graph.claim(copy, relation, clone);\r
+                }\r
+            } else {\r
+                if (advisor != null) {\r
+                    Boolean result = advisor.call(graph, stm);\r
+                    if (Boolean.TRUE.equals(result)) {\r
+                        // Don't clone the object, just add relation to the same object.\r
+                        if (inverse != null)\r
+                            addInverse = graph.hasStatement(obj, inverse, subject);\r
+\r
+                        if (DEBUG_COPY) {\r
+                            System.out.println("[" + level + "]\t\tCopyAdvisorUtil.copy.claim(" + NameUtils.getSafeName(graph, copy) + ", "\r
+                                    + NameUtils.getSafeName(graph, relation) + ", "\r
+                                    + (addInverse ? NameUtils.getSafeName(graph, inverse) : null) + ", "\r
+                                    + NameUtils.getSafeName(graph, obj));\r
+                        }\r
+\r
+                        graph.claim(copy, relation, addInverse ? inverse : null, obj);\r
+                    }\r
+                } else {\r
+                    if (DEBUG_COPY)\r
+                        System.out.println("[" + level + "]\t\tskipping statement");\r
+                }\r
+            }\r
+        }\r
+        return copy;\r
+    }\r
+\r
+    /**\r
+     * Creates and returns a copy of the specified source resource based on the\r
+     * standard Layer0 relation hierarchy by recursively including all resources\r
+     * in the copy that the source and is composed of (see\r
+     * {@link Layer0#IsComposedOf}). A customizable advisor function can be used\r
+     * to guide the copy process. The routine will always copy at least all\r
+     * L0.InstanceOf statements and tags related to its input resource. If the\r
+     * copied resource has a value attached, it will also be copied.\r
+     * \r
+     * @param graph database write access\r
+     * @param source the resource to start the copy from\r
+     * @param advisor <code>null</code> or a custom advisor to guide the copy\r
+     *        process according to the specifications of\r
+     *        {@link StatementEvaluation}. Every copied statement besides\r
+     *        L0.InstanceOf and tags will be evaluated by this advisor.\r
+     * @return the copied resource\r
+     * @throws DatabaseException\r
+     */\r
+    public static Resource copy2(WriteGraph graph, Resource source,\r
+            BinaryFunction<StatementEvaluation, ReadGraph, Statement> advisor) throws DatabaseException {\r
+        return copy2(graph, source, 0, advisor, new THashMap<Object, Object>());\r
+    }\r
+\r
+    /**\r
+     * See {@link #copy2(WriteGraph, Resource, BinaryFunction)}.\r
+     * \r
+     * @param graph\r
+     * @param source\r
+     * @param advisor\r
+     * @param copyMap a map for storing the correspondences between original and\r
+     *        copied objects. This is used to output data from the copy process.\r
+     * @return\r
+     * @throws DatabaseException \r
+     */\r
+    public static Resource copy2(WriteGraph graph, Resource source,\r
+            BinaryFunction<StatementEvaluation, ReadGraph, Statement> advisor, Map<Object, Object> copyMap)\r
+            throws DatabaseException {\r
+        return copy2(graph, source, 0, advisor, copyMap);\r
+    }\r
+\r
+    private static Resource copy2(final WriteGraph graph, final Resource source, final int level,\r
+            BinaryFunction<StatementEvaluation, ReadGraph, Statement> advisor, Map<Object, Object> copyMap)\r
+    throws DatabaseException {\r
+        if (DEBUG_COPY)\r
+            System.out.println("[" + level + "] CopyAdvisorUtil.copy(" + NameUtils.getSafeName(graph, source) + ", advisor=" + advisor + ")");\r
+\r
+        Resource copy = (Resource) copyMap.get(source);\r
+        if (copy != null) {\r
+            if (DEBUG_COPY)\r
+                System.out.println("[" + level + "] already copied: " + NameUtils.getSafeName(graph, source) + " -> " + NameUtils.getSafeName(graph, copy));\r
+            return copy;\r
+        }\r
+\r
+        Layer0 L0 = Layer0.getInstance(graph);\r
+        copy = graph.newResource();\r
+        copyMap.put(source, copy);\r
+        for (Resource type : graph.getObjects(source, L0.InstanceOf))\r
+            graph.claim(copy, L0.InstanceOf, null, type);\r
+\r
+        if (graph.hasValue(source)) {\r
+            Datatype dt = graph.getRelatedValue(source, L0.HasDataType, Bindings.getBindingUnchecked(Datatype.class));\r
+            Binding b = Bindings.getBinding(dt);\r
+            graph.claimValue(copy, graph.<Object>getValue(source, b), b);\r
+        }\r
+\r
+        // Copy tags\r
+        for (Statement stm : graph.getStatements(source, L0.IsWeaklyRelatedTo)) {\r
+            if (stm.isAsserted(source))\r
+                continue;\r
+            if (graph.isInstanceOf(stm.getPredicate(), L0.Tag)) {\r
+                if (DEBUG_COPY)\r
+                    System.out.println("[" + level + "]\tcopying tag ("\r
+                            + NameUtils.getSafeName(graph, stm.getSubject()) + ", "\r
+                            + NameUtils.getSafeName(graph, stm.getPredicate()) + ")");\r
+                graph.claim(copy, stm.getPredicate(), stm.getPredicate(), copy);\r
+            }\r
+        }\r
+\r
+        for (Statement stm : graph.getStatements(source, L0.IsRelatedTo)) {\r
+            Resource relation = stm.getPredicate();\r
+\r
+            // InstanceOf statements are handled separately, silently ignore them here.\r
+            if (L0.InstanceOf.equals(relation))\r
+                continue;\r
+\r
+            // Don't copy asserted relations!\r
+            if (stm.isAsserted(source)) {\r
+                if (DEBUG_COPY)\r
+                    System.out.println("[" + level + "]\tskipping asserted statement (" + NameUtils.toString(graph, stm) + ")");\r
+                continue;\r
+            }\r
+\r
+            if (DEBUG_COPY)\r
+                System.out.println("[" + level + "]\tprocessing statement (" + NameUtils.toString(graph, stm) + ")");\r
+\r
+            Resource subject = stm.getSubject();\r
+            Resource inverse = graph.getPossibleInverse(relation);\r
+            Resource obj = stm.getObject();\r
+            Resource propType = graph.getPossibleType(obj, L0.Literal);\r
+            boolean addInverse = false;\r
+            boolean forceIncludeAndFollow = false;\r
+\r
+            switch (evaluate(graph, stm, advisor)) {\r
+                case SKIP:\r
+                    if (DEBUG_COPY)\r
+                        System.out.println("[" + level + "]\t\tskipping statement");\r
+                    break;\r
+\r
+                case INCLUDE:\r
+                {\r
+                    // Don't clone the object, just add relation to the same object.\r
+                    if (inverse != null)\r
+                        addInverse = graph.hasStatement(obj, inverse, subject);\r
+\r
+                    if (DEBUG_COPY) {\r
+                        System.out.println("[" + level + "]\t\tCopyAdvisorUtil.copy2.claim(" + NameUtils.getSafeName(graph, copy) + ", "\r
+                                + NameUtils.getSafeName(graph, relation) + ", "\r
+                                + (addInverse ? NameUtils.getSafeName(graph, inverse) : null) + ", "\r
+                                + NameUtils.getSafeName(graph, obj));\r
+                    }\r
+\r
+                    graph.claim(copy, relation, addInverse ? inverse : null, obj);\r
+                    break;\r
+                }\r
+\r
+                case INCLUDE_AND_FOLLOW:\r
+                    // Force follow-through in the default copy logic\r
+                    forceIncludeAndFollow = true;\r
+                    // NOTE: intentional fall-through here\r
+\r
+                case USE_DEFAULT:\r
+                {\r
+                    if (forceIncludeAndFollow || propType != null || graph.isSubrelationOf(relation, L0.IsComposedOf)) {\r
+                        if (propType != null && graph.hasStatement(propType, L0.Enumeration, propType)) {\r
+                            // This logic is applied only for enumeration property\r
+                            // statements that should not have an inverse in any case.\r
+                            if (DEBUG_COPY) {\r
+                                System.out.println("[" + level + "]\t\tclaim enumeration statement("\r
+                                        + NameUtils.getSafeName(graph, copy) + ", "\r
+                                        + NameUtils.getSafeName(graph, relation)+ ", null, "\r
+                                        + NameUtils.getSafeName(graph, obj));\r
+                            }\r
+                            graph.claim(copy, relation, null, obj);\r
+                        } else {\r
+                            // This logic is applied for other properties besides enumerations\r
+                            if (inverse != null)\r
+                                addInverse = graph.hasStatement(obj, inverse, subject);\r
+\r
+                            // Copy instantiated properties, not asserted ones.\r
+                            if (DEBUG_COPY)\r
+                                System.out.println("[" + level + "]\t\tcopy whole object");\r
+\r
+                            Resource clone = copy2(graph, obj, level + 1, advisor, copyMap);\r
+                            graph.claim(copy, relation, inverse, clone);\r
+                        }\r
+                    } else {\r
+                        if (DEBUG_COPY)\r
+                            System.out.println("[" + level + "]\t\tskipping statement");\r
+                    }\r
+                }\r
+            }\r
+        }\r
+        return copy;\r
+    }\r
+\r
+    /**\r
+     * Creates and returns a copy of the specified source resource based on the\r
+     * standard Layer0 relation hierarchy by recursively including all resources\r
+     * in the copy that the source and is composed of (see\r
+     * {@link Layer0#IsComposedOf}). A customizable advisor function can be used\r
+     * to guide the copy process. The routine will always copy at least all\r
+     * L0.InstanceOf statements and tags related to its input resource. If the\r
+     * copied resource has a value attached, it will also be copied.\r
+     * \r
+     * Works exactly like {@link #copy2(WriteGraph, Resource, BinaryFunction)}\r
+     * but uses the <code>model</code> argument to make sure that the copy\r
+     * process does not propagate outside of the model. Any references that go\r
+     * to resources with URIs that are not in the model's namespace are copied\r
+     * as unidirectional.\r
+     * \r
+     * @param graph database write access\r
+     * @param source the resource to start the copy from\r
+     * @param model the model containing the source object, used to keep the\r
+     *        copy process model-local\r
+     * @param advisor <code>null</code> or a custom advisor to guide the copy\r
+     *        process according to the specifications of\r
+     *        {@link StatementEvaluation}. Every copied statement besides\r
+     *        L0.InstanceOf and tags will be evaluated by this advisor.\r
+     * @return the copied resource\r
+     * @throws DatabaseException\r
+     */\r
+    public static Resource copy3(WriteGraph graph, Resource source, Resource model,\r
+            BinaryFunction<StatementEvaluation, ReadGraph, Statement> advisor) throws DatabaseException {\r
+        String modelURI = graph.getURI(model);\r
+        return copy3(graph, modelURI, source, 0, advisor, new THashMap<Object, Object>());\r
+    }\r
+\r
+    /**\r
+     * See {@link #copy3(WriteGraph, Resource, Resource, BinaryFunction)}.\r
+     * \r
+     * @param graph\r
+     * @param source\r
+     * @param model\r
+     * @param advisor\r
+     * @param copyMap a map for storing the correspondences between original and\r
+     *        copied objects. This is used to output data from the copy process.\r
+     * @return\r
+     * @throws DatabaseException\r
+     */\r
+    public static Resource copy3(WriteGraph graph, Resource source, Resource model,\r
+            BinaryFunction<StatementEvaluation, ReadGraph, Statement> advisor, Map<Object, Object> copyMap) throws DatabaseException {\r
+        String modelURI = graph.getURI(model);\r
+        return copy3(graph, modelURI, source, 0, advisor, copyMap);\r
+    }\r
+\r
+    private static Resource copy3(WriteGraph graph, String modelURI, Resource source, int level,\r
+            BinaryFunction<StatementEvaluation, ReadGraph, Statement> advisor, Map<Object, Object> copyMap)\r
+    throws DatabaseException {\r
+        if (DEBUG_COPY)\r
+            System.out.println("[" + level + "] CopyAdvisorUtil.copy(" + NameUtils.getSafeName(graph, source) + ", advisor=" + advisor + ")");\r
+\r
+        Resource copy = (Resource) copyMap.get(source);\r
+        if (copy != null) {\r
+            if (DEBUG_COPY)\r
+                System.out.println("[" + level + "] already copied: " + NameUtils.getSafeName(graph, source) + " -> " + NameUtils.getSafeName(graph, copy));\r
+            return copy;\r
+        }\r
+\r
+        Layer0 L0 = Layer0.getInstance(graph);\r
+        copy = graph.newResource();\r
+        copyMap.put(source, copy);\r
+        for (Resource type : graph.getObjects(source, L0.InstanceOf))\r
+            graph.claim(copy, L0.InstanceOf, null, type);\r
+\r
+        if (graph.hasValue(source)) {\r
+            Datatype dt = graph.getRelatedValue(source, L0.HasDataType, Bindings.getBindingUnchecked(Datatype.class));\r
+            Binding b = Bindings.getBinding(dt);\r
+            graph.claimValue(copy, graph.<Object>getValue(source, b), b);\r
+        }\r
+\r
+        // Copy tags\r
+        for (Statement stm : graph.getStatements(source, L0.IsWeaklyRelatedTo)) {\r
+            if (stm.isAsserted(source))\r
+                continue;\r
+            if (graph.isInstanceOf(stm.getPredicate(), L0.Tag)) {\r
+                if (DEBUG_COPY)\r
+                    System.out.println("[" + level + "]\tcopying tag ("\r
+                            + NameUtils.getSafeName(graph, stm.getSubject()) + ", "\r
+                            + NameUtils.getSafeName(graph, stm.getPredicate()) + ")");\r
+                graph.claim(copy, stm.getPredicate(), stm.getPredicate(), copy);\r
+            }\r
+        }\r
+\r
+        for (Statement stm : graph.getStatements(source, L0.IsRelatedTo)) {\r
+            Resource relation = stm.getPredicate();\r
+\r
+            // InstanceOf statements are handled separately, silently ignore them here.\r
+            if (L0.InstanceOf.equals(relation))\r
+                continue;\r
+\r
+            // Don't copy asserted relations!\r
+            if (stm.isAsserted(source)) {\r
+                if (DEBUG_COPY)\r
+                    System.out.println("[" + level + "]\tskipping asserted statement (" + NameUtils.toString(graph, stm) + ")");\r
+                continue;\r
+            }\r
+\r
+            if (DEBUG_COPY)\r
+                System.out.println("[" + level + "]\tprocessing statement (" + NameUtils.toString(graph, stm) + ")");\r
+\r
+            Resource subject = stm.getSubject();\r
+            Resource inverse = graph.getPossibleInverse(relation);\r
+            Resource obj = stm.getObject();\r
+            Resource propType = graph.getPossibleType(obj, L0.Literal);\r
+            boolean addInverse = false;\r
+            boolean forceIncludeAndFollow = false;\r
+\r
+            switch (evaluate(graph, stm, advisor)) {\r
+                case SKIP:\r
+                    if (DEBUG_COPY)\r
+                        System.out.println("[" + level + "]\t\tskipping statement");\r
+                    break;\r
+\r
+                case INCLUDE:\r
+                {\r
+                    // Don't clone the object, just add relation to the same object.\r
+                    if (inverse != null)\r
+                        addInverse = graph.hasStatement(obj, inverse, subject);\r
+\r
+                    if (DEBUG_COPY) {\r
+                        System.out.println("[" + level + "]\t\tCopyAdvisorUtil.copy2.claim(" + NameUtils.getSafeName(graph, copy) + ", "\r
+                                + NameUtils.getSafeName(graph, relation) + ", "\r
+                                + (addInverse ? NameUtils.getSafeName(graph, inverse) : null) + ", "\r
+                                + NameUtils.getSafeName(graph, obj));\r
+                    }\r
+\r
+                    graph.claim(copy, relation, addInverse ? inverse : null, obj);\r
+                    break;\r
+                }\r
+\r
+                case INCLUDE_AND_FOLLOW:\r
+                    // Force follow-through in the default copy logic\r
+                    forceIncludeAndFollow = true;\r
+                    // NOTE: intentional fall-through here\r
+\r
+                case USE_DEFAULT:\r
+                {\r
+                    if (forceIncludeAndFollow || propType != null || graph.isSubrelationOf(relation, L0.IsComposedOf)) {\r
+                        if (propType != null && graph.hasStatement(propType, L0.Enumeration, propType)) {\r
+                            // This logic is applied only for enumeration property\r
+                            // statements that should not have an inverse in any case.\r
+                            if (DEBUG_COPY) {\r
+                                System.out.println("[" + level + "]\t\tclaim enumeration statement("\r
+                                        + NameUtils.getSafeName(graph, copy) + ", "\r
+                                        + NameUtils.getSafeName(graph, relation)+ ", null, "\r
+                                        + NameUtils.getSafeName(graph, obj));\r
+                            }\r
+                            graph.claim(copy, relation, null, obj);\r
+                        } else {\r
+                            // This logic is applied for other properties besides enumerations\r
+                            if (inverse != null)\r
+                                addInverse = graph.hasStatement(obj, inverse, subject);\r
+\r
+                            String objectURI = graph.getPossibleURI(obj);\r
+\r
+                            if(objectURI != null && !objectURI.startsWith(modelURI)) {\r
+\r
+                                if (DEBUG_COPY)\r
+                                    System.out.println("[" + level + "]\t\tclaim ontological reference");\r
+\r
+                                graph.claim(copy, relation, null, obj);\r
+\r
+                            } else {\r
+\r
+                                // Copy instantiated properties, not asserted ones.\r
+                                if (DEBUG_COPY)\r
+                                    System.out.println("[" + level + "]\t\tcopy whole object");\r
+\r
+                                Resource clone = copy3(graph, modelURI, obj, level + 1, advisor, copyMap);\r
+                                graph.claim(copy, relation, inverse, clone);\r
+\r
+                            }\r
+\r
+                        }\r
+                    } else {\r
+                        if (DEBUG_COPY)\r
+                            System.out.println("[" + level + "]\t\tskipping statement");\r
+                    }\r
+                }\r
+            }\r
+        }\r
+        return copy;\r
+    }\r
+\r
+    protected static StatementEvaluation evaluate(ReadGraph graph, Statement stm, BinaryFunction<StatementEvaluation, ReadGraph, Statement> tester) {\r
+        if (tester == null)\r
+            return StatementEvaluation.USE_DEFAULT;\r
+        return tester.call(graph, stm);\r
+    }\r
+\r
+    /**\r
+     * Equal to\r
+     * <code>copy4(graph, source, graph.adapt(source, CopyHandler.class)))</code>\r
+     * .\r
+     * \r
+     * @param graph database write access\r
+     * @param source the resource to start the copy from\r
+     * @return the copied resource\r
+     * @throws DatabaseException\r
+     */\r
+    public static Resource copy4(WriteGraph graph, Resource source) throws DatabaseException {\r
+        CopyHandler handler = graph.adapt(source, CopyHandler.class);\r
+        return copy4(graph, source, handler);\r
+    }\r
+\r
+    /**\r
+     * Creates and returns a copy of the specified source resource based on\r
+     * transferable graph export and import. The TG representation shall be\r
+     * generated by the specified {@link CopyHandler} into a\r
+     * {@link SimanticsClipboard} instance from where it is read back as\r
+     * {@link TransferableGraph1} and imported into the database through\r
+     * {@link TransferableGraphs#importGraph1(WriteGraph, TransferableGraph1, org.simantics.graph.db.IImportAdvisor)}.\r
+     * \r
+     * @param graph database write access\r
+     * @param source the resource to start the copy from\r
+     * @param copyHandler the handler to use for generating the transferable\r
+     *        graph representation from the copied resource\r
+     * @return the copied resource\r
+     * @throws DatabaseException\r
+     */\r
+    public static Resource copy4(WriteGraph graph, Resource source, CopyHandler copyHandler) throws DatabaseException {\r
+        SimanticsClipboardImpl builder = new SimanticsClipboardImpl();\r
+        copyHandler.copyToClipboard(graph, builder);\r
+\r
+        for(Set<Representation> object : builder.getContents()) {\r
+            TransferableGraph1 tg = ClipboardUtils.accept(graph, object, SimanticsKeys.KEY_TRANSFERABLE_GRAPH);\r
+            if(tg != null) {\r
+                FixedRootImportAdvisor advisor = new FixedRootImportAdvisor();\r
+                TransferableGraphs.importGraph1(graph, tg, advisor);\r
+                return advisor.getRoot();\r
+            }\r
+        }\r
+\r
+        String uri = graph.getPossibleURI(source);\r
+        throw new DatabaseException("Failed to copy resource " + NameUtils.getSafeName(graph, source, true)\r
+                + (uri != null ? " with URI " + uri : ""));\r
+    }\r
+\r
+}\r