You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jena.apache.org by an...@apache.org on 2023/06/28 09:48:17 UTC
[jena] branch main updated: GH-1912: GraphMem2 New in-memory, general-purpose, non-transactional graphs as successors of GraphMem: All variants strictly use term-equality and do not support Iterator#remove. (GraphMem uses value-equality for object nodes)
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 c65e4e3706 GH-1912: GraphMem2 New in-memory, general-purpose, non-transactional graphs as successors of GraphMem: All variants strictly use term-equality and do not support Iterator#remove. (GraphMem uses value-equality for object nodes)
new a8473636e5 Merge pull request #1918 from arne-bdt/GH1912_GraphMem2
c65e4e3706 is described below
commit c65e4e37062bd1d104ad46e365385a7d3e556d68
Author: Arne Bernhardt <ar...@soptim.de>
AuthorDate: Fri Jun 23 01:23:05 2023 +0200
GH-1912: GraphMem2
New in-memory, general-purpose, non-transactional graphs as successors of GraphMem:
All variants strictly use term-equality and do not support Iterator#remove.
(GraphMem uses value-equality for object nodes)
GraphMem2Legacy:
- Purpose: Use this graph implementation if you want to maintain the 'old' behavior of GraphMem or if your memory constraints
prevent you from utilizing more memory-intensive solutions.
- Slightly improved performance compared to GraphMem
- Simplified implementation, primarily due to lack of support for Iterator#remove
- The heritage of GraphMem:
- Same basic structure
- Same memory consumption
- Also based on HashCommon
GraphMem2Fast:
- Purpose: GraphMem2Fast is a strong candidate for becoming the new default in-memory graph in the upcoming Jena 5, thanks to
its improved performance and relatively minor increase in memory usage.
- Faster than GraphMem2Legacy (specially Graph#add, Graph#find and Graph#stream)
- Memory consumption is about 6-35% higher than GraphMem2Legacy
- Maps and sets are not based on HashCommon, but use a faster custom alternative (only #remove is a bit slower)
- Benefits from multiple small optimizations
- The heritage of GraphMem:
- Also uses 3 hash-maps indexed by subjects, predicates, and objects
- Values of the maps also switch from arrays to hash sets for the triples
GraphMem2Roaring
- Purpose: GraphMem2Roaring is ideal for handling extremely large graphs. If you frequently work with such massive data
structures, this implementation could be your top choice.
- Graph#contains is faster than GraphMem2Fast
- Better performance than GraphMem2Fast for operations with triple matches for the pattern S_O, SP_, and _PO on large graphs,
due to bit-operations to find intersecting triples
- Memory consumption is about 7-99% higher than GraphMem2Legacy
- Suitable for really large graphs like bsbm-5m.nt.gz, bsbm-25m.nt.gz, and possibly even larger
- Simple and straightforward implementation
- No heritage of GraphMem
- Internal structure:
- One indexed hash set (same as GraphMem2Fast uses) that holds all triples
- Three hash maps indexed by subjects, predicates, and objects with RoaringBitmaps as values
- The bitmaps contain the indices of the triples in the central hash set
Other Changes:
- org.apache.jena.graph.test.TestGraph
- added GraphMem2Fast, GraphMem2Legacy and GraphMem2Roaring to the suite
- GraphMem:
- moved property "TripleStore store" from GraphMemBase to GraphMem
--> needed this to make a clean GraphMem2, which also extends GraphMem but the TripleStore interface is slightly different.
- pom.xml:
- added dependency roaringbitmap 0.9.44
- jena-benchmarks-jmh
- added the three new graph implementations to the benchmarks
- randomized the order of test data in some benchmarks to prevent them from showing order dependent behaviour
- added benchmarks for sets and maps comparing
- HashCommonSet vs. FastHashSet vs. Java HashSet
- HashCommonMap vs. FastHashMap vs. Java HashMap
---
.../org/apache/jena/mem/graph/TestGraphAdd.java | 8 +-
.../jena/mem/graph/TestGraphContainsAnything.java | 77 +-
.../jena/mem/graph/TestGraphContainsTriple.java | 67 +-
.../org/apache/jena/mem/graph/TestGraphDelete.java | 17 +-
.../TestGraphFindAllWithForEachRemaining.java | 36 +-
.../graph/TestGraphFindAllWithHasNextAndNext.java | 39 +-
.../mem/graph/TestGraphFindByMatchAndCount.java | 85 +-
.../mem/graph/TestGraphFindByMatchAndGetFirst.java | 61 +-
.../jena/mem/graph/TestGraphMemoryConsumption.java | 38 +-
.../apache/jena/mem/graph/TestGraphStreamAll.java | 47 +-
.../mem/graph/TestGraphStreamByMatchAndCount.java | 48 +-
.../graph/TestGraphStreamByMatchAndFindAny.java | 59 +-
.../org/apache/jena/mem/graph/helper/Context.java | 41 +-
.../mem/graph/helper/GraphTripleNodeHelper480.java | 15 +-
.../graph/helper/GraphTripleNodeHelperCurrent.java | 15 +-
.../jena/mem/graph/helper/JMHDefaultOptions.java | 2 +-
.../org/apache/jena/mem/graph/helper/Releases.java | 4 +-
.../graph/helper => mem2}/JMHDefaultOptions.java | 4 +-
.../map/node/FastHashNodeMap.java} | 24 +-
.../jena/mem2/map/node/HashCommonNodeMap.java | 59 ++
.../org/apache/jena/mem2/map/node/TestMapAdd.java | 112 +++
.../apache/jena/mem2/map/node/TestMapContains.java | 218 +++++
.../set/triple/FastHashTripleSet.java} | 24 +-
.../set/triple/HashCommonTripleSet.java} | 27 +-
.../set/triple/TestSetAdd.java} | 73 +-
.../jena/mem2/set/triple/TestSetContains.java | 119 +++
.../jena/mem2/set/triple/TestSetIterate.java | 125 +++
.../set/triple/TestSetMemoryConsumption.java} | 104 +-
.../apache/jena/mem2/set/triple/TestSetRemove.java | 124 +++
.../jena/mem2/set/triple/TestSetStreamAll.java | 120 +++
.../apache/jena/mem2/set/triple/TestSetUpdate.java | 143 +++
...estSparseArraySpliteratorsForeachRemaining.java | 39 +-
.../TestSparseArraySpliteratorsStreamParallel.java | 38 +-
.../TestSparseArraySpliteratorsTryAdvance.java | 47 +-
jena-core/pom.xml | 39 +-
.../main/java/org/apache/jena/mem/GraphMem.java | 23 +-
.../java/org/apache/jena/mem/GraphMemBase.java | 18 +-
.../main/java/org/apache/jena/mem2/GraphMem2.java | 169 ++++
.../java/org/apache/jena/mem2/GraphMem2Fast.java | 45 +
.../java/org/apache/jena/mem2/GraphMem2Legacy.java | 47 +
.../org/apache/jena/mem2/GraphMem2Roaring.java | 49 +
.../apache/jena/mem2/collection/FastHashBase.java | 422 ++++++++
.../apache/jena/mem2/collection/FastHashMap.java | 208 ++++
.../apache/jena/mem2/collection/FastHashSet.java | 113 +++
.../jena/mem2/collection/HashCommonBase.java | 210 ++++
.../apache/jena/mem2/collection/HashCommonMap.java | 193 ++++
.../apache/jena/mem2/collection/HashCommonSet.java | 95 ++
.../org/apache/jena/mem2/collection/JenaMap.java | 121 +++
.../jena/mem2/collection/JenaMapSetCommon.java | 113 +++
.../org/apache/jena/mem2/collection/JenaSet.java | 28 +-
.../jena/mem2/collection/JenaSetHashOptimized.java | 19 +-
.../jena/mem2/iterator/IteratorOfJenaSets.java | 74 ++
.../jena/mem2/iterator/SparseArrayIterator.java | 97 ++
.../org/apache/jena/mem2/pattern/MatchPattern.java | 58 ++
.../jena/mem2/pattern/PatternClassifier.java | 105 ++
.../jena/mem2/spliterator/ArraySpliterator.java | 101 ++
.../jena/mem2/spliterator/ArraySubSpliterator.java | 105 ++
.../mem2/spliterator/SparseArraySpliterator.java | 106 +++
.../spliterator/SparseArraySubSpliterator.java | 113 +++
.../org/apache/jena/mem2/store/TripleStore.java | 90 ++
.../jena/mem2/store/fast/FastArrayBunch.java | 192 ++++
.../jena/mem2/store/fast/FastHashedBunchMap.java | 23 +-
.../mem2/store/fast/FastHashedTripleBunch.java | 51 +
.../jena/mem2/store/fast/FastTripleBunch.java | 51 +
.../jena/mem2/store/fast/FastTripleStore.java | 386 ++++++++
.../apache/jena/mem2/store/legacy/ArrayBunch.java | 166 ++++
.../apache/jena/mem2/store/legacy/FieldFilter.java | 69 ++
.../jena/mem2/store/legacy/HashedBunchMap.java | 28 +-
.../jena/mem2/store/legacy/HashedTripleBunch.java | 33 +-
.../jena/mem2/store/legacy/LegacyTripleStore.java | 152 +++
.../jena/mem2/store/legacy/NodeToTriplesMap.java | 61 ++
.../mem2/store/legacy/NodeToTriplesMapMem.java | 201 ++++
.../apache/jena/mem2/store/legacy/TripleBunch.java | 23 +-
.../store/roaring/RoaringBitmapTripleIterator.java | 94 ++
.../mem2/store/roaring/RoaringTripleStore.java | 335 +++++++
.../java/org/apache/jena/graph/test/TestGraph.java | 35 +-
.../apache/jena/mem2/AbstractGraphMem2Test.java | 1003 ++++++++++++++++++++
.../org/apache/jena/mem2/GraphMem2FastTest.java | 19 +-
.../org/apache/jena/mem2/GraphMem2LegacyTest.java | 18 +-
.../org/apache/jena/mem2/GraphMem2RoaringTest.java | 18 +-
.../java/org/apache/jena/mem2/GraphMem2Test.java | 173 ++++
.../mem2/collection/AbstractJenaMapNodeTest.java | 648 +++++++++++++
.../mem2/collection/AbstractJenaSetTripleTest.java | 388 ++++++++
.../jena/mem2/collection/FastHashMapTest.java | 26 +-
.../jena/mem2/collection/FastHashMapTest2.java | 73 ++
.../jena/mem2/collection/FastHashSetTest.java | 23 +-
.../jena/mem2/collection/FastHashSetTest2.java | 168 ++++
.../jena/mem2/collection/HashCommonMapTest.java | 31 +-
.../jena/mem2/collection/HashCommonSetTest.java | 27 +-
.../jena/mem2/iterator/IteratorOfJenaSetsTest.java | 107 +++
.../mem2/iterator/SparseArrayIteratorTest.java | 132 +++
.../jena/mem2/pattern/PatternClassifierTest.java | 52 +
.../mem2/spliterator/ArraySpliteratorTest.java | 295 ++++++
.../mem2/spliterator/ArraySubSpliteratorTest.java | 295 ++++++
.../spliterator/SparseArraySpliteratorTest.java | 554 +++++++++++
.../spliterator/SparseArraySubSpliteratorTest.java | 554 +++++++++++
.../jena/mem2/store/AbstractTripleStoreTest.java | 1002 +++++++++++++++++++
.../jena/mem2/store/fast/FastArrayBunchTest.java | 24 +-
.../mem2/store/fast/FastHashedTripleBunchTest.java | 63 ++
.../jena/mem2/store/fast/FastTripleStoreTest.java | 100 ++
.../jena/mem2/store/legacy/ArrayBunchTest.java | 20 +-
.../jena/mem2/store/legacy/FieldFilterTest.java | 69 ++
.../mem2/store/legacy/HashedTripleBunchTest.java | 51 +
.../mem2/store/legacy/LegacyTripleStoreTest.java | 21 +-
.../mem2/store/legacy/NodeToTriplesMapMemTest.java | 20 +-
.../roaring/RoaringBitmapTripleIteratorTest.java | 144 +++
.../mem2/store/roaring/RoaringTripleStoreTest.java | 21 +-
pom.xml | 9 +-
108 files changed, 12148 insertions(+), 720 deletions(-)
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphAdd.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphAdd.java
index e18f6f7747..1294780490 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphAdd.java
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphAdd.java
@@ -42,17 +42,17 @@ public class TestGraphAdd {
@Param({
"GraphMem (current)",
+ "GraphMem2Fast (current)",
+ "GraphMem2Legacy (current)",
+ "GraphMem2Roaring (current)",
"GraphMem (Jena 4.8.0)",
})
public String param1_GraphImplementation;
-
+ java.util.function.Supplier<Object> graphAdd;
private Context trialContext;
-
private List<Triple> triplesCurrent;
private List<org.apache.shadedJena480.graph.Triple> triples480;
- java.util.function.Supplier<Object> graphAdd;
-
@Benchmark
public Object graphAdd() {
return graphAdd.get();
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphContainsAnything.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphContainsAnything.java
index 496fb237a3..1e70deb6c6 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphContainsAnything.java
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphContainsAnything.java
@@ -28,12 +28,15 @@ import org.junit.Test;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
+import java.util.Collections;
import java.util.List;
+import java.util.Random;
import java.util.function.Predicate;
@State(Scope.Benchmark)
public class TestGraphContainsAnything {
+
@Param({
"../testing/cheeses-0.1.ttl",
"../testing/pizza.owl.rdf",
@@ -43,18 +46,18 @@ public class TestGraphContainsAnything {
@Param({
"GraphMem (current)",
+ "GraphMem2Fast (current)",
+ "GraphMem2Legacy (current)",
+ "GraphMem2Roaring (current)",
"GraphMem (Jena 4.8.0)",
})
public String param1_GraphImplementation;
-
+ java.util.function.Function<String, Boolean> graphContains;
private Graph sutCurrent;
private org.apache.shadedJena480.graph.Graph sut480;
-
private List<Triple> triplesToFindCurrent;
private List<org.apache.shadedJena480.graph.Triple> triplesToFind480;
- java.util.function.Function<String, Boolean> graphContains;
-
@Benchmark
public boolean graphContainsS__() {
return graphContains.apply("S__");
@@ -65,6 +68,11 @@ public class TestGraphContainsAnything {
return graphContains.apply("_P_");
}
+ @Benchmark
+ public boolean graphContains__O() {
+ return graphContains.apply("__O");
+ }
+
@Benchmark
public boolean graphContainsSP_() {
return graphContains.apply("SP_");
@@ -80,15 +88,12 @@ public class TestGraphContainsAnything {
return graphContains.apply("_PO");
}
- @Benchmark
- public boolean graphContains__O() {
- return graphContains.apply("__O");
- }
+
private boolean graphContainsCurrent(String pattern) {
var containsPredicate = getContainsPredicateByPatternCurrent(pattern);
var found = false;
- for(var t: triplesToFindCurrent) {
+ for (var t : triplesToFindCurrent) {
found = containsPredicate.test(t);
Assert.assertTrue(found);
}
@@ -98,7 +103,7 @@ public class TestGraphContainsAnything {
private boolean graphContains480(String pattern) {
var containsPredicate = getContainsPredicateByPattern480(pattern);
var found = false;
- for(var t: triplesToFind480) {
+ for (var t : triplesToFind480) {
found = containsPredicate.test(t);
Assert.assertTrue(found);
}
@@ -147,30 +152,34 @@ public class TestGraphContainsAnything {
public void setupTrial() throws Exception {
var trialContext = new Context(param1_GraphImplementation);
switch (trialContext.getJenaVersion()) {
- case CURRENT:
- {
- this.sutCurrent = Releases.current.createGraph(trialContext.getGraphClass());
- this.graphContains = this::graphContainsCurrent;
-
- var triples = Releases.current.readTriples(param0_GraphUri);
- triples.forEach(this.sutCurrent::add);
-
- /*clone the triples because they should not be the same objects*/
- this.triplesToFindCurrent = Releases.current.cloneTriples(triples);
- }
- break;
- case JENA_4_8_0:
- {
- this.sut480 = Releases.v480.createGraph(trialContext.getGraphClass());
- this.graphContains = this::graphContains480;
-
- var triples = Releases.v480.readTriples(param0_GraphUri);
- triples.forEach(this.sut480::add);
-
- /*clone the triples because they should not be the same objects*/
- this.triplesToFind480 = Releases.v480.cloneTriples(triples);
- }
- break;
+ case CURRENT: {
+ this.sutCurrent = Releases.current.createGraph(trialContext.getGraphClass());
+ this.graphContains = this::graphContainsCurrent;
+
+ var triples = Releases.current.readTriples(param0_GraphUri);
+ triples.forEach(this.sutCurrent::add);
+
+ /*clone the triples because they should not be the same objects*/
+ this.triplesToFindCurrent = Releases.current.cloneTriples(triples);
+
+ /* Shuffle is import because the order might play a role. We want to test the performance of the
+ contains method regardless of the order */
+ Collections.shuffle(this.triplesToFindCurrent, new Random(4721));
+ }
+ break;
+ case JENA_4_8_0: {
+ this.sut480 = Releases.v480.createGraph(trialContext.getGraphClass());
+ this.graphContains = this::graphContains480;
+
+ var triples = Releases.v480.readTriples(param0_GraphUri);
+ triples.forEach(this.sut480::add);
+
+ /* Shuffle is import because the order might play a role. We want to test the performance of the
+ contains method regardless of the order */
+ this.triplesToFind480 = Releases.v480.cloneTriples(triples);
+ Collections.shuffle(this.triplesToFind480, new Random(4721));
+ }
+ break;
default:
throw new IllegalArgumentException("Unknown Jena version: " + trialContext.getJenaVersion());
}
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphContainsTriple.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphContainsTriple.java
index b61cdfbb06..7ae86b1d50 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphContainsTriple.java
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphContainsTriple.java
@@ -29,6 +29,7 @@ import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import java.util.List;
+import java.util.Random;
@State(Scope.Benchmark)
@@ -42,19 +43,19 @@ public class TestGraphContainsTriple {
public String param0_GraphUri;
@Param({
- "GraphMem (Jena 4.8.0)",
"GraphMem (current)",
+ "GraphMem2Fast (current)",
+ "GraphMem2Legacy (current)",
+ "GraphMem2Roaring (current)",
+ "GraphMem (Jena 4.8.0)",
})
public String param1_GraphImplementation;
-
+ java.util.function.Supplier<Boolean> graphContains;
private Graph sutCurrent;
private org.apache.shadedJena480.graph.Graph sut480;
-
private List<Triple> triplesToFindCurrent;
private List<org.apache.shadedJena480.graph.Triple> triplesToFind480;
- java.util.function.Supplier<Boolean> graphContains;
-
@Benchmark
public boolean graphContains() {
return graphContains.get();
@@ -62,7 +63,7 @@ public class TestGraphContainsTriple {
private boolean graphContainsCurrent() {
var found = false;
- for(var t: triplesToFindCurrent) {
+ for (var t : triplesToFindCurrent) {
found = sutCurrent.contains(t);
Assert.assertTrue(found);
}
@@ -71,7 +72,7 @@ public class TestGraphContainsTriple {
private boolean graphContains480() {
var found = false;
- for(var t: triplesToFind480) {
+ for (var t : triplesToFind480) {
found = sut480.contains(t);
Assert.assertTrue(found);
}
@@ -83,30 +84,34 @@ public class TestGraphContainsTriple {
public void setupTrial() throws Exception {
var trialContext = new Context(param1_GraphImplementation);
switch (trialContext.getJenaVersion()) {
- case CURRENT:
- {
- this.sutCurrent = Releases.current.createGraph(trialContext.getGraphClass());
- this.graphContains = this::graphContainsCurrent;
-
- var triples = Releases.current.readTriples(param0_GraphUri);
- triples.forEach(this.sutCurrent::add);
-
- /*clone the triples because they should not be the same objects*/
- this.triplesToFindCurrent = Releases.current.cloneTriples(triples);
- }
- break;
- case JENA_4_8_0:
- {
- this.sut480 = Releases.v480.createGraph(trialContext.getGraphClass());
- this.graphContains = this::graphContains480;
-
- var triples = Releases.v480.readTriples(param0_GraphUri);
- triples.forEach(this.sut480::add);
-
- /*clone the triples because they should not be the same objects*/
- this.triplesToFind480 = Releases.v480.cloneTriples(triples);
- }
- break;
+ case CURRENT: {
+ this.sutCurrent = Releases.current.createGraph(trialContext.getGraphClass());
+ this.graphContains = this::graphContainsCurrent;
+
+ var triples = Releases.current.readTriples(param0_GraphUri);
+ triples.forEach(this.sutCurrent::add);
+
+ /*clone the triples because they should not be the same objects*/
+ this.triplesToFindCurrent = Releases.current.cloneTriples(triples);
+ /* Shuffle is import because the order might play a role. We want to test the performance of the
+ contains method regardless of the order */
+ java.util.Collections.shuffle(this.triplesToFindCurrent, new Random(4721));
+ }
+ break;
+ case JENA_4_8_0: {
+ this.sut480 = Releases.v480.createGraph(trialContext.getGraphClass());
+ this.graphContains = this::graphContains480;
+
+ var triples = Releases.v480.readTriples(param0_GraphUri);
+ triples.forEach(this.sut480::add);
+
+ /*clone the triples because they should not be the same objects*/
+ this.triplesToFind480 = Releases.v480.cloneTriples(triples);
+ /* Shuffle is import because the order might play a role. We want to test the performance of the
+ contains method regardless of the order */
+ java.util.Collections.shuffle(this.triplesToFind480, new Random(4721));
+ }
+ break;
default:
throw new IllegalArgumentException("Unknown Jena version: " + trialContext.getJenaVersion());
}
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphDelete.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphDelete.java
index 11ec23820d..6580958858 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphDelete.java
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphDelete.java
@@ -29,6 +29,7 @@ import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import java.util.List;
+import java.util.Random;
@State(Scope.Benchmark)
public class TestGraphDelete {
@@ -42,23 +43,21 @@ public class TestGraphDelete {
@Param({
"GraphMem (current)",
+ "GraphMem2Fast (current)",
+ "GraphMem2Legacy (current)",
+ "GraphMem2Roaring (current)",
"GraphMem (Jena 4.8.0)",
})
public String param1_GraphImplementation;
+ java.util.function.Supplier<Integer> graphDelete;
private Context trialContext;
-
private Graph sutCurrent;
private org.apache.shadedJena480.graph.Graph sut480;
-
private List<Triple> allTriplesCurrent;
private List<org.apache.shadedJena480.graph.Triple> allTriples480;
-
private List<Triple> triplesToDeleteFromSutCurrent;
private List<org.apache.shadedJena480.graph.Triple> triplesToDeleteFromSut480;
-
- java.util.function.Supplier<Integer> graphDelete;
-
@Benchmark
public int graphDelete() {
return graphDelete.get();
@@ -84,6 +83,9 @@ public class TestGraphDelete {
this.allTriplesCurrent.forEach(this.sutCurrent::add);
/*cloning is important so that the triples are not reference equal */
this.triplesToDeleteFromSutCurrent = Releases.current.cloneTriples(this.allTriplesCurrent);
+ /* Shuffle is import because the order might play a role. We want to test the performance of the
+ contains method regardless of the order */
+ java.util.Collections.shuffle(this.triplesToDeleteFromSutCurrent, new Random(4721));
break;
case JENA_4_8_0:
@@ -91,6 +93,9 @@ public class TestGraphDelete {
this.allTriples480.forEach(this.sut480::add);
/*cloning is important so that the triples are not reference equal */
this.triplesToDeleteFromSut480 = Releases.v480.cloneTriples(this.allTriples480);
+ /* Shuffle is import because the order might play a role. We want to test the performance of the
+ contains method regardless of the order */
+ java.util.Collections.shuffle(this.triplesToDeleteFromSut480, new Random(4721));
break;
default:
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphFindAllWithForEachRemaining.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphFindAllWithForEachRemaining.java
index bfddc7618d..b8a6639dc9 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphFindAllWithForEachRemaining.java
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphFindAllWithForEachRemaining.java
@@ -43,14 +43,16 @@ public class TestGraphFindAllWithForEachRemaining {
@Param({
"GraphMem (current)",
+ "GraphMem2Fast (current)",
+ "GraphMem2Legacy (current)",
+ "GraphMem2Roaring (current)",
"GraphMem (Jena 4.8.0)",
})
public String param1_GraphImplementation;
+ java.util.function.Supplier<Long> graphFindAll;
private Graph sutCurrent;
private org.apache.shadedJena480.graph.Graph sut480;
- java.util.function.Supplier<Long> graphFindAll;
-
@Benchmark
public Long graphFindAll() {
return graphFindAll.get();
@@ -78,24 +80,22 @@ public class TestGraphFindAllWithForEachRemaining {
public void setupTrial() throws Exception {
Context trialContext = new Context(param1_GraphImplementation);
switch (trialContext.getJenaVersion()) {
- case CURRENT:
- {
- this.sutCurrent = Releases.current.createGraph(trialContext.getGraphClass());
- this.graphFindAll = this::graphFindAllCurrent;
+ case CURRENT: {
+ this.sutCurrent = Releases.current.createGraph(trialContext.getGraphClass());
+ this.graphFindAll = this::graphFindAllCurrent;
- var triples = Releases.current.readTriples(param0_GraphUri);
- triples.forEach(this.sutCurrent::add);
- }
- break;
- case JENA_4_8_0:
- {
- this.sut480 = Releases.v480.createGraph(trialContext.getGraphClass());
- this.graphFindAll = this::graphFindAll480;
+ var triples = Releases.current.readTriples(param0_GraphUri);
+ triples.forEach(this.sutCurrent::add);
+ }
+ break;
+ case JENA_4_8_0: {
+ this.sut480 = Releases.v480.createGraph(trialContext.getGraphClass());
+ this.graphFindAll = this::graphFindAll480;
- var triples = Releases.v480.readTriples(param0_GraphUri);
- triples.forEach(this.sut480::add);
- }
- break;
+ var triples = Releases.v480.readTriples(param0_GraphUri);
+ triples.forEach(this.sut480::add);
+ }
+ break;
default:
throw new IllegalArgumentException("Unknown Jena version: " + trialContext.getJenaVersion());
}
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphFindAllWithHasNextAndNext.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphFindAllWithHasNextAndNext.java
index a4e91236d8..dc38c52eff 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphFindAllWithHasNextAndNext.java
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphFindAllWithHasNextAndNext.java
@@ -43,15 +43,16 @@ public class TestGraphFindAllWithHasNextAndNext {
@Param({
"GraphMem (current)",
+ "GraphMem2Fast (current)",
+ "GraphMem2Legacy (current)",
+ "GraphMem2Roaring (current)",
"GraphMem (Jena 4.8.0)",
})
public String param1_GraphImplementation;
-
+ java.util.function.Supplier<Long> graphFindAll;
private Graph sutCurrent;
private org.apache.shadedJena480.graph.Graph sut480;
- java.util.function.Supplier<Long> graphFindAll;
-
@Benchmark
public Long graphFindAll() {
return graphFindAll.get();
@@ -60,7 +61,7 @@ public class TestGraphFindAllWithHasNextAndNext {
private Long graphFindAllCurrent() {
var actionCounter = new ActionCount<>();
var iter = sutCurrent.find();
- while(iter.hasNext()) {
+ while (iter.hasNext()) {
actionCounter.accept(iter.next());
}
iter.close();
@@ -71,7 +72,7 @@ public class TestGraphFindAllWithHasNextAndNext {
private Long graphFindAll480() {
var actionCounter = new ActionCount<>();
var iter = sut480.find();
- while(iter.hasNext()) {
+ while (iter.hasNext()) {
actionCounter.accept(iter.next());
}
iter.close();
@@ -83,23 +84,21 @@ public class TestGraphFindAllWithHasNextAndNext {
public void setupTrial() throws Exception {
Context trialContext = new Context(param1_GraphImplementation);
switch (trialContext.getJenaVersion()) {
- case CURRENT:
- {
- this.sutCurrent = Releases.current.createGraph(trialContext.getGraphClass());
- this.graphFindAll = this::graphFindAllCurrent;
+ case CURRENT: {
+ this.sutCurrent = Releases.current.createGraph(trialContext.getGraphClass());
+ this.graphFindAll = this::graphFindAllCurrent;
- var triples = Releases.current.readTriples(param0_GraphUri);
- triples.forEach(this.sutCurrent::add);
- }
- break;
- case JENA_4_8_0:
- {
- this.sut480 = Releases.v480.createGraph(trialContext.getGraphClass());
- this.graphFindAll = this::graphFindAll480;
+ var triples = Releases.current.readTriples(param0_GraphUri);
+ triples.forEach(this.sutCurrent::add);
+ }
+ break;
+ case JENA_4_8_0: {
+ this.sut480 = Releases.v480.createGraph(trialContext.getGraphClass());
+ this.graphFindAll = this::graphFindAll480;
- var triples = Releases.v480.readTriples(param0_GraphUri);
- triples.forEach(this.sut480::add);
- }
+ var triples = Releases.v480.readTriples(param0_GraphUri);
+ triples.forEach(this.sut480::add);
+ }
break;
default:
throw new IllegalArgumentException("Unknown Jena version: " + trialContext.getJenaVersion());
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphFindByMatchAndCount.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphFindByMatchAndCount.java
index 156a7a8413..5a879e075d 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphFindByMatchAndCount.java
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphFindByMatchAndCount.java
@@ -31,6 +31,7 @@ import org.openjdk.jmh.runner.Runner;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
+import java.util.Random;
import java.util.function.Function;
import static org.junit.Assert.assertNotNull;
@@ -48,21 +49,21 @@ public class TestGraphFindByMatchAndCount {
@Param({
"GraphMem (current)",
+ "GraphMem2Fast (current)",
+ "GraphMem2Legacy (current)",
+ "GraphMem2Roaring (current)",
"GraphMem (Jena 4.8.0)",
})
public String param1_GraphImplementation;
- @Param({"500"})
+ @Param({"800"})
public int param2_sampleSize;
-
+ Function<String, Object> graphFindByMatchesAndCount;
private Graph sutCurrent;
private org.apache.shadedJena480.graph.Graph sut480;
-
private List<Triple> triplesToFindCurrent;
private List<org.apache.shadedJena480.graph.Triple> triplesToFind480;
- Function<String, Object> graphFindByMatchesAndCount;
-
@Benchmark
public Object graphFindS__() {
return graphFindByMatchesAndCount.apply("S__");
@@ -73,6 +74,11 @@ public class TestGraphFindByMatchAndCount {
return graphFindByMatchesAndCount.apply("_P_");
}
+ @Benchmark
+ public Object graphFind__O() {
+ return graphFindByMatchesAndCount.apply("__O");
+ }
+
@Benchmark
public Object graphFindSP_() {
return graphFindByMatchesAndCount.apply("SP_");
@@ -88,10 +94,6 @@ public class TestGraphFindByMatchAndCount {
return graphFindByMatchesAndCount.apply("_PO");
}
- @Benchmark
- public Object graphFind__O() {
- return graphFindByMatchesAndCount.apply("__O");
- }
private int graphFindByMatchesAndCount(String pattern) {
var findFunction = getFindFunctionByPatternCurrent(pattern);
@@ -153,39 +155,42 @@ public class TestGraphFindByMatchAndCount {
public void setupTrial() throws Exception {
Context trialContext = new Context(param1_GraphImplementation);
switch (trialContext.getJenaVersion()) {
- case CURRENT:
- {
- this.sutCurrent = Releases.current.createGraph(trialContext.getGraphClass());
- this.graphFindByMatchesAndCount = this::graphFindByMatchesAndCount;
-
- var triples = Releases.current.readTriples(param0_GraphUri);
- triples.forEach(this.sutCurrent::add);
-
- /*clone the triples because they should not be the same objects*/
- this.triplesToFindCurrent = new ArrayList<>(param2_sampleSize);
- var sampleIncrement = triples.size() / param2_sampleSize;
- for(var i=0; i< triples.size(); i+=sampleIncrement) {
- this.triplesToFindCurrent.add(Releases.current.cloneTriple(triples.get(i)));
- }
-
+ case CURRENT: {
+ this.sutCurrent = Releases.current.createGraph(trialContext.getGraphClass());
+ this.graphFindByMatchesAndCount = this::graphFindByMatchesAndCount;
+
+ var triples = Releases.current.readTriples(param0_GraphUri);
+ triples.forEach(this.sutCurrent::add);
+
+ /*clone the triples because they should not be the same objects*/
+ this.triplesToFindCurrent = new ArrayList<>(param2_sampleSize);
+ var sampleIncrement = triples.size() / param2_sampleSize;
+ for (var i = 0; i < triples.size(); i += sampleIncrement) {
+ this.triplesToFindCurrent.add(Releases.current.cloneTriple(triples.get(i)));
}
- break;
- case JENA_4_8_0:
- {
- this.sut480 = Releases.v480.createGraph(trialContext.getGraphClass());
- this.graphFindByMatchesAndCount = this::graphFindByMatchesAndCount480;
-
- var triples = Releases.v480.readTriples(param0_GraphUri);
- triples.forEach(this.sut480::add);
-
- /*clone the triples because they should not be the same objects*/
- this.triplesToFind480 = new ArrayList<>(param2_sampleSize);
- var sampleIncrement = triples.size() / param2_sampleSize;
- for(var i=0; i< triples.size(); i+=sampleIncrement) {
- this.triplesToFind480.add(Releases.v480.cloneTriple(triples.get(i)));
- }
+ /* Shuffle is import because the order might play a role. We want to test the performance of the
+ contains method regardless of the order */
+ java.util.Collections.shuffle(this.triplesToFindCurrent, new Random(4721));
+ }
+ break;
+ case JENA_4_8_0: {
+ this.sut480 = Releases.v480.createGraph(trialContext.getGraphClass());
+ this.graphFindByMatchesAndCount = this::graphFindByMatchesAndCount480;
+
+ var triples = Releases.v480.readTriples(param0_GraphUri);
+ triples.forEach(this.sut480::add);
+
+ /*clone the triples because they should not be the same objects*/
+ this.triplesToFind480 = new ArrayList<>(param2_sampleSize);
+ var sampleIncrement = triples.size() / param2_sampleSize;
+ for (var i = 0; i < triples.size(); i += sampleIncrement) {
+ this.triplesToFind480.add(Releases.v480.cloneTriple(triples.get(i)));
}
- break;
+ /* Shuffle is import because the order might play a role. We want to test the performance of the
+ contains method regardless of the order */
+ java.util.Collections.shuffle(this.triplesToFind480, new Random(4721));
+ }
+ break;
default:
throw new IllegalArgumentException("Unknown Jena version: " + trialContext.getJenaVersion());
}
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphFindByMatchAndGetFirst.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphFindByMatchAndGetFirst.java
index b440a389af..6b398e4cd8 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphFindByMatchAndGetFirst.java
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphFindByMatchAndGetFirst.java
@@ -29,6 +29,7 @@ import org.openjdk.jmh.runner.Runner;
import java.util.Iterator;
import java.util.List;
+import java.util.Random;
import java.util.function.Function;
import static org.junit.Assert.assertNotNull;
@@ -47,18 +48,18 @@ public class TestGraphFindByMatchAndGetFirst {
@Param({
"GraphMem (current)",
+ "GraphMem2Fast (current)",
+ "GraphMem2Legacy (current)",
+ "GraphMem2Roaring (current)",
"GraphMem (Jena 4.8.0)",
})
public String param1_GraphImplementation;
-
+ java.util.function.Function<String, Object> graphFind;
private Graph sutCurrent;
private org.apache.shadedJena480.graph.Graph sut480;
-
private List<Triple> triplesToFindCurrent;
private List<org.apache.shadedJena480.graph.Triple> triplesToFind480;
- java.util.function.Function<String, Object> graphFind;
-
@Benchmark
public Object graphFindS__() {
return graphFind.apply("S__");
@@ -155,30 +156,34 @@ public class TestGraphFindByMatchAndGetFirst {
public void setupTrial() throws Exception {
Context trialContext = new Context(param1_GraphImplementation);
switch (trialContext.getJenaVersion()) {
- case CURRENT:
- {
- this.sutCurrent = Releases.current.createGraph(trialContext.getGraphClass());
- this.graphFind = this::graphFindByMatchesAndGetFirstCurrent;
-
- var triples = Releases.current.readTriples(param0_GraphUri);
- triples.forEach(this.sutCurrent::add);
-
- /*clone the triples because they should not be the same objects*/
- this.triplesToFindCurrent = Releases.current.cloneTriples(triples);
- }
- break;
- case JENA_4_8_0:
- {
- this.sut480 = Releases.v480.createGraph(trialContext.getGraphClass());
- this.graphFind = this::graphFindByMatchesAndGetFirst480;
-
- var triples = Releases.v480.readTriples(param0_GraphUri);
- triples.forEach(this.sut480::add);
-
- /*clone the triples because they should not be the same objects*/
- this.triplesToFind480 = Releases.v480.cloneTriples(triples);
- }
- break;
+ case CURRENT: {
+ this.sutCurrent = Releases.current.createGraph(trialContext.getGraphClass());
+ this.graphFind = this::graphFindByMatchesAndGetFirstCurrent;
+
+ var triples = Releases.current.readTriples(param0_GraphUri);
+ triples.forEach(this.sutCurrent::add);
+
+ /*clone the triples because they should not be the same objects*/
+ this.triplesToFindCurrent = Releases.current.cloneTriples(triples);
+ /* Shuffle is import because the order might play a role. We want to test the performance of the
+ contains method regardless of the order */
+ java.util.Collections.shuffle(this.triplesToFindCurrent, new Random(4721));
+ }
+ break;
+ case JENA_4_8_0: {
+ this.sut480 = Releases.v480.createGraph(trialContext.getGraphClass());
+ this.graphFind = this::graphFindByMatchesAndGetFirst480;
+
+ var triples = Releases.v480.readTriples(param0_GraphUri);
+ triples.forEach(this.sut480::add);
+
+ /*clone the triples because they should not be the same objects*/
+ this.triplesToFind480 = Releases.v480.cloneTriples(triples);
+ /* Shuffle is import because the order might play a role. We want to test the performance of the
+ contains method regardless of the order */
+ java.util.Collections.shuffle(this.triplesToFind480, new Random(4721));
+ }
+ break;
default:
throw new IllegalArgumentException("Unknown Jena version: " + trialContext.getJenaVersion());
}
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphMemoryConsumption.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphMemoryConsumption.java
index dd59e405f4..ca56cce49c 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphMemoryConsumption.java
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphMemoryConsumption.java
@@ -43,15 +43,29 @@ public class TestGraphMemoryConsumption {
@Param({
"GraphMem (current)",
+ "GraphMem2Fast (current)",
+ "GraphMem2Legacy (current)",
+ "GraphMem2Roaring (current)",
"GraphMem (Jena 4.8.0)",
})
public String param1_GraphImplementation;
+ java.util.function.Supplier<Object> graphFill;
private Context trialContext;
-
private List<Triple> allTriplesCurrent;
private List<org.apache.shadedJena480.graph.Triple> allTriples480;
- java.util.function.Supplier<Object> graphFill;
+ /**
+ * This method is used to get the memory consumption of the current JVM.
+ *
+ * @return the memory consumption in MB
+ */
+ private static double runGcAndGetUsedMemoryInMB() {
+ System.runFinalization();
+ System.gc();
+ Runtime.getRuntime().runFinalization();
+ Runtime.getRuntime().gc();
+ return BigDecimal.valueOf(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()).divide(BigDecimal.valueOf(1024L)).divide(BigDecimal.valueOf(1024L)).doubleValue();
+ }
@Benchmark
public Object graphFill() {
@@ -65,10 +79,10 @@ public class TestGraphMemoryConsumption {
allTriplesCurrent.forEach(sut::add);
stopwatch.stop();
var memoryAfter = runGcAndGetUsedMemoryInMB();
- System.out.println(String.format("graphs: %d time to fill graphs: %s additional memory: %5.3f MB",
+ System.out.printf("graphs: %d time to fill graphs: %s additional memory: %5.3f MB%n",
sut.size(),
stopwatch.formatTime(),
- (memoryAfter - memoryBefore)));
+ (memoryAfter - memoryBefore));
return sut;
}
@@ -79,25 +93,13 @@ public class TestGraphMemoryConsumption {
allTriples480.forEach(sut::add);
stopwatch.stop();
var memoryAfter = runGcAndGetUsedMemoryInMB();
- System.out.println(String.format("graphs: %d time to fill graphs: %s additional memory: %5.3f MB",
+ System.out.printf("graphs: %d time to fill graphs: %s additional memory: %5.3f MB%n",
sut.size(),
stopwatch.formatTime(),
- (memoryAfter - memoryBefore)));
+ (memoryAfter - memoryBefore));
return sut;
}
- /**
- * This method is used to get the memory consumption of the current JVM.
- * @return the memory consumption in MB
- */
- private static double runGcAndGetUsedMemoryInMB() {
- System.runFinalization();
- System.gc();
- Runtime.getRuntime().runFinalization();
- Runtime.getRuntime().gc();
- return BigDecimal.valueOf(Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()).divide(BigDecimal.valueOf(1024l)).divide(BigDecimal.valueOf(1024l)).doubleValue();
- }
-
@Setup(Level.Trial)
public void setupTrial() throws Exception {
this.trialContext = new Context(param1_GraphImplementation);
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphStreamAll.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphStreamAll.java
index 82e3d3847d..4cb1960daf 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphStreamAll.java
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphStreamAll.java
@@ -44,15 +44,16 @@ public class TestGraphStreamAll {
@Param({
"GraphMem (current)",
+ "GraphMem2Fast (current)",
+ "GraphMem2Legacy (current)",
+ "GraphMem2Roaring (current)",
"GraphMem (Jena 4.8.0)",
})
public String param1_GraphImplementation;
-
- private Graph sutCurrent;
- private org.apache.shadedJena480.graph.Graph sut480;
-
java.util.function.Supplier<Object> graphStream;
java.util.function.Supplier<Object> graphStreamParallel;
+ private Graph sutCurrent;
+ private org.apache.shadedJena480.graph.Graph sut480;
@Benchmark
public Object graphStream() {
@@ -92,26 +93,24 @@ public class TestGraphStreamAll {
public void setupTrial() throws Exception {
Context trialContext = new Context(param1_GraphImplementation);
switch (trialContext.getJenaVersion()) {
- case CURRENT:
- {
- this.sutCurrent = Releases.current.createGraph(trialContext.getGraphClass());
- this.graphStream = this::graphStreamCurrent;
- this.graphStreamParallel = this::graphStreamParallelCurrent;
-
- var triples = Releases.current.readTriples(param0_GraphUri);
- triples.forEach(this.sutCurrent::add);
- }
- break;
- case JENA_4_8_0:
- {
- this.sut480 = Releases.v480.createGraph(trialContext.getGraphClass());
- this.graphStream = this::graphStream480;
- this.graphStreamParallel = this::graphStreamParallel480;
-
- var triples = Releases.v480.readTriples(param0_GraphUri);
- triples.forEach(this.sut480::add);
- }
- break;
+ case CURRENT: {
+ this.sutCurrent = Releases.current.createGraph(trialContext.getGraphClass());
+ this.graphStream = this::graphStreamCurrent;
+ this.graphStreamParallel = this::graphStreamParallelCurrent;
+
+ var triples = Releases.current.readTriples(param0_GraphUri);
+ triples.forEach(this.sutCurrent::add);
+ }
+ break;
+ case JENA_4_8_0: {
+ this.sut480 = Releases.v480.createGraph(trialContext.getGraphClass());
+ this.graphStream = this::graphStream480;
+ this.graphStreamParallel = this::graphStreamParallel480;
+
+ var triples = Releases.v480.readTriples(param0_GraphUri);
+ triples.forEach(this.sut480::add);
+ }
+ break;
default:
throw new IllegalArgumentException("Unknown Jena version: " + trialContext.getJenaVersion());
}
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphStreamByMatchAndCount.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphStreamByMatchAndCount.java
index 06707157d9..1985462a9c 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphStreamByMatchAndCount.java
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphStreamByMatchAndCount.java
@@ -30,6 +30,7 @@ import org.openjdk.jmh.runner.Runner;
import java.util.ArrayList;
import java.util.List;
+import java.util.Random;
import java.util.function.Function;
import java.util.stream.Stream;
@@ -48,20 +49,26 @@ public class TestGraphStreamByMatchAndCount {
@Param({
"GraphMem (current)",
+ "GraphMem2Fast (current)",
+ "GraphMem2Legacy (current)",
+ "GraphMem2Roaring (current)",
"GraphMem (Jena 4.8.0)",
})
public String param1_GraphImplementation;
- @Param({"500"})
+ @Param({"800"})
public int param2_sampleSize;
-
+ Function<String, Object> graphStreamByMatchAndCount;
private Graph sutCurrent;
private org.apache.shadedJena480.graph.Graph sut480;
-
private List<Triple> triplesToFindCurrent;
private List<org.apache.shadedJena480.graph.Triple> triplesToFind480;
- Function<String, Object> graphStreamByMatchAndCount;
+ private static int count(final Stream stream) {
+ var actionCounter = new ActionCount<>();
+ stream.forEach(actionCounter::accept);
+ return (int) actionCounter.getCount();
+ }
@Benchmark
public Object graphStreamS__() {
@@ -73,6 +80,11 @@ public class TestGraphStreamByMatchAndCount {
return graphStreamByMatchAndCount.apply("_P_");
}
+ @Benchmark
+ public Object graphStream__O() {
+ return graphStreamByMatchAndCount.apply("__O");
+ }
+
@Benchmark
public Object graphStreamSP_() {
return graphStreamByMatchAndCount.apply("SP_");
@@ -88,11 +100,6 @@ public class TestGraphStreamByMatchAndCount {
return graphStreamByMatchAndCount.apply("_PO");
}
- @Benchmark
- public Object graphStream__O() {
- return graphStreamByMatchAndCount.apply("__O");
- }
-
private int graphStreamByMatchAndCount(String pattern) {
var streamFunction = getStreamFunctionByPatternCurrent(pattern);
var total = 0;
@@ -111,12 +118,6 @@ public class TestGraphStreamByMatchAndCount {
return total;
}
- private static int count(final Stream stream) {
- var actionCounter = new ActionCount<>();
- stream.forEach(actionCounter::accept);
- return (int)actionCounter.getCount();
- }
-
Function<Triple, Stream<Triple>> getStreamFunctionByPatternCurrent(String pattern) {
switch (pattern) {
case "S__":
@@ -159,8 +160,7 @@ public class TestGraphStreamByMatchAndCount {
public void setupTrial() throws Exception {
Context trialContext = new Context(param1_GraphImplementation);
switch (trialContext.getJenaVersion()) {
- case CURRENT:
- {
+ case CURRENT: {
this.sutCurrent = Releases.current.createGraph(trialContext.getGraphClass());
this.graphStreamByMatchAndCount = this::graphStreamByMatchAndCount;
@@ -170,14 +170,15 @@ public class TestGraphStreamByMatchAndCount {
/*clone the triples because they should not be the same objects*/
this.triplesToFindCurrent = new ArrayList<>(param2_sampleSize);
var sampleIncrement = triples.size() / param2_sampleSize;
- for(var i=0; i< triples.size(); i+=sampleIncrement) {
+ for (var i = 0; i < triples.size(); i += sampleIncrement) {
this.triplesToFindCurrent.add(Releases.current.cloneTriple(triples.get(i)));
}
-
+ /* Shuffle is import because the order might play a role. We want to test the performance of the
+ contains method regardless of the order */
+ java.util.Collections.shuffle(this.triplesToFindCurrent, new Random(4721));
}
break;
- case JENA_4_8_0:
- {
+ case JENA_4_8_0: {
this.sut480 = Releases.v480.createGraph(trialContext.getGraphClass());
this.graphStreamByMatchAndCount = this::graphStreamByMatchAndCount480;
@@ -187,9 +188,12 @@ public class TestGraphStreamByMatchAndCount {
/*clone the triples because they should not be the same objects*/
this.triplesToFind480 = new ArrayList<>(param2_sampleSize);
var sampleIncrement = triples.size() / param2_sampleSize;
- for(var i=0; i< triples.size(); i+=sampleIncrement) {
+ for (var i = 0; i < triples.size(); i += sampleIncrement) {
this.triplesToFind480.add(Releases.v480.cloneTriple(triples.get(i)));
}
+ /* Shuffle is import because the order might play a role. We want to test the performance of the
+ contains method regardless of the order */
+ java.util.Collections.shuffle(this.triplesToFind480, new Random(4721));
}
break;
default:
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphStreamByMatchAndFindAny.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphStreamByMatchAndFindAny.java
index 2b7c776140..a97de8aab8 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphStreamByMatchAndFindAny.java
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphStreamByMatchAndFindAny.java
@@ -29,6 +29,7 @@ import org.openjdk.jmh.runner.Runner;
import java.util.List;
import java.util.Optional;
+import java.util.Random;
import java.util.function.Function;
import java.util.stream.Stream;
@@ -48,18 +49,18 @@ public class TestGraphStreamByMatchAndFindAny {
@Param({
"GraphMem (current)",
+ "GraphMem2Fast (current)",
+ "GraphMem2Legacy (current)",
+ "GraphMem2Roaring (current)",
"GraphMem (Jena 4.8.0)",
})
public String param1_GraphImplementation;
-
+ java.util.function.Function<String, Object> graphStream;
private Graph sutCurrent;
private org.apache.shadedJena480.graph.Graph sut480;
-
private List<Triple> triplesToFindCurrent;
private List<org.apache.shadedJena480.graph.Triple> triplesToFind480;
- java.util.function.Function<String, Object> graphStream;
-
@Benchmark
public Object graphStreamS__() {
return graphStream.apply("S__");
@@ -156,29 +157,33 @@ public class TestGraphStreamByMatchAndFindAny {
public void setupTrial() throws Exception {
Context trialContext = new Context(param1_GraphImplementation);
switch (trialContext.getJenaVersion()) {
- case CURRENT:
- {
- this.sutCurrent = Releases.current.createGraph(trialContext.getGraphClass());
- this.graphStream = this::graphStreamByMatchAndFindAnyCurrent;
-
- var triples = Releases.current.readTriples(param0_GraphUri);
- triples.forEach(this.sutCurrent::add);
-
- /*clone the triples because they should not be the same objects*/
- this.triplesToFindCurrent = Releases.current.cloneTriples(triples);
- }
- break;
- case JENA_4_8_0:
- {
- this.sut480 = Releases.v480.createGraph(trialContext.getGraphClass());
- this.graphStream = this::graphFindByMatcheAndFindAny480;
-
- var triples = Releases.v480.readTriples(param0_GraphUri);
- triples.forEach(this.sut480::add);
-
- /*clone the triples because they should not be the same objects*/
- this.triplesToFind480 = Releases.v480.cloneTriples(triples);
- }
+ case CURRENT: {
+ this.sutCurrent = Releases.current.createGraph(trialContext.getGraphClass());
+ this.graphStream = this::graphStreamByMatchAndFindAnyCurrent;
+
+ var triples = Releases.current.readTriples(param0_GraphUri);
+ triples.forEach(this.sutCurrent::add);
+
+ /*clone the triples because they should not be the same objects*/
+ this.triplesToFindCurrent = Releases.current.cloneTriples(triples);
+ /* Shuffle is import because the order might play a role. We want to test the performance of the
+ contains method regardless of the order */
+ java.util.Collections.shuffle(this.triplesToFindCurrent, new Random(4721));
+ }
+ break;
+ case JENA_4_8_0: {
+ this.sut480 = Releases.v480.createGraph(trialContext.getGraphClass());
+ this.graphStream = this::graphFindByMatcheAndFindAny480;
+
+ var triples = Releases.v480.readTriples(param0_GraphUri);
+ triples.forEach(this.sut480::add);
+
+ /*clone the triples because they should not be the same objects*/
+ this.triplesToFind480 = Releases.v480.cloneTriples(triples);
+ /* Shuffle is import because the order might play a role. We want to test the performance of the
+ contains method regardless of the order */
+ java.util.Collections.shuffle(this.triplesToFind480, new Random(4721));
+ }
break;
default:
throw new IllegalArgumentException("Unknown Jena version: " + trialContext.getJenaVersion());
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Context.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Context.java
index 7e288e7e32..43c6df65e2 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Context.java
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Context.java
@@ -24,28 +24,27 @@ import org.apache.jena.JenaVersion;
*/
public class Context {
- public GraphClass getGraphClass() {
- return graphClass;
- }
-
- public JenaVersion getJenaVersion() {
- return jenaVersion;
- }
-
- public enum GraphClass {
- GraphMem
- }
-
private final GraphClass graphClass;
private final JenaVersion jenaVersion;
-
public Context(String graphImplementation) {
switch (graphImplementation) {
case "GraphMem (current)":
this.graphClass = GraphClass.GraphMem;
this.jenaVersion = JenaVersion.CURRENT;
break;
+ case "GraphMem2Fast (current)":
+ this.graphClass = GraphClass.GraphMem2Fast;
+ this.jenaVersion = JenaVersion.CURRENT;
+ break;
+ case "GraphMem2Legacy (current)":
+ this.graphClass = GraphClass.GraphMem2Legacy;
+ this.jenaVersion = JenaVersion.CURRENT;
+ break;
+ case "GraphMem2Roaring (current)":
+ this.graphClass = GraphClass.GraphMem2Roaring;
+ this.jenaVersion = JenaVersion.CURRENT;
+ break;
case "GraphMem (Jena 4.8.0)":
this.graphClass = GraphClass.GraphMem;
this.jenaVersion = JenaVersion.JENA_4_8_0;
@@ -55,5 +54,21 @@ public class Context {
}
}
+ public GraphClass getGraphClass() {
+ return graphClass;
+ }
+
+ public JenaVersion getJenaVersion() {
+ return jenaVersion;
+ }
+
+
+ public enum GraphClass {
+ GraphMem,
+ GraphMem2Fast,
+ GraphMem2Legacy,
+ GraphMem2Roaring,
+ }
+
}
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/GraphTripleNodeHelper480.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/GraphTripleNodeHelper480.java
index 1e528b557e..b0162fc87a 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/GraphTripleNodeHelper480.java
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/GraphTripleNodeHelper480.java
@@ -26,17 +26,16 @@ import org.apache.shadedJena480.riot.RDFDataMgr;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
public class GraphTripleNodeHelper480 implements GraphTripleNodeHelper<Graph, Triple, Node> {
@Override
public Graph createGraph(Context.GraphClass graphClass) {
- switch (graphClass) {
- case GraphMem:
- return new GraphMem();
- default:
- throw new IllegalArgumentException("Unknown graph class: " + graphClass);
+ if (Objects.requireNonNull(graphClass) == Context.GraphClass.GraphMem) {
+ return new GraphMem();
}
+ throw new IllegalArgumentException("Unknown graph class: " + graphClass);
}
@Override
@@ -67,13 +66,13 @@ public class GraphTripleNodeHelper480 implements GraphTripleNodeHelper<Graph, Tr
@Override
public Node cloneNode(Node node) {
- if(node.isLiteral()) {
+ if (node.isLiteral()) {
return NodeFactory.createLiteralByValue(node.getLiteralLexicalForm(), node.getLiteralLanguage(), node.getLiteralDatatype());
}
- if(node.isURI()) {
+ if (node.isURI()) {
return NodeFactory.createURI(node.getURI());
}
- if(node.isBlank()) {
+ if (node.isBlank()) {
return NodeFactory.createBlankNode(node.getBlankNodeLabel());
}
throw new IllegalArgumentException("Only literals, URIs and blank nodes are supported");
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/GraphTripleNodeHelperCurrent.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/GraphTripleNodeHelperCurrent.java
index 88f5aa4d0e..532a583571 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/GraphTripleNodeHelperCurrent.java
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/GraphTripleNodeHelperCurrent.java
@@ -22,6 +22,9 @@ import org.apache.jena.graph.Node;
import org.apache.jena.graph.NodeFactory;
import org.apache.jena.graph.Triple;
import org.apache.jena.mem.GraphMem;
+import org.apache.jena.mem2.GraphMem2Fast;
+import org.apache.jena.mem2.GraphMem2Legacy;
+import org.apache.jena.mem2.GraphMem2Roaring;
import org.apache.jena.riot.RDFDataMgr;
import java.util.ArrayList;
@@ -34,6 +37,12 @@ public class GraphTripleNodeHelperCurrent implements GraphTripleNodeHelper<Graph
switch (graphClass) {
case GraphMem:
return new GraphMem();
+ case GraphMem2Fast:
+ return new GraphMem2Fast();
+ case GraphMem2Legacy:
+ return new GraphMem2Legacy();
+ case GraphMem2Roaring:
+ return new GraphMem2Roaring();
default:
throw new IllegalArgumentException("Unknown graph class: " + graphClass);
}
@@ -67,13 +76,13 @@ public class GraphTripleNodeHelperCurrent implements GraphTripleNodeHelper<Graph
@Override
public Node cloneNode(Node node) {
- if(node.isLiteral()) {
+ if (node.isLiteral()) {
return NodeFactory.createLiteralByValue(node.getLiteralLexicalForm(), node.getLiteralLanguage(), node.getLiteralDatatype());
}
- if(node.isURI()) {
+ if (node.isURI()) {
return NodeFactory.createURI(node.getURI());
}
- if(node.isBlank()) {
+ if (node.isBlank()) {
return NodeFactory.createBlankNode(node.getBlankNodeLabel());
}
throw new IllegalArgumentException("Only literals, URIs and blank nodes are supported");
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/JMHDefaultOptions.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/JMHDefaultOptions.java
index 1f88776775..60e41d4103 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/JMHDefaultOptions.java
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/JMHDefaultOptions.java
@@ -37,7 +37,7 @@ public class JMHDefaultOptions {
// You can be more specific if you'd like to run only one benchmark per test.
.include(c.getName())
// Set the following options as needed
- .mode (Mode.AverageTime)
+ .mode(Mode.AverageTime)
.timeUnit(TimeUnit.SECONDS)
.warmupTime(TimeValue.NONE)
.warmupIterations(5)
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java
index f601955053..a03623eae8 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java
@@ -26,7 +26,7 @@ import org.apache.jena.graph.Triple;
*/
public class Releases {
- public static GraphTripleNodeHelper<Graph, Triple, Node> current = new GraphTripleNodeHelperCurrent();
- public static GraphTripleNodeHelper<org.apache.shadedJena480.graph.Graph, org.apache.shadedJena480.graph.Triple, org.apache.shadedJena480.graph.Node> v480 = new GraphTripleNodeHelper480();
+ public static GraphTripleNodeHelper<Graph, Triple, Node> current = new GraphTripleNodeHelperCurrent();
+ public static GraphTripleNodeHelper<org.apache.shadedJena480.graph.Graph, org.apache.shadedJena480.graph.Triple, org.apache.shadedJena480.graph.Node> v480 = new GraphTripleNodeHelper480();
}
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/JMHDefaultOptions.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/JMHDefaultOptions.java
similarity index 96%
copy from jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/JMHDefaultOptions.java
copy to jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/JMHDefaultOptions.java
index 1f88776775..adb152256b 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/JMHDefaultOptions.java
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/JMHDefaultOptions.java
@@ -15,7 +15,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.jena.mem.graph.helper;
+package org.apache.jena.mem2.helper;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.results.format.ResultFormatType;
@@ -37,7 +37,7 @@ public class JMHDefaultOptions {
// You can be more specific if you'd like to run only one benchmark per test.
.include(c.getName())
// Set the following options as needed
- .mode (Mode.AverageTime)
+ .mode(Mode.AverageTime)
.timeUnit(TimeUnit.SECONDS)
.warmupTime(TimeValue.NONE)
.warmupIterations(5)
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/map/node/FastHashNodeMap.java
similarity index 60%
copy from jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java
copy to jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/map/node/FastHashNodeMap.java
index f601955053..4557ca07a5 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/map/node/FastHashNodeMap.java
@@ -15,18 +15,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.jena.mem.graph.helper;
+package org.apache.jena.mem2.map.node;
-import org.apache.jena.graph.Graph;
import org.apache.jena.graph.Node;
-import org.apache.jena.graph.Triple;
+import org.apache.jena.mem2.collection.FastHashMap;
-/**
- * Proviced the release specific access to the helper classes for working with graphs, triples and nodes.
- */
-public class Releases {
+public class FastHashNodeMap extends FastHashMap<Node, Object> {
+
+ public FastHashNodeMap() {
+ super();
+ }
- public static GraphTripleNodeHelper<Graph, Triple, Node> current = new GraphTripleNodeHelperCurrent();
- public static GraphTripleNodeHelper<org.apache.shadedJena480.graph.Graph, org.apache.shadedJena480.graph.Triple, org.apache.shadedJena480.graph.Node> v480 = new GraphTripleNodeHelper480();
+ @Override
+ protected Object[] newValuesArray(int size) {
+ return new Object[size];
+ }
+ @Override
+ protected Node[] newKeysArray(int size) {
+ return new Node[size];
+ }
}
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/map/node/HashCommonNodeMap.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/map/node/HashCommonNodeMap.java
new file mode 100644
index 0000000000..2b6ec52abb
--- /dev/null
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/map/node/HashCommonNodeMap.java
@@ -0,0 +1,59 @@
+/*
+ * 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.mem2.map.node;
+
+import org.apache.jena.graph.Node;
+import org.apache.jena.mem2.collection.HashCommonMap;
+
+public class HashCommonNodeMap extends HashCommonMap<Node, Object> {
+
+ /**
+ * Initialise this hashed thingy to have <code>initialCapacity</code> as its
+ * capacity and the corresponding threshold. All the key elements start out
+ * null.
+ *
+ * @param initialCapacity
+ */
+ public HashCommonNodeMap(int initialCapacity) {
+ super(initialCapacity);
+ }
+
+ /**
+ * Initialise this hashed thingy to have <code>10</code> as its
+ * capacity and the corresponding threshold. All the key elements start out
+ * null.
+ */
+ public HashCommonNodeMap() {
+ this(10);
+ }
+
+ @Override
+ protected Node[] newKeysArray(int size) {
+ return new Node[size];
+ }
+
+ @Override
+ public void clear() {
+ super.clear(10);
+ }
+
+ @Override
+ protected Object[] newValuesArray(int size) {
+ return new Object[size];
+ }
+}
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/map/node/TestMapAdd.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/map/node/TestMapAdd.java
new file mode 100644
index 0000000000..d30b7871b8
--- /dev/null
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/map/node/TestMapAdd.java
@@ -0,0 +1,112 @@
+/*
+ * 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.mem2.map.node;
+
+import org.apache.jena.graph.Node;
+import org.apache.jena.graph.Triple;
+import org.apache.jena.mem.graph.helper.Releases;
+import org.apache.jena.mem2.helper.JMHDefaultOptions;
+import org.junit.Assert;
+import org.junit.Test;
+import org.openjdk.jmh.annotations.*;
+import org.openjdk.jmh.runner.Runner;
+
+import java.util.HashMap;
+import java.util.List;
+
+
+@State(Scope.Benchmark)
+public class TestMapAdd {
+
+ @Param({
+ "../testing/cheeses-0.1.ttl",
+ "../testing/pizza.owl.rdf",
+ "../testing/BSBM/bsbm-1m.nt.gz",
+ })
+ public String param0_GraphUri;
+
+ @Param({
+ "HashMap",
+ "HashCommonNodeSet",
+ "FastHashNodeSet"
+ })
+ public String param1_SetImplementation;
+ java.util.function.Function<Triple.Field, Object> addToSet;
+ private List<Triple> triples;
+
+ @Benchmark
+ public Object addSubjectsToSet() {
+ return addToSet.apply(Triple.Field.fieldSubject);
+ }
+
+ @Benchmark
+ public Object addPredicatesToSet() {
+ return addToSet.apply(Triple.Field.fieldPredicate);
+ }
+
+ @Benchmark
+ public Object addObjectsToSet() {
+ return addToSet.apply(Triple.Field.fieldObject);
+ }
+
+ private Object addToHashMap(Triple.Field field) {
+ var sut = new HashMap<Node, Object>();
+ triples.forEach(t -> sut.put(field.getField(t), null));
+ return sut;
+ }
+
+ private Object addToFastHashNodeMap(Triple.Field field) {
+ var sut = new FastHashNodeMap();
+ triples.forEach(t -> sut.tryPut(field.getField(t), null));
+ return sut;
+ }
+
+ private Object addToHashCommonNodeMap(Triple.Field field) {
+ var sut = new HashCommonNodeMap();
+ triples.forEach(t -> sut.tryPut(field.getField(t), null));
+ return sut;
+ }
+
+ @Setup(Level.Trial)
+ public void setupTrial() throws Exception {
+ triples = Releases.current.readTriples(param0_GraphUri);
+ switch (param1_SetImplementation) {
+ case "HashMap":
+ this.addToSet = this::addToHashMap;
+ break;
+ case "HashCommonNodeSet":
+ this.addToSet = this::addToHashCommonNodeMap;
+ break;
+ case "FastHashNodeSet":
+ this.addToSet = this::addToFastHashNodeMap;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown set implementation: " + param1_SetImplementation);
+ }
+ }
+
+ @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-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/map/node/TestMapContains.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/map/node/TestMapContains.java
new file mode 100644
index 0000000000..87cc71f7d5
--- /dev/null
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/map/node/TestMapContains.java
@@ -0,0 +1,218 @@
+/*
+ * 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.mem2.map.node;
+
+import org.apache.jena.graph.Node;
+import org.apache.jena.graph.Triple;
+import org.apache.jena.mem.graph.helper.Releases;
+import org.apache.jena.mem2.helper.JMHDefaultOptions;
+import org.junit.Assert;
+import org.junit.Test;
+import org.openjdk.jmh.annotations.*;
+import org.openjdk.jmh.runner.Runner;
+
+import java.util.HashMap;
+import java.util.List;
+
+
+@State(Scope.Benchmark)
+public class TestMapContains {
+
+ @Param({
+ "../testing/cheeses-0.1.ttl",
+ "../testing/pizza.owl.rdf",
+ "../testing/BSBM/bsbm-1m.nt.gz",
+ })
+ public String param0_GraphUri;
+
+ @Param({
+ "HashSet",
+ "HashCommonNodeSet",
+ "FastHashNodeSet"
+ })
+ public String param1_SetImplementation;
+ java.util.function.Supplier<Boolean> setContainsSubjects;
+ java.util.function.Supplier<Boolean> setContainsPredicates;
+ java.util.function.Supplier<Boolean> setContainsObjects;
+ private List<Triple> triplesToFind;
+ private HashMap<Node, Object> subjectHashMap;
+ private HashMap<Node, Object> predicateHashMap;
+ private HashMap<Node, Object> objectHashMap;
+ private HashCommonNodeMap subjectHashCommonNodeMap;
+ private HashCommonNodeMap predicateHashCommonNodeMap;
+ private HashCommonNodeMap objectHashCommonNodeMap;
+ private FastHashNodeMap subjectFastHashNodeSet;
+ private FastHashNodeMap predicateFastHashNodeSet;
+ private FastHashNodeMap objectFastHashNodeSet;
+
+
+ @Benchmark
+ public boolean setContainsSubjects() {
+ return setContainsSubjects.get();
+ }
+
+ @Benchmark
+ public boolean setContainsPredicates() {
+ return setContainsPredicates.get();
+ }
+
+ @Benchmark
+ public boolean setContainsObjects() {
+ return setContainsObjects.get();
+ }
+
+ private boolean hashMapContainsSubjects() {
+ var found = false;
+ for (var t : triplesToFind) {
+ found = subjectHashMap.containsKey(t.getSubject());
+ Assert.assertTrue(found);
+ }
+ return found;
+ }
+
+ private boolean hashMapContainsPredicates() {
+ var found = false;
+ for (var t : triplesToFind) {
+ found = predicateHashMap.containsKey(t.getPredicate());
+ Assert.assertTrue(found);
+ }
+ return found;
+ }
+
+ private boolean hashMapContainsObjects() {
+ var found = false;
+ for (var t : triplesToFind) {
+ found = objectHashMap.containsKey(t.getObject());
+ Assert.assertTrue(found);
+ }
+ return found;
+ }
+
+
+ private boolean HashCommonNodeMapContainsSubjects() {
+ var found = false;
+ for (var t : triplesToFind) {
+ found = subjectHashCommonNodeMap.containsKey(t.getSubject());
+ Assert.assertTrue(found);
+ }
+ return found;
+ }
+
+ private boolean HashCommonNodeMapContainsPredicates() {
+ var found = false;
+ for (var t : triplesToFind) {
+ found = predicateHashCommonNodeMap.containsKey(t.getPredicate());
+ Assert.assertTrue(found);
+ }
+ return found;
+ }
+
+ private boolean HashCommonNodeMapContainsObjects() {
+ var found = false;
+ for (var t : triplesToFind) {
+ found = objectHashCommonNodeMap.containsKey(t.getObject());
+ Assert.assertTrue(found);
+ }
+ return found;
+ }
+
+ private boolean FastHashNodeMapContainsSubjects() {
+ var found = false;
+ for (var t : triplesToFind) {
+ found = subjectFastHashNodeSet.containsKey(t.getSubject());
+ Assert.assertTrue(found);
+ }
+ return found;
+ }
+
+ private boolean FastHashNodeMapContainsPredicates() {
+ var found = false;
+ for (var t : triplesToFind) {
+ found = predicateFastHashNodeSet.containsKey(t.getPredicate());
+ Assert.assertTrue(found);
+ }
+ return found;
+ }
+
+ private boolean FastHashNodeMapContainsObjects() {
+ var found = false;
+ for (var t : triplesToFind) {
+ found = objectFastHashNodeSet.containsKey(t.getObject());
+ Assert.assertTrue(found);
+ }
+ return found;
+ }
+
+ @Setup(Level.Trial)
+ public void setupTrial() throws Exception {
+ var triples = Releases.current.readTriples(param0_GraphUri);
+ this.triplesToFind = Releases.current.cloneTriples(triples);
+ switch (param1_SetImplementation) {
+ case "HashSet":
+ this.subjectHashMap = new HashMap<>();
+ this.predicateHashMap = new HashMap<>();
+ this.objectHashMap = new HashMap<>();
+ triples.forEach(t -> {
+ subjectHashMap.put(t.getSubject(), null);
+ predicateHashMap.put(t.getPredicate(), null);
+ objectHashMap.put(t.getObject(), null);
+ });
+ this.setContainsSubjects = this::hashMapContainsSubjects;
+ this.setContainsPredicates = this::hashMapContainsPredicates;
+ this.setContainsObjects = this::hashMapContainsObjects;
+ break;
+ case "HashCommonNodeSet":
+ this.subjectHashCommonNodeMap = new HashCommonNodeMap();
+ this.predicateHashCommonNodeMap = new HashCommonNodeMap();
+ this.objectHashCommonNodeMap = new HashCommonNodeMap();
+ triples.forEach(t -> {
+ subjectHashCommonNodeMap.tryPut(t.getSubject(), null);
+ predicateHashCommonNodeMap.tryPut(t.getPredicate(), null);
+ objectHashCommonNodeMap.tryPut(t.getObject(), null);
+ });
+ this.setContainsSubjects = this::HashCommonNodeMapContainsSubjects;
+ this.setContainsPredicates = this::HashCommonNodeMapContainsPredicates;
+ this.setContainsObjects = this::HashCommonNodeMapContainsObjects;
+ break;
+ case "FastHashNodeSet":
+ this.subjectFastHashNodeSet = new FastHashNodeMap();
+ this.predicateFastHashNodeSet = new FastHashNodeMap();
+ this.objectFastHashNodeSet = new FastHashNodeMap();
+ triples.forEach(t -> {
+ subjectFastHashNodeSet.tryPut(t.getSubject(), null);
+ predicateFastHashNodeSet.tryPut(t.getPredicate(), null);
+ objectFastHashNodeSet.tryPut(t.getObject(), null);
+ });
+ this.setContainsSubjects = this::FastHashNodeMapContainsSubjects;
+ this.setContainsPredicates = this::FastHashNodeMapContainsPredicates;
+ this.setContainsObjects = this::FastHashNodeMapContainsObjects;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown set implementation: " + param1_SetImplementation);
+ }
+ }
+
+ @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-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/set/triple/FastHashTripleSet.java
similarity index 60%
copy from jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java
copy to jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/set/triple/FastHashTripleSet.java
index f601955053..991a7bcc80 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/set/triple/FastHashTripleSet.java
@@ -15,18 +15,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.jena.mem.graph.helper;
+package org.apache.jena.mem2.set.triple;
-import org.apache.jena.graph.Graph;
-import org.apache.jena.graph.Node;
import org.apache.jena.graph.Triple;
+import org.apache.jena.mem2.collection.FastHashSet;
-/**
- * Proviced the release specific access to the helper classes for working with graphs, triples and nodes.
- */
-public class Releases {
+public class FastHashTripleSet extends FastHashSet<Triple> {
+
+ public FastHashTripleSet() {
+ super();
+ }
+
+ public FastHashTripleSet(int initialCapacity) {
+ super(initialCapacity);
+ }
- public static GraphTripleNodeHelper<Graph, Triple, Node> current = new GraphTripleNodeHelperCurrent();
- public static GraphTripleNodeHelper<org.apache.shadedJena480.graph.Graph, org.apache.shadedJena480.graph.Triple, org.apache.shadedJena480.graph.Node> v480 = new GraphTripleNodeHelper480();
+ @Override
+ protected Triple[] newKeysArray(int size) {
+ return new Triple[size];
+ }
}
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/set/triple/HashCommonTripleSet.java
similarity index 60%
copy from jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java
copy to jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/set/triple/HashCommonTripleSet.java
index f601955053..98eab2aa0b 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/set/triple/HashCommonTripleSet.java
@@ -15,18 +15,27 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.jena.mem.graph.helper;
+package org.apache.jena.mem2.set.triple;
-import org.apache.jena.graph.Graph;
-import org.apache.jena.graph.Node;
import org.apache.jena.graph.Triple;
+import org.apache.jena.mem2.collection.HashCommonSet;
-/**
- * Proviced the release specific access to the helper classes for working with graphs, triples and nodes.
- */
-public class Releases {
+public class HashCommonTripleSet extends HashCommonSet<Triple> {
+ public HashCommonTripleSet() {
+ super(10);
+ }
+
+ public HashCommonTripleSet(int initialCapacity) {
+ super(initialCapacity);
+ }
- public static GraphTripleNodeHelper<Graph, Triple, Node> current = new GraphTripleNodeHelperCurrent();
- public static GraphTripleNodeHelper<org.apache.shadedJena480.graph.Graph, org.apache.shadedJena480.graph.Triple, org.apache.shadedJena480.graph.Node> v480 = new GraphTripleNodeHelper480();
+ @Override
+ public void clear() {
+ super.clear(10);
+ }
+ @Override
+ protected Triple[] newKeysArray(int size) {
+ return new Triple[size];
+ }
}
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphAdd.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/set/triple/TestSetAdd.java
similarity index 51%
copy from jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphAdd.java
copy to jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/set/triple/TestSetAdd.java
index e18f6f7747..c7990e1d1b 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphAdd.java
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/set/triple/TestSetAdd.java
@@ -16,22 +16,22 @@
* limitations under the License.
*/
-package org.apache.jena.mem.graph;
+package org.apache.jena.mem2.set.triple;
import org.apache.jena.graph.Triple;
-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.helper.JMHDefaultOptions;
import org.junit.Assert;
import org.junit.Test;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
+import java.util.HashSet;
import java.util.List;
@State(Scope.Benchmark)
-public class TestGraphAdd {
+public class TestSetAdd {
@Param({
"../testing/cheeses-0.1.ttl",
@@ -41,52 +41,55 @@ public class TestGraphAdd {
public String param0_GraphUri;
@Param({
- "GraphMem (current)",
- "GraphMem (Jena 4.8.0)",
+ "HashSet",
+ "HashCommonTripleSet",
+ "FastHashTripleSet",
})
- public String param1_GraphImplementation;
-
- private Context trialContext;
-
- private List<Triple> triplesCurrent;
- private List<org.apache.shadedJena480.graph.Triple> triples480;
-
- java.util.function.Supplier<Object> graphAdd;
+ public String param1_SetImplementation;
+ java.util.function.Supplier<Object> addToSet;
+ private List<Triple> triples;
@Benchmark
- public Object graphAdd() {
- return graphAdd.get();
+ public Object addToSet() {
+ return addToSet.get();
}
- private Object graphAddCurrent() {
- var sutCurrent = Releases.current.createGraph(trialContext.getGraphClass());
- triplesCurrent.forEach(sutCurrent::add);
- Assert.assertEquals(triplesCurrent.size(), sutCurrent.size());
- return sutCurrent;
+ private Object addToHashSet() {
+ var sut = new HashSet<Triple>();
+ triples.forEach(sut::add);
+ Assert.assertEquals(triples.size(), sut.size());
+ return sut;
}
- private Object graphAdd480() {
- var sut480 = Releases.v480.createGraph(trialContext.getGraphClass());
- triples480.forEach(sut480::add);
- Assert.assertEquals(triples480.size(), sut480.size());
- return sut480;
+ private Object addToHashCommonTripleSet() {
+ var sut = new HashCommonTripleSet();
+ triples.forEach(sut::tryAdd);
+ Assert.assertEquals(triples.size(), sut.size());
+ return sut;
}
+ private Object addToFastHashTripleSet() {
+ var sut = new FastHashTripleSet();
+ triples.forEach(sut::tryAdd);
+ Assert.assertEquals(triples.size(), sut.size());
+ return sut;
+ }
@Setup(Level.Trial)
public void setupTrial() throws Exception {
- this.trialContext = new Context(param1_GraphImplementation);
- switch (this.trialContext.getJenaVersion()) {
- case CURRENT:
- triplesCurrent = Releases.current.readTriples(param0_GraphUri);
- this.graphAdd = this::graphAddCurrent;
+ triples = Releases.current.readTriples(param0_GraphUri);
+ switch (param1_SetImplementation) {
+ case "HashSet":
+ this.addToSet = this::addToHashSet;
+ break;
+ case "HashCommonTripleSet":
+ this.addToSet = this::addToHashCommonTripleSet;
break;
- case JENA_4_8_0:
- triples480 = Releases.v480.readTriples(param0_GraphUri);
- this.graphAdd = this::graphAdd480;
+ case "FastHashTripleSet":
+ this.addToSet = this::addToFastHashTripleSet;
break;
default:
- throw new IllegalArgumentException("Unknown Jena version: " + this.trialContext.getJenaVersion());
+ throw new IllegalArgumentException("Unknown set implementation: " + param1_SetImplementation);
}
}
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/set/triple/TestSetContains.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/set/triple/TestSetContains.java
new file mode 100644
index 0000000000..c3e6cc7e5e
--- /dev/null
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/set/triple/TestSetContains.java
@@ -0,0 +1,119 @@
+/*
+ * 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.mem2.set.triple;
+
+import org.apache.jena.graph.Triple;
+import org.apache.jena.mem.graph.helper.Releases;
+import org.apache.jena.mem2.helper.JMHDefaultOptions;
+import org.junit.Assert;
+import org.junit.Test;
+import org.openjdk.jmh.annotations.*;
+import org.openjdk.jmh.runner.Runner;
+
+import java.util.HashSet;
+import java.util.List;
+
+
+@State(Scope.Benchmark)
+public class TestSetContains {
+
+ @Param({
+ "../testing/cheeses-0.1.ttl",
+ "../testing/pizza.owl.rdf",
+ "../testing/BSBM/bsbm-1m.nt.gz",
+ })
+ public String param0_GraphUri;
+
+ @Param({
+ "HashSet",
+ "HashCommonTripleSet",
+ "FastHashTripleSet"
+ })
+ public String param1_SetImplementation;
+ java.util.function.Supplier<Boolean> setContains;
+ private List<Triple> triplesToFind;
+ private HashSet<Triple> tripleHashSet;
+ private HashCommonTripleSet hashCommonTripleSet;
+ private FastHashTripleSet fastHashTripleSet;
+
+ @Benchmark
+ public boolean setContains() {
+ return setContains.get();
+ }
+
+ private boolean hashSetContains() {
+ var found = false;
+ for (var t : triplesToFind) {
+ found = tripleHashSet.contains(t);
+ Assert.assertTrue(found);
+ }
+ return found;
+ }
+
+ private boolean hashCommonTripleSetContains() {
+ var found = false;
+ for (var t : triplesToFind) {
+ found = hashCommonTripleSet.containsKey(t);
+ Assert.assertTrue(found);
+ }
+ return found;
+ }
+
+ private boolean fastHashTripleSetContains() {
+ var found = false;
+ for (var t : triplesToFind) {
+ found = fastHashTripleSet.containsKey(t);
+ Assert.assertTrue(found);
+ }
+ return found;
+ }
+
+ @Setup(Level.Trial)
+ public void setupTrial() throws Exception {
+ var triples = Releases.current.readTriples(param0_GraphUri);
+ this.triplesToFind = Releases.current.cloneTriples(triples);
+ switch (param1_SetImplementation) {
+ case "HashSet":
+ this.tripleHashSet = new HashSet<>(triples.size());
+ triples.forEach(tripleHashSet::add);
+ this.setContains = this::hashSetContains;
+ break;
+ case "HashCommonTripleSet":
+ this.hashCommonTripleSet = new HashCommonTripleSet(triples.size());
+ triples.forEach(hashCommonTripleSet::addUnchecked);
+ this.setContains = this::hashCommonTripleSetContains;
+ break;
+ case "FastHashTripleSet":
+ this.fastHashTripleSet = new FastHashTripleSet(triples.size());
+ triples.forEach(fastHashTripleSet::addUnchecked);
+ this.setContains = this::fastHashTripleSetContains;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown set implementation: " + param1_SetImplementation);
+ }
+ }
+
+ @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-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/set/triple/TestSetIterate.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/set/triple/TestSetIterate.java
new file mode 100644
index 0000000000..5eef31e505
--- /dev/null
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/set/triple/TestSetIterate.java
@@ -0,0 +1,125 @@
+/*
+ * 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.mem2.set.triple;
+
+import org.apache.jena.atlas.iterator.ActionCount;
+import org.apache.jena.graph.Triple;
+import org.apache.jena.mem.graph.helper.Releases;
+import org.apache.jena.mem2.helper.JMHDefaultOptions;
+import org.junit.Assert;
+import org.junit.Test;
+import org.openjdk.jmh.annotations.*;
+import org.openjdk.jmh.runner.Runner;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+
+@State(Scope.Benchmark)
+public class TestSetIterate {
+
+ @Param({
+ "../testing/cheeses-0.1.ttl",
+ "../testing/pizza.owl.rdf",
+ "../testing/BSBM/bsbm-1m.nt.gz",
+ })
+ public String param0_GraphUri;
+
+ @Param({
+ "HashSet",
+ "HashCommonTripleSet",
+ "FastHashTripleSet"
+ })
+ public String param1_SetImplementation;
+ java.util.function.Supplier<Iterator<Triple>> getIterator;
+ private List<Triple> triples;
+ private HashSet<Triple> hashSet;
+ private HashCommonTripleSet hashCommonTripleSet;
+ private FastHashTripleSet fastHashTripleSet;
+
+ @Benchmark
+ public Object foreachRemaining() {
+ var it = getIterator.get();
+ ActionCount<Triple> counter = new ActionCount<>();
+ it.forEachRemaining(counter);
+ assertEquals(triples.size(), counter.getCount());
+ return counter;
+ }
+
+ @Benchmark
+ public Object hasNextNext() {
+ var it = getIterator.get();
+ int i = 0;
+ while (it.hasNext()) {
+ it.next();
+ i++;
+ }
+ assertEquals(triples.size(), i);
+ return i;
+ }
+
+ private Iterator<Triple> getIteratorFromHashSet() {
+ return hashSet.iterator();
+ }
+
+ private Iterator<Triple> getIteratorFromHashCommonTripleSet() {
+ return hashCommonTripleSet.keyIterator();
+ }
+
+ private Iterator<Triple> getIteratorFromFastHashTripleSet() {
+ return fastHashTripleSet.keyIterator();
+ }
+
+
+ @Setup(Level.Trial)
+ public void setupTrial() throws Exception {
+ this.triples = Releases.current.readTriples(param0_GraphUri);
+ switch (param1_SetImplementation) {
+ case "HashSet":
+ this.hashSet = new HashSet<>(triples.size());
+ triples.forEach(hashSet::add);
+ this.getIterator = this::getIteratorFromHashSet;
+ break;
+ case "HashCommonTripleSet":
+ this.hashCommonTripleSet = new HashCommonTripleSet(triples.size());
+ triples.forEach(hashCommonTripleSet::addUnchecked);
+ this.getIterator = this::getIteratorFromHashCommonTripleSet;
+ break;
+ case "FastHashTripleSet":
+ this.fastHashTripleSet = new FastHashTripleSet(triples.size());
+ triples.forEach(fastHashTripleSet::addUnchecked);
+ this.getIterator = this::getIteratorFromFastHashTripleSet;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown set implementation: " + param1_SetImplementation);
+ }
+ }
+
+ @Test
+ public void benchmark() throws Exception {
+ var opt = JMHDefaultOptions.getDefaults(this.getClass())
+ .build();
+ var results = new Runner(opt).run();
+ Assert.assertNotNull(results);
+ }
+
+}
\ No newline at end of file
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphMemoryConsumption.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/set/triple/TestSetMemoryConsumption.java
similarity index 55%
copy from jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphMemoryConsumption.java
copy to jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/set/triple/TestSetMemoryConsumption.java
index dd59e405f4..ff04809714 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/TestGraphMemoryConsumption.java
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/set/triple/TestSetMemoryConsumption.java
@@ -16,11 +16,10 @@
* limitations under the License.
*/
-package org.apache.jena.mem.graph;
+package org.apache.jena.mem2.set.triple;
import org.apache.commons.lang3.time.StopWatch;
import org.apache.jena.graph.Triple;
-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.junit.Assert;
@@ -29,10 +28,11 @@ import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import java.math.BigDecimal;
+import java.util.HashSet;
import java.util.List;
@State(Scope.Benchmark)
-public class TestGraphMemoryConsumption {
+public class TestSetMemoryConsumption {
@Param({
"../testing/cheeses-0.1.ttl",
@@ -42,76 +42,78 @@ public class TestGraphMemoryConsumption {
public String param0_GraphUri;
@Param({
- "GraphMem (current)",
- "GraphMem (Jena 4.8.0)",
+ "HashSet",
+ "HashCommonTripleSet",
+ "FastHashTripleSet"
})
- public String param1_GraphImplementation;
- private Context trialContext;
+ public String param1_SetImplementation;
+ java.util.function.Supplier<Object> fillSet;
+ private List<Triple> triples;
- private List<Triple> allTriplesCurrent;
- private List<org.apache.shadedJena480.graph.Triple> allTriples480;
-
- java.util.function.Supplier<Object> graphFill;
-
- @Benchmark
- public Object graphFill() {
- return graphFill.get();
+ /**
+ * This method is used to get the memory consumption of the current JVM.
+ *
+ * @return the memory consumption in MB
+ */
+ private static double runGcAndGetUsedMemoryInMB() {
+ System.runFinalization();
+ System.gc();
+ Runtime.getRuntime().runFinalization();
+ Runtime.getRuntime().gc();
+ return BigDecimal.valueOf(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()).divide(BigDecimal.valueOf(1024L)).divide(BigDecimal.valueOf(1024L)).doubleValue();
}
- private Object graphFillCurrent() {
+ @Benchmark
+ public Object fillSet() {
var memoryBefore = runGcAndGetUsedMemoryInMB();
var stopwatch = StopWatch.createStarted();
- var sut = Releases.current.createGraph(trialContext.getGraphClass());
- allTriplesCurrent.forEach(sut::add);
+ var sut = fillSet.get();
stopwatch.stop();
var memoryAfter = runGcAndGetUsedMemoryInMB();
- System.out.println(String.format("graphs: %d time to fill graphs: %s additional memory: %5.3f MB",
- sut.size(),
+ System.out.printf("graphs: %d time to fill graphs: %s additional memory: %5.3f MB%n",
+ triples.size(),
stopwatch.formatTime(),
- (memoryAfter - memoryBefore)));
+ (memoryAfter - memoryBefore));
return sut;
}
- private Object graphFill480() {
- var memoryBefore = runGcAndGetUsedMemoryInMB();
- var stopwatch = StopWatch.createStarted();
- var sut = Releases.v480.createGraph(trialContext.getGraphClass());
- allTriples480.forEach(sut::add);
- stopwatch.stop();
- var memoryAfter = runGcAndGetUsedMemoryInMB();
- System.out.println(String.format("graphs: %d time to fill graphs: %s additional memory: %5.3f MB",
- sut.size(),
- stopwatch.formatTime(),
- (memoryAfter - memoryBefore)));
+ private Object fillHashSet() {
+ var sut = new HashSet<Triple>();
+ triples.forEach(sut::add);
+ Assert.assertEquals(triples.size(), sut.size());
return sut;
}
- /**
- * This method is used to get the memory consumption of the current JVM.
- * @return the memory consumption in MB
- */
- private static double runGcAndGetUsedMemoryInMB() {
- System.runFinalization();
- System.gc();
- Runtime.getRuntime().runFinalization();
- Runtime.getRuntime().gc();
- return BigDecimal.valueOf(Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()).divide(BigDecimal.valueOf(1024l)).divide(BigDecimal.valueOf(1024l)).doubleValue();
+ private Object fillHashCommonTripleSet() {
+ var sut = new HashCommonTripleSet();
+ triples.forEach(sut::addUnchecked);
+ Assert.assertEquals(triples.size(), sut.size());
+ return sut;
+ }
+
+ private Object fillFastHashTripleSet() {
+ var sut = new FastHashTripleSet();
+ triples.forEach(sut::addUnchecked);
+ Assert.assertEquals(triples.size(), sut.size());
+ return sut;
}
+
@Setup(Level.Trial)
public void setupTrial() throws Exception {
- this.trialContext = new Context(param1_GraphImplementation);
- switch (this.trialContext.getJenaVersion()) {
- case CURRENT:
- this.allTriplesCurrent = Releases.current.readTriples(param0_GraphUri);
- this.graphFill = this::graphFillCurrent;
+ triples = Releases.current.readTriples(param0_GraphUri);
+ switch (param1_SetImplementation) {
+ case "HashSet":
+ this.fillSet = this::fillHashSet;
+ break;
+ case "HashCommonTripleSet":
+ this.fillSet = this::fillHashCommonTripleSet;
break;
- case JENA_4_8_0:
- this.allTriples480 = Releases.v480.readTriples(param0_GraphUri);
- this.graphFill = this::graphFill480;
+ case "FastHashTripleSet":
+ this.fillSet = this::fillFastHashTripleSet;
break;
default:
- throw new IllegalArgumentException("Unknown Jena version: " + this.trialContext.getJenaVersion());
+ throw new IllegalArgumentException("Unknown set implementation: " + param1_SetImplementation);
}
}
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/set/triple/TestSetRemove.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/set/triple/TestSetRemove.java
new file mode 100644
index 0000000000..56436e1be4
--- /dev/null
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/set/triple/TestSetRemove.java
@@ -0,0 +1,124 @@
+/*
+ * 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.mem2.set.triple;
+
+import org.apache.jena.graph.Triple;
+import org.apache.jena.mem.graph.helper.Releases;
+import org.apache.jena.mem2.helper.JMHDefaultOptions;
+import org.junit.Assert;
+import org.junit.Test;
+import org.openjdk.jmh.annotations.*;
+import org.openjdk.jmh.runner.Runner;
+
+import java.util.HashSet;
+import java.util.List;
+
+@State(Scope.Benchmark)
+public class TestSetRemove {
+
+ @Param({
+ "../testing/cheeses-0.1.ttl",
+ "../testing/pizza.owl.rdf",
+ "../testing/BSBM/bsbm-1m.nt.gz",
+ })
+ public String param0_GraphUri;
+
+ @Param({
+ "HashSet",
+ "HashCommonTripleSet",
+ "FastHashTripleSet"
+ })
+ public String param1_SetImplementation;
+ java.util.function.Supplier<Integer> removeFromSet;
+ private List<Triple> triples;
+ private List<Triple> triplesToRemove;
+ private HashSet<Triple> hashSet;
+ private HashCommonTripleSet hashCommonTripleSet;
+ private FastHashTripleSet fastHashTripleSet;
+
+ @Benchmark
+ public int setRemove() {
+ return removeFromSet.get();
+ }
+
+ private int removeFromHashSet() {
+ triplesToRemove.forEach(t -> this.hashSet.remove(t));
+ Assert.assertTrue(this.hashSet.isEmpty());
+ return this.hashSet.size();
+ }
+
+ private int removeFromHashCommonTripleSet() {
+ triplesToRemove.forEach(t -> this.hashCommonTripleSet.removeUnchecked(t));
+ Assert.assertTrue(this.hashCommonTripleSet.isEmpty());
+ return this.hashCommonTripleSet.size();
+ }
+
+ private int removeFromFastHashTripleSet() {
+ triplesToRemove.forEach(t -> this.fastHashTripleSet.removeUnchecked(t));
+ Assert.assertTrue(this.fastHashTripleSet.isEmpty());
+ return this.fastHashTripleSet.size();
+ }
+
+ @Setup(Level.Invocation)
+ public void setupInvocation() {
+ switch (param1_SetImplementation) {
+ case "HashSet":
+ this.hashSet = new HashSet<>(triples.size());
+ this.triples.forEach(hashSet::add);
+ break;
+ case "HashCommonTripleSet":
+ this.hashCommonTripleSet = new HashCommonTripleSet(triples.size());
+ this.triples.forEach(hashCommonTripleSet::addUnchecked);
+ break;
+ case "FastHashTripleSet":
+ this.fastHashTripleSet = new FastHashTripleSet(triples.size());
+ this.triples.forEach(fastHashTripleSet::addUnchecked);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown set implementation: " + param1_SetImplementation);
+ }
+ }
+
+ @Setup(Level.Trial)
+ public void setupTrial() throws Exception {
+ this.triples = Releases.current.readTriples(param0_GraphUri);
+ this.triplesToRemove = Releases.current.cloneTriples(triples);
+ switch (param1_SetImplementation) {
+ case "HashSet":
+ this.removeFromSet = this::removeFromHashSet;
+ break;
+ case "HashCommonTripleSet":
+ this.removeFromSet = this::removeFromHashCommonTripleSet;
+ break;
+ case "FastHashTripleSet":
+ this.removeFromSet = this::removeFromFastHashTripleSet;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown set implementation: " + param1_SetImplementation);
+ }
+ }
+
+ @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-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/set/triple/TestSetStreamAll.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/set/triple/TestSetStreamAll.java
new file mode 100644
index 0000000000..f9c5ef29d7
--- /dev/null
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/set/triple/TestSetStreamAll.java
@@ -0,0 +1,120 @@
+/*
+ * 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.mem2.set.triple;
+
+import org.apache.jena.graph.Triple;
+import org.apache.jena.mem.graph.helper.Releases;
+import org.apache.jena.mem2.helper.JMHDefaultOptions;
+import org.junit.Assert;
+import org.junit.Test;
+import org.openjdk.jmh.annotations.*;
+import org.openjdk.jmh.runner.Runner;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Spliterator;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import static org.junit.Assert.assertEquals;
+
+
+@State(Scope.Benchmark)
+public class TestSetStreamAll {
+
+ @Param({
+ "../testing/cheeses-0.1.ttl",
+ "../testing/pizza.owl.rdf",
+ "../testing/BSBM/bsbm-1m.nt.gz",
+ })
+ public String param0_GraphUri;
+
+ @Param({
+ "HashSet",
+ "HashCommonTripleSet",
+ "FastHashTripleSet"
+ })
+ public String param1_SetImplementation;
+ java.util.function.Supplier<Spliterator<Triple>> getSpliterator;
+ private List<Triple> triples;
+ private HashSet<Triple> hashSet;
+ private HashCommonTripleSet hashCommonTripleSet;
+ private FastHashTripleSet fastHashTripleSet;
+
+ @Benchmark
+ public Object streamSet() {
+ var list = StreamSupport.stream(getSpliterator.get(), false)
+ .collect(Collectors.toList());
+ assertEquals(triples.size(), list.size());
+ return list;
+ }
+
+ @Benchmark
+ public Object streamSetParallel() {
+ var list = StreamSupport.stream(getSpliterator.get(), true)
+ .collect(Collectors.toList());
+ assertEquals(triples.size(), list.size());
+ return list;
+ }
+
+ private Spliterator<Triple> getSpliteratorFromHashSet() {
+ return hashSet.spliterator();
+ }
+
+ private Spliterator<Triple> getSpliteratorFromHashCommonTripleSet() {
+ return hashCommonTripleSet.keySpliterator();
+ }
+
+ private Spliterator<Triple> getSpliteratorFromFastHashTripleSet() {
+ return fastHashTripleSet.keySpliterator();
+ }
+
+ @Setup(Level.Trial)
+ public void setupTrial() throws Exception {
+ this.triples = Releases.current.readTriples(param0_GraphUri);
+ switch (param1_SetImplementation) {
+ case "HashSet":
+ this.hashSet = new HashSet<>(triples.size());
+ triples.forEach(hashSet::add);
+ this.getSpliterator = this::getSpliteratorFromHashSet;
+ break;
+ case "HashCommonTripleSet":
+ this.hashCommonTripleSet = new HashCommonTripleSet(triples.size());
+ triples.forEach(hashCommonTripleSet::addUnchecked);
+ this.getSpliterator = this::getSpliteratorFromHashCommonTripleSet;
+ break;
+ case "FastHashTripleSet":
+ this.fastHashTripleSet = new FastHashTripleSet(triples.size());
+ triples.forEach(fastHashTripleSet::addUnchecked);
+ this.getSpliterator = this::getSpliteratorFromFastHashTripleSet;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown set implementation: " + param1_SetImplementation);
+ }
+ }
+
+ @Test
+ public void benchmark() throws Exception {
+ var opt = JMHDefaultOptions.getDefaults(this.getClass())
+ .build();
+ var results = new Runner(opt).run();
+ Assert.assertNotNull(results);
+ }
+
+}
\ No newline at end of file
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/set/triple/TestSetUpdate.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/set/triple/TestSetUpdate.java
new file mode 100644
index 0000000000..af885d4ceb
--- /dev/null
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/set/triple/TestSetUpdate.java
@@ -0,0 +1,143 @@
+/*
+ * 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.mem2.set.triple;
+
+import org.apache.jena.graph.Triple;
+import org.apache.jena.mem.graph.helper.Releases;
+import org.apache.jena.mem2.helper.JMHDefaultOptions;
+import org.junit.Assert;
+import org.junit.Test;
+import org.openjdk.jmh.annotations.*;
+import org.openjdk.jmh.runner.Runner;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Random;
+
+@State(Scope.Benchmark)
+public class TestSetUpdate {
+
+ @Param({
+ "../testing/cheeses-0.1.ttl",
+ "../testing/pizza.owl.rdf",
+ "../testing/BSBM/bsbm-1m.nt.gz",
+ })
+ public String param0_GraphUri;
+
+ @Param({
+ "HashSet",
+ "HashCommonTripleSet",
+ "FastHashTripleSet"
+ })
+ public String param1_SetImplementation;
+ java.util.function.Supplier<Integer> updateSet;
+ private List<Triple> triples;
+ private List<Triple> triplesToRemove;
+ private HashSet<Triple> hashSet;
+ private HashCommonTripleSet hashCommonTripleSet;
+ private FastHashTripleSet fastHashTripleSet;
+
+ @Benchmark
+ public int updateSet() {
+ return updateSet.get();
+ }
+
+ private int updateHashSet() {
+ for (int i = 0; i < triplesToRemove.size(); i += 10) {
+ triplesToRemove.subList(i, Math.min(i + 10, triplesToRemove.size()))
+ .forEach(t -> this.hashSet.remove(t));
+ triplesToRemove.subList(i, Math.min(i + 10, triplesToRemove.size()))
+ .forEach(t -> this.hashSet.add(t));
+ assert this.hashSet.size() == triples.size();
+ }
+ return this.hashSet.size();
+ }
+
+ private int updateHashCommonTripleSet() {
+ for (int i = 0; i < triplesToRemove.size(); i += 10) {
+ triplesToRemove.subList(i, Math.min(i + 10, triplesToRemove.size()))
+ .forEach(t -> this.hashCommonTripleSet.tryRemove(t));
+ triplesToRemove.subList(i, Math.min(i + 10, triplesToRemove.size()))
+ .forEach(t -> this.hashCommonTripleSet.tryAdd(t));
+ assert this.hashCommonTripleSet.size() == triples.size();
+ }
+ return this.hashCommonTripleSet.size();
+ }
+
+
+ private int updateFastHashTripleSet() {
+ for (int i = 0; i < triplesToRemove.size(); i += 10) {
+ triplesToRemove.subList(i, Math.min(i + 10, triplesToRemove.size()))
+ .forEach(t -> this.fastHashTripleSet.tryRemove(t));
+ triplesToRemove.subList(i, Math.min(i + 10, triplesToRemove.size()))
+ .forEach(t -> this.fastHashTripleSet.tryAdd(t));
+ assert this.fastHashTripleSet.size() == triples.size();
+ }
+ return this.fastHashTripleSet.size();
+ }
+
+ @Setup(Level.Invocation)
+ public void setupInvocation() {
+ switch (param1_SetImplementation) {
+ case "HashSet":
+ this.hashSet = new HashSet<>(triples.size());
+ this.triples.forEach(hashSet::add);
+ break;
+ case "HashCommonTripleSet":
+ this.hashCommonTripleSet = new HashCommonTripleSet(triples.size());
+ this.triples.forEach(hashCommonTripleSet::addUnchecked);
+ break;
+ case "FastHashTripleSet":
+ this.fastHashTripleSet = new FastHashTripleSet(triples.size());
+ this.triples.forEach(fastHashTripleSet::addUnchecked);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown set implementation: " + param1_SetImplementation);
+ }
+ }
+
+ @Setup(Level.Trial)
+ public void setupTrial() throws Exception {
+ this.triples = Releases.current.readTriples(param0_GraphUri);
+ this.triplesToRemove = Releases.current.cloneTriples(triples);
+ Collections.shuffle(triplesToRemove, new Random(4721));
+ switch (param1_SetImplementation) {
+ case "HashSet":
+ this.updateSet = this::updateHashSet;
+ break;
+ case "HashCommonTripleSet":
+ this.updateSet = this::updateHashCommonTripleSet;
+ break;
+ case "FastHashTripleSet":
+ this.updateSet = this::updateFastHashTripleSet;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown set implementation: " + param1_SetImplementation);
+ }
+ }
+
+ @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-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/spliterator/TestSparseArraySpliteratorsForeachRemaining.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/spliterator/TestSparseArraySpliteratorsForeachRemaining.java
similarity index 84%
rename from jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/spliterator/TestSparseArraySpliteratorsForeachRemaining.java
rename to jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/spliterator/TestSparseArraySpliteratorsForeachRemaining.java
index 9bd9eb5f9f..dc4f880504 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/spliterator/TestSparseArraySpliteratorsForeachRemaining.java
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/spliterator/TestSparseArraySpliteratorsForeachRemaining.java
@@ -16,11 +16,9 @@
* limitations under the License.
*/
-package org.apache.jena.mem.spliterator;
+package org.apache.jena.mem2.spliterator;
import org.apache.jena.atlas.iterator.ActionCount;
-import org.apache.jena.mem.SparseArraySpliterator;
-import org.apache.jena.mem.SparseArraySubSpliterator;
import org.junit.Assert;
import org.junit.Test;
import org.openjdk.jmh.annotations.*;
@@ -41,26 +39,21 @@ import java.util.concurrent.TimeUnit;
public class TestSparseArraySpliteratorsForeachRemaining {
- final static int[] stepsWithNull = new int[] {1, 2, 3, 4, 5};
-
- List<Object[]> arraysWithNulls = new ArrayList<>(stepsWithNull.length);
-
- List<Integer> elementsCounts = new ArrayList<>(stepsWithNull.length);
-
-
+ final static int[] stepsWithNull = new int[]{1, 2, 3, 4, 5};
@Param({"1000000", "2000000", "3000000", "5000000"})
public int param0_arraySize;
-
@Param({
- "SparseArraySpliterator",
- "SparseArraySubSpliterator",
+ "mem.SparseArraySpliterator",
+ "mem2.SparseArraySpliterator"
})
public String param1_iteratorImplementation;
+ List<Object[]> arraysWithNulls = new ArrayList<>(stepsWithNull.length);
+ List<Integer> elementsCounts = new ArrayList<>(stepsWithNull.length);
@Benchmark
public long testSpliteratorForeachRemaining() {
long total = 0;
- for(int i = 0; i < stepsWithNull.length; i++) {
+ for (int i = 0; i < stepsWithNull.length; i++) {
var arrayWithNulls = arraysWithNulls.get(i);
var elementsCount = elementsCounts.get(i);
var actionCounter = new ActionCount<>();
@@ -83,12 +76,10 @@ public class TestSparseArraySpliteratorsForeachRemaining {
}
};
switch (param1_iteratorImplementation) {
- case "SparseArraySpliterator":
- return new SparseArraySpliterator<>(arrayWithNulls, 0, checkForConcurrentModification);
-
- case "SparseArraySubSpliterator":
- return new SparseArraySubSpliterator<>(arrayWithNulls, 0, checkForConcurrentModification);
-
+ case "mem.SparseArraySpliterator":
+ return new org.apache.jena.mem.SparseArraySpliterator<>(arrayWithNulls, count, checkForConcurrentModification);
+ case "mem2.SparseArraySpliterator":
+ return new SparseArraySpliterator<>(arrayWithNulls, checkForConcurrentModification);
default:
throw new IllegalArgumentException("Unknown spliterator implementation: " + param1_iteratorImplementation);
}
@@ -96,11 +87,11 @@ public class TestSparseArraySpliteratorsForeachRemaining {
@Setup(Level.Trial)
public void setupTrial() throws Exception {
- for(int i = 0; i < stepsWithNull.length; i++) {
+ for (int i = 0; i < stepsWithNull.length; i++) {
var arrayWithNulls = new Object[param0_arraySize];
- var stepsWithNull = this.stepsWithNull[i];
+ var stepsWithNull = TestSparseArraySpliteratorsForeachRemaining.stepsWithNull[i];
var elementsCount = 0;
- for (int k = 0; k < arrayWithNulls.length; k+=1+ stepsWithNull) {
+ for (int k = 0; k < arrayWithNulls.length; k += 1 + stepsWithNull) {
arrayWithNulls[k] = new Object();
elementsCount++;
}
@@ -116,7 +107,7 @@ public class TestSparseArraySpliteratorsForeachRemaining {
// You can be more specific if you'd like to run only one benchmark per test.
.include(this.getClass().getName())
// Set the following options as needed
- .mode (Mode.AverageTime)
+ .mode(Mode.AverageTime)
.timeUnit(TimeUnit.SECONDS)
.warmupTime(TimeValue.NONE)
.warmupIterations(10)
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/spliterator/TestSparseArraySpliteratorsStreamParallel.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/spliterator/TestSparseArraySpliteratorsStreamParallel.java
similarity index 84%
rename from jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/spliterator/TestSparseArraySpliteratorsStreamParallel.java
rename to jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/spliterator/TestSparseArraySpliteratorsStreamParallel.java
index 146cff16ee..0f9732e832 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/spliterator/TestSparseArraySpliteratorsStreamParallel.java
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/spliterator/TestSparseArraySpliteratorsStreamParallel.java
@@ -16,11 +16,9 @@
* limitations under the License.
*/
-package org.apache.jena.mem.spliterator;
+package org.apache.jena.mem2.spliterator;
import org.apache.jena.atlas.iterator.ActionCount;
-import org.apache.jena.mem.SparseArraySpliterator;
-import org.apache.jena.mem.SparseArraySubSpliterator;
import org.junit.Assert;
import org.junit.Test;
import org.openjdk.jmh.annotations.*;
@@ -42,26 +40,21 @@ import java.util.stream.StreamSupport;
public class TestSparseArraySpliteratorsStreamParallel {
- final static int[] stepsWithNull = new int[] {1, 2, 3, 4, 5};
-
- List<Object[]> arraysWithNulls = new ArrayList<>(stepsWithNull.length);
-
- List<Integer> elementsCounts = new ArrayList<>(stepsWithNull.length);
-
-
+ final static int[] stepsWithNull = new int[]{1, 2, 3, 4, 5};
@Param({"1000000", "2000000", "3000000", "5000000"})
public int param0_arraySize;
-
@Param({
- "SparseArraySpliterator",
- "SparseArraySubSpliterator",
+ "mem.SparseArraySpliterator",
+ "mem2.SparseArraySpliterator"
})
public String param1_iteratorImplementation;
+ List<Object[]> arraysWithNulls = new ArrayList<>(stepsWithNull.length);
+ List<Integer> elementsCounts = new ArrayList<>(stepsWithNull.length);
@Benchmark
public long testSpliteratorForeachRemaining() {
long total = 0;
- for(int i = 0; i < stepsWithNull.length; i++) {
+ for (int i = 0; i < stepsWithNull.length; i++) {
var arrayWithNulls = arraysWithNulls.get(i);
var elementsCount = elementsCounts.get(i);
var actionCounter = new ActionCount<>();
@@ -84,11 +77,10 @@ public class TestSparseArraySpliteratorsStreamParallel {
}
};
switch (param1_iteratorImplementation) {
- case "SparseArraySpliterator":
- return new SparseArraySpliterator<>(arrayWithNulls, 0, checkForConcurrentModification);
-
- case "SparseArraySubSpliterator":
- return new SparseArraySubSpliterator<>(arrayWithNulls, 0, checkForConcurrentModification);
+ case "mem.SparseArraySpliterator":
+ return new org.apache.jena.mem.SparseArraySpliterator<>(arrayWithNulls, count, checkForConcurrentModification);
+ case "mem2.SparseArraySpliterator":
+ return new SparseArraySpliterator<>(arrayWithNulls, checkForConcurrentModification);
default:
throw new IllegalArgumentException("Unknown spliterator implementation: " + param1_iteratorImplementation);
@@ -97,11 +89,11 @@ public class TestSparseArraySpliteratorsStreamParallel {
@Setup(Level.Trial)
public void setupTrial() throws Exception {
- for(int i = 0; i < stepsWithNull.length; i++) {
+ for (int i = 0; i < stepsWithNull.length; i++) {
var arrayWithNulls = new Object[param0_arraySize];
- var stepsWithNull = this.stepsWithNull[i];
+ var stepsWithNull = TestSparseArraySpliteratorsStreamParallel.stepsWithNull[i];
var elementsCount = 0;
- for (int k = 0; k < arrayWithNulls.length; k+=1+ stepsWithNull) {
+ for (int k = 0; k < arrayWithNulls.length; k += 1 + stepsWithNull) {
arrayWithNulls[k] = new Object();
elementsCount++;
}
@@ -117,7 +109,7 @@ public class TestSparseArraySpliteratorsStreamParallel {
// You can be more specific if you'd like to run only one benchmark per test.
.include(this.getClass().getName())
// Set the following options as needed
- .mode (Mode.AverageTime)
+ .mode(Mode.AverageTime)
.timeUnit(TimeUnit.SECONDS)
.warmupTime(TimeValue.NONE)
.warmupIterations(10)
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/spliterator/TestSparseArraySpliteratorsTryAdvance.java b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/spliterator/TestSparseArraySpliteratorsTryAdvance.java
similarity index 80%
rename from jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/spliterator/TestSparseArraySpliteratorsTryAdvance.java
rename to jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/spliterator/TestSparseArraySpliteratorsTryAdvance.java
index 7335fc0db1..39ba80940a 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/spliterator/TestSparseArraySpliteratorsTryAdvance.java
+++ b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem2/spliterator/TestSparseArraySpliteratorsTryAdvance.java
@@ -16,11 +16,9 @@
* limitations under the License.
*/
-package org.apache.jena.mem.spliterator;
+package org.apache.jena.mem2.spliterator;
import org.apache.jena.atlas.iterator.ActionCount;
-import org.apache.jena.mem.SparseArraySpliterator;
-import org.apache.jena.mem.SparseArraySubSpliterator;
import org.junit.Assert;
import org.junit.Test;
import org.openjdk.jmh.annotations.*;
@@ -41,33 +39,29 @@ import java.util.concurrent.TimeUnit;
public class TestSparseArraySpliteratorsTryAdvance {
- final static int[] stepsWithNull = new int[] {1, 2, 3, 4, 5};
-
- List<Object[]> arraysWithNulls = new ArrayList<>(stepsWithNull.length);
-
- List<Integer> elementsCounts = new ArrayList<>(stepsWithNull.length);
-
-
+ final static int[] stepsWithNull = new int[]{1, 2, 3, 4, 5};
@Param({"1000000", "2000000", "3000000", "5000000"})
public int param0_arraySize;
-
@Param({
- "SparseArraySpliterator",
- "SparseArraySubSpliterator",
+ "mem.SparseArraySpliterator",
+ "mem2.SparseArraySpliterator"
})
public String param1_iteratorImplementation;
+ List<Object[]> arraysWithNulls = new ArrayList<>(stepsWithNull.length);
+ List<Integer> elementsCounts = new ArrayList<>(stepsWithNull.length);
@Benchmark
public long testSpliteratorTryAdvance() {
long total = 0;
- for(int i = 0; i < stepsWithNull.length; i++) {
+ for (int i = 0; i < stepsWithNull.length; i++) {
var arrayWithNulls = arraysWithNulls.get(i);
var elementsCount = elementsCounts.get(i);
var actionCounter = new ActionCount<>();
var sut = createSut(arrayWithNulls, elementsCount);
- do {} while (sut.tryAdvance(actionCounter::accept));
+ do {
+ } while (sut.tryAdvance(actionCounter::accept));
total += actionCounter.getCount();
Assert.assertEquals(elementsCount.longValue(), actionCounter.getCount());
@@ -75,12 +69,6 @@ public class TestSparseArraySpliteratorsTryAdvance {
return total;
}
- private long count(Spliterator<Object> spliterator) {
- var actionCount = new ActionCount<>();
- spliterator.forEachRemaining(actionCount::accept);
- return actionCount.getCount();
- }
-
public Spliterator<Object> createSut(Object[] arrayWithNulls, int elementsCount) {
var count = elementsCount;
@@ -90,11 +78,10 @@ public class TestSparseArraySpliteratorsTryAdvance {
}
};
switch (param1_iteratorImplementation) {
- case "SparseArraySpliterator":
- return new SparseArraySpliterator<>(arrayWithNulls, 0, checkForConcurrentModification);
-
- case "SparseArraySubSpliterator":
- return new SparseArraySubSpliterator<>(arrayWithNulls, 0, checkForConcurrentModification);
+ case "mem.SparseArraySpliterator":
+ return new org.apache.jena.mem.SparseArraySpliterator<>(arrayWithNulls, count, checkForConcurrentModification);
+ case "mem2.SparseArraySpliterator":
+ return new SparseArraySpliterator<>(arrayWithNulls, checkForConcurrentModification);
default:
throw new IllegalArgumentException("Unknown spliterator implementation: " + param1_iteratorImplementation);
@@ -103,11 +90,11 @@ public class TestSparseArraySpliteratorsTryAdvance {
@Setup(Level.Trial)
public void setupTrial() throws Exception {
- for(int i = 0; i < stepsWithNull.length; i++) {
+ for (int i = 0; i < stepsWithNull.length; i++) {
var arrayWithNulls = new Object[param0_arraySize];
- var stepsWithNull = this.stepsWithNull[i];
+ var stepsWithNull = TestSparseArraySpliteratorsTryAdvance.stepsWithNull[i];
var elementsCount = 0;
- for (int k = 0; k < arrayWithNulls.length; k+=1+ stepsWithNull) {
+ for (int k = 0; k < arrayWithNulls.length; k += 1 + stepsWithNull) {
arrayWithNulls[k] = new Object();
elementsCount++;
}
@@ -123,7 +110,7 @@ public class TestSparseArraySpliteratorsTryAdvance {
// You can be more specific if you'd like to run only one benchmark per test.
.include(this.getClass().getName())
// Set the following options as needed
- .mode (Mode.AverageTime)
+ .mode(Mode.AverageTime)
.timeUnit(TimeUnit.SECONDS)
.warmupTime(TimeValue.NONE)
.warmupIterations(10)
diff --git a/jena-core/pom.xml b/jena-core/pom.xml
index 37b7adfe85..0ef347ce5d 100644
--- a/jena-core/pom.xml
+++ b/jena-core/pom.xml
@@ -53,25 +53,30 @@
</dependency>
<dependency>
- <groupId>org.apache.jena</groupId>
- <artifactId>jena-iri</artifactId>
- <version>4.9.0-SNAPSHOT</version>
+ <groupId>org.apache.jena</groupId>
+ <artifactId>jena-iri</artifactId>
+ <version>4.9.0-SNAPSHOT</version>
</dependency>
- <dependency>
- <artifactId>commons-cli</artifactId>
- <groupId>commons-cli</groupId>
- </dependency>
-
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <scope>test</scope>
- </dependency>
-
- <dependency>
- <groupId>org.xenei</groupId>
- <artifactId>junit-contracts</artifactId>
+ <dependency>
+ <artifactId>commons-cli</artifactId>
+ <groupId>commons-cli</groupId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.roaringbitmap</groupId>
+ <artifactId>RoaringBitmap</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.xenei</groupId>
+ <artifactId>junit-contracts</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
diff --git a/jena-core/src/main/java/org/apache/jena/mem/GraphMem.java b/jena-core/src/main/java/org/apache/jena/mem/GraphMem.java
index a9b082eeb1..32327de109 100644
--- a/jena-core/src/main/java/org/apache/jena/mem/GraphMem.java
+++ b/jena-core/src/main/java/org/apache/jena/mem/GraphMem.java
@@ -27,18 +27,23 @@ import java.util.stream.Stream;
/** @deprecated This implementation of GraphMem will be replaced by a new implementation at Jena 4.6.0.
* Application should be using {@link GraphMemFactory#createDefaultGraph()} for a general purpose graph or {@link GraphMemFactory#createGraphMem()}
* to specific this style of implementation.
-*/
+ */
@Deprecated
-public class GraphMem extends GraphMemBase
-{
- public GraphMem()
- { super( ); }
+public class GraphMem extends GraphMemBase {
+ /**
+ This Graph's TripleStore. Visible for <i>read-only</i> purposes only.
+ */
+ public final TripleStore store;
- @Override protected TripleStore createTripleStore()
- { return new GraphTripleStoreMem( this ); }
+ public GraphMem() {
+ super();
+ store = new GraphTripleStoreMem(this);
+ }
- @Override protected void destroy()
- { store.close(); }
+ @Override
+ protected void destroy() {
+ store.close();
+ }
@Override public void performAdd( Triple t )
{ store.add( t ); }
diff --git a/jena-core/src/main/java/org/apache/jena/mem/GraphMemBase.java b/jena-core/src/main/java/org/apache/jena/mem/GraphMemBase.java
index 39ffd1d27d..e8abf6fe06 100644
--- a/jena-core/src/main/java/org/apache/jena/mem/GraphMemBase.java
+++ b/jena-core/src/main/java/org/apache/jena/mem/GraphMemBase.java
@@ -18,9 +18,8 @@
package org.apache.jena.mem;
-import org.apache.jena.graph.Triple ;
-import org.apache.jena.graph.impl.GraphBase ;
-import org.apache.jena.graph.impl.TripleStore ;
+import org.apache.jena.graph.Triple;
+import org.apache.jena.graph.impl.GraphBase;
/**
GraphMemBase - a common base class for GraphMem and SmallGraphMem.
@@ -38,23 +37,14 @@ public abstract class GraphMemBase extends GraphBase
The number-of-times-opened count.
*/
protected int count;
-
- /**
- This Graph's TripleStore. Visible for <i>read-only</i> purposes only.
- */
- public final TripleStore store;
-
+
/**
initialise a GraphMemBase with its count set to 1.
*/
public GraphMemBase( )
{
- store = createTripleStore();
- count = 1;
+ count = 1;
}
-
- protected abstract TripleStore createTripleStore();
-
/**
Note a re-opening of this graph by incrementing the count. Answer
this Graph.
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
new file mode 100644
index 0000000000..5f78988a73
--- /dev/null
+++ b/jena-core/src/main/java/org/apache/jena/mem2/GraphMem2.java
@@ -0,0 +1,169 @@
+/*
+ * 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.mem2;
+
+import org.apache.jena.graph.Capabilities;
+import org.apache.jena.graph.Node;
+import org.apache.jena.graph.Triple;
+import org.apache.jena.graph.impl.GraphWithPerform;
+import org.apache.jena.mem.GraphMemBase;
+import org.apache.jena.mem2.store.TripleStore;
+import org.apache.jena.util.iterator.ExtendedIterator;
+
+import java.util.stream.Stream;
+
+/**
+ * A graph that stores triples in memory. This class is not thread-safe.
+ * All triples are stored in a {@link TripleStore}.
+ * <p>
+ * Implementation mus always comply to term-equality semantics. The characteristics of the
+ * implementations always have handlesLiteralTyping() == false.
+ */
+public class GraphMem2 extends GraphMemBase implements GraphWithPerform {
+
+ final TripleStore tripleStore;
+
+ public GraphMem2(TripleStore tripleStore) {
+ super();
+ this.tripleStore = tripleStore;
+ }
+
+ /**
+ * Subclasses over-ride this method to release any resources they no
+ * longer need once fully closed.
+ */
+ @Override
+ public void destroy() {
+ this.tripleStore.clear();
+ }
+
+ /**
+ * Remove all the statements from this graph.
+ */
+ @Override
+ public void clear() {
+ super.clear(); /* deletes all triples and sends notifications*/
+ this.tripleStore.clear();
+ }
+
+ /**
+ * Add a triple to the graph without notifying. The default implementation throws an
+ * AddDeniedException; subclasses must override if they want to be able to
+ * add triples.
+ *
+ * @param t triple to add
+ */
+ @Override
+ public void performAdd(final Triple t) {
+ tripleStore.add(t);
+ }
+
+ /**
+ * Remove a triple from the triple store. The default implementation throws
+ * a DeleteDeniedException; subclasses must override if they want to be able
+ * to remove triples.
+ *
+ * @param t triple to delete
+ */
+ @Override
+ public void performDelete(Triple t) {
+ tripleStore.remove(t);
+ }
+
+ /**
+ * Returns a {@link Stream} of all triples in the graph.
+ * Note: {@link Stream#parallel()} is supported.
+ *
+ * @return a stream of triples in this graph.
+ */
+ @Override
+ public Stream<Triple> stream() {
+ return this.tripleStore.stream();
+ }
+
+ /**
+ * Returns a {@link Stream} of Triples matching a pattern.
+ * Note: {@link Stream#parallel()} is supported.
+ *
+ * @param sm subject node match pattern
+ * @param pm predicate node match pattern
+ * @param om object node match pattern
+ * @return a stream of triples in this graph matching the pattern.
+ */
+ @Override
+ public Stream<Triple> stream(final Node sm, final Node pm, final Node om) {
+ return this.tripleStore.stream(Triple.createMatch(sm, pm, om));
+ }
+
+ /**
+ * Returns an {@link ExtendedIterator} of all triples in the graph matching the given triple match.
+ */
+ @Override
+ public ExtendedIterator<Triple> graphBaseFind(Triple tripleMatch) {
+ return this.tripleStore.find(tripleMatch);
+ }
+
+ /**
+ * Answer true if the graph contains any triple matching <code>t</code>.
+ * The default implementation uses <code>find</code> and checks to see
+ * if the iterator is non-empty.
+ *
+ * @param tripleMatch triple match pattern, which may be contained
+ */
+ @Override
+ public boolean graphBaseContains(final Triple tripleMatch) {
+ return this.tripleStore.contains(tripleMatch);
+ }
+
+ /**
+ * Answer the number of triples in this graph. Default implementation counts its
+ * way through the results of a findAll. Subclasses must override if they want
+ * size() to be efficient.
+ */
+ @Override
+ public int graphBaseSize() {
+ return this.tripleStore.countTriples();
+ }
+
+ @Override
+ public Capabilities getCapabilities() {
+ if (capabilities == null) capabilities = new Capabilities() {
+ @Override
+ public boolean sizeAccurate() {
+ return true;
+ }
+
+ @Override
+ public boolean addAllowed() {
+ return true;
+ }
+
+ @Override
+ public boolean deleteAllowed() {
+ return true;
+ }
+
+ @Override
+ public boolean handlesLiteralTyping() {
+ return false;
+ }
+ };
+ return capabilities;
+ }
+}
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
new file mode 100644
index 0000000000..0e4d2efbab
--- /dev/null
+++ b/jena-core/src/main/java/org/apache/jena/mem2/GraphMem2Fast.java
@@ -0,0 +1,45 @@
+/*
+ * 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.mem2;
+
+import org.apache.jena.mem2.store.fast.FastTripleStore;
+
+/**
+ * A graph that stores triples in memory. This class is not thread-safe.
+ * <p>
+ * Purpose: GraphMem2Fast is a strong candidate for becoming the new default in-memory graph in the upcoming Jena 5,
+ * thanks to its improved performance and relatively minor increase in memory usage.
+ * <p>
+ * Faster than {@link GraphMem2Legacy} (specially Graph#add, Graph#find and Graph#stream)
+ * Removing triples is a bit slower than {@link GraphMem2Legacy}.
+ * Memory consumption is about 6-35% higher than {@link GraphMem2Legacy}
+ * Maps and sets are based on {@link org.apache.jena.mem2.collection.FastHashBase}
+ * Benefits from multiple small optimizations. (see: {@link FastTripleStore})
+ * <p>
+ * The heritage of GraphMem:
+ * - Also uses 3 hash-maps indexed by subjects, predicates, and objects
+ * - Values of the maps also switch from arrays to hash sets for the triples
+ */
+public class GraphMem2Fast extends GraphMem2 {
+
+ public GraphMem2Fast() {
+ super(new FastTripleStore());
+ }
+
+}
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
new file mode 100644
index 0000000000..9a182c894e
--- /dev/null
+++ b/jena-core/src/main/java/org/apache/jena/mem2/GraphMem2Legacy.java
@@ -0,0 +1,47 @@
+/*
+ * 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.mem2;
+
+import org.apache.jena.mem2.store.legacy.LegacyTripleStore;
+
+/**
+ * A graph that stores triples in memory. This class is not thread-safe.
+ * <p>
+ * Purpose: Use this graph implementation if you want to maintain the 'old' behavior of GraphMem or if your memory
+ * constraints prevent you from utilizing more memory-intensive solutions.
+ * <p>
+ * Slightly improved performance compared to {@link org.apache.jena.mem.GraphMem}
+ * Simplified implementation, primarily due to lack of support for Iterator#remove
+ * <p>
+ * The heritage of GraphMem:
+ * - Same basic structure
+ * - Same memory consumption
+ * - Also based on HashCommon
+ * <p>
+ * This implementation is based on the original {@link org.apache.jena.mem.GraphMem} implementation.
+ * The main difference is that it strictly uses term equality for all nodes.
+ * The inner workings of the used structures like ArrayBunch and HashedBunchMap are not changed.
+ */
+public class GraphMem2Legacy extends GraphMem2 {
+
+ public GraphMem2Legacy() {
+ super(new LegacyTripleStore());
+ }
+
+}
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
new file mode 100644
index 0000000000..c37f5579d5
--- /dev/null
+++ b/jena-core/src/main/java/org/apache/jena/mem2/GraphMem2Roaring.java
@@ -0,0 +1,49 @@
+/*
+ * 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.mem2;
+
+import org.apache.jena.mem2.store.roaring.RoaringTripleStore;
+
+/**
+ * A graph that stores triples in memory. This class is not thread-safe.
+ * <p>
+ * Purpose: GraphMem2Roaring is ideal for handling extremely large graphs. If you frequently work with such massive
+ * data structures, this implementation could be your top choice.
+ * <p>
+ * Graph#contains is faster than {@link GraphMem2Fast}.
+ * Removing triples is a bit slower than {@link GraphMem2Legacy}.
+ * Better performance than GraphMem2Fast for operations with triple matches for the pattern S_O, SP_, and _PO on
+ * large graphs,due to bit-operations to find intersecting triples.
+ * Memory consumption is about 7-99% higher than {@link GraphMem2Legacy}
+ * Suitable for really large graphs like bsbm-5m.nt.gz, bsbm-25m.nt.gz, and possibly even larger.
+ * Simple and straightforward implementation.
+ * No heritage of GraphMem.
+ * <p>
+ * Internal structure:
+ * - One indexed hash set (same as GraphMem2Fast uses) that holds all triples.
+ * - Three hash maps indexed by subjects, predicates, and objects with RoaringBitmaps as values.
+ * - The bitmaps contain the indices of the triples in the central hash set.
+ */
+public class GraphMem2Roaring extends GraphMem2 {
+
+ public GraphMem2Roaring() {
+ super(new RoaringTripleStore());
+ }
+
+}
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
new file mode 100644
index 0000000000..aa2823a1f2
--- /dev/null
+++ b/jena-core/src/main/java/org/apache/jena/mem2/collection/FastHashBase.java
@@ -0,0 +1,422 @@
+/*
+ * 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.mem2.collection;
+
+import org.apache.jena.mem2.iterator.SparseArrayIterator;
+import org.apache.jena.mem2.spliterator.SparseArraySpliterator;
+import org.apache.jena.util.iterator.ExtendedIterator;
+
+import java.util.ConcurrentModificationException;
+import java.util.Spliterator;
+import java.util.function.Predicate;
+
+/**
+ * This is the base class for {@link FastHashSet} and {@link FastHashSet}.
+ * It only grows but never shrinks.
+ * This map does not guarantee any order. Although due to the way it is implemented the elements have a certain order.
+ * This map does not allow null keys.
+ * This map is not thread safe.
+ * <p>
+ * The positions array stores negative indices to the entries and hashCode arrays.
+ * The positions array is implemented as a power of two sized array. (like in {@link java.util.HashMap}) This allows
+ * to use a fast modulo operation to calculate the index. The indices of the positions array are derived from the
+ * hashCodes.
+ * Any position 0 indicates an empty element. The comparison with 0 is faster than comparing elements with null.
+ * <p>
+ * The keys are stored in a keys array and the hashCodesOrDeletedIndices array
+ * stores the hashCodes of the keys.
+ * hashCodesOrDeletedIndices is also used to store the indices of the deleted keys to save memory. It works like a
+ * linked list of deleted keys. The index of the previously deleted key is stored in the hashCodesOrDeletedIndices
+ * array. lastDeletedIndex is the index of the last deleted key in the hashCodesOrDeletedIndices array and serves as
+ * the head of the linked list of deleted keys.
+ * These two arrays grow together. They grow like {@link java.util.ArrayList} with a factor of 1.5.
+ * <p>
+ * keysPos is the index of the next free position in the keys array.
+ * The keys array is usually completely filled from index 0 to keysPos. Exceptions are the deleted keys.
+ * Indices that have been deleted are reused for new keys before the keys array is extended.
+ * The dense nature of the keys array enables fast iteration.
+ * <p>
+ * The index of a key in the keys array never changes. So the index of a key can be used as a handle to the key and
+ * for random access.
+ *
+ * @param <K> the type of the keys
+ */
+public abstract class FastHashBase<K> implements JenaMapSetCommon<K> {
+ protected static final int MINIMUM_HASHES_SIZE = 16;
+ protected static final int MINIMUM_ELEMENTS_SIZE = 8;
+ protected int keysPos = 0;
+ protected K[] keys;
+ protected int[] hashCodesOrDeletedIndices;
+ protected int lastDeletedIndex = -1;
+ protected int removedKeysCount = 0;
+
+ /**
+ * The negative indices to the entries and hashCode arrays.
+ * The indices of the positions array are derived from the hashCodes.
+ * Any position 0 indicates an empty element.
+ */
+ protected int[] positions;
+
+ protected FastHashBase(int initialSize) {
+ var positionsSize = Integer.highestOneBit(initialSize << 1);
+ if (positionsSize < initialSize << 1) {
+ positionsSize <<= 1;
+ }
+ this.positions = new int[positionsSize];
+ this.keys = newKeysArray(initialSize);
+ this.hashCodesOrDeletedIndices = new int[initialSize];
+ }
+
+ protected FastHashBase() {
+ this.positions = new int[MINIMUM_HASHES_SIZE];
+ this.keys = newKeysArray(MINIMUM_ELEMENTS_SIZE);
+ this.hashCodesOrDeletedIndices = new int[MINIMUM_ELEMENTS_SIZE];
+
+ }
+
+ /**
+ * Gets a new array of keys with the given size.
+ *
+ * @param size the size of the array
+ * @return the new array
+ */
+ protected abstract K[] newKeysArray(int size);
+
+ /**
+ * Calculates a position in the positions array by the hashCode.
+ *
+ * @param hashCode the hashCode
+ * @return the start index in the positions array to search for the key
+ */
+ protected final int calcStartIndexByHashCode(final int hashCode) {
+ return hashCode & (positions.length - 1);
+ }
+
+ /**
+ * Calculates the new size of the positions array, if it needs to be grown.
+ *
+ * @return the new size or -1 if it does not need to be grown
+ */
+ private int calcNewPositionsSize() {
+ if (keysPos << 1 > positions.length) { /*grow*/
+ final var newLength = positions.length << 1;
+ return newLength < 0 ? Integer.MAX_VALUE : newLength;
+ }
+ return -1;
+ }
+
+ /**
+ * Grows the positions array if needed.
+ */
+ protected final void growPositionsArrayIfNeeded() {
+ final var newSize = calcNewPositionsSize();
+ if (newSize < 0) {
+ return;
+ }
+ final var oldPositions = this.positions;
+ this.positions = new int[newSize];
+ for (int oldPosition : oldPositions) {
+ if (0 != oldPosition) {
+ this.positions[findEmptySlotWithoutEqualityCheck(hashCodesOrDeletedIndices[~oldPosition])] = oldPosition;
+ }
+ }
+ }
+
+ /**
+ * Grow the positions array if needed.
+ *
+ * @return true if the positions array was grown
+ */
+ protected final boolean tryGrowPositionsArrayIfNeeded() {
+ final var newSize = calcNewPositionsSize();
+ if (newSize < 0) {
+ return false;
+ }
+ final var oldPositions = this.positions;
+ this.positions = new int[newSize];
+ for (int oldPosition : oldPositions) {
+ if (0 != oldPosition) {
+ this.positions[findEmptySlotWithoutEqualityCheck(hashCodesOrDeletedIndices[~oldPosition])] = oldPosition;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns the number of elements in this collection. If this collection
+ * contains more than {@code Integer.MAX_VALUE} elements, returns
+ * {@code Integer.MAX_VALUE}.
+ *
+ * @return the number of elements in this collection
+ */
+ @Override
+ public int size() {
+ return keysPos - removedKeysCount;
+ }
+
+ /**
+ * Finds the next free slot in the keys array.
+ * If the keys array needs to be grown, it is grown.
+ * If there are deleted keys, the index of the last deleted key is returned.
+ *
+ * @return the index of the next free slot
+ */
+ protected final int getFreeKeyIndex() {
+ final int index;
+ if (lastDeletedIndex == -1) {
+ index = keysPos++;
+ if (index == keys.length) {
+ growKeysAndHashCodeArrays();
+ }
+ } else {
+ index = lastDeletedIndex;
+ lastDeletedIndex = hashCodesOrDeletedIndices[lastDeletedIndex];
+ removedKeysCount--;
+ }
+ return index;
+ }
+
+ /**
+ * Grow the keys and hashCodes arrays.
+ */
+ protected void growKeysAndHashCodeArrays() {
+ var newSize = (keys.length >> 1) + keys.length;
+ if (newSize < 0) {
+ newSize = Integer.MAX_VALUE;
+ }
+ final var oldKeys = this.keys;
+ this.keys = newKeysArray(newSize);
+ System.arraycopy(oldKeys, 0, keys, 0, oldKeys.length);
+ final var oldHashCodes = this.hashCodesOrDeletedIndices;
+ this.hashCodesOrDeletedIndices = new int[newSize];
+ System.arraycopy(oldHashCodes, 0, hashCodesOrDeletedIndices, 0, oldHashCodes.length);
+ }
+
+ @Override
+ public final boolean tryRemove(K o) {
+ return tryRemove(o, o.hashCode());
+ }
+
+ public final boolean tryRemove(K e, int hashCode) {
+ final var index = findPosition(e, hashCode);
+ if (index < 0) {
+ return false;
+ }
+ removeFrom(index);
+ return true;
+ }
+
+ /**
+ * Removes the element at the given position.
+ *
+ * @param e the element
+ * @return the index of the removed element or -1 if the element was not found
+ */
+ public final int removeAndGetIndex(final K e) {
+ return removeAndGetIndex(e, e.hashCode());
+ }
+
+ /**
+ * Removes the element at the given position.
+ *
+ * @param e the element
+ * @param hashCode the hash code of the element. This is a performance optimization.
+ * @return the index of the removed element or -1 if the element was not found
+ */
+ public final int removeAndGetIndex(final K e, final int hashCode) {
+ final var pIndex = findPosition(e, hashCode);
+ if (pIndex < 0) {
+ return -1;
+ }
+ final var eIndex = ~positions[pIndex];
+ removeFrom(pIndex);
+ return eIndex;
+ }
+
+ @Override
+ public final void removeUnchecked(K e) {
+ removeUnchecked(e, e.hashCode());
+ }
+
+ public final void removeUnchecked(K e, int hashCode) {
+ removeFrom(findPosition(e, hashCode));
+ }
+
+ /**
+ * Removes the element at the given position.
+ * <p>
+ * This is an implementation of Knuth's Algorithm R from tAoCP vol3, p 527,
+ * with exchanging of the roles of i and j so that they can be usefully renamed
+ * to <i>here</i> and <i>scan</i>.
+ * <p>
+ * It relies on linear probing but doesn't require a distinguished REMOVED
+ * value. Since we resize the table when it gets fullish, we don't worry [much]
+ * about the overhead of the linear probing.
+ * <p>
+ *
+ * @param here the index in the positions array
+ */
+ protected void removeFrom(int here) {
+ final var pIndex = ~positions[here];
+ hashCodesOrDeletedIndices[pIndex] = lastDeletedIndex;
+ lastDeletedIndex = pIndex;
+ removedKeysCount++;
+ keys[pIndex] = null;
+ while (true) {
+ positions[here] = 0;
+ int scan = here;
+ while (true) {
+ if (--scan < 0) scan += positions.length;
+ if (positions[scan] == 0) return;
+ int r = calcStartIndexByHashCode(hashCodesOrDeletedIndices[~positions[scan]]);
+ if ((scan > r || r >= here) && (r >= here || here >= scan) && (here >= scan || scan > r)) {
+ positions[here] = positions[scan];
+ here = scan;
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns {@code true} if this collection contains no elements.
+ *
+ * @return {@code true} if this collection contains no elements
+ */
+ @Override
+ public final boolean isEmpty() {
+ return this.size() == 0;
+ }
+
+ @Override
+ public final boolean containsKey(K o) {
+ final int hashCode = o.hashCode();
+ var pIndex = calcStartIndexByHashCode(hashCode);
+ while (true) {
+ if (0 == positions[pIndex]) {
+ return false;
+ } else {
+ final var eIndex = ~positions[pIndex];
+ if (hashCode == hashCodesOrDeletedIndices[eIndex] && o.equals(keys[eIndex])) {
+ return true;
+ } else if (--pIndex < 0) {
+ pIndex += positions.length;
+ }
+ }
+ }
+ }
+
+ /**
+ * Attentions: Due to the ordering of the keys, this method may be slow
+ * if matching elements are at the start of the list.
+ * Try to use {@link #anyMatchRandomOrder(Predicate)} instead.
+ */
+ @Override
+ public final boolean anyMatch(Predicate<K> predicate) {
+ var pos = keysPos - 1;
+ while (-1 < pos) {
+ if (null != keys[pos] && predicate.test(keys[pos])) {
+ return true;
+ }
+ pos--;
+ }
+ return false;
+ }
+
+ /**
+ * This method can be faster than {@link #anyMatch(Predicate)} if one expects
+ * to find many matches. But it is slower if one expects to find no matches or just a single one.
+ *
+ * @param predicate the predicate to apply to elements of this collection
+ * @return {@code true} if any element of the collection matches the predicate
+ */
+ public final boolean anyMatchRandomOrder(Predicate<K> predicate) {
+ var pIndex = positions.length - 1;
+ while (-1 < pIndex) {
+ if (0 != positions[pIndex] && predicate.test(keys[~positions[pIndex]])) {
+ return true;
+ }
+ pIndex--;
+ }
+ return false;
+ }
+
+ @Override
+ public final ExtendedIterator<K> keyIterator() {
+ final var initialSize = size();
+ final Runnable checkForConcurrentModification = () ->
+ {
+ if (size() != initialSize) throw new ConcurrentModificationException();
+ };
+ return new SparseArrayIterator<>(keys, keysPos, checkForConcurrentModification);
+ }
+
+ protected final int findPosition(final K e, final int hashCode) {
+ var pIndex = calcStartIndexByHashCode(hashCode);
+ while (true) {
+ if (0 == positions[pIndex]) {
+ return ~pIndex;
+ } else {
+ final var pos = ~positions[pIndex];
+ if (hashCode == hashCodesOrDeletedIndices[pos] && e.equals(keys[pos])) {
+ return pIndex;
+ } else if (--pIndex < 0) {
+ pIndex += positions.length;
+ }
+ }
+ }
+ }
+
+ protected final int findEmptySlotWithoutEqualityCheck(final int hashCode) {
+ var pIndex = calcStartIndexByHashCode(hashCode);
+ while (true) {
+ if (0 == positions[pIndex]) {
+ return pIndex;
+ } else if (--pIndex < 0) {
+ pIndex += positions.length;
+ }
+ }
+ }
+
+ /**
+ * Removes all the elements from this collection (optional operation).
+ * The collection will be empty after this method returns.
+ *
+ * @throws UnsupportedOperationException if the {@code clear} operation
+ * is not supported by this collection
+ */
+ @Override
+ public void clear() {
+ positions = new int[MINIMUM_HASHES_SIZE];
+ keys = newKeysArray(MINIMUM_ELEMENTS_SIZE);
+ hashCodesOrDeletedIndices = new int[MINIMUM_ELEMENTS_SIZE];
+ keysPos = 0;
+ lastDeletedIndex = -1;
+ removedKeysCount = 0;
+ }
+
+ @Override
+ public final Spliterator<K> keySpliterator() {
+ final var initialSize = this.size();
+ final Runnable checkForConcurrentModification = () ->
+ {
+ if (this.size() != initialSize) throw new ConcurrentModificationException();
+ };
+ return new SparseArraySpliterator<>(keys, keysPos, checkForConcurrentModification);
+ }
+}
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
new file mode 100644
index 0000000000..b9a162908b
--- /dev/null
+++ b/jena-core/src/main/java/org/apache/jena/mem2/collection/FastHashMap.java
@@ -0,0 +1,208 @@
+/*
+ * 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.mem2.collection;
+
+import org.apache.jena.mem2.iterator.SparseArrayIterator;
+import org.apache.jena.mem2.spliterator.SparseArraySpliterator;
+import org.apache.jena.util.iterator.ExtendedIterator;
+
+import java.util.ConcurrentModificationException;
+import java.util.Spliterator;
+import java.util.function.Supplier;
+import java.util.function.UnaryOperator;
+
+/**
+ * Map which grows, if needed but never shrinks.
+ * This map does not guarantee any order. Although due to the way it is implemented the elements have a certain order.
+ * This map does not allow null keys.
+ * This map is not thread safe.
+ * It´s purpose is to support fast add, remove, contains and stream / iterate operations.
+ * Only remove operations are not as fast as in {@link java.util.HashMap}
+ * Iterating over this map does not get much faster again after removing elements because the map is not compacted.
+ */
+public abstract class FastHashMap<K, V> extends FastHashBase<K> implements JenaMap<K, V> {
+
+ protected V[] values;
+
+ protected FastHashMap(int initialSize) {
+ super(initialSize);
+ this.values = newValuesArray(keys.length);
+ }
+
+ protected FastHashMap() {
+ super();
+ this.values = newValuesArray(keys.length);
+ }
+
+ protected abstract V[] newValuesArray(int size);
+
+ @Override
+ protected void growKeysAndHashCodeArrays() {
+ super.growKeysAndHashCodeArrays();
+ final var oldValues = values;
+ values = newValuesArray(keys.length);
+ System.arraycopy(oldValues, 0, values, 0, oldValues.length);
+ }
+
+ @Override
+ protected void removeFrom(int here) {
+ values[~positions[here]] = null;
+ super.removeFrom(here);
+ }
+
+ @Override
+ public void clear() {
+ super.clear();
+ values = newValuesArray(keys.length);
+ }
+
+ @Override
+ public boolean tryPut(K key, V value) {
+ final var hashCode = key.hashCode();
+ var pIndex = findPosition(key, hashCode);
+ if (pIndex < 0) {
+ if (tryGrowPositionsArrayIfNeeded()) {
+ pIndex = findPosition(key, hashCode);
+ }
+ final var eIndex = getFreeKeyIndex();
+ keys[eIndex] = key;
+ values[eIndex] = value;
+ hashCodesOrDeletedIndices[eIndex] = hashCode;
+ positions[~pIndex] = ~eIndex;
+ return true;
+ } else {
+ values[~positions[pIndex]] = value;
+ return false;
+ }
+ }
+
+ @Override
+ public void put(K key, V value) {
+ final var hashCode = key.hashCode();
+ var pIndex = findPosition(key, hashCode);
+ if (pIndex < 0) {
+ if (tryGrowPositionsArrayIfNeeded()) {
+ pIndex = findPosition(key, hashCode);
+ }
+ final var eIndex = getFreeKeyIndex();
+ keys[eIndex] = key;
+ values[eIndex] = value;
+ hashCodesOrDeletedIndices[eIndex] = hashCode;
+ positions[~pIndex] = ~eIndex;
+ } else {
+ values[~positions[pIndex]] = value;
+ }
+ }
+
+ /**
+ * Returns the value at the given index.
+ *
+ * @param i index
+ * @return value
+ */
+ public V getValueAt(int i) {
+ return values[i];
+ }
+
+ @Override
+ public V get(K key) {
+ var pIndex = findPosition(key, key.hashCode());
+ if (pIndex < 0) {
+ return null;
+ } else {
+ return values[~positions[pIndex]];
+ }
+ }
+
+ @Override
+ public V getOrDefault(K key, V defaultValue) {
+ var pIndex = findPosition(key, key.hashCode());
+ if (pIndex < 0) {
+ return defaultValue;
+ } else {
+ return values[~positions[pIndex]];
+ }
+ }
+
+ @Override
+ public V computeIfAbsent(K key, Supplier<V> absentValueSupplier) {
+ final var hashCode = key.hashCode();
+ var pIndex = findPosition(key, hashCode);
+ if (pIndex < 0) {
+ if (tryGrowPositionsArrayIfNeeded()) {
+ pIndex = findPosition(key, hashCode);
+ }
+ final var eIndex = getFreeKeyIndex();
+ keys[eIndex] = key;
+ hashCodesOrDeletedIndices[eIndex] = hashCode;
+ final var value = absentValueSupplier.get();
+ values[eIndex] = value;
+ positions[~pIndex] = ~eIndex;
+ return value;
+ } else {
+ return values[~positions[pIndex]];
+ }
+ }
+
+ @Override
+ public void compute(K key, UnaryOperator<V> valueProcessor) {
+ final int hashCode = key.hashCode();
+ var pIndex = findPosition(key, hashCode);
+ if (pIndex < 0) {
+ final var value = valueProcessor.apply(null);
+ if (value == null)
+ return;
+ final var eIndex = getFreeKeyIndex();
+ keys[eIndex] = key;
+ hashCodesOrDeletedIndices[eIndex] = hashCode;
+ values[eIndex] = value;
+ positions[~pIndex] = ~eIndex;
+ tryGrowPositionsArrayIfNeeded();
+ } else {
+ var eIndex = ~positions[pIndex];
+ final var value = valueProcessor.apply(values[eIndex]);
+ if (value == null) {
+ removeFrom(pIndex);
+ } else {
+ values[eIndex] = value;
+ }
+ }
+ }
+
+
+ @Override
+ public ExtendedIterator<V> valueIterator() {
+ final var initialSize = size();
+ final Runnable checkForConcurrentModification = () ->
+ {
+ if (size() != initialSize) throw new ConcurrentModificationException();
+ };
+ return new SparseArrayIterator<>(values, keysPos, checkForConcurrentModification);
+ }
+
+ @Override
+ public Spliterator<V> valueSpliterator() {
+ final var initialSize = this.size();
+ final Runnable checkForConcurrentModification = () ->
+ {
+ if (this.size() != initialSize) throw new ConcurrentModificationException();
+ };
+ return new SparseArraySpliterator<>(values, keysPos, checkForConcurrentModification);
+ }
+}
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
new file mode 100644
index 0000000000..1a9d15bc07
--- /dev/null
+++ b/jena-core/src/main/java/org/apache/jena/mem2/collection/FastHashSet.java
@@ -0,0 +1,113 @@
+/*
+ * 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.mem2.collection;
+
+/**
+ * Set which grows, if needed but never shrinks.
+ * This set does not guarantee any order. Although due to the way it is implemented the elements have a certain order.
+ * This set does not allow null values.
+ * This set is not thread safe.
+ * It´s purpose is to support fast add, remove, contains and stream / iterate operations.
+ * Only remove operations are not as fast as in {@link java.util.HashSet}
+ * Iterating over this set not get much faster again after removing elements because the set is not compacted.
+ */
+public abstract class FastHashSet<K> extends FastHashBase<K> implements JenaSetHashOptimized<K> {
+
+ protected FastHashSet(int initialSize) {
+ super(initialSize);
+ }
+
+ protected FastHashSet() {
+ super();
+ }
+
+ @Override
+ public boolean tryAdd(K key) {
+ return tryAdd(key, key.hashCode());
+ }
+
+ @Override
+ public boolean tryAdd(K value, int hashCode) {
+ growPositionsArrayIfNeeded();
+ var pIndex = findPosition(value, hashCode);
+ if (pIndex < 0) {
+ final var eIndex = getFreeKeyIndex();
+ keys[eIndex] = value;
+ hashCodesOrDeletedIndices[eIndex] = hashCode;
+ positions[~pIndex] = ~eIndex;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Add and get the index of the added element.
+ *
+ * @param value the value to add
+ * @return the index of the added element or the inverse (~) index of the existing element
+ */
+ public int addAndGetIndex(K value) {
+ return addAndGetIndex(value, value.hashCode());
+ }
+
+ /**
+ * Add and get the index of the added element.
+ *
+ * @param value the value to add
+ * @param hashCode the hash code of the value. This is a performance optimization.
+ * @return the index of the added element or the inverse (~) index of the existing element
+ */
+ public int addAndGetIndex(final K value, final int hashCode) {
+ growPositionsArrayIfNeeded();
+ final var pIndex = findPosition(value, hashCode);
+ if (pIndex < 0) {
+ final var eIndex = getFreeKeyIndex();
+ keys[eIndex] = value;
+ hashCodesOrDeletedIndices[eIndex] = hashCode;
+ positions[~pIndex] = ~eIndex;
+ return eIndex;
+ } else {
+ return positions[pIndex];
+ }
+ }
+
+ @Override
+ public void addUnchecked(K key) {
+ addUnchecked(key, key.hashCode());
+ }
+
+ @Override
+ public void addUnchecked(K value, int hashCode) {
+ growPositionsArrayIfNeeded();
+ final var eIndex = getFreeKeyIndex();
+ keys[eIndex] = value;
+ hashCodesOrDeletedIndices[eIndex] = hashCode;
+ positions[findEmptySlotWithoutEqualityCheck(hashCode)] = ~eIndex;
+ }
+
+ /**
+ * Gets the key at the given index.
+ *
+ * @param i the index
+ * @return the key at the given index
+ */
+ public K getKeyAt(int i) {
+ return keys[i];
+ }
+}
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
new file mode 100644
index 0000000000..06d98eca0b
--- /dev/null
+++ b/jena-core/src/main/java/org/apache/jena/mem2/collection/HashCommonBase.java
@@ -0,0 +1,210 @@
+/*
+ * 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.mem2.collection;
+
+import org.apache.jena.mem2.iterator.SparseArrayIterator;
+import org.apache.jena.mem2.spliterator.SparseArraySpliterator;
+import org.apache.jena.shared.JenaException;
+import org.apache.jena.util.iterator.ExtendedIterator;
+
+import java.util.ConcurrentModificationException;
+import java.util.Spliterator;
+import java.util.function.Predicate;
+
+/**
+ * Common code for hash tables and sets.
+ * The hash table is a lookup table.
+ * The hash codes are not stored. When the table grows, all hash codes need to be recalculated.
+ *
+ * @param <E> the element type
+ */
+public abstract class HashCommonBase<E> {
+ /**
+ * Jeremy suggests, from his experiments, that load factors more than
+ * 0.6 leave the table too dense, and little advantage is gained below 0.4.
+ * Although that was with a quadratic probe, I'm borrowing the same
+ * plausible range, and use 0.5 by default.
+ */
+ protected static final double LOAD_FACTOR = 0.5;
+ // Hash tables are 0.25 to 0.5 full so these numbers
+ // are for storing about 1/3 of that number of items.
+ // The larger sizes are added so that the system has "soft failure"
+ // rather implying guaranteed performance.
+ // https://primes.utm.edu/lists/small/millions/
+ static final int[] primes = {7, 19, 37, 79, 149, 307, 617, 1237, 2477, 4957, 9923, 19_853, 39_709, 79_423, 158_849,
+ 317_701, 635_413, 1_270_849, 2_541_701, 5_083_423, 10_166_857, 20_333_759, 40_667_527, 81_335_047,
+ 162_670_111, 325_340_233, 650_680_469, 982_451_653 // 50 millionth prime - Largest at primes.utm.edu.
+ };
+ /**
+ * The keys of whatever table it is we're implementing. Since we share code
+ * for triple sets and for node->bunch maps, it has to be an Object array; we
+ * take the casting hit.
+ */
+ protected E[] keys;
+ /**
+ * The threshold number of elements above which we resize the table;
+ * equal to the capacity times the load factor.
+ */
+ protected int threshold;
+ /**
+ * The number of active elements in the table, maintained incrementally.
+ */
+ protected int size = 0;
+
+ protected HashCommonBase(int initialCapacity) {
+ keys = newKeysArray(initialCapacity);
+ threshold = (int) (keys.length * LOAD_FACTOR);
+ }
+
+ protected static int nextSize(int atLeast) {
+ for (int prime : primes) {
+ if (prime > atLeast) return prime;
+ }
+ //return atLeast ; // Input is 2*current capacity.
+ // There are some very large numbers in the primes table.
+ throw new JenaException("Failed to find a 'next size': atLeast = " + atLeast);
+ }
+
+ protected void clear(int initialCapacity) {
+ size = 0;
+ keys = newKeysArray(initialCapacity);
+ threshold = (int) (keys.length * LOAD_FACTOR);
+ }
+
+ public int size() {
+ return size;
+ }
+
+ public boolean isEmpty() {
+ return size == 0;
+ }
+
+
+ /**
+ * Subclasses must implement to answer a new Key[size] array.
+ */
+ protected abstract E[] newKeysArray(int size);
+
+ /**
+ * Answer the initial index for the object <code>key</code> in the table.
+ * With luck, this will be the final position for that object. The initial index
+ * will always be non-negative and less than <code>capacity</code>.
+ * <p>
+ * Implementation note: do <i>not</i> use <code>Math.abs</code> to turn a
+ * hashcode into a positive value; there is a single specific integer on which
+ * it does not work. (Hence, here, the use of bitmasks.)
+ */
+ protected final int initialIndexFor(int hashOfKey) {
+ return (improveHashCode(hashOfKey) & 0x7fffffff) % keys.length;
+ }
+
+ /**
+ * Answer the transformed hash code, intended to be an improvement
+ * on the objects own hashcode. The magic number 127 is performance
+ * voodoo to (try to) eliminate problems experienced by Wolfgang.
+ */
+ protected int improveHashCode(int hashCode) {
+ return hashCode * 127;
+ }
+
+ protected abstract void grow();
+
+ /**
+ * Work out the capacity and threshold sizes for a new improved bigger
+ * table (bigger by a factor of two, at present).
+ */
+ protected int calcGrownCapacityAndSetThreshold() {
+ final var capacity = HashCommonBase.nextSize(keys.length * 2);
+ threshold = (int) (capacity * LOAD_FACTOR);
+ return capacity;
+ }
+
+ protected abstract void removeFrom(int here);
+
+ /**
+ * Remove the object <code>key</code> from this hash's keys if it
+ * is present (if it's absent, do nothing).
+ */
+ public boolean tryRemove(final E key) {
+ int slot = findSlot(key);
+ if (slot < 0) {
+ removeFrom(~slot);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Remove the object <code>key</code> from this hash's keys if it
+ * is present (if it's absent, do nothing).
+ */
+ public void removeUnchecked(final E key) {
+ int slot = findSlot(key);
+ if (slot < 0) {
+ removeFrom(~slot);
+ }
+ }
+
+ /**
+ * Search for the slot in which <code>key</code> is found. If it is absent,
+ * return the index of the free slot in which it could be placed. If it is present,
+ * return the bitwise complement of the index of the slot it appears in. Hence,
+ * negative values imply present, positive absent, and there's no confusion
+ * around 0.
+ */
+ protected int findSlot(E key) {
+ int index = initialIndexFor(key.hashCode());
+ while (true) {
+ E current = keys[index];
+ if (current == null) return index;
+ if (key.equals(current)) return ~index;
+ if (--index < 0) index += keys.length;
+ }
+ }
+
+ public boolean containsKey(final E key) {
+ return findSlot(key) < 0;
+ }
+
+ public boolean anyMatch(final Predicate<E> predicate) {
+ var pos = keys.length - 1;
+ while (-1 < pos) {
+ if (null != keys[pos] && predicate.test(keys[pos])) {
+ return true;
+ }
+ pos--;
+ }
+ return false;
+ }
+
+ public ExtendedIterator<E> keyIterator() {
+ final var initialSize = size;
+ final Runnable checkForConcurrentModification = () -> {
+ if (size != initialSize) throw new ConcurrentModificationException();
+ };
+ return new SparseArrayIterator<>(keys, checkForConcurrentModification);
+ }
+
+ public Spliterator<E> keySpliterator() {
+ final var initialSize = size;
+ final Runnable checkForConcurrentModification = () -> {
+ if (size != initialSize) throw new ConcurrentModificationException();
+ };
+ return new SparseArraySpliterator<>(keys, checkForConcurrentModification);
+ }
+}
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
new file mode 100644
index 0000000000..52ac143561
--- /dev/null
+++ b/jena-core/src/main/java/org/apache/jena/mem2/collection/HashCommonMap.java
@@ -0,0 +1,193 @@
+/*
+ * 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.mem2.collection;
+
+import org.apache.jena.mem2.iterator.SparseArrayIterator;
+import org.apache.jena.mem2.spliterator.SparseArraySpliterator;
+import org.apache.jena.util.iterator.ExtendedIterator;
+
+import java.util.ConcurrentModificationException;
+import java.util.Spliterator;
+import java.util.function.Supplier;
+import java.util.function.UnaryOperator;
+
+/**
+ * Implementation of {@link JenaMap} based on {@link HashCommonBase}.
+ */
+public abstract class HashCommonMap<K, V> extends HashCommonBase<K> implements JenaMap<K, V> {
+
+ protected V[] values;
+
+ /**
+ * Initialise this hashed thingy to have <code>initialCapacity</code> as its
+ * capacity and the corresponding threshold. All the key elements start out
+ * null.
+ */
+ protected HashCommonMap(int initialCapacity) {
+ super(initialCapacity);
+ this.values = newValuesArray(keys.length);
+ }
+
+ @Override
+ public void clear(int initialCapacity) {
+ super.clear(initialCapacity);
+ this.values = newValuesArray(keys.length);
+ }
+
+ protected abstract V[] newValuesArray(int size);
+
+ @Override
+ public boolean tryPut(K key, V value) {
+ final var slot = findSlot(key);
+ if (slot < 0) {
+ values[~slot] = value;
+ return false;
+ }
+ keys[slot] = key;
+ values[slot] = value;
+ if (++size > threshold) grow();
+ return true;
+ }
+
+ @Override
+ public void put(K key, V value) {
+ final var slot = findSlot(key);
+ if (slot < 0) {
+ values[~slot] = value;
+ return;
+ }
+ keys[slot] = key;
+ values[slot] = value;
+ if (++size > threshold) grow();
+ }
+
+ @Override
+ public V get(K key) {
+ final var slot = findSlot(key);
+ if (slot < 0) return values[~slot];
+ return null;
+ }
+
+ @Override
+ public V getOrDefault(K key, V defaultValue) {
+ final var slot = findSlot(key);
+ if (slot < 0) return values[~slot];
+ return defaultValue;
+ }
+
+ @Override
+ public V computeIfAbsent(K key, Supplier<V> absentValueSupplier) {
+ final var slot = findSlot(key);
+ if (slot < 0) return values[~slot];
+ final var value = absentValueSupplier.get();
+ keys[slot] = key;
+ values[slot] = value;
+ if (++size > threshold) grow();
+ return value;
+ }
+
+ @Override
+ public void compute(K key, UnaryOperator<V> valueProcessor) {
+ final var slot = findSlot(key);
+ if (slot < 0) {
+ final var value = valueProcessor.apply(values[~slot]);
+ if (value == null) {
+ removeFrom(~slot);
+ } else {
+ values[~slot] = value;
+ }
+ } else {
+ final var value = valueProcessor.apply(null);
+ if (value == null)
+ return;
+ keys[slot] = key;
+ values[slot] = value;
+ if (++size > threshold) grow();
+ }
+ }
+
+
+ @Override
+ protected void grow() {
+ final K[] oldContents = keys;
+ final V[] oldValues = values;
+ keys = newKeysArray(calcGrownCapacityAndSetThreshold());
+ values = newValuesArray(keys.length);
+ for (int i = 0; i < oldContents.length; i += 1) {
+ final K key = oldContents[i];
+ if (key != null) {
+ final int slot = findSlot(key);
+ keys[slot] = key;
+ values[slot] = oldValues[i];
+ }
+ }
+ }
+
+ /**
+ * Remove the triple at element <code>i</code> of <code>contents</code>.
+ * This is an implementation of Knuth's Algorithm R from tAoCP vol3, p 527,
+ * with exchanging of the roles of i and j so that they can be usefully renamed
+ * to <i>here</i> and <i>scan</i>.
+ * <p>
+ * It relies on linear probing but doesn't require a distinguished REMOVED
+ * value. Since we resize the table when it gets fullish, we don't worry [much]
+ * about the overhead of the linear probing.
+ * <p>
+ * Iterators running over the keys may miss elements that are moved from the
+ * bottom of the table to the top because of Iterator::remove. removeFrom
+ * returns such a moved key as its result, and null otherwise.
+ */
+ @Override
+ protected void removeFrom(int here) {
+ size -= 1;
+ while (true) {
+ keys[here] = null;
+ values[here] = null;
+ int scan = here;
+ while (true) {
+ if (--scan < 0) scan += keys.length;
+ if (keys[scan] == null) return;
+ final int r = initialIndexFor(keys[scan].hashCode());
+ if ((scan > r || r >= here) && (r >= here || here >= scan) && (here >= scan || scan > r)) {
+ keys[here] = keys[scan];
+ values[here] = values[scan];
+ here = scan;
+ break;
+ }
+ }
+ }
+ }
+
+ @Override
+ public ExtendedIterator<V> valueIterator() {
+ final var initialSize = size;
+ final Runnable checkForConcurrentModification = () -> {
+ if (size != initialSize) throw new ConcurrentModificationException();
+ };
+ return new SparseArrayIterator<>(values, checkForConcurrentModification);
+ }
+
+ @Override
+ public Spliterator<V> valueSpliterator() {
+ final var initialSize = size;
+ final Runnable checkForConcurrentModification = () -> {
+ if (size != initialSize) throw new ConcurrentModificationException();
+ };
+ return new SparseArraySpliterator<>(values, checkForConcurrentModification);
+ }
+}
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
new file mode 100644
index 0000000000..5abbd60dcf
--- /dev/null
+++ b/jena-core/src/main/java/org/apache/jena/mem2/collection/HashCommonSet.java
@@ -0,0 +1,95 @@
+/*
+ * 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.mem2.collection;
+
+/**
+ * Implementation of {@link JenaSet} based on {@link HashCommonBase}.
+ */
+public abstract class HashCommonSet<K> extends HashCommonBase<K> implements JenaSet<K> {
+
+ /**
+ * Initialise this hashed thingy to have <code>initialCapacity</code> as its
+ * capacity and the corresponding threshold. All the key elements start out
+ * null.
+ */
+ protected HashCommonSet(int initialCapacity) {
+ super(initialCapacity);
+ }
+
+ @Override
+ public boolean tryAdd(K key) {
+ final var slot = findSlot(key);
+ if (slot < 0) return false;
+ keys[slot] = key;
+ if (++size > threshold) grow();
+ return true;
+ }
+
+ @Override
+ public void addUnchecked(K key) {
+ final var slot = findSlot(key);
+ if (slot < 0) return;
+ keys[slot] = key;
+ if (++size > threshold) grow();
+ }
+
+ @Override
+ protected void grow() {
+ final K[] oldContents = keys;
+ keys = newKeysArray(calcGrownCapacityAndSetThreshold());
+ for (final K key : oldContents) {
+ if (key != null) {
+ final int slot = findSlot(key);
+ keys[slot] = key;
+ }
+ }
+ }
+
+ /**
+ * Remove the triple at element <code>i</code> of <code>contents</code>.
+ * This is an implementation of Knuth's Algorithm R from tAoCP vol3, p 527,
+ * with exchanging of the roles of i and j so that they can be usefully renamed
+ * to <i>here</i> and <i>scan</i>.
+ * <p>
+ * It relies on linear probing but doesn't require a distinguished REMOVED
+ * value. Since we resize the table when it gets fullish, we don't worry [much]
+ * about the overhead of the linear probing.
+ * <p>
+ * Iterators running over the keys may miss elements that are moved from the
+ * bottom of the table to the top because of Iterator::remove. removeFrom
+ * returns such a moved key as its result, and null otherwise.
+ */
+ @Override
+ protected void removeFrom(int here) {
+ size -= 1;
+ while (true) {
+ keys[here] = null;
+ int scan = here;
+ while (true) {
+ if (--scan < 0) scan += keys.length;
+ if (keys[scan] == null) return;
+ final int r = initialIndexFor(keys[scan].hashCode());
+ if ((scan > r || r >= here) && (r >= here || here >= scan) && (here >= scan || scan > r)) {
+ keys[here] = keys[scan];
+ here = scan;
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/jena-core/src/main/java/org/apache/jena/mem2/collection/JenaMap.java b/jena-core/src/main/java/org/apache/jena/mem2/collection/JenaMap.java
new file mode 100644
index 0000000000..7b226785ec
--- /dev/null
+++ b/jena-core/src/main/java/org/apache/jena/mem2/collection/JenaMap.java
@@ -0,0 +1,121 @@
+/*
+ * 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.mem2.collection;
+
+import org.apache.jena.util.iterator.ExtendedIterator;
+
+import java.util.Spliterator;
+import java.util.function.Supplier;
+import java.util.function.UnaryOperator;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+/**
+ * A map from keys of type {@code K} to values of type {@code V}.
+ *
+ * @param <K> the type of the keys in the map
+ * @param <V> the type of the values in the map
+ */
+public interface JenaMap<K, V> extends JenaMapSetCommon<K> {
+
+ /**
+ * Try to put a key-value pair into the map. If the key is already present, the value is updated.
+ *
+ * @param key the key to put
+ * @param value the value to put
+ * @return true if the key-value pair was put into the map, false if the key was already present
+ */
+ boolean tryPut(K key, V value);
+
+ /**
+ * Put a key-value pair into the map. If the key is already present, the value is updated.
+ *
+ * @param key the key to put
+ * @param value the value to put
+ */
+ void put(K key, V value);
+
+ /**
+ * Get the value associated with the provided key.
+ *
+ * @param key the key to look up
+ * @return the value associated with the key, or null if the key is not present
+ */
+ V get(K key);
+
+ /**
+ * Get the value associated with the provided key, or a default value if the key is not present.
+ *
+ * @param key the key to look up
+ * @param defaultValue the default value to return if the key is not present
+ * @return the value associated with the key, or the default value if the key is not present
+ */
+ V getOrDefault(K key, V defaultValue);
+
+ /**
+ * Compute a value for a key if the key is not present.
+ * The value is automatically put into the map.
+ *
+ * @param key the key whose value is to retrieved or computed
+ * @param absentValueSupplier the function to compute a value for the key, if the key is not present
+ * @return the value associated with the key, or the value computed by the function if the key is not present
+ */
+ V computeIfAbsent(K key, Supplier<V> absentValueSupplier);
+
+ /**
+ * Compute a value for a key.
+ *
+ * @param key the key to compute a value for
+ * @param valueProcessor the function to compute a value for the key. The function is passed the current value
+ * associated with the key, or null if the key is not present. The function should return
+ * the new value to associate with the key, or null if the key should be removed from the map.
+ */
+ void compute(K key, UnaryOperator<V> valueProcessor);
+
+ /**
+ * Get an iterator over the values in the map.
+ *
+ * @return an iterator over the values in the map
+ */
+ ExtendedIterator<V> valueIterator();
+
+ /**
+ * Get a spliterator over the values in the map.
+ *
+ * @return a spliterator over the values in the map
+ */
+ Spliterator<V> valueSpliterator();
+
+ /**
+ * Get a stream over the values in the map.
+ *
+ * @return a stream over the values in the map
+ */
+ default Stream<V> valueStream() {
+ return StreamSupport.stream(valueSpliterator(), false);
+ }
+
+ /**
+ * Get a parallel stream over the values in the map.
+ *
+ * @return a parallel stream over the values in the map
+ */
+ default Stream<V> valueStreamParallel() {
+ return StreamSupport.stream(valueSpliterator(), false);
+ }
+}
diff --git a/jena-core/src/main/java/org/apache/jena/mem2/collection/JenaMapSetCommon.java b/jena-core/src/main/java/org/apache/jena/mem2/collection/JenaMapSetCommon.java
new file mode 100644
index 0000000000..405c94cf1d
--- /dev/null
+++ b/jena-core/src/main/java/org/apache/jena/mem2/collection/JenaMapSetCommon.java
@@ -0,0 +1,113 @@
+/*
+ * 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.mem2.collection;
+
+import org.apache.jena.util.iterator.ExtendedIterator;
+
+import java.util.Spliterator;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+/**
+ * Common interface for {@link JenaMap} and {@link JenaSet}. *
+ *
+ * @param <E> the type of the keys/elements in the collection
+ */
+public interface JenaMapSetCommon<E> {
+
+ /**
+ * Clear the collection.
+ */
+ void clear();
+
+ /**
+ * @return the number of elements in the collection
+ */
+ int size();
+
+ /**
+ * @return true if the collection is empty
+ */
+ boolean isEmpty();
+
+ /**
+ * Check whether the collection contains a given key.
+ *
+ * @param key the key to look for
+ * @return true if the collection contains the key
+ */
+ boolean containsKey(E key);
+
+ /**
+ * Check whether the collection contains any element matching the predicate.
+ *
+ * @param predicate the predicate to match
+ * @return true if the collection contains any element matching the predicate
+ */
+ boolean anyMatch(Predicate<E> predicate);
+
+ /**
+ * Tries to remove a key from the collection.
+ *
+ * @param key the key to remove
+ * @return true if the key was removed. If the key was not present, false is returned.
+ */
+ boolean tryRemove(E key);
+
+ /**
+ * Removes a key from the collection.
+ * Attention: Implementations may assume that the key is present.
+ *
+ * @param key the key to remove
+ */
+ void removeUnchecked(E key);
+
+ /**
+ * Get an iterator over the keys in the collection.
+ *
+ * @return an iterator over the keys in the collection
+ */
+ ExtendedIterator<E> keyIterator();
+
+ /**
+ * Get a spliterator over the keys in the collection.
+ *
+ * @return a spliterator over the keys in the collection
+ */
+ Spliterator<E> keySpliterator();
+
+ /**
+ * Get a stream over the keys in the collection.
+ *
+ * @return a stream over the keys in the collection
+ */
+ default Stream<E> keyStream() {
+ return StreamSupport.stream(keySpliterator(), false);
+ }
+
+ /**
+ * Get a parallel stream over the keys in the collection.
+ *
+ * @return a parallel stream over the keys in the collection
+ */
+ default Stream<E> keyStreamParallel() {
+ return StreamSupport.stream(keySpliterator(), true);
+ }
+
+}
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java b/jena-core/src/main/java/org/apache/jena/mem2/collection/JenaSet.java
similarity index 54%
copy from jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java
copy to jena-core/src/main/java/org/apache/jena/mem2/collection/JenaSet.java
index f601955053..a170cee422 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java
+++ b/jena-core/src/main/java/org/apache/jena/mem2/collection/JenaSet.java
@@ -15,18 +15,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.jena.mem.graph.helper;
-
-import org.apache.jena.graph.Graph;
-import org.apache.jena.graph.Node;
-import org.apache.jena.graph.Triple;
+package org.apache.jena.mem2.collection;
/**
- * Proviced the release specific access to the helper classes for working with graphs, triples and nodes.
+ * Set interface specialized for the use cases in triple store implementations.
+ *
+ * @param <E>
*/
-public class Releases {
+public interface JenaSet<E> extends JenaMapSetCommon<E> {
- public static GraphTripleNodeHelper<Graph, Triple, Node> current = new GraphTripleNodeHelperCurrent();
- public static GraphTripleNodeHelper<org.apache.shadedJena480.graph.Graph, org.apache.shadedJena480.graph.Triple, org.apache.shadedJena480.graph.Node> v480 = new GraphTripleNodeHelper480();
+ /**
+ * Add the key to the set if it is not already present.
+ *
+ * @param key the key to add
+ * @return true if the key was added, false if it was already present
+ */
+ boolean tryAdd(E key);
+ /**
+ * Add the key to the set without checking if it is already present.
+ * Attention: This method must only be used if it is guaranteed that the key is not already present.
+ *
+ * @param key the key to add
+ */
+ void addUnchecked(E key);
}
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java b/jena-core/src/main/java/org/apache/jena/mem2/collection/JenaSetHashOptimized.java
similarity index 58%
copy from jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java
copy to jena-core/src/main/java/org/apache/jena/mem2/collection/JenaSetHashOptimized.java
index f601955053..2400f09305 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java
+++ b/jena-core/src/main/java/org/apache/jena/mem2/collection/JenaSetHashOptimized.java
@@ -15,18 +15,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.jena.mem.graph.helper;
+package org.apache.jena.mem2.collection;
-import org.apache.jena.graph.Graph;
-import org.apache.jena.graph.Node;
-import org.apache.jena.graph.Triple;
/**
- * Proviced the release specific access to the helper classes for working with graphs, triples and nodes.
+ * Extension of {@link JenaSet} that allows to add and remove elements
+ * with a given hash code.
+ * This is useful if the hash code is already known.
+ * Attention: The hash code must be consistent with E::hashCode().
*/
-public class Releases {
+public interface JenaSetHashOptimized<E> extends JenaSet<E> {
+ boolean tryAdd(E key, int hashCode);
- public static GraphTripleNodeHelper<Graph, Triple, Node> current = new GraphTripleNodeHelperCurrent();
- public static GraphTripleNodeHelper<org.apache.shadedJena480.graph.Graph, org.apache.shadedJena480.graph.Triple, org.apache.shadedJena480.graph.Node> v480 = new GraphTripleNodeHelper480();
+ void addUnchecked(E key, int hashCode);
+ boolean tryRemove(E key, int hashCode);
+
+ void removeUnchecked(E key, int hashCode);
}
diff --git a/jena-core/src/main/java/org/apache/jena/mem2/iterator/IteratorOfJenaSets.java b/jena-core/src/main/java/org/apache/jena/mem2/iterator/IteratorOfJenaSets.java
new file mode 100644
index 0000000000..68ae6c197f
--- /dev/null
+++ b/jena-core/src/main/java/org/apache/jena/mem2/iterator/IteratorOfJenaSets.java
@@ -0,0 +1,74 @@
+/*
+ * 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.mem2.iterator;
+
+import org.apache.jena.mem2.collection.JenaSet;
+import org.apache.jena.util.iterator.ExtendedIterator;
+import org.apache.jena.util.iterator.NiceIterator;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.function.Consumer;
+
+/**
+ * Iterator that iterates over the entries of sets which are contained in the given iterator of sets.
+ *
+ * @param <E> the type of the elements
+ */
+public class IteratorOfJenaSets<E> extends NiceIterator<E> {
+
+ final Iterator<? extends JenaSet<E>> parentIterator;
+
+ ExtendedIterator<E> currentIterator;
+
+ public IteratorOfJenaSets(Iterator<? extends JenaSet<E>> parentIterator) {
+ this.parentIterator = parentIterator;
+ this.currentIterator = parentIterator.hasNext()
+ ? parentIterator.next().keyIterator()
+ : NiceIterator.emptyIterator();
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (this.currentIterator.hasNext()) {
+ return true;
+ }
+ while (this.parentIterator.hasNext()) {
+ this.currentIterator = this.parentIterator.next().keyIterator();
+ if (this.currentIterator.hasNext()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public E next() {
+ if (this.hasNext()) {
+ return this.currentIterator.next();
+ }
+ throw new NoSuchElementException();
+ }
+
+ @Override
+ public void forEachRemaining(Consumer<? super E> action) {
+ this.currentIterator.forEachRemaining(action);
+ this.parentIterator.forEachRemaining(i -> i.keyIterator().forEachRemaining(action));
+ }
+}
diff --git a/jena-core/src/main/java/org/apache/jena/mem2/iterator/SparseArrayIterator.java b/jena-core/src/main/java/org/apache/jena/mem2/iterator/SparseArrayIterator.java
new file mode 100644
index 0000000000..8e9a3a4593
--- /dev/null
+++ b/jena-core/src/main/java/org/apache/jena/mem2/iterator/SparseArrayIterator.java
@@ -0,0 +1,97 @@
+/*
+ * 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.mem2.iterator;
+
+import org.apache.jena.util.iterator.NiceIterator;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.function.Consumer;
+
+/**
+ * An iterator over a sparse array, that skips null entries.
+ *
+ * @param <E> the type of the array elements
+ */
+public class SparseArrayIterator<E> extends NiceIterator<E> implements Iterator<E> {
+
+ private final E[] entries;
+ private final Runnable checkForConcurrentModification;
+ private int pos;
+ private boolean hasNext = false;
+
+ public SparseArrayIterator(final E[] entries, final Runnable checkForConcurrentModification) {
+ this.entries = entries;
+ this.pos = entries.length - 1;
+ this.checkForConcurrentModification = checkForConcurrentModification;
+ }
+
+ public SparseArrayIterator(final E[] entries, int toIndexExclusive, final Runnable checkForConcurrentModification) {
+ this.entries = entries;
+ this.pos = toIndexExclusive - 1;
+ this.checkForConcurrentModification = checkForConcurrentModification;
+ }
+
+ /**
+ * Returns {@code true} if the iteration has more elements.
+ * (In other words, returns {@code true} if {@link #next} would
+ * return an element rather than throwing an exception.)
+ *
+ * @return {@code true} if the iteration has more elements
+ */
+ @Override
+ public boolean hasNext() {
+ while (-1 < pos) {
+ if (null != entries[pos]) {
+ hasNext = true;
+ return true;
+ }
+ pos--;
+ }
+ hasNext = false;
+ return false;
+ }
+
+ /**
+ * Returns the next element in the iteration.
+ *
+ * @return the next element in the iteration
+ * @throws NoSuchElementException if the iteration has no more elements
+ */
+ @Override
+ public E next() {
+ this.checkForConcurrentModification.run();
+ if (hasNext || hasNext()) {
+ hasNext = false;
+ return entries[pos--];
+ }
+ throw new NoSuchElementException();
+ }
+
+ @Override
+ public void forEachRemaining(Consumer<? super E> action) {
+ while (-1 < pos) {
+ if (null != entries[pos]) {
+ action.accept(entries[pos]);
+ }
+ pos--;
+ }
+ this.checkForConcurrentModification.run();
+ }
+}
diff --git a/jena-core/src/main/java/org/apache/jena/mem2/pattern/MatchPattern.java b/jena-core/src/main/java/org/apache/jena/mem2/pattern/MatchPattern.java
new file mode 100644
index 0000000000..a0337fb25b
--- /dev/null
+++ b/jena-core/src/main/java/org/apache/jena/mem2/pattern/MatchPattern.java
@@ -0,0 +1,58 @@
+/*
+ * 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.mem2.pattern;
+
+/**
+ * A pattern for matching triples.
+ * The pattern is defined by the wildcard positions for the subject, predicate and object.
+ */
+public enum MatchPattern {
+ /**
+ * Match a triple with a concrete subject, predicate and object.
+ */
+ SUB_PRE_OBJ,
+ /**
+ * Match a triple with a concrete subject and predicate, and a wildcard object.
+ */
+ SUB_PRE_ANY,
+ /**
+ * Match a triple with a concrete subject and object, and a wildcard predicate.
+ */
+ SUB_ANY_OBJ,
+ /**
+ * Match a triple with a concrete subject, and wildcard predicate and object.
+ */
+ SUB_ANY_ANY,
+ /**
+ * Match a triple with a concrete predicate and object, and a wildcard subject.
+ */
+ ANY_PRE_OBJ,
+ /**
+ * Match a triple with a concrete predicate, and wildcard subject and object.
+ */
+ ANY_PRE_ANY,
+ /**
+ * Match a triple with a concrete object, and wildcard subject and predicate.
+ */
+ ANY_ANY_OBJ,
+ /**
+ * Match a triple with a wildcard subject, predicate and object.
+ */
+ ANY_ANY_ANY
+}
diff --git a/jena-core/src/main/java/org/apache/jena/mem2/pattern/PatternClassifier.java b/jena-core/src/main/java/org/apache/jena/mem2/pattern/PatternClassifier.java
new file mode 100644
index 0000000000..422821f9a5
--- /dev/null
+++ b/jena-core/src/main/java/org/apache/jena/mem2/pattern/PatternClassifier.java
@@ -0,0 +1,105 @@
+/*
+ * 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.mem2.pattern;
+
+import org.apache.jena.graph.Node;
+import org.apache.jena.graph.Triple;
+
+/**
+ * Classify a triple match into one of the 8 match patterns.
+ * <p>
+ * The classification is based on the concrete-ness of the subject, predicate and object.
+ * A concrete node is one that is not a variable.
+ * <p>
+ * The classification is used to select the most efficient implementation of a triple store.
+ * <p>
+ * This is a utility class; there is no need to instantiate it.
+ *
+ * @see MatchPattern
+ */
+public class PatternClassifier {
+
+ private PatternClassifier() {
+ }
+
+ public static MatchPattern classify(Triple tripleMatch) {
+ if (tripleMatch.isConcrete()) {
+ return MatchPattern.SUB_PRE_OBJ;
+ } else {
+ if (tripleMatch.getSubject().isConcrete()) {
+ if (tripleMatch.getPredicate().isConcrete()) {
+ return MatchPattern.SUB_PRE_ANY;
+ } else {
+ if (tripleMatch.getObject().isConcrete()) {
+ return MatchPattern.SUB_ANY_OBJ;
+ } else {
+ return MatchPattern.SUB_ANY_ANY;
+ }
+ }
+ } else {
+ if (tripleMatch.getPredicate().isConcrete()) {
+ if (tripleMatch.getObject().isConcrete()) {
+ return MatchPattern.ANY_PRE_OBJ;
+ } else {
+ return MatchPattern.ANY_PRE_ANY;
+ }
+ } else {
+ if (tripleMatch.getObject().isConcrete()) {
+ return MatchPattern.ANY_ANY_OBJ;
+ } else {
+ return MatchPattern.ANY_ANY_ANY;
+ }
+ }
+ }
+ }
+ }
+
+ public static MatchPattern classify(Node sm, Node pm, Node om) {
+ if (null != sm && sm.isConcrete()) {
+ if (null != pm && pm.isConcrete()) {
+ if (null != om && om.isConcrete()) {
+ return MatchPattern.SUB_PRE_OBJ;
+ } else {
+ return MatchPattern.SUB_PRE_ANY;
+ }
+ } else {
+ if (null != om && om.isConcrete()) {
+ return MatchPattern.SUB_ANY_OBJ;
+ } else {
+ return MatchPattern.SUB_ANY_ANY;
+ }
+ }
+ } else {
+ if (null != pm && pm.isConcrete()) {
+ if (null != om && om.isConcrete()) {
+ return MatchPattern.ANY_PRE_OBJ;
+ } else {
+ return MatchPattern.ANY_PRE_ANY;
+ }
+ } else {
+ if (null != om && om.isConcrete()) {
+ return MatchPattern.ANY_ANY_OBJ;
+ } else {
+ return MatchPattern.ANY_ANY_ANY;
+ }
+ }
+ }
+
+ }
+}
diff --git a/jena-core/src/main/java/org/apache/jena/mem2/spliterator/ArraySpliterator.java b/jena-core/src/main/java/org/apache/jena/mem2/spliterator/ArraySpliterator.java
new file mode 100644
index 0000000000..b759e9f283
--- /dev/null
+++ b/jena-core/src/main/java/org/apache/jena/mem2/spliterator/ArraySpliterator.java
@@ -0,0 +1,101 @@
+/*
+ * 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.mem2.spliterator;
+
+import java.util.Spliterator;
+import java.util.function.Consumer;
+
+/**
+ * A spliterator for arrays. This spliterator will iterate over the array
+ * entries within the given boundaries.
+ * <p>
+ * This spliterator supports splitting into sub-spliterators.
+ * <p>
+ * The spliterator will check for concurrent modifications by invoking a {@link Runnable}
+ * before each action.
+ *
+ * @param <E>
+ */
+public class ArraySpliterator<E> implements Spliterator<E> {
+
+ private final E[] entries;
+ private final Runnable checkForConcurrentModification;
+ private int pos;
+
+ /**
+ * Create a spliterator for the given array, with the given size.
+ *
+ * @param entries the array
+ * @param toIndex the index of the last element, exclusive
+ * @param checkForConcurrentModification runnable to check for concurrent modifications
+ */
+ public ArraySpliterator(final E[] entries, final int toIndex, final Runnable checkForConcurrentModification) {
+ this.entries = entries;
+ this.pos = toIndex;
+ this.checkForConcurrentModification = checkForConcurrentModification;
+ }
+
+ /**
+ * Create a spliterator for the given array, with the given size.
+ *
+ * @param entries the array
+ * @param checkForConcurrentModification runnable to check for concurrent modifications
+ */
+ public ArraySpliterator(final E[] entries, final Runnable checkForConcurrentModification) {
+ this(entries, entries.length, checkForConcurrentModification);
+ }
+
+ @Override
+ public boolean tryAdvance(Consumer<? super E> action) {
+ this.checkForConcurrentModification.run();
+ if (-1 < --pos) {
+ action.accept(entries[pos]);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void forEachRemaining(Consumer<? super E> action) {
+ while (-1 < --pos) {
+ action.accept(entries[pos]);
+ }
+ this.checkForConcurrentModification.run();
+ }
+
+ @Override
+ public Spliterator<E> trySplit() {
+ if (pos < 2) {
+ return null;
+ }
+ final int toIndexOfSubIterator = this.pos;
+ this.pos = pos >>> 1;
+ return new ArraySubSpliterator<>(entries, this.pos, toIndexOfSubIterator, checkForConcurrentModification);
+ }
+
+ @Override
+ public long estimateSize() {
+ return pos;
+ }
+
+ @Override
+ public int characteristics() {
+ return DISTINCT | SIZED | SUBSIZED | NONNULL | IMMUTABLE;
+ }
+}
diff --git a/jena-core/src/main/java/org/apache/jena/mem2/spliterator/ArraySubSpliterator.java b/jena-core/src/main/java/org/apache/jena/mem2/spliterator/ArraySubSpliterator.java
new file mode 100644
index 0000000000..2ad449797a
--- /dev/null
+++ b/jena-core/src/main/java/org/apache/jena/mem2/spliterator/ArraySubSpliterator.java
@@ -0,0 +1,105 @@
+/*
+ * 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.mem2.spliterator;
+
+import java.util.Spliterator;
+import java.util.function.Consumer;
+
+/**
+ * A spliterator for arrays. This spliterator will iterate over the array
+ * entries within the given boundaries.
+ * <p>
+ * This spliterator supports splitting into sub-spliterators.
+ * <p>
+ * The spliterator will check for concurrent modifications by invoking a {@link Runnable}
+ * before each action.
+ *
+ * @param <E>
+ */
+public class ArraySubSpliterator<E> implements Spliterator<E> {
+
+ private final E[] entries;
+ private final int fromIndex;
+ private final Runnable checkForConcurrentModification;
+ private int pos;
+
+ /**
+ * Create a spliterator for the given array, with the given size.
+ *
+ * @param entries the array
+ * @param fromIndex the index of the first element, inclusive
+ * @param toIndex the index of the last element, exclusive
+ * @param checkForConcurrentModification runnable to check for concurrent modifications
+ */
+ public ArraySubSpliterator(final E[] entries, final int fromIndex, final int toIndex, final Runnable checkForConcurrentModification) {
+ this.entries = entries;
+ this.fromIndex = fromIndex;
+ this.pos = toIndex;
+ this.checkForConcurrentModification = checkForConcurrentModification;
+ }
+
+ /**
+ * Create a spliterator for the given array, with the given size.
+ *
+ * @param entries the array
+ * @param checkForConcurrentModification runnable to check for concurrent modifications
+ */
+ public ArraySubSpliterator(final E[] entries, final Runnable checkForConcurrentModification) {
+ this(entries, 0, entries.length, checkForConcurrentModification);
+ }
+
+ @Override
+ public boolean tryAdvance(Consumer<? super E> action) {
+ this.checkForConcurrentModification.run();
+ if (fromIndex <= --pos) {
+ action.accept(entries[pos]);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void forEachRemaining(Consumer<? super E> action) {
+ while (fromIndex <= --pos) {
+ action.accept(entries[pos]);
+ }
+ this.checkForConcurrentModification.run();
+ }
+
+ @Override
+ public Spliterator<E> trySplit() {
+ final int entriesCount = pos - fromIndex;
+ if (entriesCount < 2) {
+ return null;
+ }
+ final int toIndexOfSubIterator = this.pos;
+ this.pos = fromIndex + (entriesCount >>> 1);
+ return new ArraySubSpliterator<>(entries, this.pos, toIndexOfSubIterator, checkForConcurrentModification);
+ }
+
+ @Override
+ public long estimateSize() {
+ return pos - fromIndex;
+ }
+
+ @Override
+ public int characteristics() {
+ return DISTINCT | SIZED | SUBSIZED | NONNULL | IMMUTABLE;
+ }
+}
diff --git a/jena-core/src/main/java/org/apache/jena/mem2/spliterator/SparseArraySpliterator.java b/jena-core/src/main/java/org/apache/jena/mem2/spliterator/SparseArraySpliterator.java
new file mode 100644
index 0000000000..23efa96c8f
--- /dev/null
+++ b/jena-core/src/main/java/org/apache/jena/mem2/spliterator/SparseArraySpliterator.java
@@ -0,0 +1,106 @@
+/*
+ * 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.mem2.spliterator;
+
+import java.util.Spliterator;
+import java.util.function.Consumer;
+
+/**
+ * A spliterator for sparse arrays. This spliterator will iterate over the array
+ * skipping null entries.
+ * <p>
+ * This spliterator supports splitting into sub-spliterators.
+ * <p>
+ * The spliterator will check for concurrent modifications by invoking a {@link Runnable}
+ * before each action.
+ *
+ * @param <E> the type of the array elements
+ */
+public class SparseArraySpliterator<E> implements Spliterator<E> {
+
+ private final E[] entries;
+ private int pos;
+ private final Runnable checkForConcurrentModification;
+
+ /**
+ * Create a spliterator for the given array, with the given size.
+ *
+ * @param entries the array
+ * @param toIndex the index of the last element, exclusive
+ * @param checkForConcurrentModification runnable to check for concurrent modifications
+ */
+ public SparseArraySpliterator(final E[] entries, final int toIndex, final Runnable checkForConcurrentModification) {
+ this.entries = entries;
+ this.pos = toIndex;
+ this.checkForConcurrentModification = checkForConcurrentModification;
+ }
+
+ /**
+ * Create a spliterator for the given array, with the given size.
+ *
+ * @param entries the array
+ * @param checkForConcurrentModification runnable to check for concurrent modifications
+ */
+ public SparseArraySpliterator(final E[] entries, final Runnable checkForConcurrentModification) {
+ this(entries, entries.length, checkForConcurrentModification);
+ }
+
+
+ @Override
+ public boolean tryAdvance(Consumer<? super E> action) {
+ this.checkForConcurrentModification.run();
+ while (-1 < --pos) {
+ if (null != entries[pos]) {
+ action.accept(entries[pos]);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void forEachRemaining(Consumer<? super E> action) {
+ pos--;
+ while (-1 < pos) {
+ if (null != entries[pos]) {
+ action.accept(entries[pos]);
+ }
+ pos--;
+ }
+ this.checkForConcurrentModification.run();
+ }
+
+ @Override
+ public Spliterator<E> trySplit() {
+ if (pos < 2) {
+ return null;
+ }
+ final int toIndexOfSubIterator = this.pos;
+ this.pos = pos >>> 1;
+ return new SparseArraySubSpliterator<>(entries, this.pos, toIndexOfSubIterator, checkForConcurrentModification);
+ }
+
+ @Override
+ public long estimateSize() { return pos; }
+
+ @Override
+ public int characteristics() {
+ return DISTINCT | NONNULL | IMMUTABLE;
+ }
+}
diff --git a/jena-core/src/main/java/org/apache/jena/mem2/spliterator/SparseArraySubSpliterator.java b/jena-core/src/main/java/org/apache/jena/mem2/spliterator/SparseArraySubSpliterator.java
new file mode 100644
index 0000000000..ac1ffee05b
--- /dev/null
+++ b/jena-core/src/main/java/org/apache/jena/mem2/spliterator/SparseArraySubSpliterator.java
@@ -0,0 +1,113 @@
+/*
+ * 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.mem2.spliterator;
+
+import java.util.Spliterator;
+import java.util.function.Consumer;
+
+/**
+ * A spliterator for sparse arrays. This spliterator will iterate over the array
+ * skipping null entries.
+ * <p>
+ * This spliterator supports splitting into sub-spliterators.
+ * <p>
+ * The spliterator will check for concurrent modifications by invoking a {@link Runnable}
+ * before each action.
+ *
+ * @param <E>
+ */
+public class SparseArraySubSpliterator<E> implements Spliterator<E> {
+
+ private final E[] entries;
+ private final int fromIndex;
+ private final Runnable checkForConcurrentModification;
+ private int pos;
+
+ /**
+ * Create a spliterator for the given array, with the given size.
+ *
+ * @param entries the array
+ * @param fromIndex the index of the first element, inclusive
+ * @param toIndex the index of the last element, exclusive
+ * @param checkForConcurrentModification runnable to check for concurrent modifications
+ */
+ public SparseArraySubSpliterator(final E[] entries, final int fromIndex, final int toIndex, final Runnable checkForConcurrentModification) {
+ this.entries = entries;
+ this.fromIndex = fromIndex;
+ this.pos = toIndex;
+ this.checkForConcurrentModification = checkForConcurrentModification;
+ }
+
+ /**
+ * Create a spliterator for the given array, with the given size.
+ *
+ * @param entries the array
+ * @param checkForConcurrentModification runnable to check for concurrent modifications
+ */
+ public SparseArraySubSpliterator(final E[] entries, final Runnable checkForConcurrentModification) {
+ this(entries, 0, entries.length, checkForConcurrentModification);
+ }
+
+ @Override
+ public boolean tryAdvance(Consumer<? super E> action) {
+ this.checkForConcurrentModification.run();
+ while (fromIndex <= --pos) {
+ if (null != entries[pos]) {
+ action.accept(entries[pos]);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void forEachRemaining(Consumer<? super E> action) {
+ pos--;
+ while (fromIndex <= pos) {
+ if (null != entries[pos]) {
+ action.accept(entries[pos]);
+ }
+ pos--;
+ }
+ this.checkForConcurrentModification.run();
+ }
+
+ @Override
+ public Spliterator<E> trySplit() {
+ final int entriesCount = pos - fromIndex;
+ if (entriesCount < 2) {
+ return null;
+ }
+ final int toIndexOfSubIterator = this.pos;
+ this.pos = fromIndex + (entriesCount >>> 1);
+ return new SparseArraySubSpliterator<>(entries, this.pos, toIndexOfSubIterator, checkForConcurrentModification);
+ }
+
+
+ @Override
+ public long estimateSize() {
+ return pos - fromIndex;
+ }
+
+
+ @Override
+ public int characteristics() {
+ return DISTINCT | NONNULL | IMMUTABLE;
+ }
+}
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
new file mode 100644
index 0000000000..2b67813896
--- /dev/null
+++ b/jena-core/src/main/java/org/apache/jena/mem2/store/TripleStore.java
@@ -0,0 +1,90 @@
+/*
+ * 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.mem2.store;
+
+import org.apache.jena.graph.Triple;
+import org.apache.jena.util.iterator.ExtendedIterator;
+
+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 {
+
+ /**
+ * Add a triple to the map.
+ *
+ * @param triple to add
+ */
+ void add(final Triple triple);
+
+ /**
+ * Remove a triple from the map.
+ *
+ * @param triple to remove
+ */
+ void remove(final Triple triple);
+
+ /**
+ * Remove all triples from the map.
+ */
+ void clear();
+
+ /**
+ * Return the number of triples in the map.
+ */
+ int countTriples();
+
+ /**
+ * Return true if the map is empty.
+ */
+ boolean isEmpty();
+
+
+ /**
+ * Answer true if the graph contains any triple matching <code>t</code>.
+ *
+ * @param tripleMatch triple match pattern, which may be contained
+ */
+ boolean contains(final Triple tripleMatch);
+
+ /**
+ * Returns a {@link Stream} of all triples in the graph.
+ * Note: {@link Stream#parallel()} is supported.
+ *
+ * @return a stream of triples in this graph.
+ */
+ Stream<Triple> stream();
+
+ /**
+ * Returns a {@link Stream} of Triples matching the given pattern.
+ * Note: {@link Stream#parallel()} is supported.
+ *
+ * @param tripleMatch triple match pattern
+ * @return a stream of triples in this graph matching the pattern.
+ */
+ Stream<Triple> stream(final Triple tripleMatch);
+
+ /**
+ * Returns an {@link ExtendedIterator} of all triples in the graph matching the given triple match.
+ */
+ ExtendedIterator<Triple> find(final Triple tripleMatch);
+}
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
new file mode 100644
index 0000000000..57035f4223
--- /dev/null
+++ b/jena-core/src/main/java/org/apache/jena/mem2/store/fast/FastArrayBunch.java
@@ -0,0 +1,192 @@
+/*
+ * 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.mem2.store.fast;
+
+import org.apache.jena.graph.Triple;
+import org.apache.jena.mem2.spliterator.ArraySpliterator;
+import org.apache.jena.util.iterator.ExtendedIterator;
+import org.apache.jena.util.iterator.NiceIterator;
+
+import java.util.ConcurrentModificationException;
+import java.util.NoSuchElementException;
+import java.util.Spliterator;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+/**
+ * An ArrayBunch implements TripleBunch with a linear search of a short-ish
+ * array of Triples. The array grows by factor 2.
+ */
+public abstract class FastArrayBunch implements FastTripleBunch {
+
+ private static final int INITIAL_SIZE = 4;
+
+ protected int size = 0;
+ protected Triple[] elements;
+
+ protected FastArrayBunch() {
+ elements = new Triple[INITIAL_SIZE];
+ }
+
+ public abstract boolean areEqual(final Triple a, final Triple b);
+
+ @Override
+ public boolean containsKey(Triple t) {
+ int i = size;
+ while (i > 0) if (areEqual(t, elements[--i])) return true;
+ return false;
+ }
+
+ @Override
+ public boolean anyMatch(final Predicate<Triple> predicate) {
+ int i = size;
+ while (i > 0) if (predicate.test(elements[--i])) return true;
+ return false;
+ }
+
+ @Override
+ public boolean anyMatchRandomOrder(Predicate<Triple> predicate) {
+ return anyMatch(predicate);
+ }
+
+ @Override
+ public void clear() {
+ this.elements = new Triple[INITIAL_SIZE];
+ this.size = 0;
+ }
+
+ @Override
+ public int size() {
+ return size;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return this.size == 0;
+ }
+
+ @Override
+ public boolean tryAdd(final Triple t) {
+ if (this.containsKey(t)) return false;
+ if (size == elements.length) grow();
+ elements[size++] = t;
+ return true;
+ }
+
+ @Override
+ public void addUnchecked(final Triple t) {
+ if (size == elements.length) grow();
+ elements[size++] = t;
+ }
+
+ /**
+ * Note: linear growth is suboptimal (order n<sup>2</sup>) normally, but
+ * ArrayBunch's are meant for <i>small</i> sets and are replaced by some
+ * sort of hash- or tree- set when they get big; currently "big" means more
+ * than 9 elements, so that's only one growth spurt anyway.
+ */
+ protected void grow() {
+ final var oldElements = elements;
+ elements = new Triple[size << 1];
+ System.arraycopy(oldElements, 0, elements, 0, size);
+ }
+
+ @Override
+ public boolean tryRemove(final Triple t) {
+ for (int i = 0; i < size; i++) {
+ if (areEqual(t, elements[i])) {
+ elements[i] = elements[--size];
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void removeUnchecked(final Triple t) {
+ for (int i = 0; i < size; i++) {
+ if (areEqual(t, elements[i])) {
+ elements[i] = elements[--size];
+ return;
+ }
+ }
+ }
+
+ @Override
+ public ExtendedIterator<Triple> keyIterator() {
+ return new NiceIterator<>() {
+ private final int initialSize = size;
+
+ private int i = size;
+
+ @Override
+ public boolean hasNext() {
+ return 0 < i;
+ }
+
+ @Override
+ public Triple next() {
+ if (size != initialSize) throw new ConcurrentModificationException();
+ if (i == 0) throw new NoSuchElementException();
+ return elements[--i];
+ }
+
+ @Override
+ public void forEachRemaining(Consumer<? super Triple> action) {
+ while (0 < i--) action.accept(elements[i]);
+ if (size != initialSize) throw new ConcurrentModificationException();
+ }
+ };
+
+ }
+
+
+ @Override
+ public Spliterator<Triple> keySpliterator() {
+ final var initialSize = size;
+ final Runnable checkForConcurrentModification = () -> {
+ if (size != initialSize) throw new ConcurrentModificationException();
+ };
+ return new ArraySpliterator<>(elements, size, checkForConcurrentModification);
+ }
+
+ @Override
+ public boolean isArray() {
+ return true;
+ }
+
+ @Override
+ public boolean tryAdd(Triple key, int hashCode) {
+ return tryAdd(key);
+ }
+
+ @Override
+ public void addUnchecked(Triple key, int hashCode) {
+ addUnchecked(key);
+ }
+
+ @Override
+ public boolean tryRemove(Triple key, int hashCode) {
+ return tryRemove(key);
+ }
+
+ @Override
+ public void removeUnchecked(Triple key, int hashCode) {
+ removeUnchecked(key);
+ }
+}
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java b/jena-core/src/main/java/org/apache/jena/mem2/store/fast/FastHashedBunchMap.java
similarity index 61%
copy from jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java
copy to jena-core/src/main/java/org/apache/jena/mem2/store/fast/FastHashedBunchMap.java
index f601955053..a9e598dce6 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java
+++ b/jena-core/src/main/java/org/apache/jena/mem2/store/fast/FastHashedBunchMap.java
@@ -15,18 +15,27 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.jena.mem.graph.helper;
+package org.apache.jena.mem2.store.fast;
-import org.apache.jena.graph.Graph;
import org.apache.jena.graph.Node;
-import org.apache.jena.graph.Triple;
+import org.apache.jena.mem2.collection.FastHashMap;
/**
- * Proviced the release specific access to the helper classes for working with graphs, triples and nodes.
+ * Map from nodes to triple bunches.
*/
-public class Releases {
+public class FastHashedBunchMap extends FastHashMap<Node, FastTripleBunch> {
- public static GraphTripleNodeHelper<Graph, Triple, Node> current = new GraphTripleNodeHelperCurrent();
- public static GraphTripleNodeHelper<org.apache.shadedJena480.graph.Graph, org.apache.shadedJena480.graph.Triple, org.apache.shadedJena480.graph.Node> v480 = new GraphTripleNodeHelper480();
+ public FastHashedBunchMap() {
+ super();
+ }
+ @Override
+ protected Node[] newKeysArray(int size) {
+ return new Node[size];
+ }
+
+ @Override
+ protected FastTripleBunch[] newValuesArray(int size) {
+ return new FastTripleBunch[size];
+ }
}
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
new file mode 100644
index 0000000000..752a57971c
--- /dev/null
+++ b/jena-core/src/main/java/org/apache/jena/mem2/store/fast/FastHashedTripleBunch.java
@@ -0,0 +1,51 @@
+/*
+ * 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.mem2.store.fast;
+
+import org.apache.jena.graph.Triple;
+import org.apache.jena.mem2.collection.FastHashSet;
+import org.apache.jena.mem2.collection.JenaSet;
+
+/**
+ * A set of triples - backed by {@link FastHashSet}.
+ */
+public class FastHashedTripleBunch extends FastHashSet<Triple> implements FastTripleBunch {
+ /**
+ * Create a new triple bunch from the given set of triples.
+ *
+ * @param set the set of triples
+ */
+ public FastHashedTripleBunch(final JenaSet<Triple> set) {
+ super((set.size() >> 1) + set.size()); //it should not only fit but also have some space for growth
+ set.keyIterator().forEachRemaining(this::addUnchecked);
+ }
+
+ public FastHashedTripleBunch() {
+ super();
+ }
+
+ @Override
+ protected Triple[] newKeysArray(int size) {
+ return new Triple[size];
+ }
+
+ @Override
+ public boolean isArray() {
+ return false;
+ }
+}
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
new file mode 100644
index 0000000000..116873f1db
--- /dev/null
+++ b/jena-core/src/main/java/org/apache/jena/mem2/store/fast/FastTripleBunch.java
@@ -0,0 +1,51 @@
+/*
+ * 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.mem2.store.fast;
+
+import org.apache.jena.graph.Triple;
+import org.apache.jena.mem2.collection.JenaMapSetCommon;
+import org.apache.jena.mem2.collection.JenaSetHashOptimized;
+
+import java.util.function.Predicate;
+
+/**
+ * A bunch of triples - a stripped-down set with specialized methods. A
+ * 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> {
+ /**
+ * 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.
+ *
+ * @return true iff this bunch is implemented as an arrays
+ */
+ boolean isArray();
+
+ /**
+ * This method is used to optimize _PO match operations.
+ * The {@link JenaMapSetCommon#anyMatch(Predicate)} method is faster if there are only a few matches.
+ * This method is faster if there are many matches and the set is ordered in an unfavorable way.
+ * _PO matches usually fall into this category.
+ *
+ * @param predicate the predicate to match
+ * @return true if any triple in the bunch matches the predicate
+ */
+ boolean anyMatchRandomOrder(Predicate<Triple> predicate);
+}
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
new file mode 100644
index 0000000000..00e1c48818
--- /dev/null
+++ b/jena-core/src/main/java/org/apache/jena/mem2/store/fast/FastTripleStore.java
@@ -0,0 +1,386 @@
+/*
+ * 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.mem2.store.fast;
+
+import org.apache.jena.graph.Triple;
+import org.apache.jena.mem2.collection.FastHashMap;
+import org.apache.jena.mem2.iterator.IteratorOfJenaSets;
+import org.apache.jena.mem2.pattern.PatternClassifier;
+import org.apache.jena.mem2.store.TripleStore;
+import org.apache.jena.util.iterator.ExtendedIterator;
+import org.apache.jena.util.iterator.NiceIterator;
+import org.apache.jena.util.iterator.SingletonIterator;
+
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+/**
+ * A triple store that uses hash tables to map from nodes to triple bunches.
+ * <p>
+ * Inner structure:
+ * - three {@link FastHashMap}, one for each node (subject, predicate, object) in the triple
+ * - each map maps from a node to a {@link FastTripleBunch}
+ * - for up to 16 triples with the same subject, the bunch is an {@link FastArrayBunch}, otherwise it is
+ * a {@link FastHashedTripleBunch}
+ * - for up to 32 triples with the same predicate and object, the bunch is an {@link FastArrayBunch}, otherwise it is
+ * a {@link FastHashedTripleBunch}
+ * <p>
+ * Other optimizations:
+ * - each triple is added to three {@link FastTripleBunch}es. To avoid the overhead of calculating the hash code three
+ * times, the hash code is calculated once and passed to the {@link FastTripleBunch}es.
+ * - the different sizes for the {@link FastArrayBunch}es are chosen:
+ * - to avoid the memory-overhead of {@link FastHashedTripleBunch} for a small number of triples
+ * - the subject bunch is smaller than the predicate and object bunches, because the subject is typically used for
+ * #contains operations, which are faster for {@link FastHashedTripleBunch}es.
+ * - the predicate and object bunches are the same size, because they are typically used for #find operations, which
+ * typically do not answer #contains operations. Making them much larger might slow down #remove operations on the
+ * graph.
+ * - "ANY_PRE_OBJ" matches primarily use the object bunch unless the size of the bunch is larger than 400 triples. In
+ * that case, there is a secondary lookup in the predicate bunch. Then the smaller bunch is used for the lookup.
+ * Especially for RDF graphs there are some very common object nodes like "true"/"false" or "0"/"1". In that case,
+ * a secondary lookup in the predicate bunch might be faster than a primary lookup in the object bunch.
+ * - FastTripleStore#contains uses {@link org.apache.jena.mem2.store.fast.FastTripleBunch#anyMatchRandomOrder} for
+ * "ANY_PRE_OBJ" lookups. This is only faster than {@link org.apache.jena.mem2.collection.JenaMapSetCommon#anyMatch}
+ * if there are many matches and the set is ordered in an unfavorable way. "ANY_PRE_OBJ" matches usually fall into
+ * this category. This optimization was only needed because the {@link FastTripleBunch}es does not use the random
+ * order of the triples in #anyMatch but the ordered dense array of triples, which is faster if there are only a few
+ * matches.
+ * - for the FastArrayBunches, the equals method of the triple is not called. Instead, only the two nodes that are
+ * not part of the key of the containing map are compared.
+ */
+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();
+ private int size = 0;
+
+ @Override
+ public void add(Triple triple) {
+ final int hashCodeOfTriple = triple.hashCode();
+ final boolean added;
+ var sBunch = subjects.get(triple.getSubject());
+ if (sBunch == null) {
+ sBunch = new ArrayBunchWithSameSubject();
+ sBunch.addUnchecked(triple, hashCodeOfTriple);
+ subjects.put(triple.getSubject(), sBunch);
+ added = true;
+ } else {
+ if (sBunch.isArray() && sBunch.size() == MAX_ARRAY_BUNCH_SIZE_SUBJECT) {
+ sBunch = new FastHashedTripleBunch(sBunch);
+ subjects.put(triple.getSubject(), sBunch);
+ }
+ added = sBunch.tryAdd(triple, hashCodeOfTriple);
+ }
+ if (added) {
+ size++;
+ var pBunch = predicates.computeIfAbsent(triple.getPredicate(), ArrayBunchWithSamePredicate::new);
+ if (pBunch.isArray() && pBunch.size() == MAX_ARRAY_BUNCH_SIZE_PREDICATE_OBJECT) {
+ pBunch = new FastHashedTripleBunch(pBunch);
+ predicates.put(triple.getPredicate(), pBunch);
+ }
+ pBunch.addUnchecked(triple, hashCodeOfTriple);
+ var oBunch = objects.computeIfAbsent(triple.getObject(), ArrayBunchWithSameObject::new);
+ if (oBunch.isArray() && oBunch.size() == MAX_ARRAY_BUNCH_SIZE_PREDICATE_OBJECT) {
+ oBunch = new FastHashedTripleBunch(oBunch);
+ objects.put(triple.getObject(), oBunch);
+ }
+ oBunch.addUnchecked(triple, hashCodeOfTriple);
+ }
+ }
+
+ @Override
+ public void remove(Triple triple) {
+ final int hashCodeOfTriple = triple.hashCode();
+ final var sBunch = subjects.get(triple.getSubject());
+ if (sBunch == null)
+ return;
+
+ if (sBunch.tryRemove(triple, hashCodeOfTriple)) {
+ if (sBunch.isEmpty()) {
+ subjects.removeUnchecked(triple.getSubject());
+ }
+ final var pBunch = predicates.get(triple.getPredicate());
+ pBunch.removeUnchecked(triple, hashCodeOfTriple);
+ if (pBunch.isEmpty()) {
+ predicates.removeUnchecked(triple.getPredicate());
+ }
+ final var oBunch = objects.get(triple.getObject());
+ oBunch.removeUnchecked(triple, hashCodeOfTriple);
+ if (oBunch.isEmpty()) {
+ objects.removeUnchecked(triple.getObject());
+ }
+ size--;
+ }
+ }
+
+ @Override
+ public void clear() {
+ subjects.clear();
+ predicates.clear();
+ objects.clear();
+ size = 0;
+ }
+
+ @Override
+ public int countTriples() {
+ return size;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return size == 0;
+ }
+
+ @Override
+ public boolean contains(Triple tripleMatch) {
+ switch (PatternClassifier.classify(tripleMatch)) {
+
+ case SUB_PRE_OBJ: {
+ final var triples = subjects.get(tripleMatch.getSubject());
+ if (triples == null) {
+ return false;
+ }
+ return triples.containsKey(tripleMatch);
+ }
+
+ case SUB_PRE_ANY: {
+ final var triplesBySubject = subjects.get(tripleMatch.getSubject());
+ if (triplesBySubject == null) {
+ return false;
+ }
+ return triplesBySubject.anyMatch(t -> tripleMatch.getPredicate().equals(t.getPredicate()));
+ }
+
+ case SUB_ANY_OBJ: {
+ final var triplesBySubject = subjects.get(tripleMatch.getSubject());
+ if (triplesBySubject == null) {
+ return false;
+ }
+ return triplesBySubject.anyMatch(t -> tripleMatch.getObject().equals(t.getObject()));
+ }
+
+ case SUB_ANY_ANY:
+ return subjects.containsKey(tripleMatch.getSubject());
+
+ case ANY_PRE_OBJ: {
+ final var triplesByObject = objects.get(tripleMatch.getObject());
+ if (triplesByObject == null) {
+ return false;
+ }
+ // Optimization for typical RDF data, where there may be common values like "0" or "false"/"true".
+ // In this case, there may be many matches but due to the ordered nature of FastHashBase,
+ // the same predicates are often grouped together. If they are at the beginning of the bunch,
+ // we can avoid the linear scan of the bunch. This is a common case for RDF data.
+ // #anyMatchRandomOrder is a bit slower if the predicate is not found than #anyMatch, but not by much.
+ if (triplesByObject.size() > THRESHOLD_FOR_SECONDARY_LOOKUP) {
+ final var triplesByPredicate = predicates.get(tripleMatch.getPredicate());
+ if (triplesByPredicate == null) {
+ return false;
+ }
+ if (triplesByPredicate.size() < triplesByObject.size()) {
+ return triplesByPredicate.anyMatchRandomOrder(t -> tripleMatch.getObject().equals(t.getObject()));
+ }
+ }
+ return triplesByObject.anyMatchRandomOrder(t -> tripleMatch.getPredicate().equals(t.getPredicate()));
+ }
+
+ case ANY_PRE_ANY:
+ return predicates.containsKey(tripleMatch.getPredicate());
+
+ case ANY_ANY_OBJ:
+ return objects.containsKey(tripleMatch.getObject());
+
+ case ANY_ANY_ANY:
+ return !isEmpty();
+
+ default:
+ throw new IllegalStateException(String.format("Unexpected value: %s", PatternClassifier.classify(tripleMatch)));
+ }
+ }
+
+ @Override
+ public Stream<Triple> stream() {
+ return StreamSupport.stream(subjects.valueSpliterator(), false)
+ .flatMap(bunch -> StreamSupport.stream(bunch.keySpliterator(), false));
+ }
+
+ @Override
+ public Stream<Triple> stream(Triple tripleMatch) {
+ switch (PatternClassifier.classify(tripleMatch)) {
+
+ case SUB_PRE_OBJ: {
+ final var triples = subjects.get(tripleMatch.getSubject());
+ if (triples == null) {
+ return Stream.empty();
+ }
+ return triples.containsKey(tripleMatch) ? Stream.of(tripleMatch) : Stream.empty();
+ }
+
+ case SUB_PRE_ANY: {
+ final var triplesBySubject = subjects.get(tripleMatch.getSubject());
+ if (triplesBySubject == null) {
+ return Stream.empty();
+ }
+ return triplesBySubject.keyStream().filter(t -> tripleMatch.getPredicate().equals(t.getPredicate()));
+ }
+
+ case SUB_ANY_OBJ: {
+ final var triplesBySubject = subjects.get(tripleMatch.getSubject());
+ if (triplesBySubject == null) {
+ return Stream.empty();
+ }
+ return triplesBySubject.keyStream().filter(t -> tripleMatch.getObject().equals(t.getObject()));
+ }
+
+ case SUB_ANY_ANY: {
+ final var triples = subjects.get(tripleMatch.getSubject());
+ return triples == null ? Stream.empty() : triples.keyStream();
+ }
+
+ case ANY_PRE_OBJ: {
+ final var triplesByObject = objects.get(tripleMatch.getObject());
+ if (triplesByObject == null) {
+ return Stream.empty();
+ }
+ if (triplesByObject.size() > THRESHOLD_FOR_SECONDARY_LOOKUP) {
+ final var triplesByPredicate = predicates.get(tripleMatch.getPredicate());
+ if (triplesByPredicate == null) {
+ return Stream.empty();
+ }
+ if (triplesByPredicate.size() < triplesByObject.size()) {
+ return triplesByPredicate.keyStream().filter(t -> tripleMatch.getObject().equals(t.getObject()));
+ }
+ }
+ return triplesByObject.keyStream().filter(t -> tripleMatch.getPredicate().equals(t.getPredicate()));
+ }
+
+ case ANY_PRE_ANY: {
+ final var triples = predicates.get(tripleMatch.getPredicate());
+ return triples == null ? Stream.empty() : triples.keyStream();
+ }
+
+ case ANY_ANY_OBJ: {
+ final var triples = objects.get(tripleMatch.getObject());
+ return triples == null ? Stream.empty() : triples.keyStream();
+ }
+
+ case ANY_ANY_ANY:
+ return stream();
+
+ default:
+ throw new IllegalStateException("Unexpected value: " + PatternClassifier.classify(tripleMatch));
+ }
+ }
+
+ @Override
+ public ExtendedIterator<Triple> find(Triple tripleMatch) {
+ switch (PatternClassifier.classify(tripleMatch)) {
+
+ case SUB_PRE_OBJ: {
+ final var triples = subjects.get(tripleMatch.getSubject());
+ if (triples == null) {
+ return NiceIterator.emptyIterator();
+ }
+ return triples.containsKey(tripleMatch) ? new SingletonIterator<>(tripleMatch) : NiceIterator.emptyIterator();
+ }
+
+ case SUB_PRE_ANY: {
+ final var triplesBySubject = subjects.get(tripleMatch.getSubject());
+ if (triplesBySubject == null) {
+ return NiceIterator.emptyIterator();
+ }
+ return triplesBySubject.keyIterator().filterKeep(t -> tripleMatch.getPredicate().equals(t.getPredicate()));
+ }
+
+ case SUB_ANY_OBJ: {
+ final var triplesBySubject = subjects.get(tripleMatch.getSubject());
+ if (triplesBySubject == null) {
+ return NiceIterator.emptyIterator();
+ }
+ return triplesBySubject.keyIterator().filterKeep(t -> tripleMatch.getObject().equals(t.getObject()));
+ }
+
+ case SUB_ANY_ANY: {
+ final var triples = subjects.get(tripleMatch.getSubject());
+ return triples == null ? NiceIterator.emptyIterator() : triples.keyIterator();
+ }
+
+ case ANY_PRE_OBJ: {
+ final var triplesByObject = objects.get(tripleMatch.getObject());
+ if (triplesByObject == null) {
+ return NiceIterator.emptyIterator();
+ }
+ if (triplesByObject.size() > THRESHOLD_FOR_SECONDARY_LOOKUP) {
+ final var triplesByPredicate = predicates.get(tripleMatch.getPredicate());
+ if (triplesByPredicate == null) {
+ return NiceIterator.emptyIterator();
+ }
+ if (triplesByPredicate.size() < triplesByObject.size()) {
+ return triplesByPredicate.keyIterator().filterKeep(t -> tripleMatch.getObject().equals(t.getObject()));
+ }
+ }
+ return triplesByObject.keyIterator().filterKeep(t -> tripleMatch.getPredicate().equals(t.getPredicate()));
+ }
+
+ case ANY_PRE_ANY: {
+ final var triples = predicates.get(tripleMatch.getPredicate());
+ return triples == null ? NiceIterator.emptyIterator() : triples.keyIterator();
+ }
+
+ case ANY_ANY_OBJ: {
+ final var triples = objects.get(tripleMatch.getObject());
+ return triples == null ? NiceIterator.emptyIterator() : triples.keyIterator();
+ }
+
+ case ANY_ANY_ANY:
+ return new IteratorOfJenaSets<>(subjects.valueIterator());
+
+ default:
+ throw new IllegalStateException("Unexpected value: " + PatternClassifier.classify(tripleMatch));
+ }
+ }
+
+ protected static class ArrayBunchWithSameSubject extends FastArrayBunch {
+
+ @Override
+ public boolean areEqual(final Triple a, final Triple b) {
+ return a.getPredicate().equals(b.getPredicate())
+ && a.getObject().equals(b.getObject());
+ }
+ }
+
+ protected static class ArrayBunchWithSamePredicate extends FastArrayBunch {
+ @Override
+ public boolean areEqual(final Triple a, final Triple b) {
+ return a.getSubject().equals(b.getSubject())
+ && a.getObject().equals(b.getObject());
+ }
+ }
+
+ protected static class ArrayBunchWithSameObject extends FastArrayBunch {
+ @Override
+ public boolean areEqual(final Triple a, final Triple b) {
+ return a.getSubject().equals(b.getSubject())
+ && a.getPredicate().equals(b.getPredicate());
+ }
+ }
+}
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
new file mode 100644
index 0000000000..83ca78aa3f
--- /dev/null
+++ b/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/ArrayBunch.java
@@ -0,0 +1,166 @@
+/*
+ * 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.mem2.store.legacy;
+
+import org.apache.jena.graph.Triple;
+import org.apache.jena.mem2.spliterator.ArraySpliterator;
+import org.apache.jena.util.iterator.ExtendedIterator;
+import org.apache.jena.util.iterator.NiceIterator;
+
+import java.util.ConcurrentModificationException;
+import java.util.NoSuchElementException;
+import java.util.Spliterator;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+/**
+ * An ArrayBunch implements TripleBunch with a linear search of a short-ish
+ * array of Triples. The array can grow, but it only grows by 4 elements each time
+ * (because, if it gets big enough for this linear growth to be bad, it should always
+ * have been replaced by a more efficient set-of-triples implementation).
+ */
+public class ArrayBunch implements TripleBunch {
+
+ private static final int INITIAL_SIZE = 5;
+
+ protected int size = 0;
+ protected Triple[] elements;
+
+ public ArrayBunch() {
+ elements = new Triple[INITIAL_SIZE];
+ }
+
+ @Override
+ public boolean containsKey(Triple t) {
+ int i = size;
+ while (i > 0) if (t.equals(elements[--i])) return true;
+ return false;
+ }
+
+ @Override
+ public boolean anyMatch(Predicate<Triple> predicate) {
+ int i = size;
+ while (i > 0) if (predicate.test(elements[--i])) return true;
+ return false;
+ }
+
+ @Override
+ public void clear() {
+ this.elements = new Triple[INITIAL_SIZE];
+ this.size = 0;
+ }
+
+ @Override
+ public int size() {
+ return size;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return this.size == 0;
+ }
+
+ @Override
+ public boolean tryAdd(Triple t) {
+ if (this.containsKey(t)) return false;
+ if (size == elements.length) grow();
+ elements[size++] = t;
+ return true;
+ }
+
+ @Override
+ public void addUnchecked(Triple t) {
+ if (size == elements.length) grow();
+ elements[size++] = t;
+ }
+
+ /**
+ * Note: linear growth is suboptimal (order n<sup>2</sup>) normally, but
+ * ArrayBunch's are meant for <i>small</i> sets and are replaced by some
+ * sort of hash- or tree- set when they get big; currently "big" means more
+ * than 9 elements, so that's only one growth spurt anyway.
+ */
+ protected void grow() {
+ final var oldElements = elements;
+ elements = new Triple[size + 4];
+ System.arraycopy(oldElements, 0, elements, 0, size);
+ }
+
+ @Override
+ public boolean tryRemove(Triple t) {
+ for (int i = 0; i < size; i += 1) {
+ if (t.equals(elements[i])) {
+ elements[i] = elements[--size];
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void removeUnchecked(Triple t) {
+ for (int i = 0; i < size; i += 1) {
+ if (t.equals(elements[i])) {
+ elements[i] = elements[--size];
+ return;
+ }
+ }
+ }
+
+ @Override
+ public ExtendedIterator<Triple> keyIterator() {
+ return new NiceIterator<>() {
+ private final int initialSize = size;
+
+ private int i = size;
+
+ @Override
+ public boolean hasNext() {
+ return 0 < i;
+ }
+
+ @Override
+ public Triple next() {
+ if (size != initialSize) throw new ConcurrentModificationException();
+ if (i == 0) throw new NoSuchElementException();
+ return elements[--i];
+ }
+
+ @Override
+ public void forEachRemaining(Consumer<? super Triple> action) {
+ while (0 < i--) action.accept(elements[i]);
+ if (size != initialSize) throw new ConcurrentModificationException();
+ }
+ };
+ }
+
+ @Override
+ public Spliterator<Triple> keySpliterator() {
+ final var initialSize = size;
+ final Runnable checkForConcurrentModification = () -> {
+ if (size != initialSize) throw new ConcurrentModificationException();
+ };
+ return new ArraySpliterator<>(elements, size, checkForConcurrentModification);
+ }
+
+ @Override
+ public boolean isArray() {
+ return true;
+ }
+}
diff --git a/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/FieldFilter.java b/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/FieldFilter.java
new file mode 100644
index 0000000000..3111803c03
--- /dev/null
+++ b/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/FieldFilter.java
@@ -0,0 +1,69 @@
+/*
+ * 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.mem2.store.legacy;
+
+import org.apache.jena.graph.Node;
+import org.apache.jena.graph.Triple;
+
+import java.util.function.Predicate;
+
+/**
+ * A class that encapsulates a filter on fields on a triple.
+ * <p>
+ * The filter is a predicate that takes a triple and returns true if it passes
+ * the filter and false otherwise.
+ * </p>
+ */
+public class FieldFilter {
+
+ public static final FieldFilter EMPTY = new FieldFilter();
+
+ private final Predicate<Triple> filter;
+
+ private final boolean hasFilter;
+
+ private FieldFilter(Predicate<Triple> filter) {
+ this.filter = filter;
+ this.hasFilter = true;
+ }
+
+ private FieldFilter() {
+ this.filter = null;
+ this.hasFilter = false;
+ }
+
+ public static FieldFilter filterOn(Triple.Field f1, Node n1, Triple.Field f2, Node n2) {
+ if (n1.isConcrete()) {
+ if (n2.isConcrete()) {
+ return new FieldFilter(t -> n1.equals(f1.getField(t)) && n2.equals(f2.getField(t)));
+ }
+ return new FieldFilter(t -> n1.equals(f1.getField(t)));
+ } else if (n2.isConcrete()) {
+ return new FieldFilter(t -> n2.equals(f2.getField(t)));
+ }
+ return FieldFilter.EMPTY;
+ }
+
+ public boolean hasFilter() {
+ return hasFilter;
+ }
+
+ public Predicate<Triple> getFilter() {
+ return filter;
+ }
+}
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java b/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/HashedBunchMap.java
similarity index 60%
copy from jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java
copy to jena-core/src/main/java/org/apache/jena/mem2/store/legacy/HashedBunchMap.java
index f601955053..d719d74772 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java
+++ b/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/HashedBunchMap.java
@@ -15,18 +15,32 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.jena.mem.graph.helper;
+package org.apache.jena.mem2.store.legacy;
-import org.apache.jena.graph.Graph;
import org.apache.jena.graph.Node;
-import org.apache.jena.graph.Triple;
+import org.apache.jena.mem2.collection.HashCommonMap;
/**
- * Proviced the release specific access to the helper classes for working with graphs, triples and nodes.
+ * A map from nodes to bunches of triples.
*/
-public class Releases {
+public class HashedBunchMap extends HashCommonMap<Node, TripleBunch> {
- public static GraphTripleNodeHelper<Graph, Triple, Node> current = new GraphTripleNodeHelperCurrent();
- public static GraphTripleNodeHelper<org.apache.shadedJena480.graph.Graph, org.apache.shadedJena480.graph.Triple, org.apache.shadedJena480.graph.Node> v480 = new GraphTripleNodeHelper480();
+ public HashedBunchMap() {
+ super(10);
+ }
+ @Override
+ protected Node[] newKeysArray(int size) {
+ return new Node[size];
+ }
+
+ @Override
+ protected TripleBunch[] newValuesArray(int size) {
+ return new TripleBunch[size];
+ }
+
+ @Override
+ public void clear() {
+ super.clear(10);
+ }
}
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java b/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/HashedTripleBunch.java
similarity index 52%
copy from jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java
copy to jena-core/src/main/java/org/apache/jena/mem2/store/legacy/HashedTripleBunch.java
index f601955053..9bf8eb3e6a 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java
+++ b/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/HashedTripleBunch.java
@@ -15,18 +15,37 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.jena.mem.graph.helper;
+package org.apache.jena.mem2.store.legacy;
-import org.apache.jena.graph.Graph;
-import org.apache.jena.graph.Node;
import org.apache.jena.graph.Triple;
+import org.apache.jena.mem2.collection.HashCommonSet;
+import org.apache.jena.mem2.collection.JenaSet;
/**
- * Proviced the release specific access to the helper classes for working with graphs, triples and nodes.
+ * A bunch of triples, implemented as a set of triples.
*/
-public class Releases {
+public class HashedTripleBunch extends HashCommonSet<Triple> implements TripleBunch {
+ protected HashedTripleBunch(final JenaSet<Triple> b) {
+ super(nextSize((int) (b.size() / LOAD_FACTOR)));
+ b.keyIterator().forEachRemaining(this::addUnchecked);
+ }
- public static GraphTripleNodeHelper<Graph, Triple, Node> current = new GraphTripleNodeHelperCurrent();
- public static GraphTripleNodeHelper<org.apache.shadedJena480.graph.Graph, org.apache.shadedJena480.graph.Triple, org.apache.shadedJena480.graph.Node> v480 = new GraphTripleNodeHelper480();
+ public HashedTripleBunch() {
+ super(8);
+ }
+ @Override
+ protected Triple[] newKeysArray(int size) {
+ return new Triple[size];
+ }
+
+ @Override
+ public void clear() {
+ super.clear(8);
+ }
+
+ @Override
+ public boolean isArray() {
+ return false;
+ }
}
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
new file mode 100644
index 0000000000..a8a9e91980
--- /dev/null
+++ b/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/LegacyTripleStore.java
@@ -0,0 +1,152 @@
+/*
+ * 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.mem2.store.legacy;
+
+import org.apache.jena.graph.Node;
+import org.apache.jena.graph.Triple;
+import org.apache.jena.mem2.collection.HashCommonMap;
+import org.apache.jena.mem2.collection.JenaSet;
+import org.apache.jena.mem2.store.TripleStore;
+import org.apache.jena.util.iterator.ExtendedIterator;
+import org.apache.jena.util.iterator.NiceIterator;
+import org.apache.jena.util.iterator.SingletonIterator;
+
+import java.util.stream.Stream;
+
+/**
+ * Successor of {@link org.apache.jena.mem.GraphTripleStoreMem} that uses term-equality
+ * instead of literal value equality.
+ * This implementation also does not support {@link java.util.Iterator#remove()}.
+ * <p>
+ * Inner structure:
+ * - three {@link NodeToTriplesMapMem} instances for each of the three triple fields (subject, predicate, object)
+ * - each of these maps is a {@link HashCommonMap} with {@link Node} keys and {@link JenaSet} values.
+ * - for up to 9 triples with the same subject, predicate or object, the {@link JenaSet} is
+ * a {@link ArrayBunch}, otherwise it is a {@link HashedTripleBunch}.
+ * <p>
+ * Additional optimizations:
+ * - because we know that if a triple exists in one of the maps, it also exists in the other two, we can use the
+ * {@link org.apache.jena.mem2.collection.JenaSet#addUnchecked(java.lang.Object)} and
+ * {@link org.apache.jena.mem2.collection.JenaMapSetCommon#removeUnchecked(java.lang.Object)} methods to avoid
+ * unnecessary checks.
+ */
+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);
+
+ @Override
+ public void add(Triple triple) {
+ if (subjects.tryAdd(triple)) {
+ predicates.addUnchecked(triple);
+ objects.addUnchecked(triple);
+ }
+ }
+
+ @Override
+ public void remove(Triple triple) {
+ if (subjects.tryRemove(triple)) {
+ predicates.removeUnchecked(triple);
+ objects.removeUnchecked(triple);
+ }
+ }
+
+ @Override
+ public void clear() {
+ subjects.clear();
+ predicates.clear();
+ objects.clear();
+ }
+
+ @Override
+ public int countTriples() {
+ return subjects.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return subjects.isEmpty();
+ }
+
+ @Override
+ public boolean contains(Triple tripleMatch) {
+ if (tripleMatch.isConcrete()) {
+ return subjects.containsKey(tripleMatch);
+ }
+
+ final Node pm = tripleMatch.getPredicate();
+ final Node om = tripleMatch.getObject();
+ final Node sm = tripleMatch.getSubject();
+ if (sm.isConcrete())
+ return subjects.containsMatch(sm, pm, om);
+ else if (om.isConcrete())
+ return objects.containsMatch(om, sm, pm);
+ else if (pm.isConcrete())
+ return predicates.containsMatch(pm, om, sm);
+ else
+ return !this.isEmpty();
+ }
+
+ @Override
+ public Stream<Triple> stream() {
+ return subjects.keyStream();
+ }
+
+ @Override
+ public Stream<Triple> stream(Triple tripleMatch) {
+ if (tripleMatch.isConcrete()) {
+ return subjects.containsKey(tripleMatch) ? Stream.of(tripleMatch) : Stream.empty();
+ }
+
+ final Node pm = tripleMatch.getPredicate();
+ final Node om = tripleMatch.getObject();
+ final Node sm = tripleMatch.getSubject();
+
+ if (sm.isConcrete())
+ return subjects.streamForMatches(sm, pm, om);
+ else if (om.isConcrete())
+ return objects.streamForMatches(om, sm, pm);
+ else if (pm.isConcrete())
+ return predicates.streamForMatches(pm, om, sm);
+ else
+ return subjects.keyStream();
+ }
+
+ @Override
+ public ExtendedIterator<Triple> find(Triple tripleMatch) {
+ if (tripleMatch.isConcrete()) {
+ return subjects.containsKey(tripleMatch) ? new SingletonIterator<>(tripleMatch) : NiceIterator.emptyIterator();
+ }
+ final Node pm = tripleMatch.getPredicate();
+ final Node om = tripleMatch.getObject();
+ final Node sm = tripleMatch.getSubject();
+
+ if (sm.isConcrete())
+ return subjects.iteratorForMatches(sm, pm, om);
+ else if (om.isConcrete())
+ return objects.iteratorForMatches(om, sm, pm);
+ else if (pm.isConcrete())
+ return predicates.iteratorForMatches(pm, om, sm);
+ else
+ return subjects.keyIterator();
+ }
+}
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
new file mode 100644
index 0000000000..d2bd89b966
--- /dev/null
+++ b/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/NodeToTriplesMap.java
@@ -0,0 +1,61 @@
+/*
+ * 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.mem2.store.legacy;
+
+import org.apache.jena.graph.Node;
+import org.apache.jena.graph.Triple;
+import org.apache.jena.mem2.collection.JenaSet;
+import org.apache.jena.util.iterator.ExtendedIterator;
+
+import java.util.stream.Stream;
+
+/**
+ * A map from a node to the triples that have that node as subject, predicate or object.
+ */
+public interface NodeToTriplesMap extends JenaSet<Triple> {
+
+ /**
+ * Answer an iterator over all the triples in this map that match the pattern.
+ *
+ * @param index The node to match as key.
+ * @param n2 A node to match, or Node.ANY.
+ * @param n3 A node to match, or Node.ANY.
+ * @return An iterator over all the triples in this map that match the pattern.
+ */
+ ExtendedIterator<Triple> iteratorForMatches(Node index, Node n2, Node n3);
+
+ /**
+ * Answer a stream over all the triples in this map that match the pattern.
+ *
+ * @param index The node to match as key.
+ * @param n2 A node to match, or Node.ANY.
+ * @param n3 A node to match, or Node.ANY.
+ * @return A stream over all the triples in this map that match the pattern.
+ */
+ Stream<Triple> streamForMatches(Node index, Node n2, Node n3);
+
+ /**
+ * Answer true iff this map contains a triple that matches the pattern.
+ *
+ * @param index The node to match as key.
+ * @param n2 A node to match, or Node.ANY.
+ * @param n3 A node to match, or Node.ANY.
+ * @return True iff this map contains a triple that matches the pattern.
+ */
+ boolean containsMatch(Node index, Node n2, Node n3);
+}
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
new file mode 100644
index 0000000000..bb66bc1555
--- /dev/null
+++ b/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/NodeToTriplesMapMem.java
@@ -0,0 +1,201 @@
+/*
+ * 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.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;
+
+import java.util.Spliterator;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+public class NodeToTriplesMapMem implements NodeToTriplesMap {
+
+ private final JenaMap<Node, TripleBunch> bunchMap = new HashedBunchMap();
+ private final Triple.Field indexField;
+ private final Triple.Field f2;
+ private final Triple.Field f3;
+
+ /**
+ * The number of triples held in this NTM, maintained incrementally
+ * (because it's a pain to compute from scratch).
+ */
+ private int size = 0;
+
+ public NodeToTriplesMapMem(Triple.Field indexField, Triple.Field f2, Triple.Field f3) {
+ this.indexField = indexField;
+ this.f2 = f2;
+ this.f3 = f3;
+ }
+
+ private Node getIndexNode(Triple t) {
+ return indexField.getField(t);
+ }
+
+ @Override
+ public void clear() {
+ this.bunchMap.clear();
+ this.size = 0;
+ }
+
+ @Override
+ public int size() {
+ return size;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return this.size == 0;
+ }
+
+ @Override
+ @SuppressWarnings("squid:S1121")
+ public boolean tryAdd(Triple t) {
+ final Node node = getIndexNode(t);
+
+ TripleBunch s = bunchMap.get(node);
+ if (s == null) {
+ bunchMap.put(node, s = new ArrayBunch());
+ s.addUnchecked(t);
+ size++;
+ return true;
+ }
+
+ if ((s.isArray()) && s.size() == 9) {
+ bunchMap.put(node, s = new HashedTripleBunch(s));
+ }
+ if (s.tryAdd(t)) {
+ size++;
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void addUnchecked(Triple t) {
+ final Node node = getIndexNode(t);
+ TripleBunch s = bunchMap.get(node);
+ if (s == null) {
+ s = new ArrayBunch();
+ bunchMap.put(node, s);
+ } else if ((s.isArray()) && s.size() == 9) {
+ s = new HashedTripleBunch(s);
+ bunchMap.put(node, s);
+ }
+ s.addUnchecked(t);
+ size++;
+ }
+
+ @Override
+ public boolean tryRemove(Triple t) {
+ final Node node = getIndexNode(t);
+ final TripleBunch s = bunchMap.get(node);
+
+ if (s == null)
+ return false;
+
+ if (s.tryRemove(t)) {
+ size--;
+ if (s.isEmpty()) bunchMap.removeUnchecked(node);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void removeUnchecked(Triple t) {
+ final Node node = getIndexNode(t);
+ final TripleBunch s = bunchMap.get(node);
+
+ if (s == null)
+ return;
+
+ s.removeUnchecked(t);
+ size--;
+ if (s.isEmpty()) bunchMap.removeUnchecked(node);
+ }
+
+ @Override
+ public ExtendedIterator<Triple> keyIterator() {
+ return new IteratorOfJenaSets<>(bunchMap.valueIterator());
+ }
+
+ @Override
+ public Spliterator<Triple> keySpliterator() {
+ return keyStream().spliterator();
+ }
+
+ @Override
+ public Stream<Triple> keyStream() {
+ return StreamSupport.stream(bunchMap.valueSpliterator(), false)
+ .flatMap(bunch -> StreamSupport.stream(bunch.keySpliterator(), false));
+ }
+
+ @Override
+ public ExtendedIterator<Triple> iteratorForMatches(Node index, Node n2, Node n3) {
+ final TripleBunch s = bunchMap.get(index);
+
+ if (s == null) return NullIterator.instance();
+
+ final var filter = FieldFilter.filterOn(f2, n2, f3, n3);
+ return filter.hasFilter()
+ ? s.keyIterator().filterKeep(filter.getFilter())
+ : s.keyIterator();
+ }
+
+ @Override
+ public Stream<Triple> streamForMatches(Node index, Node n2, Node n3) {
+ final TripleBunch s = bunchMap.get(index);
+ if (s == null)
+ return Stream.empty();
+ final var filter = FieldFilter.filterOn(f2, n2, f3, n3);
+ return filter.hasFilter()
+ ? StreamSupport.stream(s.keySpliterator(), false).filter(filter.getFilter())
+ : StreamSupport.stream(s.keySpliterator(), false);
+ }
+
+ @Override
+ public boolean containsMatch(Node index, Node n2, Node n3) {
+ final TripleBunch s = bunchMap.get(index);
+ if (s == null)
+ return false;
+ var filter = FieldFilter.filterOn(f2, n2, f3, n3);
+ if (!filter.hasFilter())
+ return true;
+ return s.anyMatch(filter.getFilter());
+ }
+
+ @Override
+ public boolean containsKey(Triple triple) {
+ final TripleBunch s = bunchMap.get(getIndexNode(triple));
+ if (s == null)
+ return false;
+
+ return s.containsKey(triple);
+ }
+
+ @Override
+ public boolean anyMatch(Predicate<Triple> predicate) {
+ return bunchMap.valueStream().anyMatch(bunch -> bunch.anyMatch(predicate));
+ }
+}
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java b/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/TripleBunch.java
similarity index 58%
copy from jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java
copy to jena-core/src/main/java/org/apache/jena/mem2/store/legacy/TripleBunch.java
index f601955053..0c595cf3ac 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java
+++ b/jena-core/src/main/java/org/apache/jena/mem2/store/legacy/TripleBunch.java
@@ -15,18 +15,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.jena.mem.graph.helper;
-import org.apache.jena.graph.Graph;
-import org.apache.jena.graph.Node;
+package org.apache.jena.mem2.store.legacy;
+
import org.apache.jena.graph.Triple;
+import org.apache.jena.mem2.collection.JenaSet;
/**
- * Proviced the release specific access to the helper classes for working with graphs, triples and nodes.
+ * A bunch of triples - a stripped-down set with specialized methods. A
+ * bunch is expected to store triples that share some useful property
+ * (such as having the same subject or predicate).
*/
-public class Releases {
-
- public static GraphTripleNodeHelper<Graph, Triple, Node> current = new GraphTripleNodeHelperCurrent();
- public static GraphTripleNodeHelper<org.apache.shadedJena480.graph.Graph, org.apache.shadedJena480.graph.Triple, org.apache.shadedJena480.graph.Node> v480 = new GraphTripleNodeHelper480();
-
+public interface TripleBunch extends JenaSet<Triple> {
+ /**
+ * 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.
+ *
+ * @return true iff this bunch is implemented as an arrays
+ */
+ boolean isArray();
}
diff --git a/jena-core/src/main/java/org/apache/jena/mem2/store/roaring/RoaringBitmapTripleIterator.java b/jena-core/src/main/java/org/apache/jena/mem2/store/roaring/RoaringBitmapTripleIterator.java
new file mode 100644
index 0000000000..d0fa825bc0
--- /dev/null
+++ b/jena-core/src/main/java/org/apache/jena/mem2/store/roaring/RoaringBitmapTripleIterator.java
@@ -0,0 +1,94 @@
+/*
+ * 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.mem2.store.roaring;
+
+import org.apache.jena.graph.Triple;
+import org.apache.jena.mem2.collection.FastHashSet;
+import org.apache.jena.util.iterator.NiceIterator;
+import org.roaringbitmap.BatchIterator;
+import org.roaringbitmap.ImmutableBitmapDataProvider;
+
+import java.util.ConcurrentModificationException;
+import java.util.NoSuchElementException;
+import java.util.function.Consumer;
+
+/**
+ * A triple iterator that iterates over triple indices in a RoaringBitmap {@link BatchIterator}.
+ * Only the #forEachRemaining method uses {@link ImmutableBitmapDataProvider#forEach} if there has been no
+ * #next or #hasNext call before.
+ * All triples are stored in a {@link FastHashSet} and each triple is retrieved from the set by its index.
+ * The bitmap typically is a subset of the triple indices in the set.
+ */
+public class RoaringBitmapTripleIterator extends NiceIterator<Triple> {
+ protected static final int BUFFER_SIZE = 64;
+ private final ImmutableBitmapDataProvider bitmap;
+ private final FastHashSet<Triple> triples;
+ private final int initialSize;
+ private final BatchIterator batchIterator;
+ private final int[] buffer = new int[BUFFER_SIZE];
+ private int bufferIndex = -1;
+ private boolean batchIteratorHasBeenUsed = false;
+
+ public RoaringBitmapTripleIterator(final ImmutableBitmapDataProvider bitmap, final FastHashSet<Triple> triples) {
+ this.bitmap = bitmap;
+ this.batchIterator = bitmap.getBatchIterator();
+ this.triples = triples;
+ this.initialSize = triples.size();
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (bufferIndex > 0)
+ return true;
+ if (this.batchIterator.hasNext()) {
+ if (!batchIteratorHasBeenUsed) {
+ batchIteratorHasBeenUsed = true;
+ }
+ bufferIndex = batchIterator.nextBatch(buffer);
+ }
+ return bufferIndex > 0;
+ }
+
+ @Override
+ public Triple next() {
+ if (triples.size() != initialSize) throw new ConcurrentModificationException();
+
+ if (this.hasNext())
+ return triples.getKeyAt(buffer[--bufferIndex]);
+
+ throw new NoSuchElementException();
+ }
+
+ @Override
+ public void forEachRemaining(Consumer<? super Triple> action) {
+ if (batchIteratorHasBeenUsed) {
+ while (-1 < --bufferIndex) {
+ action.accept(triples.getKeyAt(buffer[bufferIndex]));
+ }
+ while (batchIterator.hasNext()) {
+ bufferIndex = batchIterator.nextBatch(buffer);
+ while (-1 < --bufferIndex) {
+ action.accept(triples.getKeyAt(buffer[bufferIndex]));
+ }
+ }
+ } else {
+ bitmap.forEach((int index) -> action.accept(triples.getKeyAt(index)));
+ }
+ if (triples.size() != initialSize) throw new ConcurrentModificationException();
+ }
+}
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
new file mode 100644
index 0000000000..547dd0a916
--- /dev/null
+++ b/jena-core/src/main/java/org/apache/jena/mem2/store/roaring/RoaringTripleStore.java
@@ -0,0 +1,335 @@
+/*
+ * 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.mem2.store.roaring;
+
+import org.apache.jena.graph.Node;
+import org.apache.jena.graph.Triple;
+import org.apache.jena.mem2.collection.FastHashMap;
+import org.apache.jena.mem2.collection.FastHashSet;
+import org.apache.jena.mem2.pattern.MatchPattern;
+import org.apache.jena.mem2.pattern.PatternClassifier;
+import org.apache.jena.mem2.store.TripleStore;
+import org.apache.jena.util.iterator.ExtendedIterator;
+import org.apache.jena.util.iterator.NiceIterator;
+import org.apache.jena.util.iterator.SingletonIterator;
+import org.roaringbitmap.FastAggregation;
+import org.roaringbitmap.ImmutableBitmapDataProvider;
+import org.roaringbitmap.RoaringBitmap;
+
+import java.util.stream.Stream;
+
+/**
+ * A triple store that is ideal for handling extremely large graphs.
+ * <p>
+ * Internal structure:
+ * - One indexed hash set (same as GraphMem2Fast uses) that holds all triples
+ * - Three hash maps indexed by subjects, predicates, and objects with RoaringBitmaps as values
+ * - The bitmaps contain the indices of the triples in the central hash set
+ * <p>
+ * The bitmaps are used to quickly find triples that match a given pattern.
+ * The bitmaps operations like {@link FastAggregation#naive_and(RoaringBitmap...)} and
+ * {@link RoaringBitmap#intersects(RoaringBitmap, RoaringBitmap)} are used to find matches for the pattern
+ * S_O, SP_, and _PO pretty fast, even in large graphs.
+ * <p>
+ * Additional optimizations:
+ * - because we know that if a triple exists in one of the maps, it also exists in the other two, we can use the
+ * {@link org.apache.jena.mem2.collection.JenaMapSetCommon#removeUnchecked(java.lang.Object)} method to avoid
+ * unnecessary checks.
+ */
+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
+
+ private static void addIndex(final NodesToBitmapsMap map, final Node node, final int index) {
+ final var bitmap = map.computeIfAbsent(node, RoaringBitmap::new);
+ bitmap.add(index);
+ }
+
+ private static void removeIndex(final NodesToBitmapsMap map, final Node node, final int index) {
+ final var bitmap = map.get(node);
+ bitmap.remove(index);
+ if (bitmap.isEmpty()) {
+ map.removeUnchecked(node);
+ }
+ }
+
+ @Override
+ public void add(final Triple triple) {
+ final var index = triples.addAndGetIndex(triple);
+ if (index < 0) { /*triple already exists*/
+ return;
+ }
+ addIndex(this.subjectBitmaps, triple.getSubject(), index);
+ addIndex(this.predicateBitmaps, triple.getPredicate(), index);
+ addIndex(this.objectBitmaps, triple.getObject(), index);
+ }
+
+ @Override
+ public void remove(final Triple triple) {
+ final var index = triples.removeAndGetIndex(triple);
+ if (index < 0) { /*triple does not exist*/
+ return;
+ }
+ removeIndex(this.subjectBitmaps, triple.getSubject(), index);
+ removeIndex(this.predicateBitmaps, triple.getPredicate(), index);
+ removeIndex(this.objectBitmaps, triple.getObject(), index);
+ }
+
+ @Override
+ public void clear() {
+ this.subjectBitmaps.clear();
+ this.predicateBitmaps.clear();
+ this.objectBitmaps.clear();
+ this.triples.clear();
+ }
+
+ @Override
+ public int countTriples() {
+ return this.triples.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return this.triples.isEmpty();
+ }
+
+ @Override
+ public boolean contains(Triple tripleMatch) {
+ 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:
+ return hasMatchInBitmaps(tripleMatch, matchPattern);
+
+ case SUB_PRE_OBJ:
+ return this.triples.containsKey(tripleMatch);
+
+ case ANY_ANY_ANY:
+ return !this.isEmpty();
+
+ default:
+ throw new IllegalStateException(String.format(UNKNOWN_PATTERN_CLASSIFIER, PatternClassifier.classify(tripleMatch)));
+ }
+ }
+
+ private ImmutableBitmapDataProvider getBitmapForMatch(final Triple tripleMatch, final MatchPattern matchPattern) {
+ switch (matchPattern) {
+
+ case SUB_ANY_ANY:
+ return this.subjectBitmaps.getOrDefault(tripleMatch.getSubject(), EMPTY_BITMAP);
+ case ANY_PRE_ANY:
+ return this.predicateBitmaps.getOrDefault(tripleMatch.getPredicate(), EMPTY_BITMAP);
+ case ANY_ANY_OBJ:
+ return this.objectBitmaps.getOrDefault(tripleMatch.getObject(), EMPTY_BITMAP);
+
+ case SUB_PRE_ANY: {
+ final var subjectBitmap = this.subjectBitmaps.get(tripleMatch.getSubject());
+ if (null == subjectBitmap)
+ return EMPTY_BITMAP;
+
+ final var predicateBitmap = this.predicateBitmaps.get(tripleMatch.getPredicate());
+ if (null == predicateBitmap)
+ return EMPTY_BITMAP;
+
+ return FastAggregation.naive_and(subjectBitmap, predicateBitmap);
+ }
+
+ case ANY_PRE_OBJ: {
+ final var predicateBitmap = this.predicateBitmaps.get(tripleMatch.getPredicate());
+ if (null == predicateBitmap)
+ return EMPTY_BITMAP;
+
+ final var objectBitmap = this.objectBitmaps.get(tripleMatch.getObject());
+ if (null == objectBitmap)
+ return EMPTY_BITMAP;
+
+ return FastAggregation.naive_and(predicateBitmap, objectBitmap);
+ }
+
+ case SUB_ANY_OBJ: {
+ final var subjectBitmap = this.subjectBitmaps.get(tripleMatch.getSubject());
+ if (null == subjectBitmap)
+ return EMPTY_BITMAP;
+
+ final var objectBitmap = this.objectBitmaps.get(tripleMatch.getObject());
+ if (null == objectBitmap)
+ return EMPTY_BITMAP;
+
+ return FastAggregation.naive_and(subjectBitmap, objectBitmap);
+ }
+
+ case SUB_PRE_OBJ:
+ throw new IllegalArgumentException("Getting bitmap for match pattern SPO ist not supported because it is not efficient");
+
+ case ANY_ANY_ANY:
+ throw new IllegalArgumentException("Cannot get bitmap for match pattern ___");
+
+ default:
+ throw new IllegalStateException(String.format(UNKNOWN_PATTERN_CLASSIFIER, PatternClassifier.classify(tripleMatch)));
+ }
+ }
+
+ private boolean hasMatchInBitmaps(final Triple tripleMatch, final MatchPattern matchPattern) {
+ switch (matchPattern) {
+
+ case SUB_ANY_ANY:
+ return this.subjectBitmaps.containsKey(tripleMatch.getSubject());
+ case ANY_PRE_ANY:
+ return this.predicateBitmaps.containsKey(tripleMatch.getPredicate());
+ case ANY_ANY_OBJ:
+ return this.objectBitmaps.containsKey(tripleMatch.getObject());
+
+ case SUB_PRE_ANY: {
+ final var subjectBitmap = this.subjectBitmaps.get(tripleMatch.getSubject());
+ if (null == subjectBitmap)
+ return false;
+
+ final var predicateBitmap = this.predicateBitmaps.get(tripleMatch.getPredicate());
+ if (null == predicateBitmap)
+ return false;
+
+ return RoaringBitmap.intersects(subjectBitmap, predicateBitmap);
+ }
+
+ case ANY_PRE_OBJ: {
+ final var predicateBitmap = this.predicateBitmaps.get(tripleMatch.getPredicate());
+ if (null == predicateBitmap)
+ return false;
+
+ final var objectBitmap = this.objectBitmaps.get(tripleMatch.getObject());
+ if (null == objectBitmap)
+ return false;
+
+ return RoaringBitmap.intersects(objectBitmap, predicateBitmap);
+ }
+
+ case SUB_ANY_OBJ: {
+ final var subjectBitmap = this.subjectBitmaps.get(tripleMatch.getSubject());
+ if (null == subjectBitmap)
+ return false;
+
+ final var objectBitmap = this.objectBitmaps.get(tripleMatch.getObject());
+ if (null == objectBitmap)
+ return false;
+
+ return RoaringBitmap.intersects(subjectBitmap, objectBitmap);
+ }
+
+ case SUB_PRE_OBJ:
+ throw new IllegalArgumentException("Getting bitmap for match pattern SPO ist not supported because it is not efficient");
+
+ case ANY_ANY_ANY:
+ throw new IllegalArgumentException("Cannot get bitmap for match pattern ___");
+
+ default:
+ throw new IllegalStateException(String.format(UNKNOWN_PATTERN_CLASSIFIER, PatternClassifier.classify(tripleMatch)));
+ }
+ }
+
+ @Override
+ public Stream<Triple> stream() {
+ return this.triples.keyStream();
+ }
+
+ @Override
+ public Stream<Triple> stream(Triple tripleMatch) {
+ var pattern = PatternClassifier.classify(tripleMatch);
+ switch (pattern) {
+
+ 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:
+ return this.getBitmapForMatch(tripleMatch, pattern)
+ .stream().mapToObj(this.triples::getKeyAt);
+
+ case ANY_ANY_ANY:
+ return this.stream();
+
+ default:
+ throw new IllegalStateException("Unknown pattern classifier: " + PatternClassifier.classify(tripleMatch));
+ }
+ }
+
+ @Override
+ public ExtendedIterator<Triple> find(Triple tripleMatch) {
+ var pattern = PatternClassifier.classify(tripleMatch);
+ switch (pattern) {
+
+ 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:
+ return new RoaringBitmapTripleIterator(this.getBitmapForMatch(tripleMatch, pattern), this.triples);
+
+ case ANY_ANY_ANY:
+ return this.triples.keyIterator();
+
+ default:
+ throw new IllegalStateException("Unknown pattern classifier: " + PatternClassifier.classify(tripleMatch));
+ }
+ }
+
+ /**
+ * Set of triples that is backed by a {@link TripleSet}.
+ */
+ private static class TripleSet extends FastHashSet<Triple> {
+
+ @Override
+ protected Triple[] newKeysArray(int size) {
+ return new Triple[size];
+ }
+ }
+
+ /**
+ * Map from {@link Node} to {@link RoaringBitmap}.
+ */
+ private static class NodesToBitmapsMap extends FastHashMap<Node, RoaringBitmap> {
+
+ @Override
+ protected Node[] newKeysArray(int size) {
+ return new Node[size];
+ }
+
+ @Override
+ protected RoaringBitmap[] newValuesArray(int size) {
+ return new RoaringBitmap[size];
+ }
+ }
+}
diff --git a/jena-core/src/test/java/org/apache/jena/graph/test/TestGraph.java b/jena-core/src/test/java/org/apache/jena/graph/test/TestGraph.java
index f650c2d474..5c25c2a3d2 100644
--- a/jena-core/src/test/java/org/apache/jena/graph/test/TestGraph.java
+++ b/jena-core/src/test/java/org/apache/jena/graph/test/TestGraph.java
@@ -23,12 +23,15 @@ package org.apache.jena.graph.test;
and reifier test suites.
*/
-import junit.framework.Test ;
-import junit.framework.TestSuite ;
-import org.apache.jena.graph.GraphMemFactory ;
-import org.apache.jena.graph.Graph ;
-import org.apache.jena.graph.impl.WrappedGraph ;
-import org.apache.jena.mem.GraphMem ;
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.jena.graph.Graph;
+import org.apache.jena.graph.GraphMemFactory;
+import org.apache.jena.graph.impl.WrappedGraph;
+import org.apache.jena.mem.GraphMem;
+import org.apache.jena.mem2.GraphMem2Fast;
+import org.apache.jena.mem2.GraphMem2Legacy;
+import org.apache.jena.mem2.GraphMem2Roaring;
@SuppressWarnings("deprecation")
public class TestGraph extends GraphTestBase
@@ -42,13 +45,19 @@ public class TestGraph extends GraphTestBase
*/
public static TestSuite suite()
{
- TestSuite result = new TestSuite( TestGraph.class );
- result.addTest( suite( MetaTestGraph.class, GraphMem.class ) );
- result.addTest( suite( TestReifier.class, GraphMem.class ) );
- result.addTest( suite( MetaTestGraph.class, WrappedGraphMem.class ) );
- result.addTest( suite( TestReifier.class, WrappedGraphMem.class ) );
- result.addTest( TestGraphListener.suite() );
- result.addTestSuite( TestRegisterGraphListener.class );
+ TestSuite result = new TestSuite(TestGraph.class);
+ result.addTest(suite(MetaTestGraph.class, GraphMem.class));
+ result.addTest(suite(TestReifier.class, GraphMem.class));
+ result.addTest(suite(MetaTestGraph.class, WrappedGraphMem.class));
+ result.addTest(suite(TestReifier.class, WrappedGraphMem.class));
+ result.addTest(suite(MetaTestGraph.class, GraphMem2Fast.class));
+ result.addTest(suite(TestReifier.class, GraphMem2Fast.class));
+ result.addTest(suite(MetaTestGraph.class, GraphMem2Legacy.class));
+ result.addTest(suite(TestReifier.class, GraphMem2Legacy.class));
+ result.addTest(suite(MetaTestGraph.class, GraphMem2Roaring.class));
+ result.addTest(suite(TestReifier.class, GraphMem2Roaring.class));
+ result.addTest(TestGraphListener.suite());
+ result.addTestSuite(TestRegisterGraphListener.class);
return result;
}
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
new file mode 100644
index 0000000000..a0ad17d208
--- /dev/null
+++ b/jena-core/src/test/java/org/apache/jena/mem2/AbstractGraphMem2Test.java
@@ -0,0 +1,1003 @@
+/*
+ * 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.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;
+import org.hamcrest.collection.IsIterableContainingInAnyOrder;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.stream.Collectors;
+
+import static org.apache.jena.testing_framework.GraphHelper.node;
+import static org.apache.jena.testing_framework.GraphHelper.triple;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.*;
+
+public abstract class AbstractGraphMem2Test {
+
+ protected Graph sut;
+
+ protected abstract Graph createGraph();
+
+ @Before
+ public void setUp() throws Exception {
+ sut = createGraph();
+ }
+
+ @Test
+ public void testClear() {
+ sut.add(triple("x R y"));
+ assertEquals(1, sut.size());
+ sut.clear();
+ assertEquals(0, sut.size());
+ assertTrue(sut.isEmpty());
+ }
+
+
+ @Test
+ public void testDelete() {
+ sut.add(triple("x R y"));
+ assertEquals(1, sut.size());
+ sut.delete(triple("x R y"));
+ assertEquals(0, sut.size());
+ assertTrue(sut.isEmpty());
+ }
+
+ @Test
+ public void testFind() {
+ sut.add(triple("x R y"));
+ assertEquals(1, sut.find(triple("x R y")).toList().size());
+ assertEquals(0, sut.find(triple("x R z")).toList().size());
+ }
+
+ @Test
+ public void testFind1() {
+ sut.add(triple("x R y"));
+ assertEquals(1, sut.find(null, null, null).toList().size());
+ assertEquals(1, sut.find(null, null, node("y")).toList().size());
+ assertEquals(1, sut.find(null, node("R"), null).toList().size());
+ assertEquals(1, sut.find(null, node("R"), node("y")).toList().size());
+ assertEquals(1, sut.find(node("x"), null, null).toList().size());
+ assertEquals(1, sut.find(node("x"), null, node("y")).toList().size());
+ assertEquals(1, sut.find(node("x"), node("R"), null).toList().size());
+ assertEquals(1, sut.find(node("x"), node("R"), node("y")).toList().size());
+ }
+
+ @Test
+ public void testFind2() {
+ sut.add(triple("x R y"));
+ assertEquals(1, sut.find(null, null, null).toList().size());
+ assertEquals(0, sut.find(null, null, node("z")).toList().size());
+ assertEquals(0, sut.find(null, node("S"), null).toList().size());
+ assertEquals(0, sut.find(null, node("S"), node("y")).toList().size());
+ assertEquals(0, sut.find(node("y"), null, null).toList().size());
+ assertEquals(0, sut.find(node("y"), null, node("y")).toList().size());
+ assertEquals(0, sut.find(node("y"), node("R"), null).toList().size());
+ assertEquals(0, sut.find(node("y"), node("R"), node("y")).toList().size());
+ }
+
+ @Test
+ public void testFindWithIteratorHasNextNext() {
+ sut.add(triple("x R y"));
+ var iter = sut.find(triple("x R y"));
+ assertTrue(iter.hasNext());
+ assertEquals(triple("x R y"), iter.next());
+ assertFalse(iter.hasNext());
+ }
+
+ @Test
+ public void testFindSPO() {
+ sut.add(triple("x R y"));
+ assertEquals(1, sut.find(node("x"), node("R"), node("y")).toList().size());
+ assertEquals(0, sut.find(node("x"), node("R"), node("z")).toList().size());
+ }
+
+ @Test
+ public void testFind___() {
+ final var aAa = triple("a A a");
+ final var aAb = triple("a A b");
+ final var aAc = triple("a A c");
+ final var bAa = triple("b A a");
+ final var bAb = triple("b A b");
+ final var bAc = triple("b A c");
+ final var cBa = triple("c B a");
+ final var cBb = triple("c B b");
+ final var cBc = triple("c B c");
+
+ sut.add(aAa);
+ sut.add(aAb);
+ sut.add(aAc);
+ sut.add(bAa);
+ sut.add(bAb);
+ sut.add(bAc);
+ sut.add(cBa);
+ sut.add(cBb);
+ sut.add(cBc);
+
+ var findings = sut.find(null, null, null).toList();
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(aAa, aAb, aAc, bAa, bAb, bAc, cBa, cBb, cBc));
+ }
+
+ @Test
+ public void testFindS__() {
+ assertFalse(sut.find(node("a"), null, null).hasNext());
+
+ final var aAa = triple("a A a");
+ final var aAb = triple("a A b");
+ final var aAc = triple("a A c");
+ final var bAa = triple("b A a");
+ final var bAb = triple("b A b");
+ final var bAc = triple("b A c");
+ final var cBa = triple("c B a");
+ final var cBb = triple("c B b");
+ final var cBc = triple("c B c");
+
+ sut.add(aAa);
+ sut.add(aAb);
+ sut.add(aAc);
+ sut.add(bAa);
+ sut.add(bAb);
+ sut.add(bAc);
+ sut.add(cBa);
+ sut.add(cBb);
+ sut.add(cBc);
+
+ var findings = sut.find(node("a"), null, null).toList();
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(aAa, aAb, aAc));
+
+ findings = sut.find(node("b"), null, null).toList();
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(bAa, bAb, bAc));
+
+ findings = sut.find(node("c"), null, null).toList();
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(cBa, cBb, cBc));
+
+ findings = sut.find(node("d"), null, null).toList();
+ assertThat(findings, IsEmptyCollection.empty());
+ }
+
+ @Test
+ public void testFind_P_() {
+ assertFalse(sut.find(null, node("A"), null).hasNext());
+
+ final var aAa = triple("a A a");
+ final var aAb = triple("a A b");
+ final var aAc = triple("a A c");
+ final var bAa = triple("b A a");
+ final var bAb = triple("b A b");
+ final var bAc = triple("b A c");
+ final var cBa = triple("c B a");
+ final var cBb = triple("c B b");
+ final var cBc = triple("c B c");
+
+ sut.add(aAa);
+ sut.add(aAb);
+ sut.add(aAc);
+ sut.add(bAa);
+ sut.add(bAb);
+ sut.add(bAc);
+ sut.add(cBa);
+ sut.add(cBb);
+ sut.add(cBc);
+
+ var findings = sut.find(null, node("A"), null).toList();
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(aAa, aAb, aAc, bAa, bAb, bAc));
+
+ findings = sut.find(null, node("B"), null).toList();
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(cBa, cBb, cBc));
+
+ findings = sut.find(null, node("C"), null).toList();
+ assertThat(findings, IsEmptyCollection.empty());
+ }
+
+ @Test
+ public void testFind__O() {
+ assertFalse(sut.find(null, null, node("a")).hasNext());
+
+ final var aAa = triple("a A a");
+ final var aAb = triple("a A b");
+ final var aAc = triple("a A c");
+ final var bAa = triple("b A a");
+ final var bAb = triple("b A b");
+ final var bAc = triple("b A c");
+ final var cBa = triple("c B a");
+ final var cBb = triple("c B b");
+ final var cBc = triple("c B c");
+
+ sut.add(aAa);
+ sut.add(aAb);
+ sut.add(aAc);
+ sut.add(bAa);
+ sut.add(bAb);
+ sut.add(bAc);
+ sut.add(cBa);
+ sut.add(cBb);
+ sut.add(cBc);
+
+ var findings = sut.find(null, null, node("a")).toList();
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(aAa, bAa, cBa));
+
+ findings = sut.find(null, null, node("b")).toList();
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(aAb, bAb, cBb));
+
+ findings = sut.find(null, null, node("c")).toList();
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(aAc, bAc, cBc));
+
+ findings = sut.find(null, null, node("d")).toList();
+ assertThat(findings, IsEmptyCollection.empty());
+ }
+
+ @Test
+ public void testFindSP_() {
+ assertFalse(sut.find(node("a"), node("A"), null).hasNext());
+
+ final var aAa = triple("a A a");
+ final var aAb = triple("a A b");
+ final var aAc = triple("a A c");
+ final var bAa = triple("b A a");
+ final var bAb = triple("b A b");
+ final var bAc = triple("b A c");
+ final var cBa = triple("c B a");
+ final var cBb = triple("c B b");
+ final var cBc = triple("c B c");
+
+ sut.add(aAa);
+ sut.add(aAb);
+ sut.add(aAc);
+ sut.add(bAa);
+ sut.add(bAb);
+ sut.add(bAc);
+ sut.add(cBa);
+ sut.add(cBb);
+ sut.add(cBc);
+
+ var findings = sut.find(node("a"), node("A"), null).toList();
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(aAa, aAb, aAc));
+
+ findings = sut.find(node("b"), node("A"), null).toList();
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(bAa, bAb, bAc));
+
+ findings = sut.find(node("c"), node("B"), null).toList();
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(cBa, cBb, cBc));
+
+ findings = sut.find(node("d"), node("C"), null).toList();
+ assertThat(findings, IsEmptyCollection.empty());
+
+ findings = sut.find(node("a"), node("B"), null).toList();
+ assertThat(findings, IsEmptyCollection.empty());
+ }
+
+ @Test
+ public void testFindS_O() {
+ assertFalse(sut.find(node("a"), null, node("a")).hasNext());
+
+ final var aAa = triple("a A a");
+ final var aAb = triple("a A b");
+ final var aAc = triple("a A c");
+ final var bAa = triple("b A a");
+ final var bAb = triple("b A b");
+ final var bAc = triple("b A c");
+ final var cBa = triple("c B a");
+ final var cBb = triple("c B b");
+ final var cBc = triple("c B c");
+
+ sut.add(aAa);
+ sut.add(aAb);
+ sut.add(aAc);
+ sut.add(bAa);
+ sut.add(bAb);
+ sut.add(bAc);
+ sut.add(cBa);
+ sut.add(cBb);
+ sut.add(cBc);
+
+ var findings = sut.find(node("a"), null, node("a")).toList();
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(aAa));
+
+ findings = sut.find(node("b"), null, node("a")).toList();
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(bAa));
+
+ findings = sut.find(node("c"), null, node("a")).toList();
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(cBa));
+
+ findings = sut.find(node("d"), null, node("a")).toList();
+ assertThat(findings, IsEmptyCollection.empty());
+
+ findings = sut.find(node("a"), null, node("d")).toList();
+ assertThat(findings, IsEmptyCollection.empty());
+ }
+
+ @Test
+ public void testFind_PO() {
+ assertFalse(sut.find(null, node("A"), node("a")).hasNext());
+
+ final var aAa = triple("a A a");
+ final var aAb = triple("a A b");
+ final var aAc = triple("a A c");
+ final var bAa = triple("b A a");
+ final var bAb = triple("b A b");
+ final var bAc = triple("b A c");
+ final var cBa = triple("c B a");
+ final var cBb = triple("c B b");
+ final var cBc = triple("c B c");
+
+ sut.add(aAa);
+ sut.add(aAb);
+ sut.add(aAc);
+ sut.add(bAa);
+ sut.add(bAb);
+ sut.add(bAc);
+ sut.add(cBa);
+ sut.add(cBb);
+ sut.add(cBc);
+
+ var findings = sut.find(null, node("A"), node("a")).toList();
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(aAa, bAa));
+
+ findings = sut.find(null, node("B"), node("a")).toList();
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(cBa));
+
+ findings = sut.find(null, node("C"), node("a")).toList();
+ assertThat(findings, IsEmptyCollection.empty());
+
+ findings = sut.find(null, node("A"), node("d")).toList();
+ assertThat(findings, IsEmptyCollection.empty());
+ }
+
+ @Test
+ public void testStream() {
+ sut.add(triple("x R y"));
+ assertEquals(1, sut.stream().count());
+ }
+
+ @Test
+ public void testStreamEmpty() {
+ assertEquals(0, sut.stream().count());
+ }
+
+ @Test
+ public void testStreamSPO() {
+ assertEquals(0, sut.stream(node("x"), node("R"), node("y")).count());
+
+ var t = triple("x R y");
+ sut.add(t);
+ var findings = sut.stream(t.getSubject(), t.getPredicate(), t.getObject()).collect(Collectors.toList());
+ assertEquals(1, findings.size());
+ assertEquals(findings.get(0), t);
+ }
+
+ @Test
+ public void testStream___() {
+ assertEquals(0, sut.stream(null, null, null).count());
+
+ final var aAa = triple("a A a");
+ final var aAb = triple("a A b");
+ final var aAc = triple("a A c");
+ final var bAa = triple("b A a");
+ final var bAb = triple("b A b");
+ final var bAc = triple("b A c");
+ final var cBa = triple("c B a");
+ final var cBb = triple("c B b");
+ final var cBc = triple("c B c");
+
+ sut.add(aAa);
+ sut.add(aAb);
+ sut.add(aAc);
+ sut.add(bAa);
+ sut.add(bAb);
+ sut.add(bAc);
+ sut.add(cBa);
+ sut.add(cBb);
+ sut.add(cBc);
+
+ var findings = sut.stream(null, null, null).collect(Collectors.toList());
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(aAa, aAb, aAc, bAa, bAb, bAc, cBa, cBb, cBc));
+ }
+
+ @Test
+ public void testStreamS__() {
+ assertEquals(0, sut.stream(node("a"), null, null).count());
+
+ final var aAa = triple("a A a");
+ final var aAb = triple("a A b");
+ final var aAc = triple("a A c");
+ final var bAa = triple("b A a");
+ final var bAb = triple("b A b");
+ final var bAc = triple("b A c");
+ final var cBa = triple("c B a");
+ final var cBb = triple("c B b");
+ final var cBc = triple("c B c");
+
+ sut.add(aAa);
+ sut.add(aAb);
+ sut.add(aAc);
+ sut.add(bAa);
+ sut.add(bAb);
+ sut.add(bAc);
+ sut.add(cBa);
+ sut.add(cBb);
+ sut.add(cBc);
+
+ var findings = sut.stream(aAa.getSubject(), null, null).collect(Collectors.toList());
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(aAa, aAb, aAc));
+
+ findings = sut.stream(bAa.getSubject(), null, null).collect(Collectors.toList());
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(bAa, bAb, bAc));
+
+ findings = sut.stream(cBa.getSubject(), null, null).collect(Collectors.toList());
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(cBa, cBb, cBc));
+
+ findings = sut.stream(node("d"), null, null).collect(Collectors.toList());
+ assertThat(findings, IsEmptyCollection.empty());
+ }
+
+ @Test
+ public void testStream_P_() {
+ assertEquals(0, sut.stream(null, node("A"), null).count());
+
+ final var aAa = triple("a A a");
+ final var aAb = triple("a A b");
+ final var aAc = triple("a A c");
+ final var bAa = triple("b A a");
+ final var bAb = triple("b A b");
+ final var bAc = triple("b A c");
+ final var cBa = triple("c B a");
+ final var cBb = triple("c B b");
+ final var cBc = triple("c B c");
+
+ sut.add(aAa);
+ sut.add(aAb);
+ sut.add(aAc);
+ sut.add(bAa);
+ sut.add(bAb);
+ sut.add(bAc);
+ sut.add(cBa);
+ sut.add(cBb);
+ sut.add(cBc);
+
+ var findings = sut.stream(null, aAa.getPredicate(), null).collect(Collectors.toList());
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(aAa, aAb, aAc, bAa, bAb, bAc));
+
+ findings = sut.stream(null, cBa.getPredicate(), null).collect(Collectors.toList());
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(cBa, cBb, cBc));
+
+ findings = sut.stream(null, node("C"), null).collect(Collectors.toList());
+ assertThat(findings, IsEmptyCollection.empty());
+ }
+
+ @Test
+ public void testStream__O() {
+ assertEquals(0, sut.stream(null, null, node("a")).count());
+
+ final var aAa = triple("a A a");
+ final var aAb = triple("a A b");
+ final var aAc = triple("a A c");
+ final var bAa = triple("b A a");
+ final var bAb = triple("b A b");
+ final var bAc = triple("b A c");
+ final var cBa = triple("c B a");
+ final var cBb = triple("c B b");
+ final var cBc = triple("c B c");
+
+ sut.add(aAa);
+ sut.add(aAb);
+ sut.add(aAc);
+ sut.add(bAa);
+ sut.add(bAb);
+ sut.add(bAc);
+ sut.add(cBa);
+ sut.add(cBb);
+ sut.add(cBc);
+
+ var findings = sut.stream(null, null, aAa.getObject()).collect(Collectors.toList());
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(aAa, bAa, cBa));
+
+ findings = sut.stream(null, null, aAb.getObject()).collect(Collectors.toList());
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(aAb, bAb, cBb));
+
+ findings = sut.stream(null, null, aAc.getObject()).collect(Collectors.toList());
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(aAc, bAc, cBc));
+
+ findings = sut.stream(null, null, node("d")).collect(Collectors.toList());
+ assertThat(findings, IsEmptyCollection.empty());
+ }
+
+ @Test
+ public void testStreamSP_() {
+ assertEquals(0, sut.stream(node("a"), node("A"), null).count());
+
+ final var aAa = triple("a A a");
+ final var aAb = triple("a A b");
+ final var aAc = triple("a A c");
+ final var bAa = triple("b A a");
+ final var bAb = triple("b A b");
+ final var bAc = triple("b A c");
+ final var cBa = triple("c B a");
+ final var cBb = triple("c B b");
+ final var cBc = triple("c B c");
+
+ sut.add(aAa);
+ sut.add(aAb);
+ sut.add(aAc);
+ sut.add(bAa);
+ sut.add(bAb);
+ sut.add(bAc);
+ sut.add(cBa);
+ sut.add(cBb);
+ sut.add(cBc);
+
+ var findings = sut.stream(aAa.getSubject(), aAa.getPredicate(), null).collect(Collectors.toList());
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(aAa, aAb, aAc));
+
+ findings = sut.stream(bAa.getSubject(), bAa.getPredicate(), null).collect(Collectors.toList());
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(bAa, bAb, bAc));
+
+ findings = sut.stream(cBa.getSubject(), cBa.getPredicate(), null).collect(Collectors.toList());
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(cBa, cBb, cBc));
+
+ findings = sut.stream(node("a"), node("C"), null).collect(Collectors.toList());
+ assertThat(findings, IsEmptyCollection.empty());
+
+ findings = sut.stream(node("d"), node("D"), null).collect(Collectors.toList());
+ assertThat(findings, IsEmptyCollection.empty());
+ }
+
+ @Test
+ public void testStreamS_O() {
+ assertEquals(0, sut.stream(node("a"), null, node("a")).count());
+
+ final var aAa = triple("a A a");
+ final var aAb = triple("a A b");
+ final var aAc = triple("a A c");
+ final var bAa = triple("b A a");
+ final var bAb = triple("b A b");
+ final var bAc = triple("b A c");
+ final var cBa = triple("c B a");
+ final var cBb = triple("c B b");
+ final var cBc = triple("c B c");
+
+ sut.add(aAa);
+ sut.add(aAb);
+ sut.add(aAc);
+ sut.add(bAa);
+ sut.add(bAb);
+ sut.add(bAc);
+ sut.add(cBa);
+ sut.add(cBb);
+ sut.add(cBc);
+
+ var findings = sut.stream(aAa.getSubject(), null, aAa.getObject()).collect(Collectors.toList());
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(aAa));
+
+ findings = sut.stream(bAa.getSubject(), null, bAa.getObject()).collect(Collectors.toList());
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(bAa));
+
+ findings = sut.stream(cBa.getSubject(), null, cBa.getObject()).collect(Collectors.toList());
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(cBa));
+
+ findings = sut.stream(node("d"), null, node("d")).collect(Collectors.toList());
+ assertThat(findings, IsEmptyCollection.empty());
+
+ findings = sut.stream(node("d"), null, node("a")).collect(Collectors.toList());
+ assertThat(findings, IsEmptyCollection.empty());
+
+ findings = sut.stream(node("a"), null, node("d")).collect(Collectors.toList());
+ assertThat(findings, IsEmptyCollection.empty());
+ }
+
+ @Test
+ public void testStream_PO() {
+ assertEquals(0, sut.stream(null, node("A"), node("a")).count());
+
+ final var aAa = triple("a A a");
+ final var aAb = triple("a A b");
+ final var aAc = triple("a A c");
+ final var bAa = triple("b A a");
+ final var bAb = triple("b A b");
+ final var bAc = triple("b A c");
+ final var cBa = triple("c B a");
+ final var cBb = triple("c B b");
+ final var cBc = triple("c B c");
+
+ sut.add(aAa);
+ sut.add(aAb);
+ sut.add(aAc);
+ sut.add(bAa);
+ sut.add(bAb);
+ sut.add(bAc);
+ sut.add(cBa);
+ sut.add(cBb);
+ sut.add(cBc);
+
+ var findings = sut.stream(null, aAa.getPredicate(), aAa.getObject()).collect(Collectors.toList());
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(aAa, bAa));
+
+ findings = sut.stream(null, bAa.getPredicate(), bAa.getObject()).collect(Collectors.toList());
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(aAa, bAa));
+
+ findings = sut.stream(null, cBa.getPredicate(), cBa.getObject()).collect(Collectors.toList());
+ assertThat(findings, IsIterableContainingInAnyOrder.containsInAnyOrder(cBa));
+
+ findings = sut.stream(null, node("C"), node("a")).collect(Collectors.toList());
+ assertThat(findings, IsEmptyCollection.empty());
+
+ findings = sut.stream(null, node("A"), node("d")).collect(Collectors.toList());
+ assertThat(findings, IsEmptyCollection.empty());
+
+ findings = sut.stream(null, node("D"), node("d")).collect(Collectors.toList());
+ assertThat(findings, IsEmptyCollection.empty());
+ }
+
+ @Test
+ public void testContains() {
+ sut.add(triple("x R y"));
+ assertTrue(sut.contains(triple("x R y")));
+ assertFalse(sut.contains(triple("x R z")));
+ }
+
+ @Test
+ public void testContains1() {
+ sut.add(triple("x R y"));
+ sut.add(triple("y S z"));
+ sut.add(triple("z T a"));
+ assertTrue(sut.contains(null, null, null));
+ assertTrue(sut.contains(null, null, node("y")));
+ assertTrue(sut.contains(null, node("R"), null));
+ assertTrue(sut.contains(null, node("R"), node("y")));
+ assertTrue(sut.contains(node("x"), null, null));
+ assertTrue(sut.contains(node("x"), null, node("y")));
+ assertTrue(sut.contains(node("x"), node("R"), null));
+ assertTrue(sut.contains(node("x"), node("R"), node("y")));
+ }
+
+ @Test
+ public void testContains2() {
+ sut.add(triple("x R y"));
+ sut.add(triple("y S z"));
+ sut.add(triple("z T a"));
+ assertTrue(sut.contains(null, null, null));
+ assertFalse(sut.contains(null, null, node("x")));
+ assertFalse(sut.contains(null, node("U"), null));
+ assertFalse(sut.contains(null, node("R"), node("z")));
+ assertFalse(sut.contains(node("a"), null, null));
+ assertFalse(sut.contains(node("x"), null, node("x")));
+ assertFalse(sut.contains(node("y"), node("R"), null));
+ assertFalse(sut.contains(node("y"), node("T"), node("a")));
+ }
+
+ @Test
+ public void testContainsSPO() {
+ assertFalse(sut.contains(node("a"), node("A"), node("a")));
+
+ var t = triple("a A a");
+ sut.add(t);
+ assertTrue(sut.contains(t.getSubject(), t.getPredicate(), t.getObject()));
+ assertFalse(sut.contains(t.getSubject(), t.getPredicate(), node("b")));
+ assertFalse(sut.contains(t.getSubject(), node("B"), t.getObject()));
+ assertFalse(sut.contains(node("b"), t.getPredicate(), t.getObject()));
+ }
+
+ @Test
+ public void testContains___() {
+ assertFalse(sut.contains(null, null, null));
+
+ final var aAa = triple("a A a");
+ final var aAb = triple("a A b");
+ final var aAc = triple("a A c");
+ final var bAa = triple("b A a");
+ final var bAb = triple("b A b");
+ final var bAc = triple("b A c");
+ final var cBa = triple("c B a");
+ final var cBb = triple("c B b");
+ final var cBc = triple("c B c");
+
+ sut.add(aAa);
+ sut.add(aAb);
+ sut.add(aAc);
+ sut.add(bAa);
+ sut.add(bAb);
+ sut.add(bAc);
+ sut.add(cBa);
+ sut.add(cBb);
+ sut.add(cBc);
+
+ assertTrue(sut.contains(null, null, null));
+ }
+
+ @Test
+ public void testContainsS__() {
+ assertFalse(sut.contains(node("a"), null, null));
+
+ final var aAa = triple("a A a");
+ final var aAb = triple("a A b");
+ final var aAc = triple("a A c");
+ final var bAa = triple("b A a");
+ final var bAb = triple("b A b");
+ final var bAc = triple("b A c");
+ final var cBa = triple("c B a");
+ final var cBb = triple("c B b");
+ final var cBc = triple("c B c");
+
+ sut.add(aAa);
+ sut.add(aAb);
+ sut.add(aAc);
+ sut.add(bAa);
+ sut.add(bAb);
+ sut.add(bAc);
+ sut.add(cBa);
+ sut.add(cBb);
+ sut.add(cBc);
+
+ assertTrue(sut.contains(aAa.getSubject(), null, null));
+
+ assertTrue(sut.contains(bAa.getSubject(), null, null));
+
+ assertTrue(sut.contains(cBa.getSubject(), null, null));
+
+ assertFalse(sut.contains(node("d"), null, null));
+ }
+
+ @Test
+ public void testContains_P_() {
+ assertFalse(sut.contains(null, node("A"), null));
+
+ final var aAa = triple("a A a");
+ final var aAb = triple("a A b");
+ final var aAc = triple("a A c");
+ final var bAa = triple("b A a");
+ final var bAb = triple("b A b");
+ final var bAc = triple("b A c");
+ final var cBa = triple("c B a");
+ final var cBb = triple("c B b");
+ final var cBc = triple("c B c");
+
+ sut.add(aAa);
+ sut.add(aAb);
+ sut.add(aAc);
+ sut.add(bAa);
+ sut.add(bAb);
+ sut.add(bAc);
+ sut.add(cBa);
+ sut.add(cBb);
+ sut.add(cBc);
+
+ assertTrue(sut.contains(null, aAa.getPredicate(), null));
+
+ assertTrue(sut.contains(null, cBa.getPredicate(), null));
+
+ assertFalse(sut.contains(null, node("C"), null));
+ }
+
+ @Test
+ public void testContains__O() {
+ assertFalse(sut.contains(null, null, node("a")));
+
+ final var aAa = triple("a A a");
+ final var aAb = triple("a A b");
+ final var aAc = triple("a A c");
+ final var bAa = triple("b A a");
+ final var bAb = triple("b A b");
+ final var bAc = triple("b A c");
+ final var cBa = triple("c B a");
+ final var cBb = triple("c B b");
+ final var cBc = triple("c B c");
+
+ sut.add(aAa);
+ sut.add(aAb);
+ sut.add(aAc);
+ sut.add(bAa);
+ sut.add(bAb);
+ sut.add(bAc);
+ sut.add(cBa);
+ sut.add(cBb);
+ sut.add(cBc);
+
+ assertTrue(sut.contains(null, null, aAa.getObject()));
+
+ assertTrue(sut.contains(null, null, aAb.getObject()));
+
+ assertTrue(sut.contains(null, null, aAc.getObject()));
+
+ assertFalse(sut.contains(null, null, node("d")));
+ }
+
+ @Test
+ public void testContainsSP_() {
+ assertFalse(sut.contains(node("a"), node("A"), null));
+
+ final var aAa = triple("a A a");
+ final var aAb = triple("a A b");
+ final var aAc = triple("a A c");
+ final var bAa = triple("b A a");
+ final var bAb = triple("b A b");
+ final var bAc = triple("b A c");
+ final var cBa = triple("c B a");
+ final var cBb = triple("c B b");
+ final var cBc = triple("c B c");
+
+ sut.add(aAa);
+ sut.add(aAb);
+ sut.add(aAc);
+ sut.add(bAa);
+ sut.add(bAb);
+ sut.add(bAc);
+ sut.add(cBa);
+ sut.add(cBb);
+ sut.add(cBc);
+
+ assertTrue(sut.contains(aAa.getSubject(), aAa.getPredicate(), null));
+
+ assertTrue(sut.contains(bAa.getSubject(), bAa.getPredicate(), null));
+
+ assertTrue(sut.contains(cBa.getSubject(), cBa.getPredicate(), null));
+
+ assertFalse(sut.contains(node("a"), node("C"), null));
+
+ assertFalse(sut.contains(node("d"), node("D"), null));
+ }
+
+ @Test
+ public void testContainsS_O() {
+ assertFalse(sut.contains(node("a"), null, node("a")));
+
+ final var aAa = triple("a A a");
+ final var aAb = triple("a A b");
+ final var aAc = triple("a A c");
+ final var bAa = triple("b A a");
+ final var bAb = triple("b A b");
+ final var bAc = triple("b A c");
+ final var cBa = triple("c B a");
+ final var cBb = triple("c B b");
+ final var cBc = triple("c B c");
+
+ sut.add(aAa);
+ sut.add(aAb);
+ sut.add(aAc);
+ sut.add(bAa);
+ sut.add(bAb);
+ sut.add(bAc);
+ sut.add(cBa);
+ sut.add(cBb);
+ sut.add(cBc);
+
+ assertTrue(sut.contains(aAa.getSubject(), null, aAa.getObject()));
+
+ assertTrue(sut.contains(bAa.getSubject(), null, bAa.getObject()));
+
+ assertTrue(sut.contains(cBa.getSubject(), null, cBa.getObject()));
+
+ assertFalse(sut.contains(node("d"), null, node("d")));
+
+ assertFalse(sut.contains(node("d"), null, node("a")));
+
+ assertFalse(sut.contains(node("a"), null, node("d")));
+ }
+
+ @Test
+ public void testContains_PO() {
+ assertFalse(sut.contains(null, node("A"), node("a")));
+
+ final var aAa = triple("a A a");
+ final var aAb = triple("a A b");
+ final var aAc = triple("a A c");
+ final var bAa = triple("b A a");
+ final var bAb = triple("b A b");
+ final var bAc = triple("b A c");
+ final var cBa = triple("c B a");
+ final var cBb = triple("c B b");
+ final var cBc = triple("c B c");
+
+ sut.add(aAa);
+ sut.add(aAb);
+ sut.add(aAc);
+ sut.add(bAa);
+ sut.add(bAb);
+ sut.add(bAc);
+ sut.add(cBa);
+ sut.add(cBb);
+ sut.add(cBc);
+
+ assertTrue(sut.contains(null, aAa.getPredicate(), aAa.getObject()));
+
+ assertTrue(sut.contains(null, bAa.getPredicate(), bAa.getObject()));
+
+ assertTrue(sut.contains(null, cBa.getPredicate(), cBa.getObject()));
+
+ assertFalse(sut.contains(null, node("C"), node("a")));
+
+ assertFalse(sut.contains(null, node("A"), node("d")));
+
+ assertFalse(sut.contains(null, node("D"), node("d")));
+ }
+
+ @Test
+ public void testContainsValueObject() {
+ sut.add(Triple.create(
+ NodeFactory.createURI("x"),
+ NodeFactory.createURI("R"),
+ NodeFactory.createLiteral("0.1", XSDDouble.XSDdouble)));
+ assertTrue(sut.contains(Triple.create(
+ NodeFactory.createURI("x"),
+ NodeFactory.createURI("R"),
+ NodeFactory.createLiteral("0.1", XSDDouble.XSDdouble))));
+ assertFalse(sut.contains(Triple.create(
+ NodeFactory.createURI("x"),
+ NodeFactory.createURI("R"),
+ NodeFactory.createLiteral("0.10", XSDDouble.XSDdouble))));
+ assertFalse(sut.contains(Triple.create(
+ NodeFactory.createURI("x"),
+ NodeFactory.createURI("R"),
+ NodeFactory.createLiteral("0.11", XSDDouble.XSDdouble))));
+ }
+
+ @Test
+ public void testContainsValueSubject() {
+ var containedTriple = Triple.create(
+ NodeFactory.createLiteral("0.1", XSDDouble.XSDdouble),
+ NodeFactory.createURI("x"),
+ NodeFactory.createURI("R"));
+ sut.add(containedTriple);
+
+ var match = Triple.create(
+ NodeFactory.createLiteral("0.1", XSDDouble.XSDdouble),
+ NodeFactory.createURI("x"),
+ NodeFactory.createURI("R"));
+ assertTrue(sut.contains(match));
+ assertEquals(containedTriple, sut.find(match).next());
+
+ match = Triple.create(
+ NodeFactory.createLiteral("0.10", XSDDouble.XSDdouble),
+ NodeFactory.createURI("x"),
+ NodeFactory.createURI("R"));
+ assertFalse(sut.contains(match));
+ assertFalse(sut.find(match).hasNext());
+
+ match = Triple.create(
+ NodeFactory.createLiteral("0.11", XSDDouble.XSDdouble),
+ NodeFactory.createURI("x"),
+ NodeFactory.createURI("R"));
+ assertFalse(sut.contains(match));
+ assertFalse(sut.find(match).hasNext());
+ }
+
+ @Test
+ public void testContainsValuePredicate() {
+ sut.add(Triple.create(
+ NodeFactory.createURI("x"),
+ NodeFactory.createLiteral("0.1", XSDDouble.XSDdouble),
+ NodeFactory.createURI("R")));
+ assertTrue(sut.contains(Triple.create(
+ NodeFactory.createURI("x"),
+ NodeFactory.createLiteral("0.1", XSDDouble.XSDdouble),
+ NodeFactory.createURI("R"))));
+ assertFalse(sut.contains(Triple.create(
+ NodeFactory.createURI("x"),
+ NodeFactory.createLiteral("0.10", XSDDouble.XSDdouble),
+ NodeFactory.createURI("R"))));
+ assertFalse(sut.contains(Triple.create(
+ NodeFactory.createURI("x"),
+ NodeFactory.createLiteral("0.11", XSDDouble.XSDdouble),
+ NodeFactory.createURI("R"))));
+ }
+
+}
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java b/jena-core/src/test/java/org/apache/jena/mem2/GraphMem2FastTest.java
similarity index 60%
copy from jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java
copy to jena-core/src/test/java/org/apache/jena/mem2/GraphMem2FastTest.java
index f601955053..2a03057c62 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java
+++ b/jena-core/src/test/java/org/apache/jena/mem2/GraphMem2FastTest.java
@@ -15,18 +15,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.jena.mem.graph.helper;
-import org.apache.jena.graph.Graph;
-import org.apache.jena.graph.Node;
-import org.apache.jena.graph.Triple;
+package org.apache.jena.mem2;
-/**
- * Proviced the release specific access to the helper classes for working with graphs, triples and nodes.
- */
-public class Releases {
+import org.apache.jena.graph.Graph;
- public static GraphTripleNodeHelper<Graph, Triple, Node> current = new GraphTripleNodeHelperCurrent();
- public static GraphTripleNodeHelper<org.apache.shadedJena480.graph.Graph, org.apache.shadedJena480.graph.Triple, org.apache.shadedJena480.graph.Node> v480 = new GraphTripleNodeHelper480();
+public class GraphMem2FastTest extends AbstractGraphMem2Test {
-}
+ @Override
+ protected Graph createGraph() {
+ return new GraphMem2Fast();
+ }
+}
\ No newline at end of file
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java b/jena-core/src/test/java/org/apache/jena/mem2/GraphMem2LegacyTest.java
similarity index 60%
copy from jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java
copy to jena-core/src/test/java/org/apache/jena/mem2/GraphMem2LegacyTest.java
index f601955053..2f2944a096 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java
+++ b/jena-core/src/test/java/org/apache/jena/mem2/GraphMem2LegacyTest.java
@@ -15,18 +15,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.jena.mem.graph.helper;
+package org.apache.jena.mem2;
import org.apache.jena.graph.Graph;
-import org.apache.jena.graph.Node;
-import org.apache.jena.graph.Triple;
-/**
- * Proviced the release specific access to the helper classes for working with graphs, triples and nodes.
- */
-public class Releases {
-
- public static GraphTripleNodeHelper<Graph, Triple, Node> current = new GraphTripleNodeHelperCurrent();
- public static GraphTripleNodeHelper<org.apache.shadedJena480.graph.Graph, org.apache.shadedJena480.graph.Triple, org.apache.shadedJena480.graph.Node> v480 = new GraphTripleNodeHelper480();
+public class GraphMem2LegacyTest extends AbstractGraphMem2Test {
-}
+ @Override
+ protected Graph createGraph() {
+ return new GraphMem2Legacy();
+ }
+}
\ No newline at end of file
diff --git a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java b/jena-core/src/test/java/org/apache/jena/mem2/GraphMem2RoaringTest.java
similarity index 60%
copy from jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java
copy to jena-core/src/test/java/org/apache/jena/mem2/GraphMem2RoaringTest.java
index f601955053..bc539bab1a 100644
--- a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/mem/graph/helper/Releases.java
+++ b/jena-core/src/test/java/org/apache/jena/mem2/GraphMem2RoaringTest.java
@@ -15,18 +15,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.jena.mem.graph.helper;
+package org.apache.jena.mem2;
import org.apache.jena.graph.Graph;
-import org.apache.jena.graph.Node;
-import org.apache.jena.graph.Triple;
-/**
- * Proviced the release specific access to the helper classes for working with graphs, triples and nodes.
- */
-public class Releases {
-
- public static GraphTripleNodeHelper<Graph, Triple, Node> current = new GraphTripleNodeHelperCurrent();
- public static GraphTripleNodeHelper<org.apache.shadedJena480.graph.Graph, org.apache.shadedJena480.graph.Triple, org.apache.shadedJena480.graph.Node> v480 = new GraphTripleNodeHelper480();
+public class GraphMem2RoaringTest extends AbstractGraphMem2Test {
-}
+ @Override
+ protected Graph 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
new file mode 100644
index 0000000000..aabb0c3c6a
--- /dev/null
+++ b/jena-core/src/test/java/org/apache/jena/mem2/GraphMem2Test.java
@@ -0,0 +1,173 @@
+/*
+ * 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.mem2;
+
+import org.apache.jena.graph.Triple;
+import org.apache.jena.mem2.store.TripleStore;
+import org.apache.jena.util.iterator.NullIterator;
+import org.junit.Test;
+
+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.mockito.Mockito.*;
+
+public class GraphMem2Test {
+
+ @Test
+ public void destroy() {
+ TripleStore mockStore = mock();
+
+ var sut = new GraphMem2(mockStore);
+ sut.destroy();
+
+ inOrder(mockStore).verify(mockStore, times(1)).clear();
+ verifyNoMoreInteractions(mockStore);
+ }
+
+ @Test
+ public void clear() {
+ TripleStore mockStore = mock();
+
+ when(mockStore.find(any())).thenReturn(NullIterator.emptyIterator());
+
+ var sut = new GraphMem2(mockStore);
+ sut.clear();
+
+ inOrder(mockStore).verify(mockStore, times(1)).find(any());
+ inOrder(mockStore).verify(mockStore, times(1)).clear();
+ verifyNoMoreInteractions(mockStore);
+ }
+
+ @Test
+ public void performAdd() {
+ TripleStore mockStore = mock();
+
+ var triple = triple("a b x");
+
+ var sut = new GraphMem2(mockStore);
+ sut.performAdd(triple);
+
+ inOrder(mockStore).verify(mockStore, times(1)).add(triple);
+ verifyNoMoreInteractions(mockStore);
+ }
+
+ @Test
+ public void performDelete() {
+ TripleStore mockStore = mock();
+
+ var triple = triple("a b x");
+
+ var sut = new GraphMem2(mockStore);
+ sut.performDelete(triple);
+
+ inOrder(mockStore).verify(mockStore, times(1)).remove(triple);
+ verifyNoMoreInteractions(mockStore);
+ }
+
+ @Test
+ public void stream() {
+ TripleStore mockStore = mock();
+
+ when(mockStore.stream()).thenReturn(Stream.empty());
+
+ var sut = new GraphMem2(mockStore);
+ sut.stream();
+
+ inOrder(mockStore).verify(mockStore, times(1)).stream();
+ verifyNoMoreInteractions(mockStore);
+ }
+
+ @Test
+ public void StreamMatch() {
+ TripleStore mockStore = mock();
+
+ var s = node("s");
+ var p = node("p");
+ var o = node("o");
+ var triple = Triple.create(s, p, o);
+
+ when(mockStore.stream(triple)).thenReturn(Stream.of(triple));
+
+ var sut = new GraphMem2(mockStore);
+ sut.stream(s, p, o);
+
+ inOrder(mockStore).verify(mockStore, times(1)).stream(triple);
+ verifyNoMoreInteractions(mockStore);
+ }
+
+ @Test
+ public void graphBaseFind() {
+ TripleStore mockStore = mock();
+
+ var triple = triple("a b x");
+
+ when(mockStore.find(triple)).thenReturn(NullIterator.emptyIterator());
+
+ var sut = new GraphMem2(mockStore);
+ sut.graphBaseFind(triple);
+
+ inOrder(mockStore).verify(mockStore, times(1)).find(triple);
+ verifyNoMoreInteractions(mockStore);
+ }
+
+ @Test
+ public void graphBaseContains() {
+ TripleStore mockStore = mock();
+
+ var triple = triple("a b x");
+
+ when(mockStore.contains(triple)).thenReturn(false);
+
+ var sut = new GraphMem2(mockStore);
+ sut.graphBaseContains(triple);
+
+ inOrder(mockStore).verify(mockStore, times(1)).contains(triple);
+ verifyNoMoreInteractions(mockStore);
+ }
+
+ @Test
+ public void graphBaseSize() {
+ TripleStore mockStore = mock();
+
+ when(mockStore.countTriples()).thenReturn(0);
+
+ var sut = new GraphMem2(mockStore);
+ sut.graphBaseSize();
+
+ inOrder(mockStore).verify(mockStore, times(1)).countTriples();
+ verifyNoMoreInteractions(mockStore);
+ }
+
+ @Test
+ public void testGetCapabilities() {
+ TripleStore mockStore = mock();
+
+ var sut = new GraphMem2(mockStore);
+ var capapbilities = sut.getCapabilities();
+
+ assertTrue(capapbilities.sizeAccurate());
+ assertTrue(capapbilities.addAllowed());
+ assertTrue(capapbilities.deleteAllowed());
+ assertFalse(capapbilities.handlesLiteralTyping());
+
+ }
+}
\ No newline at end of file
diff --git a/jena-core/src/test/java/org/apache/jena/mem2/collection/AbstractJenaMapNodeTest.java b/jena-core/src/test/java/org/apache/jena/mem2/collection/AbstractJenaMapNodeTest.java
new file mode 100644
index 0000000000..69ba1bc9f9
--- /dev/null
+++ b/jena-core/src/test/java/org/apache/jena/mem2/collection/AbstractJenaMapNodeTest.java
@@ -0,0 +1,648 @@
+/*
+ * 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.mem2.collection;
+
+import org.apache.jena.graph.Node;
+import org.hamcrest.collection.IsEmptyCollection;
+import org.hamcrest.collection.IsIterableContainingInAnyOrder;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.ConcurrentModificationException;
+import java.util.NoSuchElementException;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import static org.apache.jena.testing_framework.GraphHelper.node;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.*;
+
+public abstract class AbstractJenaMapNodeTest {
+
+ protected JenaMap<Node, Object> sut;
+
+ protected abstract JenaMap<Node, Object> createNodeMap();
+
+ @Before
+ public void setUp() throws Exception {
+ sut = createNodeMap();
+ }
+
+ @Test
+ public void isEmpty() {
+ assertTrue(sut.isEmpty());
+ sut.tryPut(node("s"), null);
+ assertFalse(sut.isEmpty());
+ }
+
+ @Test
+ public void testTryPut() {
+ assertTrue(sut.tryPut(node("s"), 1));
+ assertEquals(1, sut.size());
+ assertFalse(sut.tryPut(node("s"), 2));
+ assertEquals(1, sut.size());
+ }
+
+ @Test
+ public void testTryPutOverridesValue() {
+ sut.tryPut(node("s"), 1);
+ assertEquals(1, sut.get(node("s")));
+ sut.tryPut(node("s"), 2);
+ assertEquals(2, sut.get(node("s")));
+ }
+
+ @Test
+ public void testPut() {
+ sut.put(node("s"), 1);
+ assertEquals(1, sut.size());
+ sut.put(node("s"), 2);
+ assertEquals(1, sut.size());
+ }
+
+ @Test
+ public void testPutOverridesValue() {
+ sut.put(node("s"), 1);
+ assertEquals(1, sut.get(node("s")));
+ sut.put(node("s"), 2);
+ assertEquals(2, sut.get(node("s")));
+ }
+
+ @Test
+ public void testTryRemove() {
+ sut.put(node("s"), null);
+ assertTrue(sut.tryRemove(node("s")));
+ assertEquals(0, sut.size());
+ assertFalse(sut.tryRemove(node("s")));
+ }
+
+ @Test
+ public void testRemoveUnchecked() {
+ sut.put(node("s"), null);
+ sut.removeUnchecked(node("s"));
+ assertEquals(0, sut.size());
+ }
+
+ @Test
+ public void testGet() {
+ sut.put(node("s"), 1);
+ assertEquals(1, sut.get(node("s")));
+ assertNull(sut.get(node("s2")));
+ }
+
+ @Test
+ public void testGet2() {
+ sut.put(node("s"), 1);
+ sut.put(node("s2"), 2);
+ assertEquals(1, sut.get(node("s")));
+ assertEquals(2, sut.get(node("s2")));
+ }
+
+ @Test
+ public void testGetOrDefault() {
+ sut.put(node("s"), 1);
+ assertEquals(1, sut.getOrDefault(node("s"), 2));
+ assertEquals(2, sut.getOrDefault(node("s2"), 2));
+ }
+
+ @Test
+ public void testComputeIfAbsent() {
+ sut.computeIfAbsent(node("s"), () -> 1);
+ assertEquals(1, sut.get(node("s")));
+ sut.computeIfAbsent(node("s"), () -> 2);
+ assertEquals(1, sut.get(node("s")));
+ }
+
+ @Test
+ public void testCompute() {
+ sut.compute(node("s"), (v) -> {
+ assertNull(v);
+ return 1;
+ });
+ assertEquals(1, sut.get(node("s")));
+ sut.compute(node("s"), (v) -> {
+ assertEquals(1, v);
+ return 2;
+ });
+ assertEquals(2, sut.get(node("s")));
+ sut.compute(node("s"), (v) -> {
+ assertEquals(2, v);
+ return null;
+ });
+ assertNull(sut.get(node("s")));
+ assertTrue(sut.isEmpty());
+ }
+
+ @Test
+ public void testClear() {
+ sut.put(node("s"), null);
+ sut.clear();
+ assertEquals(0, sut.size());
+ }
+
+ @Test
+ public void testContainKey() {
+ assertFalse(sut.containsKey(node("s")));
+ sut.put(node("s"), null);
+ assertTrue(sut.containsKey(node("s")));
+ assertFalse(sut.containsKey(node("s2")));
+ }
+
+ @Test
+ public void testKeyIteratorEmpty() {
+ var iter = sut.keyIterator();
+ assertFalse(iter.hasNext());
+ assertThrows(NoSuchElementException.class, () -> iter.next());
+ }
+
+ @Test
+ public void testValueIteratorEmpty2() {
+ var iter = sut.valueIterator();
+ assertFalse(iter.hasNext());
+ assertThrows(NoSuchElementException.class, () -> iter.next());
+ }
+
+ @Test
+ public void testKeyIteratorNextThrowsConcurrentModificationException() {
+ sut.put(node("s"), null);
+ var iter = sut.keyIterator();
+ sut.put(node("s2"), null);
+ assertThrows(ConcurrentModificationException.class, iter::next);
+ }
+
+ @Test
+ public void testKeySpliteratorAdvanceThrowsConcurrentModificationException() {
+ sut.put(node("s"), null);
+ var spliterator = sut.keySpliterator();
+ sut.put(node("s2"), null);
+ assertThrows(ConcurrentModificationException.class, () -> spliterator.tryAdvance(t -> {
+ }));
+ }
+
+ @Test
+ public void testValueIteratorNextThrowsConcurrentModificationException() {
+ sut.put(node("s"), null);
+ var iter = sut.keyIterator();
+ sut.put(node("s2"), null);
+ assertThrows(ConcurrentModificationException.class, iter::next);
+ }
+
+ @Test
+ public void testValueSpliteratorAdvanceThrowsConcurrentModificationException() {
+ sut.put(node("s"), null);
+ var spliterator = sut.valueSpliterator();
+ sut.put(node("s2"), null);
+ assertThrows(ConcurrentModificationException.class, () -> spliterator.tryAdvance(t -> {
+ }));
+ }
+
+ @Test
+ public void testKeyIterator1() {
+ final var n0 = node("s");
+
+ sut.put(n0, null);
+
+ final var iter = sut.keyIterator();
+ assertThat(iter.toList(), IsIterableContainingInAnyOrder.containsInAnyOrder(n0));
+ }
+
+ @Test
+ public void testValueIterator1() {
+ final var n0 = node("s");
+
+ sut.put(n0, 1);
+
+ final var iter = sut.valueIterator();
+ assertThat(iter.toList(), IsIterableContainingInAnyOrder.containsInAnyOrder(1));
+
+ }
+
+ @Test
+ public void testKeyIterator2() {
+ final var n0 = node("s");
+ final var n1 = node("s2");
+
+ sut.put(n0, null);
+ sut.put(n1, null);
+
+ final var iter = sut.keyIterator();
+ assertThat(iter.toList(), IsIterableContainingInAnyOrder.containsInAnyOrder(n0, n1));
+ }
+
+ @Test
+ public void testValueIterator2() {
+ final var n0 = node("s");
+ final var n1 = node("s2");
+
+ sut.put(n0, 1);
+ sut.put(n1, 2);
+
+ final var iter = sut.valueIterator();
+ assertThat(iter.toList(), IsIterableContainingInAnyOrder.containsInAnyOrder(1, 2));
+ }
+
+ @Test
+ public void testKeyIterator3() {
+ final var n0 = node("s");
+ final var n1 = node("s2");
+ final var n2 = node("s3");
+
+ sut.put(n0, null);
+ sut.put(n1, null);
+ sut.put(n2, null);
+
+ final var iter = sut.keyIterator();
+ assertThat(iter.toList(), IsIterableContainingInAnyOrder.containsInAnyOrder(n0, n1, n2));
+ }
+
+ @Test
+ public void testValueIterator3() {
+ final var n0 = node("s");
+ final var n1 = node("s2");
+ final var n2 = node("s3");
+
+ sut.put(n0, 1);
+ sut.put(n1, 2);
+ sut.put(n2, 3);
+
+ final var iter = sut.valueIterator();
+ assertThat(iter.toList(), IsIterableContainingInAnyOrder.containsInAnyOrder(1, 2, 3));
+ }
+
+ @Test
+ public void testKeySpliteratorEmpty() {
+ var spliterator = sut.keySpliterator();
+ assertFalse(spliterator.tryAdvance(t -> fail()));
+ }
+
+ @Test
+ public void testValueSpliteratorEmpty() {
+ var spliterator = sut.valueSpliterator();
+ assertFalse(spliterator.tryAdvance(t -> fail()));
+ }
+
+ @Test
+ public void testKeySpliterator1() {
+ final var n0 = node("s");
+
+ sut.put(n0, null);
+
+ final var spliterator = sut.keySpliterator();
+ final var list = StreamSupport.stream(spliterator, false).collect(Collectors.toList());
+ assertThat(list, IsIterableContainingInAnyOrder.containsInAnyOrder(n0));
+ }
+
+ @Test
+ public void testValueSpliterator1() {
+ sut.put(node("s"), 1);
+
+ final var spliterator = sut.valueSpliterator();
+ final var list = StreamSupport.stream(spliterator, false).collect(Collectors.toList());
+ assertThat(list, IsIterableContainingInAnyOrder.containsInAnyOrder(1));
+ }
+
+ @Test
+ public void testKeySpliterator2() {
+ final var n0 = node("s");
+ final var n1 = node("s2");
+
+ sut.put(n0, null);
+ sut.put(n1, null);
+
+ final var spliterator = sut.keySpliterator();
+ final var list = StreamSupport.stream(spliterator, false).collect(Collectors.toList());
+ assertThat(list, IsIterableContainingInAnyOrder.containsInAnyOrder(n0, n1));
+ }
+
+ @Test
+ public void testValueSpliterator2() {
+ sut.put(node("s"), 1);
+ sut.put(node("s2"), 2);
+
+ final var spliterator = sut.valueSpliterator();
+ final var list = StreamSupport.stream(spliterator, false).collect(Collectors.toList());
+ assertThat(list, IsIterableContainingInAnyOrder.containsInAnyOrder(1, 2));
+ }
+
+ @Test
+ public void testKeySpliterator3() {
+ final var n0 = node("s");
+ final var n1 = node("s2");
+ final var n2 = node("s3");
+
+ sut.put(n0, null);
+ sut.put(n1, null);
+ sut.put(n2, null);
+
+ final var spliterator = sut.keySpliterator();
+ final var list = StreamSupport.stream(spliterator, false).collect(Collectors.toList());
+ assertThat(list, IsIterableContainingInAnyOrder.containsInAnyOrder(n0, n1, n2));
+ }
+
+ @Test
+ public void testValueSpliterator3() {
+ sut.put(node("s"), 1);
+ sut.put(node("s2"), 2);
+ sut.put(node("s3"), 3);
+
+ final var spliterator = sut.valueSpliterator();
+ final var list = StreamSupport.stream(spliterator, false).collect(Collectors.toList());
+ assertThat(list, IsIterableContainingInAnyOrder.containsInAnyOrder(1, 2, 3));
+ }
+
+ @Test
+ public void testKeyStreamEmpty() {
+ var stream = sut.keyStream();
+ assertThat(stream.collect(Collectors.toList()), IsEmptyCollection.empty());
+ }
+
+ @Test
+ public void testValueStreamEmpty() {
+ var stream = sut.valueStream();
+ assertThat(stream.collect(Collectors.toList()), IsEmptyCollection.empty());
+ }
+
+ @Test
+ public void testKeyStream1() {
+ final var n0 = node("s");
+
+ sut.put(n0, null);
+
+ final var stream = sut.keyStream();
+ assertThat(stream.collect(Collectors.toList()), IsIterableContainingInAnyOrder.containsInAnyOrder(n0));
+ }
+
+ @Test
+ public void testValueStream1() {
+ sut.put(node("s"), 1);
+
+ final var stream = sut.valueStream();
+ assertThat(stream.collect(Collectors.toList()), IsIterableContainingInAnyOrder.containsInAnyOrder(1));
+ }
+
+ @Test
+ public void testKeyStream2() {
+ final var n0 = node("s");
+ final var n1 = node("s2");
+
+ sut.put(n0, null);
+ sut.put(n1, null);
+
+ final var stream = sut.keyStream();
+ assertThat(stream.collect(Collectors.toList()), IsIterableContainingInAnyOrder.containsInAnyOrder(n0, n1));
+ }
+
+ @Test
+ public void testValueStream2() {
+ sut.put(node("s"), 1);
+ sut.put(node("s2"), 2);
+
+ final var stream = sut.valueStream();
+ assertThat(stream.collect(Collectors.toList()), IsIterableContainingInAnyOrder.containsInAnyOrder(1, 2));
+ }
+
+ @Test
+ public void testKeyStream3() {
+ final var n0 = node("s");
+ final var n1 = node("s2");
+ final var n2 = node("s3");
+
+ sut.put(n0, null);
+ sut.put(n1, null);
+ sut.put(n2, null);
+
+ final var stream = sut.keyStream();
+ assertThat(stream.collect(Collectors.toList()), IsIterableContainingInAnyOrder.containsInAnyOrder(n0, n1, n2));
+ }
+
+ @Test
+ public void testValueStream3() {
... 4765 lines suppressed ...