* <#rdfsDS> rdf:type ja:DatasetRDFS ;
- * ja:rdfs "vocab.ttl";
+ * ja:rdfsSchema "vocab.ttl";
* ja:dataset <#baseDS> ;
* .
*
@@ -58,7 +57,6 @@ public Map pool() {
* ja:name "TIM database" # optional: this is need if the base database is accessed directly.
* ja:data "data1.trig";
* ## ja:data "data2.trig";
- *
* .
*
*/
@@ -76,7 +74,7 @@ public DatasetGraph createDataset(Assembler a, Resource root) {
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 f0869d31905..8d1315bf330 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 Object open(Assembler a, Resource root, Mode mode) {
/**
*
* <#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 9d69662f51e..69943e75153 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 @@ final private void range(X s, X p, X o, Output out) {
return;
}
Set 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/engine/DatasetGraphWithGraphTransform.java b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/DatasetGraphWithGraphTransform.java
new file mode 100644
index 00000000000..aacd9c5ac17
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/DatasetGraphWithGraphTransform.java
@@ -0,0 +1,157 @@
+/*
+ * 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 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;
+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.DatasetGraphWrapperView;
+import org.apache.jena.sparql.core.Quad;
+import org.apache.jena.sparql.util.Context;
+
+public class DatasetGraphWithGraphTransform extends DatasetGraphWrapper implements DatasetGraphWrapperView {
+ // Do not unwrap for query execution.
+
+ private Function graphTransform;
+
+ public DatasetGraphWithGraphTransform(DatasetGraph dsg, Function graphTransform) {
+ super(dsg);
+ this.graphTransform = graphTransform;
+ }
+
+ public DatasetGraphWithGraphTransform(DatasetGraph dsg, Context cxt, Function graphTransform) {
+ super(dsg, cxt);
+ 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 wrapGraph(base);
+ }
+
+ @Override
+ public Graph getUnionGraph() {
+ Graph base = getG().getUnionGraph();
+ return wrapGraph(base);
+ }
+
+ @Override
+ public Graph getGraph(Node graphNode) {
+ Graph base = getG().getGraph(graphNode);
+ if ( base == null )
+ return null;
+ return wrapGraph(base);
+ }
+
+ @Override
+ public Iterator find()
+ { return find(Node.ANY, Node.ANY, Node.ANY, Node.ANY); }
+
+ // Quad-centric access
+ @Override
+ public Iterator find(Quad quad) {
+ return find(quad.getGraph(), quad.getSubject(), quad.getPredicate(), quad.getObject());
+ }
+
+ @Override
+ public Iterator find(Node g, Node s, Node p, Node o) {
+ Iterator iter = findInf(g, s, p, o);
+ if ( iter == null )
+ return Iter.nullIterator();
+ return iter;
+ }
+
+// private Iterator 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 infMatcher = new InfFindQuad(setup, g, getR());
+// Stream quads = infMatcher.match(s, p, o);
+// Iterator iter = quads.iterator();
+// iter = Iter.onClose(iter, ()->quads.close());
+// return iter;
+// }
+
+ /**
+ * Find, graph by graph.
+ */
+ private Iterator 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 iter1 = findOneGraphInf(Quad.defaultGraphIRI, s, p, o);
+ Iterator 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 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 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 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 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/engine/GraphIncRDFS.java b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/GraphIncRDFS.java
index 0fcfa02e2dd..2eb3a4ea7ab 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 GraphIncRDFS(Graph graph, ConfigRDFS setup) {
.filter(type->!setup.getSubClassHierarchy().keySet().contains(type))
.map(type->Triple.create(type, rdfsSubClassOf, type))
);
-
}
@Override
@@ -73,6 +73,11 @@ public ExtendedIterator find(Node s, Node p, Node o) {
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 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/engine/GraphMatch.java b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/GraphMatch.java
new file mode 100644
index 00000000000..8e9eee0d7fa
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/GraphMatch.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.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.sparql.graph.GraphWrapper;
+import org.apache.jena.util.iterator.ExtendedIterator;
+import org.apache.jena.util.iterator.WrappedIterator;
+
+/**
+ * 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 GraphMatch extends GraphWrapper {
+ private final Match source;
+
+ public GraphMatch(Graph graph, Match match) {
+ super(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 Graph adapt(Graph baseGraph, Match match) {
+ return new GraphMatch(baseGraph, new MatchAdapter<>(match, match.getMapper()));
+ }
+
+ public Match getMatch() {
+ return source;
+ }
+
+ @Override
+ public ExtendedIterator find(Triple m) {
+ return find(m.getSubject(), m.getPredicate(), m.getObject());
+ }
+
+ @Override
+ public ExtendedIterator find(Node s, Node p, Node o) {
+ Stream stream = source.match(s, p, o);
+ ExtendedIterator iter = WrappedIterator.ofStream(stream);
+ return iter;
+ }
+
+ @Override
+ public Stream stream(Node s, Node p, Node o) {
+ return source.match(s, p, o);
+ }
+
+ @Override
+ public boolean contains(Node s, Node p, Node o) {
+ return source.contains(s, p, o);
+ }
+
+ @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();
+ }
+
+ @Override
+ public boolean dependsOn(Graph other) {
+ if ( other == super.get() )
+ return true;
+ return super.dependsOn(other);
+ }
+}
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 c9a771bd399..97d86206e10 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 Stream sourceFind(Node s, Node p, Node o) {
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 9511acd0e16..2f152f3c3c8 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 Stream sourceFind(Node s, Node p, Node o) {
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 29a3650e476..bf136cdbbbc 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 {
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 5c70427c1e1..2666cc0a7c6 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 @@ private static class MapperTriple implements MapperX {
@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 {
@@ -52,6 +55,7 @@ private static class MapperQuad implements MapperX {
@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> {
@@ -60,6 +64,7 @@ private static class MapperTuple implements MapperX> {
@Override public Node subject(Tuple tuple) { return offset(tuple, 0); }
@Override public Node predicate(Tuple tuple) { return offset(tuple, 1); }
@Override public Node object(Tuple tuple) { return offset(tuple, 2); }
+ @Override public Tuple3 tuple(Node s, Node p, Node o) { return TupleFactory.create3(s, p, o); }
private static Node offset(Tuple 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 2fa7cf4151f..32601b45c78 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 @@
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 {
public Stream match(X s, X p, X o);
+
+ public default boolean contains(X s, X p, X o) {
+ try (Stream 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 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 00000000000..0d4aaf5e799
--- /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
+ implements Match
+{
+ private Match below;
+ private MapperX mapper;
+
+ public MatchAdapter(Match below, MapperX mapper) {
+ super();
+ this.mapper = Objects.requireNonNull(mapper);
+ this.below = Objects.requireNonNull(below);
+ }
+
+ @Override
+ public Stream 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 getMapper() {
+ return Mappers.mapperTriple();
+ }
+}
diff --git a/jena-arq/src/main/java/org/apache/jena/rdfs/engine/MatchGraph.java b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/MatchGraph.java
new file mode 100644
index 00000000000..40d064eed59
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/MatchGraph.java
@@ -0,0 +1,55 @@
+/*
+ * 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.Graph;
+import org.apache.jena.graph.Node;
+import org.apache.jena.graph.Triple;
+
+/**
+ * A {@link Match} view over a {@link Graph}.
+ * This class is final. Use {@link MatchWrapper} to modify match behavior.
+ */
+public final class MatchGraph
+ implements Match
+{
+ private Graph base;
+
+ public MatchGraph(Graph base) {
+ super();
+ this.base = Objects.requireNonNull(base);
+ }
+
+ public Graph getGraph() {
+ return base;
+ }
+
+ @Override
+ public Stream match(Node s, Node p, Node o) {
+ return base.stream(s, p, o);
+ }
+
+ @Override
+ public MapperX 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 cef8c1af9f8..9ab430f61df 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 MatchRDFS(ConfigRDFS setup, MapperX mapper) {
};
}
+ @Override
+ public MapperX getMapper() {
+ return mapper;
+ }
+
@Override
public final Stream match(X s, X p, X o) { return matchWithInf(s, p ,o); }
@@ -84,7 +89,6 @@ public MatchRDFS(ConfigRDFS setup, MapperX mapper) {
// Access data.
protected abstract boolean sourceContains(X s, X p, X o);
protected abstract Stream 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 @@ private static boolean isEmpty(Map map) {
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> 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/MatchRDFSWrapper.java b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/MatchRDFSWrapper.java
new file mode 100644
index 00000000000..eee372f35e7
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/MatchRDFSWrapper.java
@@ -0,0 +1,46 @@
+/*
+ * 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.rdfs.setup.ConfigRDFS;
+
+/** MatchRDFS implementation as a wrapper over another Match source. */
+public class MatchRDFSWrapper
+ extends MatchRDFS
+{
+ protected Match source;
+
+ public MatchRDFSWrapper(ConfigRDFS setup, Match source) {
+ super(setup, source.getMapper());
+ this.source = Objects.requireNonNull(source);
+ }
+
+ @Override
+ public Stream 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/MatchWrapper.java b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/MatchWrapper.java
new file mode 100644
index 00000000000..b36f43f9798
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/rdfs/engine/MatchWrapper.java
@@ -0,0 +1,46 @@
+/*
+ * 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.stream.Stream;
+
+public class MatchWrapper>
+ implements Match
+{
+ protected D delegate;
+
+ public MatchWrapper(D delegate) {
+ super();
+ this.delegate = delegate;
+ }
+
+ public D getDelegate() {
+ return delegate;
+ }
+
+ @Override
+ public Stream match(X s, X p, X o) {
+ return getDelegate().match(s, p, o);
+ }
+
+ @Override
+ public MapperX 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 56d94426fae..81e5408638c 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 @@ private void exec(String qs, Graph graph, Map> multimap1, Map void put(Map> multimap, X n1, X n2) {
if ( !multimap.containsKey(n1) )
- multimap.put(n1, new HashSet());
+ 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 7e3eb4ac8ba..18bf0a083d1 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 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 @@ else if ( p.equals(rdfsRange) )
.stream()
.flatMap( e->e.getValue().stream().map(obj->Triple.create(e.getKey(), p, obj)) );
}
+
+ @Override
+ public MapperX 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 00000000000..dc21b41c5e3
--- /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 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 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:
+ *
+ * {(g, s, p, ANY), (g, s, ANY, o), (g, s, ANY, ANY), ...}
+ *
+ */
+ public static Stream createFindQuads(DatasetGraph dataSource) {
+ Node[] baseMetaPattern = new Node[]{OUT, OUT, OUT, OUT};
+ Stream 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 lookups = createFindQuads(dataSource, metaQuad);
+ return lookups.stream();
+ });
+ return result;
+ }
+
+ private static Quad toQuad(Iterator 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 createFindQuads(DatasetGraph dsg, Quad metaQuad) {
+ // Replace IN and OUT with ANY - this retains only term nodes.
+ Quad p = outToAny(inToAny(metaQuad));
+ Set 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.
+ *
+ *
+ * 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 findQuads;
+ private boolean compareAsSet;
+
+ protected GraphFindTestBuilder(String testLabel, DatasetGraph referenceDsg, DatasetGraph testDsg, List findQuads) {
+ super();
+ this.testLabel = Objects.requireNonNull(testLabel);
+ this.referenceDsg = Objects.requireNonNull(referenceDsg);
+ this.testDsg = Objects.requireNonNull(testDsg);
+ this.findQuads = findQuads;
+ }
+
+ public List getFindQuads() {
+ return findQuads;
+ }
+
+ public GraphFindTestBuilder compareAsSet(boolean onOrOff) {
+ this.compareAsSet = onOrOff;
+ return this;
+ }
+
+ public List build() {
+ List 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 00000000000..116e50553a0
--- /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 testSubPropertyOfRdfType01() {
+ List tests = prepareRdfsFindTestsSSE(
+ "(graph (:directType rdfs:subPropertyOf rdf:type) )",
+ "(graph (:fido :directType :Dog) )"
+ ).build();
+ return tests;
+ }
+
+ @TestFactory
+ public List testSubClassOf01() {
+ List tests = prepareRdfsFindTestsSSE(
+ "(graph (:Dog rdfs:subClassOf rdf:Mammal) )",
+ "(graph (:fido rdf:type :Dog) )"
+ ).build();
+ return tests;
+ }
+
+ @TestFactory
+ public List testRange01() {
+ List tests = prepareRdfsFindTestsSSE(
+ "(graph (:owner rdfs:range :Person) )",
+ "(graph (:fido :owner :alice) )"
+ ).build();
+ return tests;
+ }
+
+ @TestFactory
+ public List testRangeWithLiteral01() {
+ List tests = prepareRdfsFindTestsSSE(
+ "(graph (:name rdfs:range :Literal) )",
+ "(graph (:fido :name 'Fido') )"
+ ).build();
+ return tests;
+ }
+
+ @TestFactory
+ public List testDomain01() {
+ List 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 00000000000..21e0d9c015b
--- /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.
+ *
+ *
+ * BEWARE: find() is used to produce the reference data. Errors in the reference data
+ * will likely cause otherwise correctly functioning tests to fail.
+ *
+ *
+ * (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 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 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 stream = dataDsg.stream()) {
+ List 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 00000000000..304ceebd90f
--- /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 expectedList = Iter.toList(referenceDsg.find(findPattern));
+ List actualList = Iter.toList(testDsg.find(findPattern));
+
+ if (compareAsSet) {
+ Set expectedSet = new LinkedHashSet<>(expectedList);
+ Set 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 83419bd6f01..6a957a15972 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 @@
, TestInfSPARQL.class
, TestAssemblerRDFS.class
+
+ , TestDatasetGraphFindRDFS.class
})
public class TS_InfRdfs { }
diff --git a/jena-arq/src/test/java/org/apache/jena/rdfs/TestDatasetGraphFindRDFS.java b/jena-arq/src/test/java/org/apache/jena/rdfs/TestDatasetGraphFindRDFS.java
new file mode 100644
index 00000000000..716c403ae3d
--- /dev/null
+++ b/jena-arq/src/test/java/org/apache/jena/rdfs/TestDatasetGraphFindRDFS.java
@@ -0,0 +1,41 @@
+/*
+ * 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 org.apache.jena.graph.Node;
+import org.apache.jena.rdfs.setup.ConfigRDFS;
+import org.apache.jena.sparql.core.DatasetGraph;
+
+public class TestDatasetGraphFindRDFS
+ extends AbstractTestRDFS_Extra
+{
+ public TestDatasetGraphFindRDFS() {
+ super("RDFS");
+ }
+
+ @Override
+ protected boolean defaultCompareAsSet() {
+ return true;
+ }
+
+ @Override
+ protected DatasetGraph applyRdfs(DatasetGraph dsg, ConfigRDFS 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 095a366c3e3..4843851a177 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.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 @@
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 static void beforeClass() {
);
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 96dfa5e029e..b9015a7e889 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 implements Predicate {
public boolean test(T item) {
return seen.add(item);
}
-
}
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/iterator/FilterUniqueCache.java b/jena-base/src/main/java/org/apache/jena/atlas/iterator/FilterUniqueCache.java
new file mode 100644
index 00000000000..886865a4be4
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/FilterUniqueCache.java
@@ -0,0 +1,40 @@
+/*
+ * 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.atlas.iterator;
+
+import java.util.function.Predicate;
+
+import org.apache.jena.atlas.lib.Cache;
+import org.apache.jena.atlas.lib.CacheFactory;
+
+public class FilterUniqueCache implements Predicate {
+ private final Cache seen;
+
+ public FilterUniqueCache(int size) {
+ super();
+ this.seen = CacheFactory.createCache(size);
+ }
+
+ @Override
+ public boolean test(T 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 e72c3d46085..4db202f60ab 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.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;
@@ -113,6 +114,10 @@ public static Iter ofNullable(T t) {
return t == null ? Iter.empty() : Iter.of(t);
}
+ public static Iterator ofStream(Stream stream) {
+ return Iter.onClose(stream.iterator(), stream::close);
+ }
+
/**
* Return an iterator that does not permit remove.
* This makes an "UnmodifiableIterator".
@@ -530,6 +535,14 @@ public static Iterator distinct(Iterator iter) {
return filter(iter, new FilterUnique());
}
+ /** 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 Iterator distinctCached(Iterator iter, int cacheMaxSize) {
+ return filter(iter, new FilterUniqueCache(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 04b82cfd9cf..5d251639686 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 void forEachRemaining(Consumer super T> action) {
@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 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 00000000000..067520f05cf
--- /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>
+{
+ private NodeTable nodeTable;
+
+ protected MapperXTDB(NodeTable nodeTable) {
+ super();
+ this.nodeTable = nodeTable;
+ }
+
+ public static MapperX> 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 tuple) { return tuple.get(0); }
+ @Override public NodeId predicate(Tuple3 tuple) { return tuple.get(1); }
+ @Override public NodeId object (Tuple3 tuple) { return tuple.get(2); }
+
+ @Override public Tuple3 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 00000000000..2aac132b691
--- /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>
+{
+ private GraphTDB graph;
+ private MapperX> mapper;
+
+ protected MatchTDB(GraphTDB graph, MapperX> 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> mapper = new MapperXTDB(nodeTable);
+ return new MatchTDB(tdbGraph, mapper);
+ }
+
+ @Override
+ public Stream> 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> 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 00000000000..a77091a55a3
--- /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> 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 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 { a ?o }").construct();
+
+ Set expected = Iter.toSet(expectedGraph.find());
+ Set actual = Iter.toSet(actualGraph.find());
+
+ assertEquals(expected, actual);
+ txn.commit();
+ }
+ }
+}
diff --git a/pom.xml b/pom.xml
index 19e673ce6a2..091234ec33a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -89,6 +89,7 @@
2.20.0
1.7.0
3.19.0
+ 4.0-beta1
0.5.0
1.14.1
1.19.0
@@ -104,7 +105,7 @@
4.13.2
6.0.0
-
+
5.20.0
4.3.0
@@ -421,6 +422,12 @@
${ver.commons-lang3}
+
+ org.apache.commons
+ commons-math4-legacy
+ ${ver.commons-math4}
+
+
org.apache.commons
commons-collections4
@@ -948,7 +955,7 @@
${java.version}
-proc:none
-