This is an automated email from the ASF dual-hosted git repository.

andy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/jena.git


The following commit(s) were added to refs/heads/main by this push:
     new cf0e1f352b GH-2169: Enhanced GraphMem Cloning: - added new interface 
Copyable<>   - implemented Copyable#copy in GraphMem2 all three descendants 
(Legacy, Fast and Roaring)   - extended TripleStore to implement Copyable<> and 
implemented it in all three descendants (Legacy, Fast and Roaring) - added copy 
constructors to mem2/collections/*, mem2/store/*/*Bunch, mem2/store/*/*HashMap 
and mem2/store/*/*Set - added unit tests to keep the test coverage for mem2/** 
at 100% - added bench [...]
cf0e1f352b is described below

commit cf0e1f352b40708ccfcb4d9228bc8c0e51531acd
Author: arne-bdt <[email protected]>
AuthorDate: Wed Jan 31 22:56:55 2024 +0100

    GH-2169: Enhanced GraphMem Cloning:
    - added new interface Copyable<>
      - implemented Copyable#copy in GraphMem2 all three descendants (Legacy, 
Fast and Roaring)
      - extended TripleStore to implement Copyable<> and implemented it in all 
three descendants (Legacy, Fast and Roaring)
    - added copy constructors to mem2/collections/*, mem2/store/*/*Bunch, 
mem2/store/*/*HashMap and mem2/store/*/*Set
    - added unit tests to keep the test coverage for mem2/** at 100%
    - added benchmark for GraphMem2#copy
    - implemented new method G#copy which uses Copyable#copy if the graph 
implements it, otherwise G#copyGraphSrcToDst is used as fallback.
      - added a unit test for G#copy
---
 .../src/main/java/org/apache/jena/system/G.java    |  32 ++++--
 .../test/java/org/apache/jena/system/GTest.java    |  81 +++++++++++++++
 .../java/org/apache/jena/atlas/lib/Copyable.java   |  21 ++--
 .../org/apache/jena/mem/graph/TestGraphCopy.java   | 111 ++++++++++++++++++++
 .../main/java/org/apache/jena/mem2/GraphMem2.java  |  17 +++-
 .../java/org/apache/jena/mem2/GraphMem2Fast.java   |   9 ++
 .../java/org/apache/jena/mem2/GraphMem2Legacy.java |   9 ++
 .../org/apache/jena/mem2/GraphMem2Roaring.java     |  10 +-
 .../apache/jena/mem2/collection/FastHashBase.java  |  20 ++++
 .../apache/jena/mem2/collection/FastHashMap.java   |  29 ++++++
 .../apache/jena/mem2/collection/FastHashSet.java   |  10 ++
 .../jena/mem2/collection/HashCommonBase.java       |  13 +++
 .../apache/jena/mem2/collection/HashCommonMap.java |  29 ++++++
 .../apache/jena/mem2/collection/HashCommonSet.java |  10 ++
 .../org/apache/jena/mem2/store/TripleStore.java    |  11 +-
 .../jena/mem2/store/fast/FastArrayBunch.java       |  13 +++
 .../jena/mem2/store/fast/FastHashedBunchMap.java   |  20 +++-
 .../mem2/store/fast/FastHashedTripleBunch.java     |  15 +++
 .../jena/mem2/store/fast/FastTripleBunch.java      |   3 +-
 .../jena/mem2/store/fast/FastTripleStore.java      |  65 +++++++++++-
 .../apache/jena/mem2/store/legacy/ArrayBunch.java  |  18 ++++
 .../jena/mem2/store/legacy/HashedBunchMap.java     |  21 ++++
 .../jena/mem2/store/legacy/HashedTripleBunch.java  |   9 ++
 .../jena/mem2/store/legacy/LegacyTripleStore.java  |  26 +++--
 .../jena/mem2/store/legacy/NodeToTriplesMap.java   |   8 ++
 .../mem2/store/legacy/NodeToTriplesMapMem.java     |  25 ++++-
 .../apache/jena/mem2/store/legacy/TripleBunch.java |   3 +-
 .../mem2/store/roaring/RoaringTripleStore.java     | 109 +++++++++++++++-----
 .../apache/jena/mem2/AbstractGraphMem2Test.java    |  48 ++++++++-
 .../org/apache/jena/mem2/GraphMem2FastTest.java    |   4 +-
 .../org/apache/jena/mem2/GraphMem2LegacyTest.java  |   4 +-
 .../org/apache/jena/mem2/GraphMem2RoaringTest.java |   4 +-
 .../java/org/apache/jena/mem2/GraphMem2Test.java   |  17 +++-
 .../jena/mem2/collection/FastHashMapTest2.java     |  82 +++++++++++++--
 .../jena/mem2/collection/FastHashSetTest2.java     |  49 +++++++++
 .../jena/mem2/collection/HashCommonMapTest.java    | 112 ++++++++++++++++++---
 .../jena/mem2/collection/HashCommonSetTest.java    |  80 +++++++++++++--
 .../jena/mem2/store/AbstractTripleStoreTest.java   |  82 +++++++++++++++
 .../jena/mem2/store/fast/FastArrayBunchTest.java   |  29 ++++--
 .../mem2/store/fast/FastHashedTripleBunchTest.java |  37 ++++---
 40 files changed, 1165 insertions(+), 130 deletions(-)

diff --git a/jena-arq/src/main/java/org/apache/jena/system/G.java 
b/jena-arq/src/main/java/org/apache/jena/system/G.java
index d4a5209c7f..ea6033caa1 100644
--- a/jena-arq/src/main/java/org/apache/jena/system/G.java
+++ b/jena-arq/src/main/java/org/apache/jena/system/G.java
@@ -18,14 +18,8 @@
 
 package org.apache.jena.system;
 
-import static org.apache.jena.graph.Node.ANY;
-
-import java.util.*;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-import java.util.function.Supplier;
-
 import org.apache.jena.atlas.iterator.Iter;
+import org.apache.jena.atlas.lib.Copyable;
 import org.apache.jena.datatypes.RDFDatatype;
 import org.apache.jena.datatypes.xsd.XSDDatatype;
 import org.apache.jena.graph.*;
@@ -39,6 +33,13 @@ import org.apache.jena.sparql.util.graph.GraphList;
 import org.apache.jena.util.IteratorCollection;
 import org.apache.jena.util.iterator.ExtendedIterator;
 
+import java.util.*;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+import static org.apache.jena.graph.Node.ANY;
+
 /**
  * A library of functions for working with {@link Graph}. Internally all
  * {@link ExtendedIterator ExtendedIterators} used, run to completion or have
@@ -807,6 +808,23 @@ public class G {
         apply(src, dst::add);
     }
 
+    /**
+     * Creates a copy of the given graph.
+     * If the graph implements Copyable<Graph> then the copy method is called.
+     * Otherwise, a new system default memory-based graph is created and the 
triples are copied
+     * into it.
+     * @param src the graph to copy
+     * @return a copy of the graph
+     */
+    public static Graph copy(Graph src) {
+        if(src instanceof Copyable<?>) {
+            return ((Copyable<Graph>)src).copy();
+        }
+        Graph dst = GraphMemFactory.createDefaultGraph();
+        copyGraphSrcToDst(src, dst);
+        return dst;
+    }
+
     /**
      * Clear graph.
      */
diff --git a/jena-arq/src/test/java/org/apache/jena/system/GTest.java 
b/jena-arq/src/test/java/org/apache/jena/system/GTest.java
new file mode 100644
index 0000000000..e32f851110
--- /dev/null
+++ b/jena-arq/src/test/java/org/apache/jena/system/GTest.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.system;
+
+import org.apache.jena.atlas.lib.Copyable;
+import org.apache.jena.mem.GraphMem;
+import org.apache.jena.mem2.GraphMem2Fast;
+import org.junit.Test;
+
+import static org.apache.jena.testing_framework.GraphHelper.triple;
+import static org.junit.Assert.*;
+
+public class GTest {
+
+    @Test
+    public void copy() {
+        // Test graph which implements Copyable<>
+        {
+            var graphImplementingCopyable = new GraphMem2Fast();
+
+            assertTrue(graphImplementingCopyable instanceof Copyable<?>);
+
+            graphImplementingCopyable.add(triple("s1 p1 o1"));
+            graphImplementingCopyable.add(triple("s1 p2 o1"));
+            graphImplementingCopyable.add(triple("s2 p1 o1"));
+            graphImplementingCopyable.add(triple("s2 p1 o2"));
+            graphImplementingCopyable.add(triple("s2 p1 o2"));
+
+            var copy = G.copy(graphImplementingCopyable);
+
+            assertEquals(graphImplementingCopyable.size(), copy.size());
+
+            copy.delete(triple("s1 p1 o1"));
+            assertEquals(graphImplementingCopyable.size() - 1, copy.size());
+
+            copy.add(triple("s3 p3 o3"));
+            copy.add(triple("s4 p4 o4"));
+            assertEquals(graphImplementingCopyable.size() + 1, copy.size());
+        }
+
+        // Test graph which does not implement Copyable<>
+        {
+            var notCopyableGraph = new GraphMem();
+
+            assertFalse(notCopyableGraph instanceof Copyable<?>);
+
+            notCopyableGraph.add(triple("s1 p1 o1"));
+            notCopyableGraph.add(triple("s1 p2 o1"));
+            notCopyableGraph.add(triple("s2 p1 o1"));
+            notCopyableGraph.add(triple("s2 p1 o2"));
+            notCopyableGraph.add(triple("s2 p1 o2"));
+
+            var copy = G.copy(notCopyableGraph);
+
+            assertEquals(notCopyableGraph.size(), copy.size());
+
+            copy.delete(triple("s1 p1 o1"));
+            assertEquals(notCopyableGraph.size() - 1, copy.size());
+
+            copy.add(triple("s3 p3 o3"));
+            copy.add(triple("s4 p4 o4"));
+            assertEquals(notCopyableGraph.size() + 1, copy.size());
+        }
+    }
+}
\ No newline at end of file
diff --git 
a/jena-core/src/test/java/org/apache/jena/mem2/GraphMem2LegacyTest.java 
b/jena-base/src/main/java/org/apache/jena/atlas/lib/Copyable.java
similarity index 68%
copy from jena-core/src/test/java/org/apache/jena/mem2/GraphMem2LegacyTest.java
copy to jena-base/src/main/java/org/apache/jena/atlas/lib/Copyable.java
index 2f2944a096..6d8ab8a775 100644
--- a/jena-core/src/test/java/org/apache/jena/mem2/GraphMem2LegacyTest.java
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/Copyable.java
@@ -15,14 +15,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.jena.mem2;
 
-import org.apache.jena.graph.Graph;
+package org.apache.jena.atlas.lib;
 
-public class GraphMem2LegacyTest extends AbstractGraphMem2Test {
+/**
+ * Generic interface for objects that can create an independent copy of 
themselves.
+ * Any operations on the copy must not affect the original in any way.
+ * @param <T> must be the type of the implementing class
+ */
+public interface Copyable<T> {
 
-    @Override
-    protected Graph createGraph() {
-        return new GraphMem2Legacy();
-    }
-}
\ No newline at end of file
+    /**
+     * Create a copy of this object.
+     * @return
+     */
+    T copy();
+}
diff --git 
a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphCopy.java
 
