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 286abc034f GH-2487: add StdModels#findShortestPath
286abc034f is described below
commit 286abc034f40c059ae81abc635d9196d13ca5100
Author: sszuev <[email protected]>
AuthorDate: Sun Jul 14 14:12:33 2024 +0300
GH-2487: add StdModels#findShortestPath
---
.../org/apache/jena/ontapi/utils/StdModels.java | 79 ++++++++
.../java/org/apache/jena/ontapi/StdModelsTest.java | 218 +++++++++++++++++++++
2 files changed, 297 insertions(+)
diff --git
a/jena-ontapi/src/main/java/org/apache/jena/ontapi/utils/StdModels.java
b/jena-ontapi/src/main/java/org/apache/jena/ontapi/utils/StdModels.java
index 5bd085396c..1bf7372f22 100644
--- a/jena-ontapi/src/main/java/org/apache/jena/ontapi/utils/StdModels.java
+++ b/jena-ontapi/src/main/java/org/apache/jena/ontapi/utils/StdModels.java
@@ -41,12 +41,19 @@ import org.apache.jena.sparql.util.NodeCmp;
import org.apache.jena.util.iterator.ExtendedIterator;
import org.apache.jena.vocabulary.RDF;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
+import java.util.Deque;
+import java.util.HashSet;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
@@ -233,4 +240,76 @@ public class StdModels {
if (p != null) return false;
return o == null;
}
+
+ /**
+ * Answers the shortest path from the {@code start} resource to the {@code
end} RDF node,
+ * such that every step on the path is accepted by the given filter.
+ * A path is a {@link List} of RDF {@link Statement}s.
+ * The subject of the first statement in the list is {@code start},
+ * and the object of the last statement in the list is {@code end}.
+ * <p>
+ * The {@code onPath} argument is a {@link Predicate}, which accepts a
statement and returns
+ * {@code true} if the statement should be considered to be on the path.
+ * To search for an unconstrained path, pass {@code ()->true} or {@code
null} as an argument.
+ * If there is more than one path of minimal length from {@code start} to
{@code end},
+ * this method returns an arbitrary one.
+ * The algorithm is blind breadth-first search, with loop detection.
+ *
+ * @param m the model in which we are seeking a path, not {@code null}
+ * @param start the starting resource, not {@code null}
+ * @param end the end, or goal, node, not {@code null}
+ * @param onPath a filter which determines whether a given statement can
be considered part of the path
+ * @return a path, consisting of a list of statements whose first subject
is {@code start},
+ * and whose last object is {@code end}, empty if no such path exists
+ */
+ public static List<Statement> findShortestPath(Model m, Resource start,
RDFNode end, Predicate<Statement> onPath) {
+ Objects.requireNonNull(m);
+ Objects.requireNonNull(start);
+ Objects.requireNonNull(end);
+ if (onPath == null) {
+ onPath = s -> true;
+ }
+ Deque<List<Statement>> bfs = new ArrayDeque<>();
+ Set<RDFNode> seen = new HashSet<>();
+
+ // initialize the paths
+ for (Iterator<Statement> i = m.listStatements(start, null, (RDFNode)
null).filterKeep(onPath); i.hasNext(); ) {
+ List<Statement> statements = new ArrayList<>();
+ statements.add(i.next());
+ bfs.add(statements);
+ }
+
+ // search
+ List<Statement> solution = new ArrayList<>();
+ while (solution.isEmpty() && !bfs.isEmpty()) {
+ List<Statement> candidate = bfs.removeFirst();
+
+ RDFNode terminalNode = candidate.isEmpty() ? null :
candidate.get(candidate.size() - 1).getObject();
+ if (terminalNode == null) {
+ continue;
+ }
+ if (end.equals(terminalNode)) {
+ solution = candidate;
+ continue;
+ }
+ if (!terminalNode.isResource()) {
+ continue;
+ }
+ Resource terminalResource = terminalNode.asResource();
+ seen.add(terminalResource);
+
+ // breadth-first expansion
+ for (Iterator<Statement> i =
terminalResource.listProperties().filterKeep(onPath); i.hasNext(); ) {
+ Statement link = i.next();
+
+ // no looping allowed, so we skip this link if it takes us to
a node we've seen
+ if (!seen.contains(link.getObject())) {
+ List<Statement> statements = new ArrayList<>(candidate);
+ statements.add(link);
+ bfs.add(statements);
+ }
+ }
+ }
+ return solution;
+ }
}
diff --git
a/jena-ontapi/src/test/java/org/apache/jena/ontapi/StdModelsTest.java
b/jena-ontapi/src/test/java/org/apache/jena/ontapi/StdModelsTest.java
new file mode 100644
index 0000000000..435a845fb6
--- /dev/null
+++ b/jena-ontapi/src/test/java/org/apache/jena/ontapi/StdModelsTest.java
@@ -0,0 +1,218 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.ontapi;
+
+import org.apache.jena.ontapi.utils.StdModels;
+import org.apache.jena.rdf.model.Model;
+import org.apache.jena.rdf.model.ModelFactory;
+import org.apache.jena.rdf.model.Property;
+import org.apache.jena.rdf.model.Resource;
+import org.apache.jena.rdf.model.ResourceFactory;
+import org.apache.jena.rdf.model.Statement;
+import org.apache.jena.vocabulary.OWL2;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.Set;
+
+public class StdModelsTest {
+ static final String NS = "http://example.com/test#";
+
+ static Model createStdModelClassesABCDEFGThing() {
+ Model m = ModelFactory.createDefaultModel();
+ m.createResource(NS + "A", OWL2.Class);
+ m.createResource(NS + "B", OWL2.Class);
+ m.createResource(NS + "C", OWL2.Class);
+ m.createResource(NS + "D", OWL2.Class);
+ m.createResource(NS + "E", OWL2.Class);
+ m.createResource(NS + "F", OWL2.Class);
+ m.createResource(NS + "G", OWL2.Class);
+ m.createResource(OWL2.Thing.getURI(), OWL2.Class);
+ return m;
+ }
+
+ @Test
+ public void testShortestPath0() {
+ Model m = createStdModelClassesABCDEFGThing();
+ Resource A = m.getResource(NS + "A");
+ Resource B = m.getResource(NS + "B");
+
+ List<Statement> actual = StdModels.findShortestPath(m, A, B, s ->
true);
+ Assertions.assertTrue(actual.isEmpty());
+ }
+
+ @Test
+ public void testShortestPath1() {
+ Model m = createStdModelClassesABCDEFGThing();
+ Resource A = m.getResource(NS + "A");
+ Resource B = m.getResource(NS + "B");
+ Property p = m.createProperty(NS + "p");
+ A.addProperty(p, B);
+
+ List<Statement> actual = StdModels.findShortestPath(m, A, B, s ->
true);
+ Assertions.assertEquals(List.of(p),
actual.stream().map(Statement::getPredicate).toList());
+ }
+
+ @Test
+ public void testShortestPath2() {
+ Model m = createStdModelClassesABCDEFGThing();
+ Resource A = m.getResource(NS + "A");
+ Resource B = m.getResource(NS + "B");
+ Resource C = m.getResource(NS + "C");
+ Property p = m.createProperty(NS + "p");
+ A.addProperty(p, B);
+ B.addProperty(p, C);
+
+ List<Statement> actual = StdModels.findShortestPath(m, A, C, s ->
true);
+ Assertions.assertEquals(List.of(p, p),
actual.stream().map(Statement::getPredicate).toList());
+ }
+
+ @Test
+ public void testShortestPath3() {
+ Model m = createStdModelClassesABCDEFGThing();
+ Resource A = m.getResource(NS + "A");
+ Resource B = m.getResource(NS + "B");
+ Resource C = m.getResource(NS + "C");
+ Resource D = m.getResource(NS + "D");
+ Resource E = m.getResource(NS + "E");
+ Resource F = m.getResource(NS + "F");
+ Property p = m.createProperty(NS + "p");
+ // a - b - c
+ A.addProperty(p, B);
+ B.addProperty(p, C);
+
+ // a - d - e - f
+ A.addProperty(p, D);
+ D.addProperty(p, E);
+ E.addProperty(p, F);
+
+ List<Statement> actual1 = StdModels.findShortestPath(m, A, C, s ->
true);
+ Assertions.assertEquals(List.of(p, p),
actual1.stream().map(Statement::getPredicate).toList());
+
+ List<Statement> actual2 = StdModels.findShortestPath(m, A, F, s ->
true);
+ Assertions.assertEquals(List.of(p, p, p),
actual2.stream().map(Statement::getPredicate).toList());
+
+ List<Statement> actual3 = StdModels.findShortestPath(m, A, C, s ->
p.equals(s.getPredicate()));
+ Assertions.assertEquals(List.of(p, p),
actual3.stream().map(Statement::getPredicate).toList());
+
+ List<Statement> actual4 = StdModels.findShortestPath(m, A, F, s ->
p.equals(s.getPredicate()));
+ Assertions.assertEquals(List.of(p, p, p),
actual4.stream().map(Statement::getPredicate).toList());
+ }
+
+ @Test
+ public void testShortestPath4() {
+ Model m = createStdModelClassesABCDEFGThing();
+ Resource A = m.getResource(NS + "A");
+ Resource B = m.getResource(NS + "B");
+ Resource C = m.getResource(NS + "C");
+ Resource D = m.getResource(NS + "D");
+ Resource E = m.getResource(NS + "E");
+ Resource F = m.getResource(NS + "F");
+ Property p = m.createProperty(NS + "p");
+ Property q = m.createProperty(NS + "q");
+
+ // a - b - c by q
+ A.addProperty(q, B);
+ B.addProperty(q, C);
+
+ // a - d - e - f by p
+ A.addProperty(p, D);
+ D.addProperty(p, E);
+ E.addProperty(p, F);
+
+ List<Statement> actual1 = StdModels.findShortestPath(m, A, C, s ->
p.equals(s.getPredicate()));
+ Assertions.assertTrue(actual1.isEmpty());
+ List<Statement> actual2 = StdModels.findShortestPath(m, A, F, s ->
p.equals(s.getPredicate()));
+ Assertions.assertEquals(List.of(p, p, p),
actual2.stream().map(Statement::getPredicate).toList());
+ }
+
+ @Test
+ public void testShortestPath5() {
+ Model m = createStdModelClassesABCDEFGThing();
+ Resource A = m.getResource(NS + "A");
+ Property p = m.createProperty(NS + "p");
+ A.addProperty(p, A);
+
+ List<Statement> actual = StdModels.findShortestPath(m, A, A, s ->
true);
+ Assertions.assertEquals(List.of(p),
actual.stream().map(Statement::getPredicate).toList());
+ }
+
+ @Test
+ public void testShortestPath6() {
+ Model m = createStdModelClassesABCDEFGThing();
+ Resource A = m.getResource(NS + "A");
+ Resource B = m.getResource(NS + "B");
+ Resource C = m.getResource(NS + "C");
+ Property p = m.createProperty(NS + "p");
+ Property q = m.createProperty(NS + "q");
+ // a - b - a by q
+ // tests loop detection
+ A.addProperty(q, B);
+ B.addProperty(q, A);
+
+ List<Statement> actual = StdModels.findShortestPath(m, A, C, s ->
Set.of(p, q).contains(s.getPredicate()));
+ Assertions.assertTrue(actual.isEmpty());
+ }
+
+ @Test
+ public void testShortestPath7() {
+ Model m = createStdModelClassesABCDEFGThing();
+ Resource A = m.getResource(NS + "A");
+ Resource B = m.getResource(NS + "B");
+ Resource D = m.getResource(NS + "D");
+ Resource E = m.getResource(NS + "E");
+ Resource F = m.getResource(NS + "F");
+ Property p = m.createProperty(NS + "p");
+ Property q = m.createProperty(NS + "q");
+
+ // a - d - e - f by p and q
+ A.addProperty(p, D);
+ D.addProperty(q, E);
+ D.addProperty(q, B);
+ E.addProperty(p, F);
+
+ List<Statement> actual = StdModels.findShortestPath(m, A, F, s ->
Set.of(p, q).contains(s.getPredicate()));
+ Assertions.assertEquals(List.of(p, q, p),
actual.stream().map(Statement::getPredicate).toList());
+ }
+
+ @Test
+ public void testShortestPath8() {
+ Model m = createStdModelClassesABCDEFGThing();
+ Resource A = m.getResource(NS + "A");
+ Resource B = m.getResource(NS + "B");
+ Resource D = m.getResource(NS + "D");
+ Resource E = m.getResource(NS + "E");
+ Resource F = m.getResource(NS + "F");
+ Property p = m.createProperty(NS + "p");
+ Property q = m.createProperty(NS + "q");
+
+ // a - d - e - f by p and q
+ A.addProperty(p, D);
+ D.addProperty(q, E);
+ D.addProperty(q, "bluff");
+ D.addProperty(q, B);
+ E.addProperty(p, F);
+ F.addProperty(q, "arnie");
+
+ List<Statement> actual = StdModels.findShortestPath(m, A,
ResourceFactory.createPlainLiteral("arnie"),
+ s -> Set.of(p, q).contains(s.getPredicate()));
+ Assertions.assertEquals(List.of(p, q, p, q),
actual.stream().map(Statement::getPredicate).toList());
+ }
+}