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 d3fa1e9fd8 GH-3507: RDFS testing/wrapping framework. Fixed
literals-in-subject inferences.
d3fa1e9fd8 is described below
commit d3fa1e9fd86af9931715b1c720f26f4479e9efa7
Author: Claus Stadler <[email protected]>
AuthorDate: Fri Aug 8 14:47:47 2025 +0200
GH-3507: RDFS testing/wrapping framework. Fixed literals-in-subject
inferences.
---
.gitignore | 4 +
jena-arq/pom.xml | 19 +-
.../org/apache/jena/rdfs/DatasetGraphRDFS.java | 120 +-----------
.../main/java/org/apache/jena/rdfs/GraphRDFS.java | 53 +----
.../java/org/apache/jena/rdfs/RDFSFactory.java | 18 +-
.../jena/rdfs/assembler/DatasetRDFSAssembler.java | 6 +-
.../jena/rdfs/assembler/GraphRDFSAssembler.java | 2 +-
.../org/apache/jena/rdfs/engine/ApplyRDFS.java | 10 +-
.../DatasetGraphWithGraphTransform.java} | 26 ++-
.../org/apache/jena/rdfs/engine/GraphIncRDFS.java | 9 +-
.../{GraphRDFS.java => engine/GraphMatch.java} | 37 ++--
.../org/apache/jena/rdfs/engine/InfFindQuad.java | 6 -
.../org/apache/jena/rdfs/engine/InfFindTriple.java | 5 -
.../java/org/apache/jena/rdfs/engine/MapperX.java | 6 +
.../java/org/apache/jena/rdfs/engine/Mappers.java | 5 +
.../java/org/apache/jena/rdfs/engine/Match.java | 15 ++
.../org/apache/jena/rdfs/engine/MatchAdapter.java | 72 +++++++
.../engine/{InfFindTriple.java => MatchGraph.java} | 37 ++--
.../org/apache/jena/rdfs/engine/MatchRDFS.java | 11 +-
.../engine/{Match.java => MatchRDFSWrapper.java} | 29 ++-
.../rdfs/engine/{Match.java => MatchWrapper.java} | 29 ++-
.../org/apache/jena/rdfs/setup/BaseSetupRDFS.java | 2 +-
.../org/apache/jena/rdfs/setup/MatchVocabRDFS.java | 7 +
.../jena/rdfs/AbstractDatasetGraphCompare.java | 217 +++++++++++++++++++++
.../apache/jena/rdfs/AbstractTestRDFS_Extra.java | 85 ++++++++
.../apache/jena/rdfs/AbstractTestRDFS_Find.java | 127 ++++++++++++
.../org/apache/jena/rdfs/GraphFindExecutable.java | 99 ++++++++++
.../test/java/org/apache/jena/rdfs/TS_InfRdfs.java | 2 +
.../jena/rdfs/TestDatasetGraphFindRDFS.java} | 26 ++-
.../org/apache/jena/rdfs/TestDatasetGraphRDFS.java | 5 +-
.../apache/jena/atlas/iterator/FilterUnique.java | 1 -
.../{FilterUnique.java => FilterUniqueCache.java} | 19 +-
.../java/org/apache/jena/atlas/iterator/Iter.java | 13 ++
.../apache/jena/atlas/iterator/IteratorConcat.java | 4 +-
.../org/apache/jena/tdb2/match/MapperXTDB.java | 57 ++++++
.../java/org/apache/jena/tdb2/match/MatchTDB.java | 74 +++++++
.../org/apache/jena/tdb2/match/TestMatchTDB2.java | 82 ++++++++
pom.xml | 9 +-
38 files changed, 1072 insertions(+), 276 deletions(-)
diff --git a/.gitignore b/.gitignore
index e93f7bff2a..7e29af4163 100644
--- a/.gitignore
+++ b/.gitignore
@@ -45,3 +45,7 @@ hs_err_*
# Fuseki file area
run/
+
+# Benchmark JSON results
+jena-benchmarks/jena-benchmarks-jmh/*.json
+
diff --git a/jena-arq/pom.xml b/jena-arq/pom.xml
index 2939e959ab..a1698678ce 100644
--- a/jena-arq/pom.xml
+++ b/jena-arq/pom.xml
@@ -45,7 +45,7 @@
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
- </dependency>
+ </dependency>
<dependency>
<groupId>org.slf4j</groupId>
@@ -71,12 +71,12 @@
<artifactId>jakarta.json</artifactId>
</dependency>
<!-- End Titanium JSON-LD 1.1 -->
-
+
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
</dependency>
-
+
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
@@ -124,6 +124,11 @@
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-math4-legacy</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
@@ -139,7 +144,7 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
</plugin>
-
+
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
@@ -172,11 +177,11 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
- <executions>
+ <executions>
<execution>
- <id>attach-sources-test</id>
+ <id>attach-sources-test</id>
<goals>
- <goal>test-jar-no-fork</goal>
+ <goal>test-jar-no-fork</goal>
</goals>
</execution>
</executions>
diff --git a/jena-arq/src/main/java/org/apache/jena/rdfs/DatasetGraphRDFS.java
b/jena-arq/src/main/java/org/apache/jena/rdfs/DatasetGraphRDFS.java
index 1ca880b9c8..765334920b 100644
--- a/jena-arq/src/main/java/org/apache/jena/rdfs/DatasetGraphRDFS.java
+++ b/jena-arq/src/main/java/org/apache/jena/rdfs/DatasetGraphRDFS.java
@@ -18,134 +18,22 @@
package org.apache.jena.rdfs;
-import static org.apache.jena.atlas.iterator.Iter.iter;
-
-import java.util.Iterator;
-
-import org.apache.jena.atlas.iterator.Iter;
-import org.apache.jena.graph.Graph;
-import org.apache.jena.graph.Node;
+import org.apache.jena.rdfs.engine.DatasetGraphWithGraphTransform;
import org.apache.jena.sparql.core.DatasetGraph;
-import org.apache.jena.sparql.core.DatasetGraphWrapper;
import org.apache.jena.sparql.core.DatasetGraphWrapperView;
-import org.apache.jena.sparql.core.Quad;
import org.apache.jena.sparql.util.Context;
-public class DatasetGraphRDFS extends DatasetGraphWrapper implements
DatasetGraphWrapperView {
+public class DatasetGraphRDFS extends DatasetGraphWithGraphTransform
implements DatasetGraphWrapperView {
// Do not unwrap for query execution.
-
private final SetupRDFS setup;
public DatasetGraphRDFS(DatasetGraph dsg, SetupRDFS setup) {
- super(dsg);
+ super(dsg, g -> new GraphRDFS(g, setup));
this.setup = setup;
}
public DatasetGraphRDFS(DatasetGraph dsg, SetupRDFS setup, Context cxt) {
- super(dsg, cxt);
+ super(dsg, cxt, g -> new GraphRDFS(g, setup));
this.setup = setup;
}
-
- // Graph-centric access.
- @Override
- public Graph getDefaultGraph() {
- Graph base = getG().getDefaultGraph();
- return new GraphRDFS(base, setup);
- }
-
- @Override
- public Graph getUnionGraph() {
- Graph base = getG().getUnionGraph();
- return new GraphRDFS(base, setup);
- }
-
- @Override
- public Graph getGraph(Node graphNode) {
- Graph base = getG().getGraph(graphNode);
- if ( base == null )
- return null;
- return new GraphRDFS(base, setup);
- }
-
- @Override
- public Iterator<Quad> find()
- { return find(Node.ANY, Node.ANY, Node.ANY, Node.ANY); }
-
- // Quad-centric access
- @Override
- public Iterator<Quad> find(Quad quad) {
- return find(quad.getGraph(), quad.getSubject(), quad.getPredicate(),
quad.getObject());
- }
-
- @Override
- public Iterator<Quad> find(Node g, Node s, Node p, Node o) {
- Iterator<Quad> iter = findInf(g, s, p, o);
- if ( iter == null )
- return Iter.nullIterator();
- return iter;
- }
-
-// private Iterator<Quad> findInf(Node g, Node s, Node p, Node o) {
-// // Puts in the graph name for the quad base don g even if g is ANY
or null.
-// MatchRDFS<Node, Quad> infMatcher = new InfFindQuad(setup, g, getR());
-// Stream<Quad> quads = infMatcher.match(s, p, o);
-// Iterator<Quad> iter = quads.iterator();
-// iter = Iter.onClose(iter, ()->quads.close());
-// return iter;
-// }
-
- /**
- * Find, graph by graph.
- */
- private Iterator<Quad> findInf(Node g, Node s, Node p, Node o) {
- if ( g != null && g.isConcrete() ) {
- // Includes the union graph case.
- return findOneGraphInf(g, s, p, o);
- }
- // Wildcard. Do each graph in-term.
- // This ensures the graph node of the quad corresponds to where the
inference came from.
- Iter<Quad> iter1 = findOneGraphInf(Quad.defaultGraphIRI, s, p, o);
- Iterator<Quad> iter2 = findAllNamedGraphInf(s, p, o);
- return iter1.append(iter2);
- }
-
- // All named graphs, with inference. Quads refer to the name graph they
were caused by.
- private Iterator<Quad> findAllNamedGraphInf(Node s, Node p, Node o) {
- return Iter.flatMap(listGraphNodes(), gn -> findOneGraphInf(gn, s, p,
o));
- }
-
- // Single graph (inc. union graph). Quads refer to the name graph they
were caused by.
- private Iter<Quad> findOneGraphInf(Node g, Node s, Node p, Node o) {
- if ( ! g.isConcrete() )
- throw new IllegalStateException();
- // f ( Quad.isUnionGraph(g) ) {}
- // Specific named graph.
- return iter(getGraph(g).find(s,p,o)).map(t->Quad.create(g, t));
- }
-
- @Override
- public Iterator<Quad> findNG(Node g, Node s, Node p, Node o) {
- if ( Quad.isDefaultGraph(g) )
- throw new IllegalArgumentException("Default graph in findNG call");
- if ( g == null )
- g = Node.ANY;
- if ( g == Node.ANY )
- return findAllNamedGraphInf(s, p, o);
- // Same as specific named graph - we return quads in the union graph.
-// if ( Quad.isUnionGraph(g) ) {}
- return findOneGraphInf(g, s, p, o);
- }
-
- @Override
- public boolean contains(Quad quad)
- { return contains(quad.getGraph(), quad.getSubject(), quad.getPredicate(),
quad.getObject()); }
-
- @Override
- public boolean contains(Node g, Node s, Node p, Node o) {
- // Go through the inference machinery.
- Iterator<Quad> iter = find(g, s, p, o);
- try {
- return iter.hasNext();
- } finally { Iter.close(iter); }
- }
}
diff --git a/jena-arq/src/main/java/org/apache/jena/rdfs/GraphRDFS.java
b/jena-arq/src/main/java/org/apache/jena/rdfs/GraphRDFS.java
index 0fdece6ad2..60139ec767 100644
--- a/jena-arq/src/main/java/org/apache/jena/rdfs/GraphRDFS.java
+++ b/jena-arq/src/main/java/org/apache/jena/rdfs/GraphRDFS.java
@@ -18,66 +18,21 @@
package org.apache.jena.rdfs;
-import java.util.stream.Stream;
-
import org.apache.jena.graph.Graph;
import org.apache.jena.graph.Node;
-import org.apache.jena.graph.Triple;
+import org.apache.jena.rdfs.engine.GraphMatch;
import org.apache.jena.rdfs.engine.InfFindTriple;
-import org.apache.jena.rdfs.engine.MatchRDFS;
import org.apache.jena.rdfs.setup.ConfigRDFS;
-import org.apache.jena.sparql.graph.GraphWrapper;
-import org.apache.jena.util.iterator.ExtendedIterator;
-import org.apache.jena.util.iterator.WrappedIterator;
/**
* RDFS graph over a base graph.
*/
-public class GraphRDFS extends GraphWrapper {
- private final MatchRDFS<Node, Triple> source;
+public class GraphRDFS extends GraphMatch {
private final ConfigRDFS<Node> setup;
public GraphRDFS(Graph graph, ConfigRDFS<Node> setup) {
- super(graph);
+ super(graph, new InfFindTriple(setup, graph));
this.setup = setup;
- this.source = new InfFindTriple(setup, graph);
- }
-
- @Override
- public ExtendedIterator<Triple> find(Triple m) {
- return find(m.getSubject(), m.getPredicate(), m.getObject());
- }
-
- @Override
- public ExtendedIterator<Triple> find(Node s, Node p, Node o) {
- Stream<Triple> stream = source.match(s, p, o);
- ExtendedIterator<Triple> iter = WrappedIterator.ofStream(stream);
- return iter;
- }
-
- @Override
- public Stream<Triple> stream(Node s, Node p, Node o) {
- return source.match(s, p, o);
- }
-
- @Override
- public boolean contains(Node s, Node p, Node o) {
- // Must go via find()-like functionality.
- ExtendedIterator<Triple> iter = find(s, p, o);
- try {
- return iter.hasNext();
- } finally { iter.close(); }
- }
-
- @Override
- public boolean contains(Triple t) {
- return contains(t.getSubject(), t.getPredicate(), t.getObject());
- }
-
- @Override
- public int size() {
- // Report the size of the underlying graph.
- // Even better, don't ask.
- return super.size();
}
}
+
diff --git a/jena-arq/src/main/java/org/apache/jena/rdfs/RDFSFactory.java
b/jena-arq/src/main/java/org/apache/jena/rdfs/RDFSFactory.java
index 37e42fe723..034d73eb8a 100644
--- a/jena-arq/src/main/java/org/apache/jena/rdfs/RDFSFactory.java
+++ b/jena-arq/src/main/java/org/apache/jena/rdfs/RDFSFactory.java
@@ -18,8 +18,12 @@
package org.apache.jena.rdfs;
import org.apache.jena.graph.Graph;
+import org.apache.jena.graph.Node;
import org.apache.jena.query.Dataset;
import org.apache.jena.query.DatasetFactory;
+import org.apache.jena.rdfs.engine.MapperX;
+import org.apache.jena.rdfs.setup.BaseSetupRDFS;
+import org.apache.jena.rdfs.setup.ConfigRDFS;
import org.apache.jena.riot.system.StreamRDF;
import org.apache.jena.sparql.core.DatasetGraph;
import org.apache.jena.sparql.util.NodeUtils;
@@ -56,13 +60,13 @@ public class RDFSFactory {
/** Create an RDFS inference dataset. */
public static DatasetGraph datasetRDFS(DatasetGraph data, Graph vocab ) {
SetupRDFS setup = setupRDFS(vocab);
- return new DatasetGraphRDFS(data, setup);
+ return datasetRDFS(data, setup);
}
/** Create an RDFS inference dataset. */
public static Dataset datasetRDFS(Dataset data, Graph vocab ) {
SetupRDFS setup = setupRDFS(vocab);
- return DatasetFactory.wrap(new DatasetGraphRDFS(data.asDatasetGraph(),
setup));
+ return DatasetFactory.wrap(datasetRDFS(data.asDatasetGraph(), setup));
}
/** Create an {@link SetupRDFS} */
@@ -70,6 +74,16 @@ public class RDFSFactory {
return new SetupRDFS(vocab);
}
+ /** Create a {@link ConfigRDFS} via a {@link MapperX}. */
+ public static <X> ConfigRDFS<X> setupRDFS(Graph vocab, MapperX<X, ?>
mapper) {
+ return new BaseSetupRDFS<>(vocab) {
+ @Override
+ protected X fromNode(Node node) {
+ return mapper.fromNode(node);
+ }
+ };
+ }
+
/** Stream expand data based on a separate vocabulary */
public static StreamRDF streamRDFS(StreamRDF data, Graph vocab) {
SetupRDFS setup = new SetupRDFS(vocab);
diff --git
a/jena-arq/src/main/java/org/apache/jena/rdfs/assembler/DatasetRDFSAssembler.java
b/jena-arq/src/main/java/org/apache/jena/rdfs/assembler/DatasetRDFSAssembler.java
index f82ec6c80e..7ac921a05d 100644
---
a/jena-arq/src/main/java/org/apache/jena/rdfs/assembler/DatasetRDFSAssembler.java
+++
b/jena-arq/src/main/java/org/apache/jena/rdfs/assembler/DatasetRDFSAssembler.java
@@ -26,7 +26,6 @@ import org.apache.jena.assembler.Assembler;
import org.apache.jena.assembler.exceptions.AssemblerException;
import org.apache.jena.graph.Graph;
import org.apache.jena.rdf.model.Resource;
-import org.apache.jena.rdfs.DatasetGraphRDFS;
import org.apache.jena.rdfs.RDFSFactory;
import org.apache.jena.rdfs.SetupRDFS;
import org.apache.jena.riot.RDFDataMgr;
@@ -50,7 +49,7 @@ public class DatasetRDFSAssembler extends
NamedDatasetAssembler {
/**
* <pre>
* <#rdfsDS> rdf:type ja:DatasetRDFS ;
- * ja:rdfs "vocab.ttl";
+ * ja:rdfsSchema "vocab.ttl";
* ja:dataset <#baseDS> ;
* .
*
@@ -58,7 +57,6 @@ public class DatasetRDFSAssembler extends
NamedDatasetAssembler {
* ja:name "TIM database" # optional: this is need if the base
database is accessed directly.
* ja:data "data1.trig";
* ## ja:data "data2.trig";
- *
* .
* </pre>
*/
@@ -76,7 +74,7 @@ public class DatasetRDFSAssembler extends
NamedDatasetAssembler {
Graph schema = RDFDataMgr.loadGraph(schemaFile);
SetupRDFS setup = RDFSFactory.setupRDFS(schema);
- DatasetGraph dsg = new DatasetGraphRDFS(base, setup);
+ DatasetGraph dsg = RDFSFactory.datasetRDFS(base, setup);
AssemblerUtils.mergeContext(root, dsg.getContext());
return dsg;
}
diff --git
a/jena-arq/src/main/java/org/apache/jena/rdfs/assembler/GraphRDFSAssembler.java
b/jena-arq/src/main/java/org/apache/jena/rdfs/assembler/GraphRDFSAssembler.java
index f0869d3190..8d1315bf33 100644
---
a/jena-arq/src/main/java/org/apache/jena/rdfs/assembler/GraphRDFSAssembler.java
+++
b/jena-arq/src/main/java/org/apache/jena/rdfs/assembler/GraphRDFSAssembler.java
@@ -48,7 +48,7 @@ public class GraphRDFSAssembler extends AssemblerBase
implements Assembler {
/**
* <pre>
* <#rdfsGraph> rdf:type ja:GraphRDFS ;
- * ja:rdfs "vocab.ttl";
+ * ja:rdfsSchema "vocab.ttl";
* ja:graph <#baseGraph> ;
* .
*
diff --git a/jena-arq/src/main/java/org/apache/jena/rdfs/engine/ApplyRDFS.java
b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/ApplyRDFS.java
index 9d69662f51..69943e7515 100644
--- a/jena-arq/src/main/java/org/apache/jena/rdfs/engine/ApplyRDFS.java
+++ b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/ApplyRDFS.java
@@ -129,10 +129,12 @@ public class ApplyRDFS<X, T> extends CxtInf<X,T>{
return;
}
Set<X> x = setup.getRange(p);
- x.forEach(c -> {
- derive(o, rdfType, c, out);
- subClass(o, rdfType, c, out);
- });
+ if (!mapper.isLiteral(o)) {
+ x.forEach(c -> {
+ derive(o, rdfType, c, out);
+ subClass(o, rdfType, c, out);
+ });
+ }
}
}
diff --git a/jena-arq/src/main/java/org/apache/jena/rdfs/DatasetGraphRDFS.java
b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/DatasetGraphWithGraphTransform.java
similarity index 86%
copy from jena-arq/src/main/java/org/apache/jena/rdfs/DatasetGraphRDFS.java
copy to
jena-arq/src/main/java/org/apache/jena/rdfs/engine/DatasetGraphWithGraphTransform.java
index 1ca880b9c8..aacd9c5ac1 100644
--- a/jena-arq/src/main/java/org/apache/jena/rdfs/DatasetGraphRDFS.java
+++
b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/DatasetGraphWithGraphTransform.java
@@ -16,11 +16,12 @@
* limitations under the License.
*/
-package org.apache.jena.rdfs;
+package org.apache.jena.rdfs.engine;
import static org.apache.jena.atlas.iterator.Iter.iter;
import java.util.Iterator;
+import java.util.function.Function;
import org.apache.jena.atlas.iterator.Iter;
import org.apache.jena.graph.Graph;
@@ -31,32 +32,37 @@ import org.apache.jena.sparql.core.DatasetGraphWrapperView;
import org.apache.jena.sparql.core.Quad;
import org.apache.jena.sparql.util.Context;
-public class DatasetGraphRDFS extends DatasetGraphWrapper implements
DatasetGraphWrapperView {
+public class DatasetGraphWithGraphTransform extends DatasetGraphWrapper
implements DatasetGraphWrapperView {
// Do not unwrap for query execution.
- private final SetupRDFS setup;
+ private Function<Graph, ? extends Graph> graphTransform;
- public DatasetGraphRDFS(DatasetGraph dsg, SetupRDFS setup) {
+ public DatasetGraphWithGraphTransform(DatasetGraph dsg, Function<Graph, ?
extends Graph> graphTransform) {
super(dsg);
- this.setup = setup;
+ this.graphTransform = graphTransform;
}
- public DatasetGraphRDFS(DatasetGraph dsg, SetupRDFS setup, Context cxt) {
+ public DatasetGraphWithGraphTransform(DatasetGraph dsg, Context cxt,
Function<Graph, ? extends Graph> graphTransform) {
super(dsg, cxt);
- this.setup = setup;
+ this.graphTransform = graphTransform;
+ }
+
+ private Graph wrapGraph(Graph graph) {
+ Graph result = graphTransform.apply(graph);
+ return result;
}
// Graph-centric access.
@Override
public Graph getDefaultGraph() {
Graph base = getG().getDefaultGraph();
- return new GraphRDFS(base, setup);
+ return wrapGraph(base);
}
@Override
public Graph getUnionGraph() {
Graph base = getG().getUnionGraph();
- return new GraphRDFS(base, setup);
+ return wrapGraph(base);
}
@Override
@@ -64,7 +70,7 @@ public class DatasetGraphRDFS extends DatasetGraphWrapper
implements DatasetGrap
Graph base = getG().getGraph(graphNode);
if ( base == null )
return null;
- return new GraphRDFS(base, setup);
+ return wrapGraph(base);
}
@Override
diff --git
a/jena-arq/src/main/java/org/apache/jena/rdfs/engine/GraphIncRDFS.java
b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/GraphIncRDFS.java
index 0fcfa02e2d..2eb3a4ea7a 100644
--- a/jena-arq/src/main/java/org/apache/jena/rdfs/engine/GraphIncRDFS.java
+++ b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/GraphIncRDFS.java
@@ -18,7 +18,8 @@
package org.apache.jena.rdfs.engine;
-import static org.apache.jena.rdfs.engine.ConstRDFS.*;
+import static org.apache.jena.rdfs.engine.ConstRDFS.rdfType;
+import static org.apache.jena.rdfs.engine.ConstRDFS.rdfsSubClassOf;
import java.util.Set;
import java.util.stream.Stream;
@@ -53,7 +54,6 @@ public class GraphIncRDFS extends GraphRDFS {
.filter(type->!setup.getSubClassHierarchy().keySet().contains(type))
.map(type->Triple.create(type, rdfsSubClassOf, type))
);
-
}
@Override
@@ -73,6 +73,11 @@ public class GraphIncRDFS extends GraphRDFS {
return iter;
}
+ @Override
+ public boolean contains(Node s, Node p, Node o) {
+ return vocab.contains(s, p, o) || super.contains(s, p, o);
+ }
+
private Stream<Triple> extras(Node s, Node p, Node o) {
return extra.stream().filter(t->
matchTerm(t.getSubject(),s) && matchTerm(t.getPredicate(),p) &&
matchTerm(t.getObject(),o) );
diff --git a/jena-arq/src/main/java/org/apache/jena/rdfs/GraphRDFS.java
b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/GraphMatch.java
similarity index 70%
copy from jena-arq/src/main/java/org/apache/jena/rdfs/GraphRDFS.java
copy to jena-arq/src/main/java/org/apache/jena/rdfs/engine/GraphMatch.java
index 0fdece6ad2..32b9e633e5 100644
--- a/jena-arq/src/main/java/org/apache/jena/rdfs/GraphRDFS.java
+++ b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/GraphMatch.java
@@ -16,31 +16,40 @@
* limitations under the License.
*/
-package org.apache.jena.rdfs;
+package org.apache.jena.rdfs.engine;
import java.util.stream.Stream;
import org.apache.jena.graph.Graph;
import org.apache.jena.graph.Node;
import org.apache.jena.graph.Triple;
-import org.apache.jena.rdfs.engine.InfFindTriple;
-import org.apache.jena.rdfs.engine.MatchRDFS;
-import org.apache.jena.rdfs.setup.ConfigRDFS;
import org.apache.jena.sparql.graph.GraphWrapper;
import org.apache.jena.util.iterator.ExtendedIterator;
import org.apache.jena.util.iterator.WrappedIterator;
/**
- * RDFS graph over a base graph.
+ * A Graph view over a {@link Match}. A graph can be specified as a delegate
+ * for all functionality that is not covered by the Match.
*/
-public class GraphRDFS extends GraphWrapper {
- private final MatchRDFS<Node, Triple> source;
- private final ConfigRDFS<Node> setup;
+public class GraphMatch extends GraphWrapper {
+ private final Match<Node, Triple> source;
- public GraphRDFS(Graph graph, ConfigRDFS<Node> setup) {
+ public GraphMatch(Graph graph, Match<Node, Triple> match) {
super(graph);
- this.setup = setup;
- this.source = new InfFindTriple(setup, graph);
+ this.source = match;
+ }
+
+ /**
+ * Wrap a base graph such that its find() and contains() methods
+ * are delegated to the match.
+ * Other methods, such as those for updates, go to the base graph.
+ */
+ public static <X, T> Graph adapt(Graph baseGraph, Match<X, T> match) {
+ return new GraphMatch(baseGraph, new MatchAdapter<>(match,
match.getMapper()));
+ }
+
+ public Match<Node, Triple> getMatch() {
+ return source;
}
@Override
@@ -62,11 +71,7 @@ public class GraphRDFS extends GraphWrapper {
@Override
public boolean contains(Node s, Node p, Node o) {
- // Must go via find()-like functionality.
- ExtendedIterator<Triple> iter = find(s, p, o);
- try {
- return iter.hasNext();
- } finally { iter.close(); }
+ return source.contains(s, p, o);
}
@Override
diff --git
a/jena-arq/src/main/java/org/apache/jena/rdfs/engine/InfFindQuad.java
b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/InfFindQuad.java
index c9a771bd39..97d86206e1 100644
--- a/jena-arq/src/main/java/org/apache/jena/rdfs/engine/InfFindQuad.java
+++ b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/InfFindQuad.java
@@ -59,10 +59,4 @@ public class InfFindQuad extends MatchRDFS<Node, Quad> {
protected boolean sourceContains(Node s, Node p, Node o) {
return dsg.contains(graph, s, p, o);
}
-
- @Override
- protected Quad dstCreate(Node s, Node p, Node o) {
- // Must be concrete for this quad creation.
- return Quad.create(graph, s, p, o);
- }
}
diff --git
a/jena-arq/src/main/java/org/apache/jena/rdfs/engine/InfFindTriple.java
b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/InfFindTriple.java
index 9511acd0e1..2f152f3c3c 100644
--- a/jena-arq/src/main/java/org/apache/jena/rdfs/engine/InfFindTriple.java
+++ b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/InfFindTriple.java
@@ -50,9 +50,4 @@ public class InfFindTriple extends MatchRDFS<Node, Triple> {
protected boolean sourceContains(Node s, Node p, Node o) {
return graph.contains(s, p, o);
}
-
- @Override
- protected Triple dstCreate(Node s, Node p, Node o) {
- return Triple.create(s, p, o);
- }
}
diff --git a/jena-arq/src/main/java/org/apache/jena/rdfs/engine/MapperX.java
b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/MapperX.java
index 29a3650e47..bf136cdbbb 100644
--- a/jena-arq/src/main/java/org/apache/jena/rdfs/engine/MapperX.java
+++ b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/MapperX.java
@@ -28,4 +28,10 @@ public interface MapperX<X,T> {
public abstract X subject(T tuple);
public abstract X predicate(T tuple);
public abstract X object(T tuple);
+
+ public abstract T tuple(X s, X p, X o);
+
+ public default boolean isLiteral(X x) {
+ return toNode(x).isLiteral();
+ }
}
diff --git a/jena-arq/src/main/java/org/apache/jena/rdfs/engine/Mappers.java
b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/Mappers.java
index 5c70427c1e..2666cc0a7c 100644
--- a/jena-arq/src/main/java/org/apache/jena/rdfs/engine/Mappers.java
+++ b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/Mappers.java
@@ -19,6 +19,8 @@
package org.apache.jena.rdfs.engine;
import org.apache.jena.atlas.lib.tuple.Tuple;
+import org.apache.jena.atlas.lib.tuple.Tuple3;
+import org.apache.jena.atlas.lib.tuple.TupleFactory;
import org.apache.jena.graph.Node;
import org.apache.jena.graph.Triple;
import org.apache.jena.sparql.core.Quad;
@@ -42,6 +44,7 @@ public class Mappers {
@Override public Node subject(Triple triple) { return
triple.getSubject(); }
@Override public Node predicate(Triple triple) { return
triple.getPredicate(); }
@Override public Node object(Triple triple) { return
triple.getObject(); }
+ @Override public Triple tuple(Node s, Node p, Node o) { return
Triple.create(s, p, o); }
}
private static class MapperQuad implements MapperX<Node, Quad> {
@@ -52,6 +55,7 @@ public class Mappers {
@Override public Node subject(Quad quad) { return
quad.getSubject(); }
@Override public Node predicate(Quad quad) { return
quad.getPredicate(); }
@Override public Node object(Quad quad) { return quad.getObject();
}
+ @Override public Quad tuple(Node s, Node p, Node o) { return
Quad.create(graph, s, p, o); }
}
private static class MapperTuple implements MapperX<Node, Tuple<Node>> {
@@ -60,6 +64,7 @@ public class Mappers {
@Override public Node subject(Tuple<Node> tuple) { return
offset(tuple, 0); }
@Override public Node predicate(Tuple<Node> tuple) { return
offset(tuple, 1); }
@Override public Node object(Tuple<Node> tuple) { return
offset(tuple, 2); }
+ @Override public Tuple3<Node> tuple(Node s, Node p, Node o) { return
TupleFactory.create3(s, p, o); }
private static Node offset(Tuple<Node> tuple, int i) {
int idx = ( tuple.len() == 3 ) ? i : i+1;
diff --git a/jena-arq/src/main/java/org/apache/jena/rdfs/engine/Match.java
b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/Match.java
index 2fa7cf4151..32601b45c7 100644
--- a/jena-arq/src/main/java/org/apache/jena/rdfs/engine/Match.java
+++ b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/Match.java
@@ -20,10 +20,25 @@ package org.apache.jena.rdfs.engine;
import java.util.stream.Stream;
+import org.apache.jena.graph.Node;
+
/**
* Match by S/P/O where {@code X} is the RDF term representation (Node,
NodeId) and
* {@code T} is the tuple (triple, quad, tuple) representation.
*/
public interface Match<X, T> {
public Stream<T> match(X s, X p, X o);
+
+ public default boolean contains(X s, X p, X o) {
+ try (Stream<T> stream = match(s, p, o)) {
+ return stream.findFirst().isPresent();
+ }
+ }
+
+ /**
+ * The mapper for reuse with wrappers.
+ * Note that this indirectly ties the {@link Match} interface to the
{@link Node} realm:
+ * One can use the mapper to obtain X for e.g. RDF.Nodes.type.
+ */
+ MapperX<X, T> getMapper();
}
diff --git
a/jena-arq/src/main/java/org/apache/jena/rdfs/engine/MatchAdapter.java
b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/MatchAdapter.java
new file mode 100644
index 0000000000..0d4aaf5e79
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/MatchAdapter.java
@@ -0,0 +1,72 @@
+/*
+ * 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.rdfs.engine;
+
+import java.util.Objects;
+import java.util.stream.Stream;
+
+import org.apache.jena.graph.Node;
+import org.apache.jena.graph.Triple;
+
+/**
+ * This is the bridge between the Node/Triple level and some lower level such
as one based on NodeIds.
+ */
+public final class MatchAdapter<X, T>
+ implements Match<Node, Triple>
+{
+ private Match<X, T> below;
+ private MapperX<X, T> mapper;
+
+ public MatchAdapter(Match<X, T> below, MapperX<X, T> mapper) {
+ super();
+ this.mapper = Objects.requireNonNull(mapper);
+ this.below = Objects.requireNonNull(below);
+ }
+
+ @Override
+ public Stream<Triple> match(Node s, Node p, Node o) {
+ X sd = down(s);
+ X pd = down(p);
+ X od = down(o);
+ return below.match(sd, pd, od).map(this::up);
+ }
+
+ private X down(Node node) {
+ return mapper.fromNode(node);
+ }
+
+ private Triple up(T tuple) {
+ X sd = mapper.subject(tuple);
+ X pd = mapper.predicate(tuple);
+ X od = mapper.object(tuple);
+ Node s = mapper.toNode(sd);
+ Node p = mapper.toNode(pd);
+ Node o = mapper.toNode(od);
+ return dstCreate(s, p, o);
+ }
+
+ private Triple dstCreate(Node s, Node p, Node o) {
+ return Triple.create(s, p, o);
+ }
+
+ @Override
+ public MapperX<Node, Triple> getMapper() {
+ return Mappers.mapperTriple();
+ }
+}
diff --git
a/jena-arq/src/main/java/org/apache/jena/rdfs/engine/InfFindTriple.java
b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/MatchGraph.java
similarity index 56%
copy from jena-arq/src/main/java/org/apache/jena/rdfs/engine/InfFindTriple.java
copy to jena-arq/src/main/java/org/apache/jena/rdfs/engine/MatchGraph.java
index 9511acd0e1..40d064eed5 100644
--- a/jena-arq/src/main/java/org/apache/jena/rdfs/engine/InfFindTriple.java
+++ b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/MatchGraph.java
@@ -18,41 +18,38 @@
package org.apache.jena.rdfs.engine;
+import java.util.Objects;
import java.util.stream.Stream;
-import org.apache.jena.atlas.iterator.Iter;
import org.apache.jena.graph.Graph;
import org.apache.jena.graph.Node;
import org.apache.jena.graph.Triple;
-import org.apache.jena.rdfs.setup.ConfigRDFS;
-import org.apache.jena.util.iterator.ExtendedIterator;
/**
- * Find in one graph.
+ * A {@link Match} view over a {@link Graph}.
+ * This class is final. Use {@link MatchWrapper} to modify match behavior.
*/
-public class InfFindTriple extends MatchRDFS<Node, Triple> {
-
- private final Graph graph;
-
- public InfFindTriple(ConfigRDFS<Node> setup, Graph graph) {
- super(setup, Mappers.mapperTriple());
- this.graph = graph;
+public final class MatchGraph
+ implements Match<Node, Triple>
+{
+ private Graph base;
+
+ public MatchGraph(Graph base) {
+ super();
+ this.base = Objects.requireNonNull(base);
}
- @Override
- public Stream<Triple> sourceFind(Node s, Node p, Node o) {
- ExtendedIterator<Triple> iter = graph.find(s,p,o);
- Stream<Triple> stream = Iter.asStream(iter);
- return stream;
+ public Graph getGraph() {
+ return base;
}
@Override
- protected boolean sourceContains(Node s, Node p, Node o) {
- return graph.contains(s, p, o);
+ public Stream<Triple> match(Node s, Node p, Node o) {
+ return base.stream(s, p, o);
}
@Override
- protected Triple dstCreate(Node s, Node p, Node o) {
- return Triple.create(s, p, o);
+ public MapperX<Node, Triple> getMapper() {
+ return Mappers.mapperTriple();
}
}
diff --git a/jena-arq/src/main/java/org/apache/jena/rdfs/engine/MatchRDFS.java
b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/MatchRDFS.java
index cef8c1af9f..9ab430f61d 100644
--- a/jena-arq/src/main/java/org/apache/jena/rdfs/engine/MatchRDFS.java
+++ b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/MatchRDFS.java
@@ -73,6 +73,11 @@ public abstract class MatchRDFS<X, T> extends CxtInf<X, T>
implements Match<X,T>
};
}
+ @Override
+ public MapperX<X, T> getMapper() {
+ return mapper;
+ }
+
@Override
public final Stream<T> match(X s, X p, X o) { return matchWithInf(s, p
,o); }
@@ -84,7 +89,6 @@ public abstract class MatchRDFS<X, T> extends CxtInf<X, T>
implements Match<X,T>
// Access data.
protected abstract boolean sourceContains(X s, X p, X o);
protected abstract Stream<T> sourceFind(X s, X p, X o);
- protected abstract T dstCreate(X s, X p, X o);
protected final X subject(T tuple) { return mapper.subject(tuple); }
protected final X predicate(T tuple) { return
mapper.predicate(tuple); }
@@ -460,6 +464,11 @@ public abstract class MatchRDFS<X, T> extends CxtInf<X, T>
implements Match<X,T>
return map == null || map.isEmpty();
}
+ /** Inherit tuple construction from the mapper. */
+ protected T dstCreate(X s, X p, X o) {
+ return getMapper().tuple(s, p, o);
+ }
+
// private void print(Map<X, Set<X>> map) {
// System.out.println("{");
// CollectionUtils.forEach(map, (k,v)->System.out.printf(" %-20s %s\n",
k, v));
diff --git a/jena-arq/src/main/java/org/apache/jena/rdfs/engine/Match.java
b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/MatchRDFSWrapper.java
similarity index 57%
copy from jena-arq/src/main/java/org/apache/jena/rdfs/engine/Match.java
copy to jena-arq/src/main/java/org/apache/jena/rdfs/engine/MatchRDFSWrapper.java
index 2fa7cf4151..eee372f35e 100644
--- a/jena-arq/src/main/java/org/apache/jena/rdfs/engine/Match.java
+++ b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/MatchRDFSWrapper.java
@@ -18,12 +18,29 @@
package org.apache.jena.rdfs.engine;
+import java.util.Objects;
import java.util.stream.Stream;
-/**
- * Match by S/P/O where {@code X} is the RDF term representation (Node,
NodeId) and
- * {@code T} is the tuple (triple, quad, tuple) representation.
- */
-public interface Match<X, T> {
- public Stream<T> match(X s, X p, X o);
+import org.apache.jena.rdfs.setup.ConfigRDFS;
+
+/** MatchRDFS implementation as a wrapper over another Match source. */
+public class MatchRDFSWrapper<X, T>
+ extends MatchRDFS<X, T>
+{
+ protected Match<X, T> source;
+
+ public MatchRDFSWrapper(ConfigRDFS<X> setup, Match<X, T> source) {
+ super(setup, source.getMapper());
+ this.source = Objects.requireNonNull(source);
+ }
+
+ @Override
+ public Stream<T> sourceFind(X s, X p, X o) {
+ return source.match(s,p,o);
+ }
+
+ @Override
+ protected boolean sourceContains(X s, X p, X o) {
+ return source.contains(s, p, o);
+ }
}
diff --git a/jena-arq/src/main/java/org/apache/jena/rdfs/engine/Match.java
b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/MatchWrapper.java
similarity index 64%
copy from jena-arq/src/main/java/org/apache/jena/rdfs/engine/Match.java
copy to jena-arq/src/main/java/org/apache/jena/rdfs/engine/MatchWrapper.java
index 2fa7cf4151..b36f43f979 100644
--- a/jena-arq/src/main/java/org/apache/jena/rdfs/engine/Match.java
+++ b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/MatchWrapper.java
@@ -20,10 +20,27 @@ package org.apache.jena.rdfs.engine;
import java.util.stream.Stream;
-/**
- * Match by S/P/O where {@code X} is the RDF term representation (Node,
NodeId) and
- * {@code T} is the tuple (triple, quad, tuple) representation.
- */
-public interface Match<X, T> {
- public Stream<T> match(X s, X p, X o);
+public class MatchWrapper<X, T, D extends Match<X, T>>
+ implements Match<X, T>
+{
+ protected D delegate;
+
+ public MatchWrapper(D delegate) {
+ super();
+ this.delegate = delegate;
+ }
+
+ public D getDelegate() {
+ return delegate;
+ }
+
+ @Override
+ public Stream<T> match(X s, X p, X o) {
+ return getDelegate().match(s, p, o);
+ }
+
+ @Override
+ public MapperX<X, T> getMapper() {
+ return getDelegate().getMapper();
+ }
}
diff --git
a/jena-arq/src/main/java/org/apache/jena/rdfs/setup/BaseSetupRDFS.java
b/jena-arq/src/main/java/org/apache/jena/rdfs/setup/BaseSetupRDFS.java
index 56d94426fa..81e5408638 100644
--- a/jena-arq/src/main/java/org/apache/jena/rdfs/setup/BaseSetupRDFS.java
+++ b/jena-arq/src/main/java/org/apache/jena/rdfs/setup/BaseSetupRDFS.java
@@ -308,7 +308,7 @@ public abstract class BaseSetupRDFS<X> implements
ConfigRDFS<X>{
private static <X> void put(Map<X, Set<X>> multimap, X n1, X n2) {
if ( !multimap.containsKey(n1) )
- multimap.put(n1, new HashSet<X>());
+ multimap.put(n1, new HashSet<>());
multimap.get(n1).add(n2);
}
diff --git
a/jena-arq/src/main/java/org/apache/jena/rdfs/setup/MatchVocabRDFS.java
b/jena-arq/src/main/java/org/apache/jena/rdfs/setup/MatchVocabRDFS.java
index 7e3eb4ac8b..18bf0a083d 100644
--- a/jena-arq/src/main/java/org/apache/jena/rdfs/setup/MatchVocabRDFS.java
+++ b/jena-arq/src/main/java/org/apache/jena/rdfs/setup/MatchVocabRDFS.java
@@ -31,6 +31,8 @@ import java.util.stream.Stream;
import org.apache.jena.graph.Node;
import org.apache.jena.graph.Triple;
+import org.apache.jena.rdfs.engine.MapperX;
+import org.apache.jena.rdfs.engine.Mappers;
import org.apache.jena.rdfs.engine.Match;
/**
@@ -112,4 +114,9 @@ public class MatchVocabRDFS implements Match<Node, Triple>{
.stream()
.flatMap(
e->e.getValue().stream().map(obj->Triple.create(e.getKey(), p, obj)) );
}
+
+ @Override
+ public MapperX<Node, Triple> getMapper() {
+ return Mappers.mapperTriple();
+ }
}
diff --git
a/jena-arq/src/test/java/org/apache/jena/rdfs/AbstractDatasetGraphCompare.java
b/jena-arq/src/test/java/org/apache/jena/rdfs/AbstractDatasetGraphCompare.java
new file mode 100644
index 0000000000..dc21b41c5e
--- /dev/null
+++
b/jena-arq/src/test/java/org/apache/jena/rdfs/AbstractDatasetGraphCompare.java
@@ -0,0 +1,217 @@
+/*
+ * 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.rdfs;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.stream.IntStreams;
+import org.apache.commons.numbers.combinatorics.Combinations;
+import org.apache.jena.atlas.iterator.Iter;
+import org.apache.jena.graph.Node;
+import org.apache.jena.graph.NodeFactory;
+import org.apache.jena.sparql.core.DatasetGraph;
+import org.apache.jena.sparql.core.DatasetGraphFactory;
+import org.apache.jena.sparql.core.Quad;
+import org.apache.jena.sparql.graph.NodeTransformLib;
+import org.junit.jupiter.api.DynamicTest;
+
+/**
+ * Test to check consistency of the find() method.
+ *
+ * Base class for generating tests that invoke the find() method of a dataset
graph
+ * with all combinations of patterns.
+ */
+public abstract class AbstractDatasetGraphCompare {
+ private String testLabel;
+
+ public AbstractDatasetGraphCompare(String testLabel) {
+ super();
+ this.testLabel = testLabel;
+ }
+
+ public String getTestLabel() {
+ return testLabel;
+ }
+
+ /** By default consider cardinality when evaluating result sets. */
+ protected boolean defaultCompareAsSet() {
+ return true;
+ }
+
+
+ /** Sub classes can use this method to generate dynamic tests. */
+ public GraphFindTestBuilder prepareFindTests(DatasetGraph referenceDsg,
DatasetGraph testDsg, DatasetGraph dataDsg) {
+ List<Quad> findQuads = createFindQuads(dataDsg).toList();
+ return new GraphFindTestBuilder(testLabel, referenceDsg, testDsg,
findQuads)
+ .compareAsSet(defaultCompareAsSet());
+ }
+
+ /** Derive a reference dataset by materalizing the dataset into a copy. */
+ public GraphFindTestBuilder prepareFindTests(DatasetGraph testDsg,
DatasetGraph dataDsg) {
+ DatasetGraph referenceDsg = DatasetGraphFactory.create();
+ referenceDsg.addAll(testDsg);
+
+ List<Quad> findQuads = createFindQuads(dataDsg).toList();
+ return new GraphFindTestBuilder(testLabel, referenceDsg, testDsg,
findQuads)
+ .compareAsSet(defaultCompareAsSet());
+ }
+
+ /** Materialize the dataset as the reference dataset. Use reference
dataset as dataDsg. */
+ public GraphFindTestBuilder prepareFindTests(DatasetGraph testDsg) {
+ DatasetGraph referenceDsg = DatasetGraphFactory.create();
+ referenceDsg.addAll(testDsg);
+ return prepareFindTests(testDsg, referenceDsg, testDsg)
+ .compareAsSet(defaultCompareAsSet());
+ }
+
+ // Markers for find-quad generation w.r.t. a dataset and a
+ // "meta pattern" such as (IN, foo, OUT, OUT).
+ // IN becomes substituted with concrete values, out becomes ANY.
+ private static final Node IN = NodeFactory.createBlankNode("IN");
+ private static final Node OUT = NodeFactory.createBlankNode("OUT");
+
+ /**
+ * Generate the set of find patterns for each quad in the source dataset.
+ * This is the set of combinations by substituting components with ANY.
+ * For example, the derivations for a concrete quad (g, s, p, o) are:
+ * <pre>
+ * {(g, s, p, ANY), (g, s, ANY, o), (g, s, ANY, ANY), ...}
+ * </pre>
+ */
+ public static Stream<Quad> createFindQuads(DatasetGraph dataSource) {
+ Node[] baseMetaPattern = new Node[]{OUT, OUT, OUT, OUT};
+ Stream<Quad> result = IntStream.rangeClosed(0, 4).boxed()
+ .flatMap(k -> Iter.asStream(Combinations.of(4, k).iterator()))
+ .flatMap(ins -> {
+ Node[] metaPattern = Arrays.copyOf(baseMetaPattern,
baseMetaPattern.length);
+
+ // Use IN to mark the components that we want to substitute
with concrete values.
+ // OUT becomes ANY.
+ IntStreams.of(ins).forEach(i -> metaPattern[i] = IN);
+ Quad metaQuad = toQuad(List.of(metaPattern).iterator());
+
+ Set<Quad> lookups = createFindQuads(dataSource, metaQuad);
+ return lookups.stream();
+ });
+ return result;
+ }
+
+ private static Quad toQuad(Iterator<Node> it) {
+ Quad r = Quad.create(it.next(), it.next(), it.next(), it.next());
+ if (it.hasNext()) {
+ throw new IllegalArgumentException("Iterator of exactly 4 elements
expected.");
+ }
+ return r;
+ }
+
+ private static Node outToAny(Node pattern, Node concrete) {
+ Node r = (OUT.equals(pattern)) ? Node.ANY : concrete;
+ return r;
+ }
+
+ private static Node inToAny(Node node) {
+ Node r = (IN.equals(node)) ? Node.ANY : node;
+ return r;
+ }
+
+ private static Node outToAny(Node node) {
+ Node r = (OUT.equals(node)) ? Node.ANY : node;
+ return r;
+ }
+
+ private static Quad inToAny(Quad metaQuad) {
+ return
NodeTransformLib.transform(AbstractDatasetGraphCompare::inToAny, metaQuad);
+ }
+
+ private static Quad outToAny(Quad metaQuad) {
+ return
NodeTransformLib.transform(AbstractDatasetGraphCompare::outToAny, metaQuad);
+ }
+
+ // !!! This method implicitly gets rid of 'IN' !!!
+ // Components of the input quads are processed as follows:
+ // If a component of pattern is OUT then it becomes is ANY.
+ // Otherwise, *always* return the corresponding component of 'concrete'.
+ private static Quad createFindQuad(Quad meta, Quad concrete) {
+ Quad result = Quad.create(
+ outToAny(meta.getGraph(), concrete.getGraph()),
+ outToAny(meta.getSubject(), concrete.getSubject()),
+ outToAny(meta.getPredicate(), concrete.getPredicate()),
+ outToAny(meta.getObject(), concrete.getObject()));
+ return result;
+ }
+
+ /**
+ * Expand a pattern such as (IN, s, OUT, OUT) into { (g1, s, ANY, ANY),
(g2, s, ANY, ANY) }
+ * based on the concrete quads in dsg.
+ */
+ private static Set<Quad> createFindQuads(DatasetGraph dsg, Quad metaQuad) {
+ // Replace IN and OUT with ANY - this retains only term nodes.
+ Quad p = outToAny(inToAny(metaQuad));
+ Set<Quad> result = Iter.collect(Iter.map(dsg.find(p), q ->
createFindQuad(metaQuad, q)),
+ Collectors.toCollection(LinkedHashSet::new));
+ return result;
+ }
+
+ /**
+ * Builder that accepts a reference dataset, a test dataset
+ * and a list of quads for which to produce {@link DynamicTest} instances.
+ *
+ * <p>
+ * In addition, it allows to configure whether result sets should be
compared as sets instead of lists.
+ * Useful to assess correctness in disregard of cardinality.
+ */
+ public static class GraphFindTestBuilder {
+ private String testLabel;
+ private DatasetGraph referenceDsg;
+ private DatasetGraph testDsg;
+ private List<Quad> findQuads;
+ private boolean compareAsSet;
+
+ protected GraphFindTestBuilder(String testLabel, DatasetGraph
referenceDsg, DatasetGraph testDsg, List<Quad> findQuads) {
+ super();
+ this.testLabel = Objects.requireNonNull(testLabel);
+ this.referenceDsg = Objects.requireNonNull(referenceDsg);
+ this.testDsg = Objects.requireNonNull(testDsg);
+ this.findQuads = findQuads;
+ }
+
+ public List<Quad> getFindQuads() {
+ return findQuads;
+ }
+
+ public GraphFindTestBuilder compareAsSet(boolean onOrOff) {
+ this.compareAsSet = onOrOff;
+ return this;
+ }
+
+ public List<DynamicTest> build() {
+ List<DynamicTest> tests = findQuads.stream().map(q ->
DynamicTest.dynamicTest(testLabel + " " + q,
+ new GraphFindExecutable(testLabel, q, referenceDsg, testDsg,
compareAsSet))).toList();
+ return tests;
+ }
+ }
+}
diff --git
a/jena-arq/src/test/java/org/apache/jena/rdfs/AbstractTestRDFS_Extra.java
b/jena-arq/src/test/java/org/apache/jena/rdfs/AbstractTestRDFS_Extra.java
new file mode 100644
index 0000000000..116e50553a
--- /dev/null
+++ b/jena-arq/src/test/java/org/apache/jena/rdfs/AbstractTestRDFS_Extra.java
@@ -0,0 +1,85 @@
+/*
+ * 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.rdfs;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.DynamicTest;
+import org.junit.jupiter.api.TestFactory;
+
+public abstract class AbstractTestRDFS_Extra
+ extends AbstractTestRDFS_Find
+{
+ public AbstractTestRDFS_Extra(String testLabel) {
+ super(testLabel);
+ }
+
+ /** Some RDFS reasoners so far produce duplicates which fail cardinality
tests. */
+ @Override
+ protected boolean defaultCompareAsSet() {
+ return true;
+ }
+
+ @TestFactory
+ @Disabled("Fails on certain patterns - Needs investigation.")
+ public List<DynamicTest> testSubPropertyOfRdfType01() {
+ List<DynamicTest> tests = prepareRdfsFindTestsSSE(
+ "(graph (:directType rdfs:subPropertyOf rdf:type) )",
+ "(graph (:fido :directType :Dog) )"
+ ).build();
+ return tests;
+ }
+
+ @TestFactory
+ public List<DynamicTest> testSubClassOf01() {
+ List<DynamicTest> tests = prepareRdfsFindTestsSSE(
+ "(graph (:Dog rdfs:subClassOf rdf:Mammal) )",
+ "(graph (:fido rdf:type :Dog) )"
+ ).build();
+ return tests;
+ }
+
+ @TestFactory
+ public List<DynamicTest> testRange01() {
+ List<DynamicTest> tests = prepareRdfsFindTestsSSE(
+ "(graph (:owner rdfs:range :Person) )",
+ "(graph (:fido :owner :alice) )"
+ ).build();
+ return tests;
+ }
+
+ @TestFactory
+ public List<DynamicTest> testRangeWithLiteral01() {
+ List<DynamicTest> tests = prepareRdfsFindTestsSSE(
+ "(graph (:name rdfs:range :Literal) )",
+ "(graph (:fido :name 'Fido') )"
+ ).build();
+ return tests;
+ }
+
+ @TestFactory
+ public List<DynamicTest> testDomain01() {
+ List<DynamicTest> tests = prepareRdfsFindTestsSSE(
+ "(graph (:owner rdfs:domain :Pet) )",
+ "(graph (:fido :owner :alice) )"
+ ).build();
+ return tests;
+ }
+}
diff --git
a/jena-arq/src/test/java/org/apache/jena/rdfs/AbstractTestRDFS_Find.java
b/jena-arq/src/test/java/org/apache/jena/rdfs/AbstractTestRDFS_Find.java
new file mode 100644
index 0000000000..21e0d9c015
--- /dev/null
+++ b/jena-arq/src/test/java/org/apache/jena/rdfs/AbstractTestRDFS_Find.java
@@ -0,0 +1,127 @@
+/*
+ * 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.rdfs;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+import org.apache.jena.graph.Graph;
+import org.apache.jena.graph.Node;
+import org.apache.jena.rdfs.setup.ConfigRDFS;
+import org.apache.jena.riot.Lang;
+import org.apache.jena.riot.RDFDataMgr;
+import org.apache.jena.riot.RDFFormat;
+import org.apache.jena.riot.RDFParser;
+import org.apache.jena.sparql.core.DatasetGraph;
+import org.apache.jena.sparql.core.DatasetGraphFactory;
+import org.apache.jena.sparql.core.Quad;
+import org.apache.jena.sparql.sse.SSE;
+import org.apache.jena.vocabulary.RDF;
+import org.apache.jena.vocabulary.RDFS;
+
+/**
+ * Test consistency of DatasetGraph.find(g, s, p, o) w.r.t. an RDFS setup.
+ * <p>
+ *
+ * <b>BEWARE:</b> find() is used to produce the reference data. Errors in the
reference data
+ * will likely cause otherwise correctly functioning tests to fail.
+ * <p>
+ *
+ * (1) Materializes the result of dsg.find() into a reference dataset.
+ * (2) Invokes find(g, s, p, o) with all combinations and compares the results.
+ */
+public abstract class AbstractTestRDFS_Find
+ extends AbstractDatasetGraphCompare
+{
+ public AbstractTestRDFS_Find(String testLabel) {
+ super(testLabel);
+ }
+
+ /** Some RDFS reasoners so far produce duplicates which fail cardinality
tests. */
+ @Override
+ protected boolean defaultCompareAsSet() {
+ return true;
+ }
+
+ /** Sub classes need to implement this method and return a DatasetGraph
with RDFS inferencing. */
+ protected abstract DatasetGraph applyRdfs(DatasetGraph dsg,
ConfigRDFS<Node> configRDFS);
+
+ /**
+ * Prepare test cases.
+ *
+ * @param schemaStr SSE expression that parses as a {@link Graph}.
+ * @param dataStr SSE expression that parses as a {@link DatasetGraph}.
+ * @return A builder for the concrete test instances.
+ */
+ public GraphFindTestBuilder prepareRdfsFindTestsSSE(String schemaStr,
String dataStr) {
+ Graph graph = SSE.parseGraph(schemaStr);
+ DatasetGraph inputDsg = SSE.parseDatasetGraph(dataStr);
+ return prepareRdfsFindTests(graph, inputDsg);
+ }
+
+ /**
+ * Prepare test cases.
+ *
+ * @param schemaStr RDF data in TRIG syntax that parses as a {@link Graph}.
+ * @param dataStr RDF data in TRIG syntax that parses as a {@link
DatasetGraph}.
+ * @return A builder for the concrete test instances.
+ */
+ public GraphFindTestBuilder prepareRdfsFindTestsTrig(String schemaStr,
String dataStr) {
+ Graph schemaGraph = RDFParser.fromString(schemaStr,
Lang.TTL).toGraph();
+ SetupRDFS setup = RDFSFactory.setupRDFS(schemaGraph);
+
+ DatasetGraph inputDsg = RDFParser.fromString(dataStr,
Lang.TRIG).toDatasetGraph();
+ return prepareRdfsFindTests(setup, inputDsg);
+ }
+
+ public GraphFindTestBuilder prepareRdfsFindTests(Graph schemaGraph,
DatasetGraph inputDsg) {
+ SetupRDFS setup = RDFSFactory.setupRDFS(schemaGraph);
+ return prepareRdfsFindTests(setup, inputDsg);
+ }
+
+ public GraphFindTestBuilder prepareRdfsFindTests(ConfigRDFS<Node>
configRDFS, DatasetGraph inputDsg) {
+ DatasetGraph testDsg = applyRdfs(inputDsg, configRDFS);
+
+ // Build reference data by materializing a copy of testDsg via
findAll().
+ DatasetGraph referenceDsg = DatasetGraphFactory.create();
+ referenceDsg.prefixes().putAll(testDsg.prefixes());
+ referenceDsg.addAll(testDsg);
+
+ // dataDsg that is the source for substituting placeholders
+ // in the quads that will be used to test find(quad) calls.
+ DatasetGraph dataDsg = DatasetGraphFactory.create();
+ dataDsg.prefixes().putAll(referenceDsg.prefixes());
+ dataDsg.addAll(referenceDsg);
+
+ boolean debugPrintReferenceData = false;
+ if (debugPrintReferenceData) {
+ RDFDataMgr.write(System.out, referenceDsg, RDFFormat.TRIG_PRETTY);
+ }
+
+ // Add all (non-literal) objects to the source Data for more extensive
testing of lookups.
+ try (Stream<Quad> stream = dataDsg.stream()) {
+ List<Quad> extra = stream.flatMap(q -> Stream.of(q.getPredicate(),
q.getObject())
+ .map(x -> Quad.create(q.getGraph(), x, RDF.Nodes.type,
RDFS.Nodes.Resource)))
+ .toList();
+ extra.forEach(dataDsg::add);
+ }
+
+ return prepareFindTests(referenceDsg, testDsg, referenceDsg);
+ }
+}
diff --git
a/jena-arq/src/test/java/org/apache/jena/rdfs/GraphFindExecutable.java
b/jena-arq/src/test/java/org/apache/jena/rdfs/GraphFindExecutable.java
new file mode 100644
index 0000000000..304ceebd90
--- /dev/null
+++ b/jena-arq/src/test/java/org/apache/jena/rdfs/GraphFindExecutable.java
@@ -0,0 +1,99 @@
+/*
+ * 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.rdfs;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.jena.atlas.iterator.Iter;
+import org.apache.jena.atlas.lib.ListUtils;
+import org.apache.jena.riot.out.NodeFmtLib;
+import org.apache.jena.sparql.core.DatasetGraph;
+import org.apache.jena.sparql.core.Quad;
+import org.junit.jupiter.api.function.Executable;
+
+/**
+ * JUnit test case executable to compare two datasets w.r.t.
+ * their result of find(pattern).
+ */
+public class GraphFindExecutable
+ implements Executable
+{
+ private static PrintStream out = System.err;
+
+ private String testLabel;
+ private Quad findPattern;
+ private DatasetGraph referenceDsg;
+ private DatasetGraph testDsg;
+
+ /** Compare expected and actual data as sets instead of lists. */
+ private boolean compareAsSet;
+
+ public GraphFindExecutable(String testLabel, Quad findPattern,
DatasetGraph referenceDsg, DatasetGraph testDsg, boolean compareAsSet) {
+ super();
+ this.testLabel = testLabel;
+ this.referenceDsg = referenceDsg;
+ this.testDsg = testDsg;
+ this.findPattern = findPattern;
+ this.compareAsSet = compareAsSet;
+ }
+
+ public String getTestLabel() {
+ return testLabel;
+ }
+
+ public Quad getFindPattern() {
+ return findPattern;
+ }
+
+ /**
+ * Assert that graph.find() returned the same set of quads for the given
pattern.
+ * Duplicates are ignored.
+ */
+ @Override
+ public void execute() throws Throwable {
+ List<Quad> expectedList = Iter.toList(referenceDsg.find(findPattern));
+ List<Quad> actualList = Iter.toList(testDsg.find(findPattern));
+
+ if (compareAsSet) {
+ Set<Quad> expectedSet = new LinkedHashSet<>(expectedList);
+ Set<Quad> actualSet = new LinkedHashSet<>(actualList);
+ expectedList = new ArrayList<>(expectedSet);
+ actualList = new ArrayList<>(actualSet);
+ }
+
+ boolean b = ListUtils.equalsUnordered(expectedList, actualList);
+ if ( ! b ) {
+ out.println("Fail: find(" + NodeFmtLib.str(findPattern) + ")");
+ LibTestRDFS.printDiff(out, expectedList, actualList);
+ }
+
+ assertTrue(b,()->getTestLabel());
+ }
+
+ @Override
+ public String toString() {
+ return getTestLabel() + " " + getFindPattern();
+ }
+}
diff --git a/jena-arq/src/test/java/org/apache/jena/rdfs/TS_InfRdfs.java
b/jena-arq/src/test/java/org/apache/jena/rdfs/TS_InfRdfs.java
index 83419bd6f0..6a957a1597 100644
--- a/jena-arq/src/test/java/org/apache/jena/rdfs/TS_InfRdfs.java
+++ b/jena-arq/src/test/java/org/apache/jena/rdfs/TS_InfRdfs.java
@@ -51,6 +51,8 @@ import org.junit.platform.suite.api.Suite;
, TestInfSPARQL.class
, TestAssemblerRDFS.class
+
+ , TestDatasetGraphFindRDFS.class
})
public class TS_InfRdfs { }
diff --git a/jena-arq/src/main/java/org/apache/jena/rdfs/engine/MapperX.java
b/jena-arq/src/test/java/org/apache/jena/rdfs/TestDatasetGraphFindRDFS.java
similarity index 61%
copy from jena-arq/src/main/java/org/apache/jena/rdfs/engine/MapperX.java
copy to
jena-arq/src/test/java/org/apache/jena/rdfs/TestDatasetGraphFindRDFS.java
index 29a3650e47..716c403ae3 100644
--- a/jena-arq/src/main/java/org/apache/jena/rdfs/engine/MapperX.java
+++ b/jena-arq/src/test/java/org/apache/jena/rdfs/TestDatasetGraphFindRDFS.java
@@ -16,16 +16,26 @@
* limitations under the License.
*/
-package org.apache.jena.rdfs.engine;
+package org.apache.jena.rdfs;
import org.apache.jena.graph.Node;
+import org.apache.jena.rdfs.setup.ConfigRDFS;
+import org.apache.jena.sparql.core.DatasetGraph;
-/** Bridge between Node and X; 3-tuples and Triple/Quad */
-public interface MapperX<X,T> {
- public abstract X fromNode(Node n);
- public abstract Node toNode(X x);
+public class TestDatasetGraphFindRDFS
+ extends AbstractTestRDFS_Extra
+{
+ public TestDatasetGraphFindRDFS() {
+ super("RDFS");
+ }
- public abstract X subject(T tuple);
- public abstract X predicate(T tuple);
- public abstract X object(T tuple);
+ @Override
+ protected boolean defaultCompareAsSet() {
+ return true;
+ }
+
+ @Override
+ protected DatasetGraph applyRdfs(DatasetGraph dsg, ConfigRDFS<Node>
configRDFS) {
+ return new DatasetGraphRDFS(dsg, (SetupRDFS)configRDFS);
+ }
}
diff --git
a/jena-arq/src/test/java/org/apache/jena/rdfs/TestDatasetGraphRDFS.java
b/jena-arq/src/test/java/org/apache/jena/rdfs/TestDatasetGraphRDFS.java
index 6588923d2c..10a9231362 100644
--- a/jena-arq/src/test/java/org/apache/jena/rdfs/TestDatasetGraphRDFS.java
+++ b/jena-arq/src/test/java/org/apache/jena/rdfs/TestDatasetGraphRDFS.java
@@ -42,6 +42,7 @@ import org.apache.jena.atlas.lib.StrUtils;
import org.apache.jena.graph.Graph;
import org.apache.jena.graph.Node;
import org.apache.jena.sparql.core.DatasetGraph;
+import org.apache.jena.sparql.core.DatasetGraphWrapper;
import org.apache.jena.sparql.core.Quad;
import org.apache.jena.sparql.sse.SSE;
@@ -54,7 +55,7 @@ import org.apache.jena.sparql.sse.SSE;
public class TestDatasetGraphRDFS {
private static PrintStream out = System.out;
- private static DatasetGraphRDFS dsg;
+ private static DatasetGraphWrapper dsg;
@BeforeAll
public static void beforeClass() {
@@ -66,7 +67,7 @@ public class TestDatasetGraphRDFS {
);
DatasetGraph dsgBase = SSE.parseDatasetGraph(x);
Graph schema = SSE.parseGraph("(graph (:A rdfs:subClassOf :B))");
- dsg = (DatasetGraphRDFS)RDFSFactory.datasetRDFS(dsgBase, schema);
+ dsg = (DatasetGraphWrapper)RDFSFactory.datasetRDFS(dsgBase, schema);
}
@Test public void dsg_access_1() {
diff --git
a/jena-base/src/main/java/org/apache/jena/atlas/iterator/FilterUnique.java
b/jena-base/src/main/java/org/apache/jena/atlas/iterator/FilterUnique.java
index 96dfa5e029..b9015a7e88 100644
--- a/jena-base/src/main/java/org/apache/jena/atlas/iterator/FilterUnique.java
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/FilterUnique.java
@@ -29,5 +29,4 @@ public class FilterUnique<T> implements Predicate<T> {
public boolean test(T item) {
return seen.add(item);
}
-
}
diff --git
a/jena-base/src/main/java/org/apache/jena/atlas/iterator/FilterUnique.java
b/jena-base/src/main/java/org/apache/jena/atlas/iterator/FilterUniqueCache.java
similarity index 69%
copy from
jena-base/src/main/java/org/apache/jena/atlas/iterator/FilterUnique.java
copy to
jena-base/src/main/java/org/apache/jena/atlas/iterator/FilterUniqueCache.java
index 96dfa5e029..886865a4be 100644
--- a/jena-base/src/main/java/org/apache/jena/atlas/iterator/FilterUnique.java
+++
b/jena-base/src/main/java/org/apache/jena/atlas/iterator/FilterUniqueCache.java
@@ -18,16 +18,23 @@
package org.apache.jena.atlas.iterator;
-import java.util.HashSet;
-import java.util.Set;
import java.util.function.Predicate;
-public class FilterUnique<T> implements Predicate<T> {
- private final Set<T> seen = new HashSet<>();
+import org.apache.jena.atlas.lib.Cache;
+import org.apache.jena.atlas.lib.CacheFactory;
+
+public class FilterUniqueCache<T> implements Predicate<T> {
+ private final Cache<T, Object> seen;
+
+ public FilterUniqueCache(int size) {
+ super();
+ this.seen = CacheFactory.createCache(size);
+ }
@Override
public boolean test(T item) {
- return seen.add(item);
+ boolean wasSeen = seen.containsKey(item);
+ seen.put(item, Boolean.TRUE);
+ return !wasSeen;
}
-
}
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/iterator/Iter.java
b/jena-base/src/main/java/org/apache/jena/atlas/iterator/Iter.java
index e8ab211939..17da693695 100644
--- a/jena-base/src/main/java/org/apache/jena/atlas/iterator/Iter.java
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/Iter.java
@@ -27,6 +27,7 @@ import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.apache.jena.atlas.io.IO;
+import org.apache.jena.atlas.lib.CacheFactory;
import org.apache.jena.atlas.lib.Closeable;
import org.apache.jena.atlas.lib.Sink;
@@ -107,6 +108,10 @@ public class Iter<T> implements IteratorCloseable<T> {
return t == null ? Iter.empty() : Iter.of(t);
}
+ public static <T> Iterator<T> ofStream(Stream<T> stream) {
+ return Iter.onClose(stream.iterator(), stream::close);
+ }
+
/**
* Return an iterator that does not permit remove.
* This makes an "UnmodifiableIterator".
@@ -518,6 +523,14 @@ public class Iter<T> implements IteratorCloseable<T> {
return filter(iter, new FilterUnique<T>());
}
+ /** Returns an iterator that uses an LRU cache to filter out duplicates.
+ * This provides a best-effort distinct view within a sliding window of
recent elements.
+ * Memory usage is bounded by the specified cache size.
+ */
+ public static <T> Iterator<T> distinctCached(Iterator<T> iter, int
cacheMaxSize) {
+ return filter(iter, new FilterUniqueCache<T>(cacheMaxSize));
+ }
+
/** Remove adjacent duplicates. This operation does not need
* working memory to remember the all elements already seen,
* just a slot for the last element seen.
diff --git
a/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorConcat.java
b/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorConcat.java
index 04b82cfd9c..5d25163968 100644
--- a/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorConcat.java
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorConcat.java
@@ -114,11 +114,11 @@ public class IteratorConcat<T> implements
IteratorCloseable<T> {
@Override
public void close() {
//iterators.forEach(Iter::close);
- // Earlier iterators already closed
+ // Earlier iterators already closed. Handle case where hasNext has
never been called.
+ if (idx == -1) idx = 0;
for ( int i = idx ; i < iterators.size() ; i++ ) {
Iterator<T> iter = iterators.get(idx);
Iter.close(iter);
}
-
}
}
diff --git a/jena-tdb2/src/main/java/org/apache/jena/tdb2/match/MapperXTDB.java
b/jena-tdb2/src/main/java/org/apache/jena/tdb2/match/MapperXTDB.java
new file mode 100644
index 0000000000..067520f05c
--- /dev/null
+++ b/jena-tdb2/src/main/java/org/apache/jena/tdb2/match/MapperXTDB.java
@@ -0,0 +1,57 @@
+/*
+ * 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.tdb2.match;
+
+import org.apache.jena.atlas.lib.tuple.Tuple3;
+import org.apache.jena.atlas.lib.tuple.TupleFactory;
+import org.apache.jena.graph.Node;
+import org.apache.jena.rdfs.engine.MapperX;
+import org.apache.jena.sparql.core.DatasetGraph;
+import org.apache.jena.tdb2.store.DatasetGraphTDB;
+import org.apache.jena.tdb2.store.NodeId;
+import org.apache.jena.tdb2.store.nodetable.NodeTable;
+import org.apache.jena.tdb2.sys.TDBInternal;
+
+public class MapperXTDB
+ implements MapperX<NodeId, Tuple3<NodeId>>
+{
+ private NodeTable nodeTable;
+
+ protected MapperXTDB(NodeTable nodeTable) {
+ super();
+ this.nodeTable = nodeTable;
+ }
+
+ public static MapperX<NodeId, Tuple3<NodeId>> create(DatasetGraph dsg) {
+ DatasetGraphTDB tdb = TDBInternal.getDatasetGraphTDB(dsg);
+ if (tdb == null) {
+ throw new IllegalArgumentException("Argument must be a TDB2
dataset graph.");
+ }
+ return new
MapperXTDB(tdb.getQuadTable().getNodeTupleTable().getNodeTable());
+ }
+
+ @Override public NodeId fromNode(Node n) { return
nodeTable.getNodeIdForNode(n); }
+ @Override public Node toNode (NodeId x) { return
nodeTable.getNodeForNodeId(x); }
+
+ @Override public NodeId subject (Tuple3<NodeId> tuple) { return
tuple.get(0); }
+ @Override public NodeId predicate(Tuple3<NodeId> tuple) { return
tuple.get(1); }
+ @Override public NodeId object (Tuple3<NodeId> tuple) { return
tuple.get(2); }
+
+ @Override public Tuple3<NodeId> tuple(NodeId s, NodeId p, NodeId o) {
return TupleFactory.create3(s, p, o); }
+}
diff --git a/jena-tdb2/src/main/java/org/apache/jena/tdb2/match/MatchTDB.java
b/jena-tdb2/src/main/java/org/apache/jena/tdb2/match/MatchTDB.java
new file mode 100644
index 0000000000..2aac132b69
--- /dev/null
+++ b/jena-tdb2/src/main/java/org/apache/jena/tdb2/match/MatchTDB.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.tdb2.match;
+
+import java.util.stream.Stream;
+
+import org.apache.jena.atlas.iterator.Iter;
+import org.apache.jena.atlas.lib.tuple.Tuple3;
+import org.apache.jena.atlas.lib.tuple.TupleFactory;
+import org.apache.jena.graph.Graph;
+import org.apache.jena.rdfs.engine.MapperX;
+import org.apache.jena.rdfs.engine.Match;
+import org.apache.jena.tdb2.store.GraphTDB;
+import org.apache.jena.tdb2.store.GraphViewSwitchable;
+import org.apache.jena.tdb2.store.NodeId;
+import org.apache.jena.tdb2.store.nodetable.NodeTable;
+
+public class MatchTDB
+ implements Match<NodeId, Tuple3<NodeId>>
+{
+ private GraphTDB graph;
+ private MapperX<NodeId, Tuple3<NodeId>> mapper;
+
+ protected MatchTDB(GraphTDB graph, MapperX<NodeId, Tuple3<NodeId>> mapper)
{
+ super();
+ this.graph = graph;
+ this.mapper = mapper;
+ }
+
+ public static MatchTDB wrap(Graph g) {
+ // Same pattern used in stage generator - move to TDBInternal?
+ if ( g instanceof GraphViewSwitchable gvs )
+ g = gvs.getBaseGraph();
+
+ if (!(g instanceof GraphTDB tdbGraph)) {
+ throw new IllegalArgumentException("Not a TDB2 graph");
+ }
+
+ NodeTable nodeTable = tdbGraph.getNodeTupleTable().getNodeTable();
+ MapperX<NodeId, Tuple3<NodeId>> mapper = new MapperXTDB(nodeTable);
+ return new MatchTDB(tdbGraph, mapper);
+ }
+
+ @Override
+ public Stream<Tuple3<NodeId>> match(NodeId s, NodeId p, NodeId o) {
+ return Iter.asStream(graph.getNodeTupleTable().find(s, p, o))
+ .filter(t -> {
+ boolean b = NodeId.isDoesNotExist(t.get(0)) ||
NodeId.isDoesNotExist(t.get(1)) || NodeId.isDoesNotExist(t.get(2));
+ return !b;
+ })
+ .map(t -> TupleFactory.create3(t.get(0), t.get(1), t.get(2)));
+ }
+
+ @Override
+ public MapperX<NodeId, Tuple3<NodeId>> getMapper() {
+ return mapper;
+ }
+}
diff --git
a/jena-tdb2/src/test/java/org/apache/jena/tdb2/match/TestMatchTDB2.java
b/jena-tdb2/src/test/java/org/apache/jena/tdb2/match/TestMatchTDB2.java
new file mode 100644
index 0000000000..a77091a55a
--- /dev/null
+++ b/jena-tdb2/src/test/java/org/apache/jena/tdb2/match/TestMatchTDB2.java
@@ -0,0 +1,82 @@
+/*
+ * 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.tdb2.match;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Set;
+
+import org.apache.jena.atlas.iterator.Iter;
+import org.apache.jena.atlas.lib.tuple.Tuple3;
+import org.apache.jena.graph.Graph;
+import org.apache.jena.graph.Triple;
+import org.apache.jena.query.Dataset;
+import org.apache.jena.query.ReadWrite;
+import org.apache.jena.rdfs.RDFSFactory;
+import org.apache.jena.rdfs.engine.DatasetGraphWithGraphTransform;
+import org.apache.jena.rdfs.engine.GraphMatch;
+import org.apache.jena.rdfs.engine.MapperX;
+import org.apache.jena.rdfs.engine.MatchRDFSWrapper;
+import org.apache.jena.rdfs.setup.ConfigRDFS;
+import org.apache.jena.sparql.core.DatasetGraph;
+import org.apache.jena.sparql.exec.QueryExec;
+import org.apache.jena.sparql.sse.SSE;
+import org.apache.jena.system.AutoTxn;
+import org.apache.jena.system.G;
+import org.apache.jena.system.Txn;
+import org.apache.jena.tdb2.TDB2Factory;
+import org.apache.jena.tdb2.store.NodeId;
+import org.junit.jupiter.api.Test;
+
+public class TestMatchTDB2 {
+ @Test
+ public void testRdfsOnNodeIdLevel() {
+ Graph schema = SSE.parseGraph("(graph (rdf:type rdf:type rdf:Property)
(:p rdfs:domain :C) )");
+
+ Dataset baseDs = TDB2Factory.createDataset();
+ DatasetGraph baseDsg = baseDs.asDatasetGraph();
+
+ MapperX<NodeId, Tuple3<NodeId>> mapper = MapperXTDB.create(baseDsg);
+
+ try (AutoTxn txn = Txn.autoTxn(baseDsg, ReadWrite.WRITE)) {
+ // !!! The schema must be added first, otherwise we won't have
NodeIds !!!
+ // !!! Also, all terms (especially rdf:type) must have
corresponding NodeIds !!!
+ G.addInto(baseDsg.getDefaultGraph(), schema);
+ ConfigRDFS<NodeId> configRDFS = RDFSFactory.setupRDFS(schema,
mapper);
+
+ // Add wrapping on NodeId level.
+ DatasetGraph rdfsDsg = new DatasetGraphWithGraphTransform(baseDsg,
+ g -> GraphMatch.adapt(g, new MatchRDFSWrapper<>(configRDFS,
MatchTDB.wrap(g))));
+
+ // Add data.
+ Graph data = SSE.parseGraph("(graph (:s :p :o) )");
+ G.addInto(rdfsDsg.getDefaultGraph(), data);
+
+ // Execute queries and compare.
+ Graph expectedGraph = SSE.parseGraph("(graph (:s rdf:type :C) )");
+ Graph actualGraph = QueryExec.dataset(rdfsDsg).query("CONSTRUCT
WHERE { <http://example/s> a ?o }").construct();
+
+ Set<Triple> expected = Iter.toSet(expectedGraph.find());
+ Set<Triple> actual = Iter.toSet(actualGraph.find());
+
+ assertEquals(expected, actual);
+ txn.commit();
+ }
+ }
+}
diff --git a/pom.xml b/pom.xml
index 6ad43872b7..9d4af1b85e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -89,6 +89,7 @@
<ver.commons-io>2.21.0</ver.commons-io>
<ver.commons-cli>1.7.0</ver.commons-cli>
<ver.commons-lang3>3.20.0</ver.commons-lang3>
+ <ver.commons-math4>4.0-beta1</ver.commons-math4>
<ver.commons-rdf>0.5.0</ver.commons-rdf>
<ver.commons-csv>1.14.1</ver.commons-csv>
<ver.commons-codec>1.20.0</ver.commons-codec>
@@ -422,6 +423,12 @@
<version>${ver.commons-lang3}</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-math4-legacy</artifactId>
+ <version>${ver.commons-math4}</version>
+ </dependency>
+
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
@@ -949,7 +956,7 @@
<release>${java.version}</release>
<compilerArgs>
<arg>-proc:none</arg>
- <!--
+ <!--
In preparation for Jena6, silence deprecation=removal
warnings.
There are many.
Remove Xlint setting at or after Jena6.