b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphCopy.java
new file mode 100644
index 0000000000..2a44483369
--- /dev/null
+++ 
b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphCopy.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.mem.graph;
+
+import org.apache.jena.mem.graph.helper.Context;
+import org.apache.jena.mem.graph.helper.JMHDefaultOptions;
+import org.apache.jena.mem.graph.helper.Releases;
+import org.apache.jena.mem2.GraphMem2;
+import org.junit.Assert;
+import org.junit.Test;
+import org.openjdk.jmh.annotations.*;
+import org.openjdk.jmh.runner.Runner;
+
+import java.util.function.Supplier;
+
+
+@State(Scope.Benchmark)
+public class TestGraphCopy {
+
+    @Param({
+            "../testing/cheeses-0.1.ttl",
+            "../testing/pizza.owl.rdf",
+            "../testing/BSBM/bsbm-1m.nt.gz",
+    })
+    public String param0_GraphUri;
+
+    @Param({
+            "GraphMem2Fast (current)",
+            "GraphMem2Legacy (current)",
+            "GraphMem2Roaring (current)",
+    })
+    public String param1_GraphImplementation;
+
+    @Param({
+            "copy",
+            "findAndAddAll",
+    })
+    public String param2_CopyOrConstruct;
+
+    Supplier<GraphMem2> copySupplier;
+
+    Supplier<GraphMem2> newGraphSupplier;
+    private GraphMem2 sutCurrent;
+
+    @Benchmark
+    public GraphMem2 copy() {
+        return copySupplier.get();
+    }
+
+    private GraphMem2 nativeCopy() {
+        var copy = sutCurrent.copy();
+        return copy;
+    }
+
+    private GraphMem2 findAndAddAll() {
+        var copy = (GraphMem2) newGraphSupplier.get();
+        sutCurrent.find().forEachRemaining(copy::add);
+        return copy;
+    }
+
+    @Setup(Level.Trial)
+    public void setupTrial() throws Exception {
+        var trialContext = new Context(param1_GraphImplementation);
+        switch (trialContext.getJenaVersion()) {
+            case CURRENT: {
+                this.newGraphSupplier = () -> (GraphMem2) 
Releases.current.createGraph(trialContext.getGraphClass());
+                this.sutCurrent = this.newGraphSupplier.get();
+
+                var triples = Releases.current.readTriples(param0_GraphUri);
+                triples.forEach(this.sutCurrent::add);
+            }
+            break;
+            default:
+                throw new IllegalArgumentException("Unsupported Jena version: 
" + trialContext.getJenaVersion());
+        }
+        switch (param2_CopyOrConstruct) {
+            case "copy":
+                this.copySupplier = this::nativeCopy;
+                break;
+            case "findAndAddAll":
+                this.copySupplier = this::findAndAddAll;
+                break;
+            default:
+                throw new IllegalArgumentException("Unsupported copy or 
construct: " + param2_CopyOrConstruct);
+        }
+    }
+
+    @Test
+    public void benchmark() throws Exception {
+        var opt = JMHDefaultOptions.getDefaults(this.getClass())
+                .build();
+        var results = new Runner(opt).run();
+        Assert.assertNotNull(results);
+    }
+}
diff --git a/jena-core/src/main/java/org/apache/jena/mem2/GraphMem2.java 
b/jena-core/src/main/java/org/apache/jena/mem2/GraphMem2.java
index 7c115f4b17..cb2be229ce 100644
--- a/jena-core/src/main/java/org/apache/jena/mem2/GraphMem2.java
+++ b/jena-core/src/main/java/org/apache/jena/mem2/GraphMem2.java
@@ -18,6 +18,7 @@
 
 package org.apache.jena.mem2;
 
+import org.apache.jena.atlas.lib.Copyable;
 import org.apache.jena.graph.Capabilities;
 import org.apache.jena.graph.Node;
 import org.apache.jena.graph.Triple;
@@ -36,11 +37,11 @@ import java.util.stream.Stream;
  * Implementation must always comply to term-equality semantics. The 
characteristics of the
  * implementations always have handlesLiteralTyping() == false.
  */
-public class GraphMem2 extends GraphMemBase implements GraphWithPerform {
+public class GraphMem2 extends GraphMemBase implements GraphWithPerform, 
Copyable<GraphMem2> {
 
     final TripleStore tripleStore;
 
-    public GraphMem2(TripleStore tripleStore) {
+    protected GraphMem2(TripleStore tripleStore) {
         super();
         this.tripleStore = tripleStore;
     }
@@ -146,4 +147,16 @@ public class GraphMem2 extends GraphMemBase implements 
GraphWithPerform {
     public Capabilities getCapabilities() {
         return AllCapabilities.updateAllowed;
     }
+
+    /**
+     * Creates a copy of this graph.
+     * Since the triples and nodes are immutable, the copy contains the same 
triples and nodes as this graph.
+     * Modifications to the copy will not affect this graph.
+     *
+     * @return independent copy of the current graph
+     */
+    @Override
+    public GraphMem2 copy() {
+        return new GraphMem2(this.tripleStore.copy());
+    }
 }
diff --git a/jena-core/src/main/java/org/apache/jena/mem2/GraphMem2Fast.java 
b/jena-core/src/main/java/org/apache/jena/mem2/GraphMem2Fast.java
index 0e4d2efbab..b32d9495be 100644
--- a/jena-core/src/main/java/org/apache/jena/mem2/GraphMem2Fast.java
+++ b/jena-core/src/main/java/org/apache/jena/mem2/GraphMem2Fast.java
@@ -18,6 +18,7 @@
 
 package org.apache.jena.mem2;
 
+import org.apache.jena.mem2.store.TripleStore;
 import org.apache.jena.mem2.store.fast.FastTripleStore;
 
 /**
@@ -42,4 +43,12 @@ public class GraphMem2Fast extends GraphMem2 {
         super(new FastTripleStore());
     }
 
+    private GraphMem2Fast(final TripleStore tripleStore) {
+        super(tripleStore);
+    }
+
+    @Override
+    public GraphMem2Fast copy() {
+        return new GraphMem2Fast(this.tripleStore.copy());
+    }
 }
diff --git a/jena-core/src/main/java/org/apache/jena/mem2/GraphMem2Legacy.java 
b/jena-core/src/main/java/org/apache/jena/mem2/GraphMem2Legacy.java
index 9a182c894e..bb8e40adf3 100644
--- a/jena-core/src/main/java/org/apache/jena/mem2/GraphMem2Legacy.java
+++ b/jena-core/src/main/java/org/apache/jena/mem2/GraphMem2Legacy.java
@@ -18,6 +18,7 @@
 
 package org.apache.jena.mem2;
 
+import org.apache.jena.mem2.store.TripleStore;
 import org.apache.jena.mem2.store.legacy.LegacyTripleStore;
 
 /**
@@ -44,4 +45,12 @@ public class GraphMem2Legacy extends GraphMem2 {
         super(new LegacyTripleStore());
     }
 
+    private GraphMem2Legacy(final TripleStore tripleStore) {
+        super(tripleStore);
+    }
+
+    @Override
+    public GraphMem2Legacy copy() {
+        return new GraphMem2Legacy(this.tripleStore.copy());
+    }
 }
diff --git a/jena-core/src/main/java/org/apache/jena/mem2/GraphMem2Roaring.java 
b/jena-core/src/main/java/org/apache/jena/mem2/GraphMem2Roaring.java
index c37f5579d5..bee2ab43c1 100644
--- a/jena-core/src/main/java/org/apache/jena/mem2/GraphMem2Roaring.java
+++ b/jena-core/src/main/java/org/apache/jena/mem2/GraphMem2Roaring.java
@@ -18,6 +18,7 @@
 
 package org.apache.jena.mem2;
 
+import org.apache.jena.mem2.store.TripleStore;
 import org.apache.jena.mem2.store.roaring.RoaringTripleStore;
 
 /**
@@ -41,9 +42,16 @@ import org.apache.jena.mem2.store.roaring.RoaringTripleStore;
  * - The bitmaps contain the indices of the triples in the central hash set.
  */
 public class GraphMem2Roaring extends GraphMem2 {
-
     public GraphMem2Roaring() {
         super(new RoaringTripleStore());
     }
 
+    private GraphMem2Roaring(final TripleStore tripleStore) {
+        super(tripleStore);
+    }
+
+    @Override
+    public GraphMem2Roaring copy() {
+        return new GraphMem2Roaring(this.tripleStore.copy());
+    }
 }
diff --git 
a/jena-core/src/main/java/org/apache/jena/mem2/collection/FastHashBase.java 
b/jena-core/src/main/java/org/apache/jena/mem2/collection/FastHashBase.java
index aa2823a1f2..7f895067e7 100644
--- a/jena-core/src/main/java/org/apache/jena/mem2/collection/FastHashBase.java
+++ b/jena-core/src/main/java/org/apache/jena/mem2/collection/FastHashBase.java
@@ -86,7 +86,27 @@ public abstract class FastHashBase<K> implements 
JenaMapSetCommon<K> {
         this.positions = new int[MINIMUM_HASHES_SIZE];
         this.keys = newKeysArray(MINIMUM_ELEMENTS_SIZE);
         this.hashCodesOrDeletedIndices = new int[MINIMUM_ELEMENTS_SIZE];
+    }
+
+    /**
+     * Copy constructor.
+     * The new map will contain all the same keys of the map to copy.
+     *
+     * @param baseToCopy
+     */
+    protected <T extends FastHashBase> FastHashBase(final T baseToCopy)  {
+        this.positions = new int[baseToCopy.positions.length];
+        System.arraycopy(baseToCopy.positions, 0, this.positions, 0, 
baseToCopy.positions.length);
+
+        this.hashCodesOrDeletedIndices = new 
int[baseToCopy.hashCodesOrDeletedIndices.length];
+        System.arraycopy(baseToCopy.hashCodesOrDeletedIndices, 0, 
this.hashCodesOrDeletedIndices, 0, baseToCopy.hashCodesOrDeletedIndices.length);
+
+        this.keys = newKeysArray(baseToCopy.keys.length);
+        System.arraycopy(baseToCopy.keys, 0, this.keys, 0, 
baseToCopy.keys.length);
 
+        this.keysPos = baseToCopy.keysPos;
+        this.lastDeletedIndex = baseToCopy.lastDeletedIndex;
+        this.removedKeysCount = baseToCopy.removedKeysCount;
     }
 
     /**
diff --git 
a/jena-core/src/main/java/org/apache/jena/mem2/collection/FastHashMap.java 
b/jena-core/src/main/java/org/apache/jena/mem2/collection/FastHashMap.java
index b9a162908b..62d4010415 100644
--- a/jena-core/src/main/java/org/apache/jena/mem2/collection/FastHashMap.java
+++ b/jena-core/src/main/java/org/apache/jena/mem2/collection/FastHashMap.java
@@ -50,6 +50,35 @@ public abstract class FastHashMap<K, V> extends 
FastHashBase<K> implements JenaM
         this.values = newValuesArray(keys.length);
     }
 
+    /**
+     * Copy constructor.
+     * The new map will contain all the same keys and values of the map to 
copy.
+     *
+     * @param mapToCopy
+     */
+    protected FastHashMap(final FastHashMap<K, V> mapToCopy) {
+        super(mapToCopy);
+        this.values = newValuesArray(keys.length);
+        System.arraycopy(mapToCopy.values, 0, this.values, 0, 
mapToCopy.values.length);
+    }
+
+    /**
+     * Copy constructor with value processor.
+     *
+     * @param mapToCopy
+     * @param valueProcessor
+     */
+    protected FastHashMap(final FastHashMap<K, V> mapToCopy, final 
UnaryOperator<V> valueProcessor) {
+        super(mapToCopy);
+        this.values = newValuesArray(keys.length);
+        for (int i = 0; i < mapToCopy.values.length; i++) {
+            final var value = mapToCopy.values[i];
+            if (value != null) {
+                this.values[i] = valueProcessor.apply(value);
+            }
+        }
+    }
+
     protected abstract V[] newValuesArray(int size);
 
     @Override
diff --git 
a/jena-core/src/main/java/org/apache/jena/mem2/collection/FastHashSet.java 
b/jena-core/src/main/java/org/apache/jena/mem2/collection/FastHashSet.java
index 1a9d15bc07..78adb1f0d2 100644
--- a/jena-core/src/main/java/org/apache/jena/mem2/collection/FastHashSet.java
+++ b/jena-core/src/main/java/org/apache/jena/mem2/collection/FastHashSet.java
@@ -37,6 +37,16 @@ public abstract class FastHashSet<K> extends FastHashBase<K> 
implements JenaSetH
         super();
     }
 
+    /**
+     * Copy constructor.
+     * The new set will contain all the same keys of the set to copy.
+     *
+     * @param setToCopy
+     */
+    protected FastHashSet(final FastHashSet<K> setToCopy) {
+        super(setToCopy);
+    }
+
     @Override
     public boolean tryAdd(K key) {
         return tryAdd(key, key.hashCode());
diff --git 
a/jena-core/src/main/java/org/apache/jena/mem2/collection/HashCommonBase.java 
b/jena-core/src/main/java/org/apache/jena/mem2/collection/HashCommonBase.java
index 06d98eca0b..87c8932590 100644
--- 
a/jena-core/src/main/java/org/apache/jena/mem2/collection/HashCommonBase.java
+++ 
b/jena-core/src/main/java/org/apache/jena/mem2/collection/HashCommonBase.java
@@ -71,6 +71,19 @@ public abstract class HashCommonBase<E> {
         threshold = (int) (keys.length * LOAD_FACTOR);
     }
 
+    /**
+     * Copy constructor.
+     * The new table will contain all the same keys of the table to copy.
+     *
+     * @param baseToCopy
+     */
+    protected HashCommonBase(final HashCommonBase<E> baseToCopy) {
+        this.keys = newKeysArray(baseToCopy.keys.length);
+        System.arraycopy(baseToCopy.keys, 0, this.keys, 0, 
baseToCopy.keys.length);
+        this.threshold = baseToCopy.threshold;
+        this.size = baseToCopy.size;
+    }
+
     protected static int nextSize(int atLeast) {
         for (int prime : primes) {
             if (prime > atLeast) return prime;
diff --git 
a/jena-core/src/main/java/org/apache/jena/mem2/collection/HashCommonMap.java 
b/jena-core/src/main/java/org/apache/jena/mem2/collection/HashCommonMap.java
index 52ac143561..b5acc20727 100644
--- a/jena-core/src/main/java/org/apache/jena/mem2/collection/HashCommonMap.java
+++ b/jena-core/src/main/java/org/apache/jena/mem2/collection/HashCommonMap.java
@@ -43,6 +43,35 @@ public abstract class HashCommonMap<K, V> extends 
HashCommonBase<K> implements J
         this.values = newValuesArray(keys.length);
     }
 
+    /**
+     * Copy constructor.
+     * The new map will contain all the same keys and values of the map to 
copy.
+     *
+     * @param mapToCopy
+     */
+    protected HashCommonMap(final HashCommonMap<K, V> mapToCopy) {
+        super(mapToCopy);
+        this.values = newValuesArray(keys.length);
+        System.arraycopy(mapToCopy.values, 0, this.values, 0, 
mapToCopy.values.length);
+    }
+
+    /**
+     * Copy constructor with value processor.
+     *
+     * @param mapToCopy
+     * @param valueProcessor
+     */
+    protected HashCommonMap(final HashCommonMap<K, V> mapToCopy, final 
UnaryOperator<V> valueProcessor) {
+        super(mapToCopy);
+        this.values = newValuesArray(keys.length);
+        for (int i = 0; i < mapToCopy.values.length; i++) {
+            final var value = mapToCopy.values[i];
+            if (value != null) {
+                this.values[i] = valueProcessor.apply(value);
+            }
+        }
+    }
+
     @Override
     public void clear(int initialCapacity) {
         super.clear(initialCapacity);
diff --git 
a/jena-core/src/main/java/org/apache/jena/mem2/collection/HashCommonSet.java 
b/jena-core/src/main/java/org/apache/jena/mem2/collection/HashCommonSet.java
index 5abbd60dcf..291dce7209 100644
--- a/jena-core/src/main/java/org/apache/jena/mem2/collection/HashCommonSet.java
+++ b/jena-core/src/main/java/org/apache/jena/mem2/collection/HashCommonSet.java
@@ -31,6 +31,16 @@ public abstract class HashCommonSet<K> extends 
HashCommonBase<K> implements Jena
         super(initialCapacity);
     }
 
+    /**
+     * Copy constructor.
+     * The new set will contain all the same keys of the set to copy.
+     *
+     * @param setToCopy
+     */
+    protected HashCommonSet(final HashCommonSet<K> setToCopy) {
+        super(setToCopy);
+    }
+
     @Override
     public boolean tryAdd(K key) {
         final var slot = findSlot(key);
diff --git 
a/jena-core/src/main/java/org/apache/jena/mem2/store/TripleStore.java 
b/jena-core/src/main/java/org/apache/jena/mem2/store/TripleStore.java
index 2b67813896..8530709b4c 100644
--- a/jena-core/src/main/java/org/apache/jena/mem2/store/TripleStore.java
+++ b/jena-core/src/main/java/org/apache/jena/mem2/store/TripleStore.java
@@ -18,6 +18,7 @@
 
 package org.apache.jena.mem2.store;
 
+import org.apache.jena.atlas.lib.Copyable;
 import org.apache.jena.graph.Triple;
 import org.apache.jena.util.iterator.ExtendedIterator;
 
@@ -27,7 +28,7 @@ import java.util.stream.Stream;
  * A triple store is a collection of triples that supports access to
  * triples matching a triple pattern.
  */
-public interface TripleStore {
+public interface TripleStore extends Copyable<TripleStore> {
 
     /**
      * Add a triple to the map.
@@ -87,4 +88,12 @@ public interface TripleStore {
      * Returns an {@link ExtendedIterator} of all triples in the graph 
matching the given triple match.
      */
     ExtendedIterator<Triple> find(final Triple tripleMatch);
+
+    /**
+     * Return a new triple store that is a copy of this one.
+     * Since Nodes and Triples are immutable and shared, the copy can share 
the same Nodes and Triples.
+     *
+     * @return an independent copy of this store
+     */
+    TripleStore copy();
 }
diff --git 
a/jena-core/src/main/java/org/apache/jena/mem2/store/fast/FastArrayBunch.java 
b/jena-core/src/main/java/org/apache/jena/mem2/store/fast/FastArrayBunch.java
index 57035f4223..f46ffe285c 100644
--- 
a/jena-core/src/main/java/org/apache/jena/mem2/store/fast/FastArrayBunch.java
+++ 
b/jena-core/src/main/java/org/apache/jena/mem2/store/fast/FastArrayBunch.java
@@ -43,6 +43,19 @@ public abstract class FastArrayBunch implements 
FastTripleBunch {
         elements = new Triple[INITIAL_SIZE];
     }
 
+    /**
+     * Copy constructor.
+     * The new bunch will contain all the same triples of the bunch to copy.
+     * But it will reserve only the space needed to contain them. Growing is 
still possible.
+     *
+     * @param bunchToCopy
+     */
+    protected FastArrayBunch(final FastArrayBunch bunchToCopy) {
+        this.elements = new Triple[bunchToCopy.size];
+        System.arraycopy(bunchToCopy.elements, 0, this.elements, 0, 
bunchToCopy.size);
+        this.size = bunchToCopy.size;
+    }
+
     public abstract boolean areEqual(final Triple a, final Triple b);
 
     @Override
diff --git 
a/jena-core/src/main/java/org/apache/jena/mem2/store/fast/FastHashedBunchMap.java
 
b/jena-core/src/main/java/org/apache/jena/mem2/store/fast/FastHashedBunchMap.java
index a9e598dce6..1f96f85294 100644
--- 
a/jena-core/src/main/java/org/apache/jena/mem2/store/fast/FastHashedBunchMap.java
+++ 
b/jena-core/src/main/java/org/apache/jena/mem2/store/fast/FastHashedBunchMap.java
@@ -17,18 +17,31 @@
  */
 package org.apache.jena.mem2.store.fast;
 
+import org.apache.jena.atlas.lib.Copyable;
 import org.apache.jena.graph.Node;
 import org.apache.jena.mem2.collection.FastHashMap;
 
 /**
  * Map from nodes to triple bunches.
  */
-public class FastHashedBunchMap extends FastHashMap<Node, FastTripleBunch> {
+public class FastHashedBunchMap
+        extends FastHashMap<Node, FastTripleBunch>
+        implements Copyable<FastHashedBunchMap> {
 
     public FastHashedBunchMap() {
         super();
     }
 
+    /**
+     * Copy constructor.
+     * The new map will contain all the same nodes as keys of the map to copy, 
but copies of the bunches as values .
+     *
+     * @param mapToCopy
+     */
+    private FastHashedBunchMap(final FastHashedBunchMap mapToCopy) {
+        super(mapToCopy, FastTripleBunch::copy);
+    }
+
     @Override
     protected Node[] newKeysArray(int size) {
         return new Node[size];
@@ -38,4 +51,9 @@ public class FastHashedBunchMap extends FastHashMap<Node, 
FastTripleBunch> {
     protected FastTripleBunch[] newValuesArray(int size) {
         return new FastTripleBunch[size];
     }
+
+    @Override
+    public FastHashedBunchMap copy() {
+        return new FastHashedBunchMap(this);
+    }
 }
diff --git 
a/jena-core/src/main/java/org/apache/jena/mem2/store/fast/FastHashedTripleBunch.java
 
b/jena-core/src/main/java/org/apache/jena/mem2/store/fast/FastHashedTripleBunch.java
index 752a57971c..7d09fc6ba5 100644
--- 
a/jena-core/src/main/java/org/apache/jena/mem2/store/fast/FastHashedTripleBunch.java
+++ 
b/jena-core/src/main/java/org/apache/jena/mem2/store/fast/FastHashedTripleBunch.java
@@ -35,6 +35,16 @@ public class FastHashedTripleBunch extends 
FastHashSet<Triple> implements FastTr
         set.keyIterator().forEachRemaining(this::addUnchecked);
     }
 
+    /**
+     * Copy constructor.
+     * The new bunch will contain all the same triples of the bunch to copy.
+     *
+     * @param bunchToCopy
+     */
+    private FastHashedTripleBunch(final FastHashedTripleBunch bunchToCopy) {
+        super(bunchToCopy);
+    }
+
     public FastHashedTripleBunch() {
         super();
     }
@@ -48,4 +58,9 @@ public class FastHashedTripleBunch extends 
FastHashSet<Triple> implements FastTr
     public boolean isArray() {
         return false;
     }
+
+    @Override
+    public FastHashedTripleBunch copy() {
+        return new FastHashedTripleBunch(this);
+    }
 }
diff --git 
a/jena-core/src/main/java/org/apache/jena/mem2/store/fast/FastTripleBunch.java 
b/jena-core/src/main/java/org/apache/jena/mem2/store/fast/FastTripleBunch.java
index 116873f1db..5de934a946 100644
--- 
a/jena-core/src/main/java/org/apache/jena/mem2/store/fast/FastTripleBunch.java
+++ 
b/jena-core/src/main/java/org/apache/jena/mem2/store/fast/FastTripleBunch.java
@@ -18,6 +18,7 @@
 
 package org.apache.jena.mem2.store.fast;
 
+import org.apache.jena.atlas.lib.Copyable;
 import org.apache.jena.graph.Triple;
 import org.apache.jena.mem2.collection.JenaMapSetCommon;
 import org.apache.jena.mem2.collection.JenaSetHashOptimized;
@@ -29,7 +30,7 @@ import java.util.function.Predicate;
  * bunch is expected to store triples that share some useful property
  * (such as having the same subject or predicate).
  */
-public interface FastTripleBunch extends JenaSetHashOptimized<Triple> {
+public interface FastTripleBunch extends JenaSetHashOptimized<Triple>, 
Copyable<FastTripleBunch> {
     /**
      * Answer true iff this bunch is implemented as an array.
      * This field is used to optimize some operations by avoiding the need for 
instanceOf tests.
diff --git 
a/jena-core/src/main/java/org/apache/jena/mem2/store/fast/FastTripleStore.java 
b/jena-core/src/main/java/org/apache/jena/mem2/store/fast/FastTripleStore.java
index 00e1c48818..e6cb9292c9 100644
--- 
a/jena-core/src/main/java/org/apache/jena/mem2/store/fast/FastTripleStore.java
+++ 
b/jena-core/src/main/java/org/apache/jena/mem2/store/fast/FastTripleStore.java
@@ -68,11 +68,24 @@ public class FastTripleStore implements TripleStore {
     protected static final int THRESHOLD_FOR_SECONDARY_LOOKUP = 400;
     protected static final int MAX_ARRAY_BUNCH_SIZE_SUBJECT = 16;
     protected static final int MAX_ARRAY_BUNCH_SIZE_PREDICATE_OBJECT = 32;
-    final FastHashedBunchMap subjects = new FastHashedBunchMap();
-    final FastHashedBunchMap predicates = new FastHashedBunchMap();
-    final FastHashedBunchMap objects = new FastHashedBunchMap();
+    final FastHashedBunchMap subjects;
+    final FastHashedBunchMap predicates;
+    final FastHashedBunchMap objects;
     private int size = 0;
 
+    public FastTripleStore() {
+        subjects = new FastHashedBunchMap();
+        predicates = new FastHashedBunchMap();
+        objects = new FastHashedBunchMap();
+    }
+
+    private FastTripleStore(final FastTripleStore tripleStoreToCopy) {
+        subjects = tripleStoreToCopy.subjects.copy();
+        predicates = tripleStoreToCopy.predicates.copy();
+        objects = tripleStoreToCopy.objects.copy();
+        size = tripleStoreToCopy.size;
+    }
+
     @Override
     public void add(Triple triple) {
         final int hashCodeOfTriple = triple.hashCode();
@@ -359,8 +372,26 @@ public class FastTripleStore implements TripleStore {
         }
     }
 
+    @Override
+    public FastTripleStore copy() {
+        return new FastTripleStore(this);
+    }
+
     protected static class ArrayBunchWithSameSubject extends FastArrayBunch {
 
+        public ArrayBunchWithSameSubject() {
+            super();
+        }
+
+        private ArrayBunchWithSameSubject(ArrayBunchWithSameSubject 
bunchToCopy) {
+            super(bunchToCopy);
+        }
+
+        @Override
+        public ArrayBunchWithSameSubject copy() {
+            return new ArrayBunchWithSameSubject(this);
+        }
+
         @Override
         public boolean areEqual(final Triple a, final Triple b) {
             return a.getPredicate().equals(b.getPredicate())
@@ -369,6 +400,20 @@ public class FastTripleStore implements TripleStore {
     }
 
     protected static class ArrayBunchWithSamePredicate extends FastArrayBunch {
+
+        public ArrayBunchWithSamePredicate() {
+            super();
+        }
+
+        private ArrayBunchWithSamePredicate(ArrayBunchWithSamePredicate 
bunchToCopy) {
+            super(bunchToCopy);
+        }
+
+        @Override
+        public ArrayBunchWithSamePredicate copy() {
+            return new ArrayBunchWithSamePredicate(this);
+        }
+
         @Override
         public boolean areEqual(final Triple a, final Triple b) {
             return a.getSubject().equals(b.getSubject())
@@ -377,6 +422,20 @@ public class FastTripleStore implements TripleStore {
     }
 
     protected static class ArrayBunchWithSameObject extends FastArrayBunch {
+
+        public ArrayBunchWithSameObject() {
+            super();
+        }
+
+        private ArrayBunchWithSameObject(ArrayBunchWithSameObject bunchToCopy) 
{
+            super(bunchToCopy);
+        }
+
+        @Override
+        public ArrayBunchWithSameObject copy() {
+            return new ArrayBunchWithSameObject(this);
+        }
+
         @Override
         public boolean areEqual(final Triple a, final Triple b) {
             return a.getSubject().equals(b.getSubject())
diff --git 
a/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/ArrayBunch.java 
b/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/ArrayBunch.java
index 83ca78aa3f..8387210195 100644
--- a/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/ArrayBunch.java
+++ b/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/ArrayBunch.java
@@ -46,6 +46,19 @@ public class ArrayBunch implements TripleBunch {
         elements = new Triple[INITIAL_SIZE];
     }
 
+    /**
+     * Copy constructor.
+     * The new bunch will contain all the same triples of the bunch to copy.
+     * But it will reserve only the space needed to contain them. Growing is 
still possible.
+     *
+     * @param bunchToCopy
+     */
+    private ArrayBunch(final ArrayBunch bunchToCopy) {
+        this.elements = new Triple[bunchToCopy.size];
+        System.arraycopy(bunchToCopy.elements, 0, this.elements, 0, 
bunchToCopy.size);
+        this.size = bunchToCopy.size;
+    }
+
     @Override
     public boolean containsKey(Triple t) {
         int i = size;
@@ -163,4 +176,9 @@ public class ArrayBunch implements TripleBunch {
     public boolean isArray() {
         return true;
     }
+
+    @Override
+    public ArrayBunch copy() {
+        return new ArrayBunch(this);
+    }
 }
diff --git 
a/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/HashedBunchMap.java 
b/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/HashedBunchMap.java
index d719d74772..426d8d06d3 100644
--- 
a/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/HashedBunchMap.java
+++ 
b/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/HashedBunchMap.java
@@ -29,6 +29,17 @@ public class HashedBunchMap extends HashCommonMap<Node, 
TripleBunch> {
         super(10);
     }
 
+    /**
+     * Copy constructor.
+     * The new map will contain all the same nodes as keys of the map to copy, 
but copies of the bunches as values .
+     *
+     * @param mapToCopy
+     */
+    private HashedBunchMap(final HashedBunchMap mapToCopy) {
+        super(mapToCopy, TripleBunch::copy);
+    }
+
+
     @Override
     protected Node[] newKeysArray(int size) {
         return new Node[size];
@@ -43,4 +54,14 @@ public class HashedBunchMap extends HashCommonMap<Node, 
TripleBunch> {
     public void clear() {
         super.clear(10);
     }
+
+    /**
+     * Create a copy of this map.
+     * The new map will contain all the same nodes as keys of this map, but 
copies of the bunches as values.
+     *
+     * @return an independent copy of this map
+     */
+    public HashedBunchMap copy() {
+        return new HashedBunchMap(this);
+    }
 }
diff --git 
a/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/HashedTripleBunch.java
 
b/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/HashedTripleBunch.java
index 9bf8eb3e6a..2e18ffa57f 100644
--- 
a/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/HashedTripleBunch.java
+++ 
b/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/HashedTripleBunch.java
@@ -30,6 +30,10 @@ public class HashedTripleBunch extends HashCommonSet<Triple> 
implements TripleBu
         b.keyIterator().forEachRemaining(this::addUnchecked);
     }
 
+    private HashedTripleBunch(final HashedTripleBunch bunchToCopy) {
+        super(bunchToCopy);
+    }
+
     public HashedTripleBunch() {
         super(8);
     }
@@ -48,4 +52,9 @@ public class HashedTripleBunch extends HashCommonSet<Triple> 
implements TripleBu
     public boolean isArray() {
         return false;
     }
+
+    @Override
+    public HashedTripleBunch copy() {
+        return new HashedTripleBunch(this);
+    }
 }
diff --git 
a/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/LegacyTripleStore.java
 
b/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/LegacyTripleStore.java
index a8a9e91980..b9ef20818a 100644
--- 
a/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/LegacyTripleStore.java
+++ 
b/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/LegacyTripleStore.java
@@ -47,12 +47,21 @@ import java.util.stream.Stream;
  */
 public class LegacyTripleStore implements TripleStore {
 
-    private final NodeToTriplesMap subjects
-            = new NodeToTriplesMapMem(Triple.Field.fieldSubject, 
Triple.Field.fieldPredicate, Triple.Field.fieldObject);
-    private final NodeToTriplesMap predicates
-            = new NodeToTriplesMapMem(Triple.Field.fieldPredicate, 
Triple.Field.fieldObject, Triple.Field.fieldSubject);
-    private final NodeToTriplesMap objects
-            = new NodeToTriplesMapMem(Triple.Field.fieldObject, 
Triple.Field.fieldSubject, Triple.Field.fieldPredicate);
+    private final NodeToTriplesMap subjects;
+    private final NodeToTriplesMap predicates;
+    private final NodeToTriplesMap objects;
+
+    public LegacyTripleStore() {
+        subjects = new NodeToTriplesMapMem(Triple.Field.fieldSubject, 
Triple.Field.fieldPredicate, Triple.Field.fieldObject);
+        predicates = new NodeToTriplesMapMem(Triple.Field.fieldPredicate, 
Triple.Field.fieldObject, Triple.Field.fieldSubject);
+        objects = new NodeToTriplesMapMem(Triple.Field.fieldObject, 
Triple.Field.fieldSubject, Triple.Field.fieldPredicate);
+    }
+
+    private LegacyTripleStore(final LegacyTripleStore toCopy) {
+        subjects = toCopy.subjects.copy();
+        predicates = toCopy.predicates.copy();
+        objects = toCopy.objects.copy();
+    }
 
     @Override
     public void add(Triple triple) {
@@ -149,4 +158,9 @@ public class LegacyTripleStore implements TripleStore {
         else
             return subjects.keyIterator();
     }
+
+    @Override
+    public LegacyTripleStore copy() {
+        return new LegacyTripleStore(this);
+    }
 }
diff --git 
a/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/NodeToTriplesMap.java
 
b/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/NodeToTriplesMap.java
index d2bd89b966..1b8f6e49c8 100644
--- 
a/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/NodeToTriplesMap.java
+++ 
b/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/NodeToTriplesMap.java
@@ -58,4 +58,12 @@ public interface NodeToTriplesMap extends JenaSet<Triple> {
      * @return True iff this map contains a triple that matches the pattern.
      */
     boolean containsMatch(Node index, Node n2, Node n3);
+
+    /**
+     * Create a copy of this map.
+     * The new map will contain all the same nodes as keys of this map, but 
copies of the bunches as values.
+     *
+     * @return an independent copy of this map
+     */
+    NodeToTriplesMap copy();
 }
diff --git 
a/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/NodeToTriplesMapMem.java
 
b/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/NodeToTriplesMapMem.java
index 9476e70a09..24c2fbfce3 100644
--- 
a/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/NodeToTriplesMapMem.java
+++ 
b/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/NodeToTriplesMapMem.java
@@ -19,7 +19,6 @@ package org.apache.jena.mem2.store.legacy;
 
 import org.apache.jena.graph.Node;
 import org.apache.jena.graph.Triple;
-import org.apache.jena.mem2.collection.JenaMap;
 import org.apache.jena.mem2.iterator.IteratorOfJenaSets;
 import org.apache.jena.util.iterator.ExtendedIterator;
 import org.apache.jena.util.iterator.NullIterator;
@@ -31,7 +30,7 @@ import java.util.stream.StreamSupport;
 
 public class NodeToTriplesMapMem implements NodeToTriplesMap {
 
-    private final JenaMap<Node, TripleBunch> bunchMap = new HashedBunchMap();
+    private final HashedBunchMap bunchMap;
     private final Triple.Field indexField;
     private final Triple.Field f2;
     private final Triple.Field f3;
@@ -42,10 +41,19 @@ public class NodeToTriplesMapMem implements 
NodeToTriplesMap {
      */
     private int size = 0;
 
-    public NodeToTriplesMapMem(Triple.Field indexField, Triple.Field f2, 
Triple.Field f3) {
+    public NodeToTriplesMapMem(final Triple.Field indexField, final 
Triple.Field f2, final Triple.Field f3) {
         this.indexField = indexField;
         this.f2 = f2;
         this.f3 = f3;
+        this.bunchMap = new HashedBunchMap();
+    }
+
+    private NodeToTriplesMapMem(final NodeToTriplesMapMem mapToCopy) {
+        this.indexField = mapToCopy.indexField;
+        this.f2 = mapToCopy.f2;
+        this.f3 = mapToCopy.f3;
+        this.size = mapToCopy.size;
+        this.bunchMap = mapToCopy.bunchMap.copy();
     }
 
     private Node getIndexNode(Triple t) {
@@ -74,14 +82,16 @@ public class NodeToTriplesMapMem implements 
NodeToTriplesMap {
 
         TripleBunch s = bunchMap.get(node);
         if (s == null) {
-            bunchMap.put(node, s = new ArrayBunch());
+            s = new ArrayBunch();
+            bunchMap.put(node, s);
             s.addUnchecked(t);
             size++;
             return true;
         }
 
         if ((s.isArray()) && s.size() == 9) {
-            bunchMap.put(node, s = new HashedTripleBunch(s));
+            s = new HashedTripleBunch(s);
+            bunchMap.put(node, s);
         }
         if (s.tryAdd(t)) {
             size++;
@@ -184,6 +194,11 @@ public class NodeToTriplesMapMem implements 
NodeToTriplesMap {
         return s.anyMatch(filter.getFilter());
     }
 
+    @Override
+    public NodeToTriplesMapMem copy() {
+        return new NodeToTriplesMapMem(this);
+    }
+
     @Override
     public boolean containsKey(Triple triple) {
         final TripleBunch s = bunchMap.get(getIndexNode(triple));
diff --git 
a/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/TripleBunch.java 
b/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/TripleBunch.java
index 0c595cf3ac..c981ea0c9c 100644
--- a/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/TripleBunch.java
+++ b/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/TripleBunch.java
@@ -18,6 +18,7 @@
 
 package org.apache.jena.mem2.store.legacy;
 
+import org.apache.jena.atlas.lib.Copyable;
 import org.apache.jena.graph.Triple;
 import org.apache.jena.mem2.collection.JenaSet;
 
@@ -26,7 +27,7 @@ import org.apache.jena.mem2.collection.JenaSet;
  * bunch is expected to store triples that share some useful property
  * (such as having the same subject or predicate).
  */
-public interface TripleBunch extends JenaSet<Triple> {
+public interface TripleBunch extends JenaSet<Triple>, Copyable<TripleBunch> {
     /**
      * Answer true iff this bunch is implemented as an array.
      * This field is used to optimize some operations by avoiding the need for 
instanceOf tests.
diff --git 
a/jena-core/src/main/java/org/apache/jena/mem2/store/roaring/RoaringTripleStore.java
 
b/jena-core/src/main/java/org/apache/jena/mem2/store/roaring/RoaringTripleStore.java
index 547dd0a916..d35d4b5915 100644
--- 
a/jena-core/src/main/java/org/apache/jena/mem2/store/roaring/RoaringTripleStore.java
+++ 
b/jena-core/src/main/java/org/apache/jena/mem2/store/roaring/RoaringTripleStore.java
@@ -18,6 +18,7 @@
 
 package org.apache.jena.mem2.store.roaring;
 
+import org.apache.jena.atlas.lib.Copyable;
 import org.apache.jena.graph.Node;
 import org.apache.jena.graph.Triple;
 import org.apache.jena.mem2.collection.FastHashMap;
@@ -56,10 +57,24 @@ public class RoaringTripleStore implements TripleStore {
 
     private static final String UNKNOWN_PATTERN_CLASSIFIER = "Unknown pattern 
classifier: %s";
     private static final RoaringBitmap EMPTY_BITMAP = new RoaringBitmap();
-    final NodesToBitmapsMap subjectBitmaps = new NodesToBitmapsMap();
-    final NodesToBitmapsMap predicateBitmaps = new NodesToBitmapsMap();
-    final NodesToBitmapsMap objectBitmaps = new NodesToBitmapsMap();
-    final TripleSet triples = new TripleSet(); // We use a list here to 
maintain the order of triples
+    final NodesToBitmapsMap subjectBitmaps;
+    final NodesToBitmapsMap predicateBitmaps;
+    final NodesToBitmapsMap objectBitmaps;
+    final TripleSet triples; // In this special set, each element has an index
+
+    public RoaringTripleStore() {
+        subjectBitmaps = new NodesToBitmapsMap();
+        predicateBitmaps = new NodesToBitmapsMap();
+        objectBitmaps = new NodesToBitmapsMap();
+        triples = new TripleSet();
+    }
+
+    private RoaringTripleStore(final RoaringTripleStore storeToCopy) {
+        subjectBitmaps = storeToCopy.subjectBitmaps.copy();
+        predicateBitmaps = storeToCopy.predicateBitmaps.copy();
+        objectBitmaps = storeToCopy.objectBitmaps.copy();
+        triples = storeToCopy.triples.copy();
+    }
 
     private static void addIndex(final NodesToBitmapsMap map, final Node node, 
final int index) {
         final var bitmap = map.computeIfAbsent(node, RoaringBitmap::new);
@@ -119,12 +134,12 @@ public class RoaringTripleStore implements TripleStore {
         final var matchPattern = PatternClassifier.classify(tripleMatch);
         switch (matchPattern) {
 
-            case SUB_ANY_ANY:
-            case ANY_PRE_ANY:
-            case ANY_ANY_OBJ:
-            case SUB_PRE_ANY:
-            case ANY_PRE_OBJ:
-            case SUB_ANY_OBJ:
+            case SUB_ANY_ANY,
+                 ANY_PRE_ANY,
+                 ANY_ANY_OBJ,
+                 SUB_PRE_ANY,
+                 ANY_PRE_OBJ,
+                 SUB_ANY_OBJ:
                 return hasMatchInBitmaps(tripleMatch, matchPattern);
 
             case SUB_PRE_OBJ:
@@ -265,12 +280,12 @@ public class RoaringTripleStore implements TripleStore {
             case SUB_PRE_OBJ:
                 return this.triples.containsKey(tripleMatch) ? 
Stream.of(tripleMatch) : Stream.empty();
 
-            case SUB_PRE_ANY:
-            case SUB_ANY_OBJ:
-            case SUB_ANY_ANY:
-            case ANY_PRE_OBJ:
-            case ANY_PRE_ANY:
-            case ANY_ANY_OBJ:
+            case SUB_PRE_ANY,
+                 SUB_ANY_OBJ,
+                 SUB_ANY_ANY,
+                 ANY_PRE_OBJ,
+                 ANY_PRE_ANY,
+                    ANY_ANY_OBJ:
                 return this.getBitmapForMatch(tripleMatch, pattern)
                         .stream().mapToObj(this.triples::getKeyAt);
 
@@ -290,12 +305,12 @@ public class RoaringTripleStore implements TripleStore {
             case SUB_PRE_OBJ:
                 return this.triples.containsKey(tripleMatch) ? new 
SingletonIterator<>(tripleMatch) : NiceIterator.emptyIterator();
 
-            case SUB_PRE_ANY:
-            case SUB_ANY_OBJ:
-            case SUB_ANY_ANY:
-            case ANY_PRE_OBJ:
-            case ANY_PRE_ANY:
-            case ANY_ANY_OBJ:
+            case SUB_PRE_ANY,
+                 SUB_ANY_OBJ,
+                 SUB_ANY_ANY,
+                 ANY_PRE_OBJ,
+                 ANY_PRE_ANY,
+                 ANY_ANY_OBJ:
                 return new 
RoaringBitmapTripleIterator(this.getBitmapForMatch(tripleMatch, pattern), 
this.triples);
 
             case ANY_ANY_ANY:
@@ -306,21 +321,56 @@ public class RoaringTripleStore implements TripleStore {
         }
     }
 
+    @Override
+    public RoaringTripleStore copy() {
+        return new RoaringTripleStore(this);
+    }
+
     /**
      * Set of triples that is backed by a {@link TripleSet}.
      */
-    private static class TripleSet extends FastHashSet<Triple> {
+    private static class TripleSet
+            extends FastHashSet<Triple>
+            implements Copyable<TripleSet>{
+
+        public TripleSet() {
+            super();
+        }
+
+        private TripleSet(final FastHashSet<Triple> setToCopy) {
+            super(setToCopy);
+        }
 
         @Override
         protected Triple[] newKeysArray(int size) {
             return new Triple[size];
         }
+
+        /**
+         * Create a copy of this set.
+         *
+         * @return
+         */
+        @Override
+        public TripleSet copy() {
+            return new TripleSet(this);
+        }
     }
 
     /**
      * Map from {@link Node} to {@link RoaringBitmap}.
      */
-    private static class NodesToBitmapsMap extends FastHashMap<Node, 
RoaringBitmap> {
+    private static class NodesToBitmapsMap
+            extends FastHashMap<Node, RoaringBitmap>
+            implements Copyable<NodesToBitmapsMap> {
+
+        public NodesToBitmapsMap() {
+            super();
+        }
+
+        public NodesToBitmapsMap(final NodesToBitmapsMap mapToCopy) {
+            super(mapToCopy, RoaringBitmap::clone);
+        }
 
         @Override
         protected Node[] newKeysArray(int size) {
@@ -331,5 +381,16 @@ public class RoaringTripleStore implements TripleStore {
         protected RoaringBitmap[] newValuesArray(int size) {
             return new RoaringBitmap[size];
         }
+
+        /**
+         * Create a copy of this map.
+         * The new map will contain all the same nodes as keys of this map, 
but clones of the bitmaps as values.
+         *
+         * @return a copy of this map
+         */
+        @Override
+        public NodesToBitmapsMap copy() {
+            return new NodesToBitmapsMap(this);
+        }
     }
 }
diff --git 
a/jena-core/src/test/java/org/apache/jena/mem2/AbstractGraphMem2Test.java 
b/jena-core/src/test/java/org/apache/jena/mem2/AbstractGraphMem2Test.java
index a0ad17d208..2c99e7fbec 100644
--- a/jena-core/src/test/java/org/apache/jena/mem2/AbstractGraphMem2Test.java
+++ b/jena-core/src/test/java/org/apache/jena/mem2/AbstractGraphMem2Test.java
@@ -19,7 +19,6 @@
 package org.apache.jena.mem2;
 
 import org.apache.jena.datatypes.xsd.impl.XSDDouble;
-import org.apache.jena.graph.Graph;
 import org.apache.jena.graph.NodeFactory;
 import org.apache.jena.graph.Triple;
 import org.hamcrest.collection.IsEmptyCollection;
@@ -36,9 +35,9 @@ import static org.junit.Assert.*;
 
 public abstract class AbstractGraphMem2Test {
 
-    protected Graph sut;
+    protected GraphMem2 sut;
 
-    protected abstract Graph createGraph();
+    protected abstract GraphMem2 createGraph();
 
     @Before
     public void setUp() throws Exception {
@@ -54,7 +53,6 @@ public abstract class AbstractGraphMem2Test {
         assertTrue(sut.isEmpty());
     }
 
-
     @Test
     public void testDelete() {
         sut.add(triple("x R y"));
@@ -1000,4 +998,46 @@ public abstract class AbstractGraphMem2Test {
                 NodeFactory.createURI("R"))));
     }
 
+    @Test
+    public void testCopy() {
+        sut.add(triple("s p o"));
+        sut.add(triple("s1 p1 o1"));
+        sut.add(triple("s2 p2 o2"));
+        assertEquals(3, sut.size());
+
+        var copy = sut.copy();
+        assertEquals(3, copy.size());
+        assertTrue(copy.contains(triple("s p o")));
+        assertTrue(copy.contains(triple("s1 p1 o1")));
+        assertTrue(copy.contains(triple("s2 p2 o2")));
+        assertFalse(copy.contains(triple("s3 p3 o3")));
+    }
+
+    @Test
+    public void testCopyHasNoSideEffects() {
+        sut.add(triple("s p o"));
+        sut.add(triple("s1 p1 o1"));
+        sut.add(triple("s2 p2 o2"));
+        assertEquals(3, sut.size());
+
+        var copy = sut.copy();
+        copy.delete(triple("s1 p1 o1"));
+        copy.add(triple("s3 p3 o3"));
+        copy.add(triple("s4 p4 o4"));
+
+        assertEquals(4, copy.size());
+        assertTrue(copy.contains(triple("s p o")));
+        assertFalse(copy.contains(triple("s1 p1 o1")));
+        assertTrue(copy.contains(triple("s2 p2 o2")));
+        assertTrue(copy.contains(triple("s3 p3 o3")));
+        assertTrue(copy.contains(triple("s4 p4 o4")));
+
+
+        assertEquals(3, sut.size());
+        assertTrue(sut.contains(triple("s p o")));
+        assertTrue(sut.contains(triple("s1 p1 o1")));
+        assertTrue(sut.contains(triple("s2 p2 o2")));
+        assertFalse(sut.contains(triple("s3 p3 o3")));
+    }
+
 }
diff --git 
a/jena-core/src/test/java/org/apache/jena/mem2/GraphMem2FastTest.java 
b/jena-core/src/test/java/org/apache/jena/mem2/GraphMem2FastTest.java
index 2a03057c62..8c7856539e 100644
--- a/jena-core/src/test/java/org/apache/jena/mem2/GraphMem2FastTest.java
+++ b/jena-core/src/test/java/org/apache/jena/mem2/GraphMem2FastTest.java
@@ -18,12 +18,10 @@
 
 package org.apache.jena.mem2;
 
-import org.apache.jena.graph.Graph;
-
 public class GraphMem2FastTest extends AbstractGraphMem2Test {
 
     @Override
-    protected Graph createGraph() {
+    protected GraphMem2 createGraph() {
         return new GraphMem2Fast();
     }
 }
\ No newline at end of file
diff --git 
a/jena-core/src/test/java/org/apache/jena/mem2/GraphMem2LegacyTest.java 
b/jena-core/src/test/java/org/apache/jena/mem2/GraphMem2LegacyTest.java
index 2f2944a096..59b1db2df1 100644
--- a/jena-core/src/test/java/org/apache/jena/mem2/GraphMem2LegacyTest.java
+++ b/jena-core/src/test/java/org/apache/jena/mem2/GraphMem2LegacyTest.java
@@ -17,12 +17,10 @@
  */
 package org.apache.jena.mem2;
 
-import org.apache.jena.graph.Graph;
-
 public class GraphMem2LegacyTest extends AbstractGraphMem2Test {
 
     @Override
-    protected Graph createGraph() {
+    protected GraphMem2 createGraph() {
         return new GraphMem2Legacy();
     }
 }
\ No newline at end of file
diff --git 
a/jena-core/src/test/java/org/apache/jena/mem2/GraphMem2RoaringTest.java 
b/jena-core/src/test/java/org/apache/jena/mem2/GraphMem2RoaringTest.java
index bc539bab1a..0e1c66f248 100644
--- a/jena-core/src/test/java/org/apache/jena/mem2/GraphMem2RoaringTest.java
+++ b/jena-core/src/test/java/org/apache/jena/mem2/GraphMem2RoaringTest.java
@@ -17,12 +17,10 @@
  */
 package org.apache.jena.mem2;
 
-import org.apache.jena.graph.Graph;
-
 public class GraphMem2RoaringTest extends AbstractGraphMem2Test {
 
     @Override
-    protected Graph createGraph() {
+    protected GraphMem2 createGraph() {
         return new GraphMem2Roaring();
     }
 }
\ No newline at end of file
diff --git a/jena-core/src/test/java/org/apache/jena/mem2/GraphMem2Test.java 
b/jena-core/src/test/java/org/apache/jena/mem2/GraphMem2Test.java
index aabb0c3c6a..062758176a 100644
--- a/jena-core/src/test/java/org/apache/jena/mem2/GraphMem2Test.java
+++ b/jena-core/src/test/java/org/apache/jena/mem2/GraphMem2Test.java
@@ -26,8 +26,7 @@ import java.util.stream.Stream;
 
 import static org.apache.jena.testing_framework.GraphHelper.node;
 import static org.apache.jena.testing_framework.GraphHelper.triple;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
 import static org.mockito.Mockito.*;
 
 public class GraphMem2Test {
@@ -170,4 +169,18 @@ public class GraphMem2Test {
         assertFalse(capapbilities.handlesLiteralTyping());
 
     }
+
+    @Test
+    public void testCopy() {
+        TripleStore mockStore = mock();
+        TripleStore mockStoreCopy = mock();
+
+        when(mockStore.copy()).thenReturn(mockStoreCopy);
+
+        var sut = new GraphMem2(mockStore);
+        var copy = sut.copy();
+
+        assertTrue(copy instanceof GraphMem2);
+        assertEquals(mockStoreCopy, copy.tripleStore);
+    }
 }
\ No newline at end of file
diff --git 
a/jena-core/src/test/java/org/apache/jena/mem2/collection/FastHashMapTest2.java 
b/jena-core/src/test/java/org/apache/jena/mem2/collection/FastHashMapTest2.java
index d9c758d21b..6ea4ec0a60 100644
--- 
a/jena-core/src/test/java/org/apache/jena/mem2/collection/FastHashMapTest2.java
+++ 
b/jena-core/src/test/java/org/apache/jena/mem2/collection/FastHashMapTest2.java
@@ -20,17 +20,16 @@ package org.apache.jena.mem2.collection;
 import org.apache.jena.graph.Node;
 import org.junit.Test;
 
+import java.util.function.UnaryOperator;
+
 import static org.apache.jena.testing_framework.GraphHelper.node;
 import static org.junit.Assert.assertEquals;
 
 public class FastHashMapTest2 {
 
-    FastHashMap<Node, Object> sut = new FastNodeHashMap();
-
-
     @Test
     public void testConstructWithInitialSizeAndAdd() {
-        sut = new FastNodeHashMap(3);
+        var sut = new FastNodeHashMap(3);
         sut.put(node("s"), null);
         sut.put(node("s1"), null);
         sut.put(node("s2"), null);
@@ -41,13 +40,74 @@ public class FastHashMapTest2 {
 
     @Test
     public void testGetValueAt() {
+        var sut = new FastNodeHashMap();
         sut.put(node("s"), 0);
         sut.put(node("s1"), 1);
         sut.put(node("s2"), 2);
 
-        assertEquals(0, sut.getValueAt(0));
-        assertEquals(1, sut.getValueAt(1));
-        assertEquals(2, sut.getValueAt(2));
+        assertEquals(0, (int) sut.getValueAt(0));
+        assertEquals(1, (int) sut.getValueAt(1));
+        assertEquals(2, (int) sut.getValueAt(2));
+    }
+
+    @Test
+    public void testCopyConstructor() {
+        var original = new FastNodeHashMap();
+        original.put(node("s"), 0);
+        original.put(node("s1"), 1);
+        original.put(node("s2"), 2);
+        assertEquals(3, original.size());
+
+        var copy = new FastNodeHashMap(original);
+        assertEquals(3, copy.size());
+        assertEquals(0, (int) copy.get(node("s")));
+        assertEquals(1, (int) copy.get(node("s1")));
+        assertEquals(2, (int) copy.get(node("s2")));
+    }
+
+    @Test
+    public void testCopyConstructorWithValueMapping() {
+        var original = new FastNodeHashMap();
+        original.put(node("s"), 0);
+        original.put(node("s1"), 1);
+        original.put(node("s2"), 2);
+        assertEquals(3, original.size());
+
+        var copy = new FastNodeHashMap(original, i -> (int) i + 1);
+        assertEquals(3, copy.size());
+        assertEquals(1, (int) copy.get(node("s")));
+        assertEquals(2, (int) copy.get(node("s1")));
+        assertEquals(3, (int) copy.get(node("s2")));
+
+        assertEquals(0, (int) original.get(node("s")));
+        assertEquals(1, (int) original.get(node("s1")));
+        assertEquals(2, (int) original.get(node("s2")));
+    }
+
+    @Test
+    public void testCopyConstructorAddAndDeleteHasNoSideEffects() {
+        var original = new FastNodeHashMap();
+        original.put(node("s"), 0);
+        original.put(node("s1"), 1);
+        original.put(node("s2"), 2);
+        assertEquals(3, original.size());
+
+        var copy = new FastNodeHashMap(original);
+        copy.removeAndGetIndex(node("s1"));
+        copy.put(node("s3"), 3);
+        copy.put(node("s4"), 4);
+
+        assertEquals(4, copy.size());
+        assertEquals(0, (int) copy.get(node("s")));
+        assertEquals(2, (int) copy.get(node("s2")));
+        assertEquals(3, (int) copy.get(node("s3")));
+        assertEquals(4, (int) copy.get(node("s4")));
+
+
+        assertEquals(3, original.size());
+        assertEquals(0, (int) original.get(node("s")));
+        assertEquals(1, (int) original.get(node("s1")));
+        assertEquals(2, (int) original.get(node("s2")));
     }
 
     private static class FastNodeHashMap extends FastHashMap<Node, Object> {
@@ -60,6 +120,14 @@ public class FastHashMapTest2 {
             super(initialSize);
         }
 
+        public FastNodeHashMap(FastHashMap<Node, Object> mapToCopy) {
+            super(mapToCopy);
+        }
+
+        public FastNodeHashMap(FastHashMap<Node, Object> mapToCopy, 
UnaryOperator<Object> valueProcessor) {
+            super(mapToCopy, valueProcessor);
+        }
+
         @Override
         protected Object[] newValuesArray(int size) {
             return new Object[size];
diff --git 
a/jena-core/src/test/java/org/apache/jena/mem2/collection/FastHashSetTest2.java 
b/jena-core/src/test/java/org/apache/jena/mem2/collection/FastHashSetTest2.java
index 96c8384a91..99cbb73edd 100644
--- 
a/jena-core/src/test/java/org/apache/jena/mem2/collection/FastHashSetTest2.java
+++ 
b/jena-core/src/test/java/org/apache/jena/mem2/collection/FastHashSetTest2.java
@@ -20,6 +20,7 @@ package org.apache.jena.mem2.collection;
 import org.junit.Before;
 import org.junit.Test;
 
+import static org.apache.jena.testing_framework.GraphHelper.node;
 import static org.junit.Assert.*;
 
 /**
@@ -135,6 +136,50 @@ public class FastHashSetTest2 {
         assertFalse(sut.anyMatchRandomOrder(k -> k.equals("d")));
     }
 
+    @Test
+    public void testCopyConstructor() {
+        var original = new FastObjectHashSet();
+        original.addAndGetIndex(node("s"));
+        original.addAndGetIndex(node("s1"));
+        original.addAndGetIndex(node("s2"));
+        assertEquals(3, original.size());
+
+        var copy = new FastObjectHashSet(original);
+        assertEquals(3, copy.size());
+        assertTrue(copy.containsKey(node("s")));
+        assertTrue(copy.containsKey(node("s1")));
+        assertTrue(copy.containsKey(node("s2")));
+        assertFalse(copy.containsKey(node("s3")));
+    }
+
+    @Test
+    public void testCopyConstructorAddAndDeleteHasNoSideEffects() {
+        var original = new FastObjectHashSet();
+        original.addAndGetIndex(node("s"));
+        original.addAndGetIndex(node("s1"));
+        original.addAndGetIndex(node("s2"));
+        assertEquals(3, original.size());
+
+        var copy = new FastObjectHashSet(original);
+        copy.removeAndGetIndex(node("s1"));
+        copy.addAndGetIndex(node("s3"));
+        copy.addAndGetIndex(node("s4"));
+
+        assertEquals(4, copy.size());
+        assertTrue(copy.containsKey(node("s")));
+        assertFalse(copy.containsKey(node("s1")));
+        assertTrue(copy.containsKey(node("s2")));
+        assertTrue(copy.containsKey(node("s3")));
+        assertTrue(copy.containsKey(node("s4")));
+
+
+        assertEquals(3, original.size());
+        assertTrue(original.containsKey(node("s")));
+        assertTrue(original.containsKey(node("s1")));
+        assertTrue(original.containsKey(node("s2")));
+        assertFalse(original.containsKey(node("s3")));
+    }
+
 
     private static class FastObjectHashSet extends FastHashSet<Object> {
 
@@ -142,6 +187,10 @@ public class FastHashSetTest2 {
             super();
         }
 
+        public FastObjectHashSet(FastHashSet<Object> setToCopy) {
+            super(setToCopy);
+        }
+
         @Override
         protected Object[] newKeysArray(int size) {
             return new Object[size];
diff --git 
a/jena-core/src/test/java/org/apache/jena/mem2/collection/HashCommonMapTest.java
 
b/jena-core/src/test/java/org/apache/jena/mem2/collection/HashCommonMapTest.java
index 7c325cb5d3..75d13c162d 100644
--- 
a/jena-core/src/test/java/org/apache/jena/mem2/collection/HashCommonMapTest.java
+++ 
b/jena-core/src/test/java/org/apache/jena/mem2/collection/HashCommonMapTest.java
@@ -18,26 +18,106 @@
 package org.apache.jena.mem2.collection;
 
 import org.apache.jena.graph.Node;
+import org.junit.Test;
+
+import java.util.function.UnaryOperator;
+
+import static org.apache.jena.testing_framework.GraphHelper.node;
+import static org.junit.Assert.assertEquals;
 
 public class HashCommonMapTest extends AbstractJenaMapNodeTest {
 
     @Override
     protected JenaMap<Node, Object> createNodeMap() {
-        return new HashCommonMap<Node, Object>(10) {
-            @Override
-            public void clear() {
-                super.clear(10);
-            }
-
-            @Override
-            protected Object[] newValuesArray(int size) {
-                return new Object[size];
-            }
-
-            @Override
-            protected Node[] newKeysArray(int size) {
-                return new Node[size];
-            }
-        };
+        return new HashCommonNodeObjectMap(10);
+    }
+
+    @Test
+    public void testCopyConstructor() {
+        var original = new HashCommonNodeObjectMap(10);
+        original.put(node("s"), 0);
+        original.put(node("s1"), 1);
+        original.put(node("s2"), 2);
+        assertEquals(3, original.size());
+
+        var copy = new HashCommonNodeObjectMap(original);
+        assertEquals(3, copy.size());
+        assertEquals(0, (int) copy.get(node("s")));
+        assertEquals(1, (int) copy.get(node("s1")));
+        assertEquals(2, (int) copy.get(node("s2")));
+    }
+
+    @Test
+    public void testCopyConstructorWithValueMapping() {
+        var original = new HashCommonNodeObjectMap(10);
+        original.put(node("s"), 0);
+        original.put(node("s1"), 1);
+        original.put(node("s2"), 2);
+        assertEquals(3, original.size());
+
+        var copy = new HashCommonNodeObjectMap(original, i -> (int) i + 1);
+        assertEquals(3, copy.size());
+        assertEquals(1, (int) copy.get(node("s")));
+        assertEquals(2, (int) copy.get(node("s1")));
+        assertEquals(3, (int) copy.get(node("s2")));
+
+        assertEquals(0, (int) original.get(node("s")));
+        assertEquals(1, (int) original.get(node("s1")));
+        assertEquals(2, (int) original.get(node("s2")));
+    }
+
+    @Test
+    public void testCopyConstructorAddAndDeleteHasNoSideEffects() {
+        var original = new HashCommonNodeObjectMap(10);
+        original.put(node("s"), 0);
+        original.put(node("s1"), 1);
+        original.put(node("s2"), 2);
+        assertEquals(3, original.size());
+
+        var copy = new HashCommonNodeObjectMap(original);
+        copy.tryRemove(node("s1"));
+        copy.put(node("s3"), 3);
+        copy.put(node("s4"), 4);
+
+        assertEquals(4, copy.size());
+        assertEquals(0, (int) copy.get(node("s")));
+        assertEquals(2, (int) copy.get(node("s2")));
+        assertEquals(3, (int) copy.get(node("s3")));
+        assertEquals(4, (int) copy.get(node("s4")));
+
+
+        assertEquals(3, original.size());
+        assertEquals(0, (int) original.get(node("s")));
+        assertEquals(1, (int) original.get(node("s1")));
+        assertEquals(2, (int) original.get(node("s2")));
+    }
+
+    private static class HashCommonNodeObjectMap extends HashCommonMap<Node, 
Object> {
+        protected HashCommonNodeObjectMap(int initialCapacity) {
+            super(initialCapacity);
+        }
+
+        protected HashCommonNodeObjectMap(HashCommonMap<Node, Object> 
mapToCopy) {
+            super(mapToCopy);
+        }
+
+        protected HashCommonNodeObjectMap(HashCommonMap<Node, Object> 
mapToCopy, UnaryOperator<Object> valueProcessor) {
+            super(mapToCopy, valueProcessor);
+        }
+
+        @Override
+        public void clear() {
+            super.clear(10);
+        }
+
+        @Override
+        protected Object[] newValuesArray(int size) {
+            return new Object[size];
+        }
+
+        @Override
+        protected Node[] newKeysArray(int size) {
+            return new Node[size];
+        }
     }
 }
\ No newline at end of file
diff --git 
a/jena-core/src/test/java/org/apache/jena/mem2/collection/HashCommonSetTest.java
 
b/jena-core/src/test/java/org/apache/jena/mem2/collection/HashCommonSetTest.java
index 5d8cb36d74..83fa2bb073 100644
--- 
a/jena-core/src/test/java/org/apache/jena/mem2/collection/HashCommonSetTest.java
+++ 
b/jena-core/src/test/java/org/apache/jena/mem2/collection/HashCommonSetTest.java
@@ -18,22 +18,80 @@
 package org.apache.jena.mem2.collection;
 
 import org.apache.jena.graph.Triple;
+import org.junit.Test;
+
+import static org.apache.jena.testing_framework.GraphHelper.triple;
+import static org.junit.Assert.*;
 
 
 public class HashCommonSetTest extends AbstractJenaSetTripleTest {
 
     @Override
     protected JenaSet<Triple> createTripleSet() {
-        return new HashCommonSet<Triple>(10) {
-            @Override
-            protected Triple[] newKeysArray(int size) {
-                return new Triple[size];
-            }
-
-            @Override
-            public void clear() {
-                super.clear(10);
-            }
-        };
+        return new HashCommonTripleSet();
+    }
+
+    @Test
+    public void testCopyConstructor() {
+        var original = new HashCommonTripleSet();
+        original.tryAdd(triple("s p o"));
+        original.tryAdd(triple("s1 p1 o1"));
+        original.tryAdd(triple("s2 p2 o2"));
+        assertEquals(3, original.size());
+
+        var copy = new HashCommonTripleSet(original);
+        assertEquals(3, copy.size());
+        assertTrue(copy.containsKey(triple("s p o")));
+        assertTrue(copy.containsKey(triple("s1 p1 o1")));
+        assertTrue(copy.containsKey(triple("s2 p2 o2")));
+        assertFalse(copy.containsKey(triple("s3 p3 o3")));
+    }
+
+    @Test
+    public void testCopyConstructorAddAndDeleteHasNoSideEffects() {
+        var original = new HashCommonTripleSet();
+        original.tryAdd(triple("s p o"));
+        original.tryAdd(triple("s1 p1 o1"));
+        original.tryAdd(triple("s2 p2 o2"));
+        assertEquals(3, original.size());
+
+        var copy = new HashCommonTripleSet(original);
+        copy.tryRemove(triple("s1 p1 o1"));
+        copy.tryAdd(triple("s3 p3 o3"));
+        copy.tryAdd(triple("s4 p4 o4"));
+
+        assertEquals(4, copy.size());
+        assertTrue(copy.containsKey(triple("s p o")));
+        assertFalse(copy.containsKey(triple("s1 p1 o1")));
+        assertTrue(copy.containsKey(triple("s2 p2 o2")));
+        assertTrue(copy.containsKey(triple("s3 p3 o3")));
+        assertTrue(copy.containsKey(triple("s4 p4 o4")));
+
+
+        assertEquals(3, original.size());
+        assertTrue(original.containsKey(triple("s p o")));
+        assertTrue(original.containsKey(triple("s1 p1 o1")));
+        assertTrue(original.containsKey(triple("s2 p2 o2")));
+        assertFalse(original.containsKey(triple("s3 p3 o3")));
+    }
+
+    private static class HashCommonTripleSet extends HashCommonSet<Triple> {
+        public HashCommonTripleSet() {
+            super(10);
+        }
+
+        public HashCommonTripleSet(HashCommonSet<Triple> setToCopy) {
+            super(setToCopy);
+        }
+
+        @Override
+        protected Triple[] newKeysArray(int size) {
+            return new Triple[size];
+        }
+
+        @Override
+        public void clear() {
+            super.clear(10);
+        }
     }
 }
\ No newline at end of file
diff --git 
a/jena-core/src/test/java/org/apache/jena/mem2/store/AbstractTripleStoreTest.java
 
b/jena-core/src/test/java/org/apache/jena/mem2/store/AbstractTripleStoreTest.java
index 56fd3c2e72..56012cd1db 100644
--- 
a/jena-core/src/test/java/org/apache/jena/mem2/store/AbstractTripleStoreTest.java
+++ 
b/jena-core/src/test/java/org/apache/jena/mem2/store/AbstractTripleStoreTest.java
@@ -999,4 +999,86 @@ public abstract class AbstractTripleStoreTest {
                 NodeFactory.createURI("R"))));
     }
 
+    @Test
+    public void testCopy() {
+        sut.add(triple("s p o"));
+        sut.add(triple("s1 p1 o1"));
+        sut.add(triple("s2 p2 o2"));
+        assertEquals(3, sut.countTriples());
+
+        var copy = sut.copy();
+        assertEquals(3, copy.countTriples());
+        assertTrue(copy.contains(triple("s p o")));
+        assertTrue(copy.contains(triple("s1 p1 o1")));
+        assertTrue(copy.contains(triple("s2 p2 o2")));
+        assertFalse(copy.contains(triple("s3 p3 o3")));
+    }
+
+    @Test
+    public void testCopyHasNoSideEffects() {
+        sut.add(triple("s p o"));
+        sut.add(triple("s1 p1 o1"));
+        sut.add(triple("s2 p2 o2"));
+        assertEquals(3, sut.countTriples());
+
+        var copy = sut.copy();
+        copy.remove(triple("s1 p1 o1"));
+        copy.add(triple("s3 p3 o3"));
+        copy.add(triple("s4 p4 o4"));
+
+        assertEquals(4, copy.countTriples());
+        assertTrue(copy.contains(triple("s p o")));
+        assertFalse(copy.contains(triple("s1 p1 o1")));
+        assertTrue(copy.contains(triple("s2 p2 o2")));
+        assertTrue(copy.contains(triple("s3 p3 o3")));
+        assertTrue(copy.contains(triple("s4 p4 o4")));
+
+
+        assertEquals(3, sut.countTriples());
+        assertTrue(sut.contains(triple("s p o")));
+        assertTrue(sut.contains(triple("s1 p1 o1")));
+        assertTrue(sut.contains(triple("s2 p2 o2")));
+        assertFalse(sut.contains(triple("s3 p3 o3")));
+    }
+
+    @Test
+    public void testCopyWithEnoughTriplesToUseHashedBunched() {
+        for (int i = 0; i < 100; i++) {
+            sut.add(triple("s p" + i + " o" + i));
+        }
+        assertEquals(100, sut.countTriples());
+
+        var copy = sut.copy();
+        assertEquals(100, copy.countTriples());
+        assertTrue(copy.contains(triple("s p0 o0")));
+        assertTrue(copy.contains(triple("s p99 o99")));
+        assertFalse(copy.contains(triple("s p100 o100")));
+    }
+
+    @Test
+    public void testCopyHasNoSideEffectsWithEnoughTriplesToUseHashedBunched() {
+        for (int i = 0; i < 100; i++) {
+            sut.add(triple("s p" + i + " o" + i));
+        }
+        assertEquals(100, sut.countTriples());
+
+
+        var copy = sut.copy();
+        copy.remove(triple("s p50 o50"));
+        copy.add(triple("s p100 o100"));
+        copy.add(triple("s p101 o101"));
+
+        assertEquals(101, copy.countTriples());
+        assertTrue(copy.contains(triple("s p0 o0")));
+        assertFalse(copy.contains(triple("s p50 o50")));
+        assertTrue(copy.contains(triple("s p100 o100")));
+        assertTrue(copy.contains(triple("s p101 o101")));
+
+
+        assertEquals(100, sut.countTriples());
+        assertTrue(sut.contains(triple("s p0 o0")));
+        assertTrue(sut.contains(triple("s p99 o99")));
+        assertFalse(sut.contains(triple("s p100 o100")));
+    }
+
 }
diff --git 
a/jena-core/src/test/java/org/apache/jena/mem2/store/fast/FastArrayBunchTest.java
 
b/jena-core/src/test/java/org/apache/jena/mem2/store/fast/FastArrayBunchTest.java
index 83fd8aef27..29ccd7bd6f 100644
--- 
a/jena-core/src/test/java/org/apache/jena/mem2/store/fast/FastArrayBunchTest.java
+++ 
b/jena-core/src/test/java/org/apache/jena/mem2/store/fast/FastArrayBunchTest.java
@@ -23,14 +23,29 @@ import org.apache.jena.mem2.collection.JenaSet;
 
 public class FastArrayBunchTest extends AbstractJenaSetTripleTest {
 
-
     @Override
     protected JenaSet<Triple> createTripleSet() {
-        return new FastArrayBunch() {
-            @Override
-            public boolean areEqual(Triple a, Triple b) {
-                return a.equals(b);
-            }
-        };
+        return new FastTripleArrayBunch();
+    }
+
+    private static class FastTripleArrayBunch extends FastArrayBunch {
+
+        public FastTripleArrayBunch() {
+            super();
+        }
+
+        private FastTripleArrayBunch(FastTripleArrayBunch bunchToCopy) {
+            super(bunchToCopy);
+        }
+
+        @Override
+        public FastTripleArrayBunch copy() {
+            return new FastTripleArrayBunch(this);
+        }
+
+        @Override
+        public boolean areEqual(final Triple a, final Triple b) {
+            return a.equals(b);
+        }
     }
 }
\ No newline at end of file
diff --git 
a/jena-core/src/test/java/org/apache/jena/mem2/store/fast/FastHashedTripleBunchTest.java
 
b/jena-core/src/test/java/org/apache/jena/mem2/store/fast/FastHashedTripleBunchTest.java
index 7efa52a250..81b68b54b7 100644
--- 
a/jena-core/src/test/java/org/apache/jena/mem2/store/fast/FastHashedTripleBunchTest.java
+++ 
b/jena-core/src/test/java/org/apache/jena/mem2/store/fast/FastHashedTripleBunchTest.java
@@ -34,30 +34,39 @@ public class FastHashedTripleBunchTest extends 
AbstractJenaSetTripleTest {
 
     @Test
     public void testConstructorWithArrayBunchEmpty() {
-        final var arrayBunch = new FastArrayBunch() {
-
-            @Override
-            public boolean areEqual(Triple a, Triple b) {
-                return a.equals(b);
-            }
-        };
+        final var arrayBunch = new FastTripleArrayBunch();
         final var sut = new FastHashedTripleBunch(arrayBunch);
         assertEquals(0, sut.size());
     }
 
     @Test
     public void testConstructorWithArrayBunch() {
-        final var arrayBunch = new FastArrayBunch() {
-
-            @Override
-            public boolean areEqual(Triple a, Triple b) {
-                return a.equals(b);
-            }
-        };
+        final var arrayBunch = new FastTripleArrayBunch();
         arrayBunch.tryAdd(triple("s P o"));
         arrayBunch.tryAdd(triple("s P o1"));
         arrayBunch.tryAdd(triple("s P o2"));
         final var sut = new FastHashedTripleBunch(arrayBunch);
         assertEquals(3, sut.size());
     }
+
+    private static class FastTripleArrayBunch extends FastArrayBunch {
+
+        public FastTripleArrayBunch() {
+            super();
+        }
+
+        private FastTripleArrayBunch(FastTripleArrayBunch bunchToCopy) {
+            super(bunchToCopy);
+        }
+
+        @Override
+        public FastTripleArrayBunch copy() {
+            return new FastTripleArrayBunch(this);
+        }
+
+        @Override
+        public boolean areEqual(final Triple a, final Triple b) {
+            return a.equals(b);
+        }
+    }
 }
\ No newline at end of file

Reply via email to