diff --git a/src/main/java/org/topbraid/jenax/progress/ProgressMonitor.java b/src/main/java/org/topbraid/jenax/progress/ProgressMonitor.java
index 36f661e9..a445a2e3 100644
--- a/src/main/java/org/topbraid/jenax/progress/ProgressMonitor.java
+++ b/src/main/java/org/topbraid/jenax/progress/ProgressMonitor.java
@@ -21,60 +21,66 @@
/**
* Inspired by the Eclipse IProgressMonitor, this interface supports monitoring long-running processes with intermediate
* status messages and the ability to cancel.
- *
+ *
* @author Holger Knublauch
*/
public interface ProgressMonitor {
- /**
- * Typically used by the (long-running) process to determine whether the user has requested cancellation.
- * The process should then find a suitable, clean termination.
- * @return true if cancel was requested
- */
- boolean isCanceled();
-
-
- /**
- * Informs the progress monitor that a new task has been started, with a given number of expected steps.
- * A UI connected to the ProgressMonitor would typically display something like a progress bar and the task name.
- * @param label the name of the task
- * @param totalWork the number of steps (see worked
) that is expected to be needed to complete the task
- */
- void beginTask(String label, int totalWork);
-
-
- /**
- * Informs the progress monitor that all is completed.
- */
- void done();
-
-
- /**
- * Typically called from a parallel thread triggered by the user, this informs the progress monitor that it needs to
- * return true
for isCanceled
.
- * Once a process has been canceled, it should not be un-canceled.
- * @param value true if canceled
- */
- void setCanceled(boolean value);
-
-
- /**
- * Changes the name or label of the current task.
- * @param value
- */
- void setTaskName(String value);
-
-
- /**
- * Sets the label that serves as sub-task, typically printed under the main task name.
- * @param label the subtask label
- */
- void subTask(String label);
-
-
- /**
- * Informs the progress monitor that one or more steps have been completed towards the current task (see beginTask
).
- * @param amount the number of steps completed
- */
- void worked(int amount);
+ /**
+ * Typically used by the (long-running) process to determine whether the user has requested cancellation.
+ * The process should then find a suitable, clean termination.
+ *
+ * @return true if cancel was requested
+ */
+ boolean isCanceled();
+
+
+ /**
+ * Informs the progress monitor that a new task has been started, with a given number of expected steps.
+ * A UI connected to the ProgressMonitor would typically display something like a progress bar and the task name.
+ *
+ * @param label the name of the task
+ * @param totalWork the number of steps (see worked
) that is expected to be needed to complete the task
+ */
+ void beginTask(String label, int totalWork);
+
+
+ /**
+ * Informs the progress monitor that all is completed.
+ */
+ void done();
+
+
+ /**
+ * Typically called from a parallel thread triggered by the user, this informs the progress monitor that it needs to
+ * return true
for isCanceled
.
+ * Once a process has been canceled, it should not be un-canceled.
+ *
+ * @param value true if canceled
+ */
+ void setCanceled(boolean value);
+
+
+ /**
+ * Changes the name or label of the current task.
+ *
+ * @param value the task name
+ */
+ void setTaskName(String value);
+
+
+ /**
+ * Sets the label that serves as sub-task, typically printed under the main task name.
+ *
+ * @param label the subtask label
+ */
+ void subTask(String label);
+
+
+ /**
+ * Informs the progress monitor that one or more steps have been completed towards the current task (see beginTask
).
+ *
+ * @param amount the number of steps completed
+ */
+ void worked(int amount);
}
diff --git a/src/main/java/org/topbraid/jenax/util/DiffGraph.java b/src/main/java/org/topbraid/jenax/util/DiffGraph.java
index 75c77542..138af200 100644
--- a/src/main/java/org/topbraid/jenax/util/DiffGraph.java
+++ b/src/main/java/org/topbraid/jenax/util/DiffGraph.java
@@ -1,225 +1,218 @@
package org.topbraid.jenax.util;
-import java.util.HashSet;
-import java.util.Set;
-
import org.apache.jena.graph.Graph;
+import org.apache.jena.graph.GraphMemFactory;
import org.apache.jena.graph.Node;
import org.apache.jena.graph.Triple;
import org.apache.jena.graph.impl.GraphMatcher;
import org.apache.jena.graph.impl.GraphWithPerform;
-import org.apache.jena.mem.GraphMem;
import org.apache.jena.shared.PrefixMapping;
import org.apache.jena.shared.impl.PrefixMappingImpl;
import org.apache.jena.util.iterator.ExtendedIterator;
+import java.util.HashSet;
+import java.util.Set;
+
/**
* A WrappedGraph that filters out deleted triples or adds added triples, without
* modifying the underlying base graph.
- *
+ *
* This class is for single-threaded use only, typically used as temporary graph layer on top of an existing
* graph for the duration of some algorithm.
- *
+ *
* This runs in two modes, based on the updateBaseGraph flag.
- *
+ *
* By default/legacy (false) the system will only add triples that exist in none of the subgraphs of the delegate graph
* and claim to delete triples even if they exist in subgraphs only.
- *
+ *
* If true, the adds will always be applied even if one of the subgraphs already contains the triple.
* This is making sure that transformations will always produce all requested triples.
* Furthermore this mode is more correct w.r.t. deletes because it will only allow deleting triples from the editable graph.
- *
+ *
* @author Holger Knublauch
*/
public class DiffGraph extends TransparentWrappedGraph {
- /**
- * This graph has additional triples that are not in the delegate.
- */
- protected GraphWithPerform addedGraph = new GraphMem();
-
- /**
- * This Set has triples that are in the delegate but are excluded from the filtered graph.
- */
- protected Set deletedTriples = new HashSet<>();
-
- private PrefixMapping pm;
-
- // The graph that the triples will be added to
- private Graph updateableGraph;
-
-
- public DiffGraph(Graph delegate) {
- this(delegate, false);
- }
-
-
- public DiffGraph(Graph delegate, boolean updateBaseGraph) {
- super(delegate);
- if(updateBaseGraph) {
- updateableGraph = JenaUtil.getBaseGraph(delegate);
- }
- else {
- updateableGraph = delegate;
- }
- }
-
-
- @Override
- public void add(Triple t) {
- performAdd(t);
- }
-
-
- @Override
- public void delete(Triple t) {
- performDelete(t);
- }
-
-
- public Graph getAddedGraph() {
- return addedGraph;
- }
-
-
- @Override
- public boolean contains(Node s, Node p, Node o) {
- return contains(Triple.create(s == null ? Node.ANY : s, p == null ? Node.ANY : p, o == null ? Node.ANY : o));
- }
-
-
- @Override
- public boolean contains(Triple t) {
- if(addedGraph.contains(t)) {
- return true;
- }
- else {
- ExtendedIterator it = base.find(t);
- while(it.hasNext()) {
- Triple n = it.next();
- if(!deletedTriples.contains(n)) {
- it.close();
- return true;
- }
- }
- return false;
- }
- }
-
-
- // TODO: If the delegate does not use equals for add and delete
- // but sameValueAs then this code is incorrect.
- // Specifically we should be able to show bugs with TDB which does
- // something different from either equals or sameValueAs.
- protected boolean containsByEquals(Graph g, Triple t) {
- ExtendedIterator it = g.find(t);
- try {
- while (it.hasNext()) {
- if (t.equals(it.next()))
- return true;
- }
- }
- finally {
- it.close();
- }
- return false;
- }
-
-
- @Override
- public ExtendedIterator find(Node s, Node p, Node o) {
-
- // First get the underlying base query (without any buffered triples)
- ExtendedIterator base = super.find(s, p, o);
-
- // If deleted triples exist then continue with a filtered iterator
- if(deletedTriples.size() > 0) {
- // base without deleted triples.
- base = base.filterDrop(deletedTriples::contains);
- }
-
- // If added triples exist then chain the two together
- // this iterator supports remove and removes correctly for this graph
- ExtendedIterator added = addedGraph.find(s, p, o);
- if(added.hasNext()) {
- return base.andThen(added); // base and added are guaranteed disjoint
- }
- else {
- return base;
- }
- }
-
-
- @Override
- public ExtendedIterator find(Triple m) {
- return find(m.getMatchSubject(), m.getMatchPredicate(), m.getMatchObject());
- }
-
-
- public Set getDeletedTriples() {
- return deletedTriples;
- }
-
-
- @Override
- public PrefixMapping getPrefixMapping() {
- if (pm == null) {
- // copy delegate's prefix mapping.
- pm = new PrefixMappingImpl().setNsPrefixes(base.getPrefixMapping());
- }
- return pm;
- }
-
-
- @Override
- public boolean isEmpty() {
- if (!addedGraph.isEmpty()) {
- return false;
- }
- if (deletedTriples.isEmpty()) {
- return base.isEmpty();
- }
- ExtendedIterator it = find(Triple.ANY);
- try {
- return !it.hasNext();
- }
- finally {
- it.close();
- }
- }
-
-
+ /**
+ * This graph has additional triples that are not in the delegate.
+ */
+ protected GraphWithPerform addedGraph = (GraphWithPerform) GraphMemFactory.createDefaultGraph();
+
+ /**
+ * This Set has triples that are in the delegate but are excluded from the filtered graph.
+ */
+ protected Set deletedTriples = new HashSet<>();
+
+ private PrefixMapping pm;
+
+ // The graph that the triples will be added to
+ private Graph updateableGraph;
+
+
+ public DiffGraph(Graph delegate) {
+ this(delegate, false);
+ }
+
+
+ public DiffGraph(Graph delegate, boolean updateBaseGraph) {
+ super(delegate);
+ if (updateBaseGraph) {
+ updateableGraph = JenaUtil.getBaseGraph(delegate);
+ } else {
+ updateableGraph = delegate;
+ }
+ }
+
+
+ @Override
+ public void add(Triple t) {
+ performAdd(t);
+ }
+
+
+ @Override
+ public void delete(Triple t) {
+ performDelete(t);
+ }
+
+
+ public Graph getAddedGraph() {
+ return addedGraph;
+ }
+
+
+ @Override
+ public boolean contains(Node s, Node p, Node o) {
+ return contains(Triple.create(s == null ? Node.ANY : s, p == null ? Node.ANY : p, o == null ? Node.ANY : o));
+ }
+
+
@Override
- public boolean isIsomorphicWith(Graph g) {
- return g != null && GraphMatcher.equals(this, g);
- }
-
-
- @Override
- public void performAdd(Triple t) {
- if (deletedTriples.contains(t)) {
- deletedTriples.remove(t);
- }
- else if(!containsByEquals(addedGraph, t) && !containsByEquals(updateableGraph, t)) {
- addedGraph.add(t);
- }
- }
-
-
- @Override
- public void performDelete(Triple t) {
- if(containsByEquals(addedGraph, t)) {
- addedGraph.delete(t);
- }
- else if(containsByEquals(updateableGraph, t)) {
- deletedTriples.add(t);
- }
- }
-
-
- @Override
- public int size() {
- return super.size() - deletedTriples.size() + addedGraph.size();
- }
+ public boolean contains(Triple t) {
+ if (addedGraph.contains(t)) {
+ return true;
+ } else {
+ ExtendedIterator it = base.find(t);
+ while (it.hasNext()) {
+ Triple n = it.next();
+ if (!deletedTriples.contains(n)) {
+ it.close();
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+
+ // TODO: If the delegate does not use equals for add and delete
+ // but sameValueAs then this code is incorrect.
+ // Specifically we should be able to show bugs with TDB which does
+ // something different from either equals or sameValueAs.
+ protected boolean containsByEquals(Graph g, Triple t) {
+ ExtendedIterator it = g.find(t);
+ try {
+ while (it.hasNext()) {
+ if (t.equals(it.next()))
+ return true;
+ }
+ } finally {
+ it.close();
+ }
+ return false;
+ }
+
+
+ @Override
+ public ExtendedIterator find(Node s, Node p, Node o) {
+
+ // First get the underlying base query (without any buffered triples)
+ ExtendedIterator base = super.find(s, p, o);
+
+ // If deleted triples exist then continue with a filtered iterator
+ if (deletedTriples.size() > 0) {
+ // base without deleted triples.
+ base = base.filterDrop(deletedTriples::contains);
+ }
+
+ // If added triples exist then chain the two together
+ // this iterator supports remove and removes correctly for this graph
+ ExtendedIterator added = addedGraph.find(s, p, o);
+ if (added.hasNext()) {
+ return base.andThen(added); // base and added are guaranteed disjoint
+ } else {
+ return base;
+ }
+ }
+
+
+ @Override
+ public ExtendedIterator find(Triple m) {
+ return find(m.getMatchSubject(), m.getMatchPredicate(), m.getMatchObject());
+ }
+
+
+ public Set getDeletedTriples() {
+ return deletedTriples;
+ }
+
+
+ @Override
+ public PrefixMapping getPrefixMapping() {
+ if (pm == null) {
+ // copy delegate's prefix mapping.
+ pm = new PrefixMappingImpl().setNsPrefixes(base.getPrefixMapping());
+ }
+ return pm;
+ }
+
+
+ @Override
+ public boolean isEmpty() {
+ if (!addedGraph.isEmpty()) {
+ return false;
+ }
+ if (deletedTriples.isEmpty()) {
+ return base.isEmpty();
+ }
+ ExtendedIterator it = find(Triple.ANY);
+ try {
+ return !it.hasNext();
+ } finally {
+ it.close();
+ }
+ }
+
+
+ @Override
+ public boolean isIsomorphicWith(Graph g) {
+ return g != null && GraphMatcher.equals(this, g);
+ }
+
+
+ @Override
+ public void performAdd(Triple t) {
+ if (deletedTriples.contains(t)) {
+ deletedTriples.remove(t);
+ } else if (!containsByEquals(addedGraph, t) && !containsByEquals(updateableGraph, t)) {
+ addedGraph.add(t);
+ }
+ }
+
+
+ @Override
+ public void performDelete(Triple t) {
+ if (containsByEquals(addedGraph, t)) {
+ addedGraph.delete(t);
+ } else if (containsByEquals(updateableGraph, t)) {
+ deletedTriples.add(t);
+ }
+ }
+
+
+ @Override
+ public int size() {
+ return super.size() - deletedTriples.size() + addedGraph.size();
+ }
}
diff --git a/src/main/java/org/topbraid/jenax/util/GraphNotFoundException.java b/src/main/java/org/topbraid/jenax/util/GraphNotFoundException.java
index 0e8a470c..e1597b44 100644
--- a/src/main/java/org/topbraid/jenax/util/GraphNotFoundException.java
+++ b/src/main/java/org/topbraid/jenax/util/GraphNotFoundException.java
@@ -19,17 +19,16 @@
/**
* An Exception thrown if a named graph could not be resolved
* while setting the default graph of a dataset.
- *
+ *
* This is subclassing RuntimeException because otherwise a lot of
* existing code would have to catch GraphNotFoundException
* (where it would otherwise have crashed with a NullPointerException anyway).
- *
+ *
* @author Holger Knublauch
*/
-@SuppressWarnings("serial")
public class GraphNotFoundException extends RuntimeException {
- public GraphNotFoundException(String message) {
- super(message);
- }
+ public GraphNotFoundException(String message) {
+ super(message);
+ }
}
diff --git a/src/main/java/org/topbraid/jenax/util/QueryExecutionFactoryFilter.java b/src/main/java/org/topbraid/jenax/util/QueryExecutionFactoryFilter.java
index fe3ecaf2..5cbeda87 100644
--- a/src/main/java/org/topbraid/jenax/util/QueryExecutionFactoryFilter.java
+++ b/src/main/java/org/topbraid/jenax/util/QueryExecutionFactoryFilter.java
@@ -16,151 +16,150 @@
*/
package org.topbraid.jenax.util;
-import java.net.http.HttpClient;
-import java.time.format.DateTimeFormatter;
-import java.util.List;
-
-import org.apache.jena.atlas.lib.DateTimeUtils ;
-import org.apache.jena.query.Dataset;
-import org.apache.jena.query.Query;
-import org.apache.jena.query.QueryExecution;
-import org.apache.jena.query.QueryExecutionFactory;
-import org.apache.jena.query.QuerySolution;
+import org.apache.jena.atlas.lib.DateTimeUtils;
+import org.apache.jena.query.*;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.sparql.exec.http.QueryExecutionHTTP;
import org.apache.jena.sparql.exec.http.QueryExecutionHTTPBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.net.http.HttpClient;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+
public class QueryExecutionFactoryFilter {
- static final String LOG_NAME = "QueryLog";
- private Logger logger;
- private static QueryExecutionFactoryFilter singleton = new QueryExecutionFactoryFilter();
-
- // ---- Support for controlling printing queries while running. See function "printQuery".
- private static boolean PRINT = false;
+ static final String LOG_NAME = "QueryLog";
+ private Logger logger;
+ private static QueryExecutionFactoryFilter singleton = new QueryExecutionFactoryFilter();
+
+ // ---- Support for controlling printing queries while running. See function "printQuery".
+ private static boolean PRINT = false;
// ---- Support for controlling printing queries while running.
-
- /**
- * Gets the singleton instance of this class.
- * @return the singleton
- */
- public static QueryExecutionFactoryFilter get() {
- return singleton;
- }
-
- private QueryExecutionFactoryFilter() {
- logger = LoggerFactory.getLogger(LOG_NAME);
- }
-
- public QueryExecution create(Query query, Model model) {
- analyzeRequest(query, model, null);
- return QueryExecutionFactory.create(query, model);
- }
-
- public QueryExecution create(Query query, Model model, QuerySolution initialBinding) {
- analyzeRequest(query, model, initialBinding);
- return QueryExecution.create()
- .query(query)
- .model(model)
- .initialBinding(initialBinding)
- .build();
- }
-
- public QueryExecution create(Query query, Dataset dataset) {
- analyzeRequest(query, dataset, null);
- return QueryExecutionFactory.create(query, dataset);
- }
-
- public QueryExecution create(Query query, Dataset dataset, QuerySolution initialBinding) {
- analyzeRequest(query, dataset, initialBinding);
- return QueryExecutionFactory.create(query, dataset, initialBinding);
- }
-
- public QueryExecution sparqlService(String service, Query query) {
- return sparqlServiceBuilder(service, query).build();
- }
-
+
+ /**
+ * Gets the singleton instance of this class.
+ *
+ * @return the singleton
+ */
+ public static QueryExecutionFactoryFilter get() {
+ return singleton;
+ }
+
+ private QueryExecutionFactoryFilter() {
+ logger = LoggerFactory.getLogger(LOG_NAME);
+ }
+
+ public QueryExecution create(Query query, Model model) {
+ analyzeRequest(query, model, null);
+ return QueryExecutionFactory.create(query, model);
+ }
+
+ public QueryExecution create(Query query, Model model, QuerySolution initialBinding) {
+ analyzeRequest(query, model, initialBinding);
+ return QueryExecution.create()
+ .query(query)
+ .model(model)
+ .initialBinding(initialBinding)
+ .build();
+ }
+
+ public QueryExecution create(Query query, Dataset dataset) {
+ analyzeRequest(query, dataset, null);
+ return QueryExecutionFactory.create(query, dataset);
+ }
+
+ public QueryExecution create(Query query, Dataset dataset, QuerySolution querySolution) {
+ analyzeRequest(query, dataset, querySolution);
+ return QueryExecution.dataset(dataset).query(query).substitution(querySolution).build();
+ }
+
+ public QueryExecution sparqlService(String service, Query query) {
+ return sparqlServiceBuilder(service, query).build();
+ }
+
public QueryExecution sparqlService(String service, Query query, HttpClient httpClient) {
- return sparqlServiceBuilder(service, query, httpClient).build();
+ return sparqlServiceBuilder(service, query, httpClient).build();
+ }
+
+ public QueryExecutionHTTP sparqlService(String service, Query query, HttpClient httpClient, List defaultGraphURIs, List namedGraphURIs) {
+ QueryExecutionHTTPBuilder builder = sparqlServiceBuilder(service, query, httpClient);
+ defaultGraphURIs.forEach(builder::addDefaultGraphURI);
+ namedGraphURIs.forEach(builder::addNamedGraphURI);
+ return builder.build();
}
- public QueryExecutionHTTP sparqlService(String service, Query query, HttpClient httpClient, List defaultGraphURIs, List namedGraphURIs) {
- QueryExecutionHTTPBuilder builder = sparqlServiceBuilder(service, query, httpClient);
- defaultGraphURIs.forEach(uri -> builder.addDefaultGraphURI(uri));
- namedGraphURIs.forEach(uri -> builder.addNamedGraphURI(uri));
- return builder.build();
- }
-
private QueryExecutionHTTPBuilder sparqlServiceBuilder(String service, Query query, HttpClient httpClient) {
- return sparqlServiceBuilder(service, query).httpClient(httpClient);
- }
-
+ return sparqlServiceBuilder(service, query).httpClient(httpClient);
+ }
+
private QueryExecutionHTTPBuilder sparqlServiceBuilder(String service, Query query) {
- return QueryExecution.service(service).query(query);
+ return QueryExecution.service(service).query(query);
}
-
- private void analyzeRequest(Query query, Model model, QuerySolution initialBinding) {
+
+ private void analyzeRequest(Query query, Model model, QuerySolution initialBinding) {
printQuery(query, initialBinding);
- if(logger.isTraceEnabled()) {
- logger.trace("QUERY[" + analyzeQuery(query)
- + "]\nMODEL[" + analyzeModel(model) + "]"
- + serializeBindings(initialBinding));
- }
- }
-
- private void analyzeRequest(Query query, Dataset dataset, QuerySolution initialBinding) {
- printQuery(query, initialBinding);
-
- if(logger.isTraceEnabled()) {
- logger.trace("QUERY[" + analyzeQuery(query)
- + "]\nDATASET[" + analyzeDataset(dataset) + "]"
- + serializeBindings(initialBinding));
- }
- }
-
- private static final DateTimeFormatter timestamp = DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
- // Development support. Dynamically controlled print query.
- private void printQuery(Query query, QuerySolution initialBinding) {
- if ( PRINT ) {
- String time = DateTimeUtils.nowAsString(timestamp);
+ if (logger.isTraceEnabled()) {
+ logger.trace("QUERY[" + analyzeQuery(query)
+ + "]\nMODEL[" + analyzeModel(model) + "]"
+ + serializeBindings(initialBinding));
+ }
+ }
+
+ private void analyzeRequest(Query query, Dataset dataset, QuerySolution initialBinding) {
+ printQuery(query, initialBinding);
+
+ if (logger.isTraceEnabled()) {
+ logger.trace("QUERY[" + analyzeQuery(query)
+ + "]\nDATASET[" + analyzeDataset(dataset) + "]"
+ + serializeBindings(initialBinding));
+ }
+ }
+
+ private static final DateTimeFormatter timestamp = DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
+
+ // Development support. Dynamically controlled print query.
+ private void printQuery(Query query, QuerySolution initialBinding) {
+ if (PRINT) {
+ String time = DateTimeUtils.nowAsString(timestamp);
System.err.print("~~ ");
System.err.print(time);
System.err.println(" ~~");
System.err.println(initialBinding);
System.err.print(query);
}
- }
+ }
/**
* Allow query printing to be switched on/off around specific sections of code that
* are issuing queries.
- * @param value true to enable
+ *
+ * @param value true to enable
*/
public static void enableQueryPrinting(boolean value) {
PRINT = value;
}
-
- private String serializeBindings(QuerySolution bindings) {
- if(bindings == null) return "";
- return "\nINITIAL BINDINGS[" + bindings.toString() + "]";
- }
-
- private String analyzeQuery(Query query) {
- if(query == null) return "null query";
- return query.toString();
- }
-
- private String analyzeModel(Model model) {
- if(model == null) return "null model";
-
- return "this space for rent";
- }
-
- private String analyzeDataset(Dataset dataset) {
- if(dataset == null) return "null dataset";
-
- return "A Dataset";
- }
+
+ private String serializeBindings(QuerySolution bindings) {
+ if (bindings == null) return "";
+ return "\nINITIAL BINDINGS[" + bindings.toString() + "]";
+ }
+
+ private String analyzeQuery(Query query) {
+ if (query == null) return "null query";
+ return query.toString();
+ }
+
+ private String analyzeModel(Model model) {
+ if (model == null) return "null model";
+
+ return "this space for rent";
+ }
+
+ private String analyzeDataset(Dataset dataset) {
+ if (dataset == null) return "null dataset";
+
+ return "A Dataset";
+ }
}
diff --git a/src/main/java/org/topbraid/jenax/util/SystemTriples.java b/src/main/java/org/topbraid/jenax/util/SystemTriples.java
index 6dad3c2e..3b3401c6 100644
--- a/src/main/java/org/topbraid/jenax/util/SystemTriples.java
+++ b/src/main/java/org/topbraid/jenax/util/SystemTriples.java
@@ -17,11 +17,6 @@
package org.topbraid.jenax.util;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.jena.datatypes.xsd.impl.XMLLiteralType;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.rdf.model.StmtIterator;
@@ -31,92 +26,97 @@
import org.apache.jena.vocabulary.RDFS;
import org.apache.jena.vocabulary.XSD;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Provides access to the RDF/RDFS/OWL system triples.
- *
+ *
* TopBraid and this API adds some extra triples (such as declaring
* superclasses for each system class) that make life easier.
- *
+ *
* @author Holger Knublauch
*/
public class SystemTriples {
- private static Model vocabulary;
-
-
- private static void ensureSuperClasses(Resource metaClass, Resource superClass) {
- List toAdd = collectMissingSuperClasses(metaClass, superClass);
- for (Resource c: toAdd) {
- vocabulary.add(c, RDFS.subClassOf, superClass);
- }
- }
-
-
- private static List collectMissingSuperClasses(Resource metaClass,
- Resource superClass) {
- List toAdd = new ArrayList();
- StmtIterator it = vocabulary.listStatements(null, RDF.type, metaClass);
- while (it.hasNext()) {
- Resource c = it.nextStatement().getSubject();
- if (!c.equals(superClass)) {
- if (c.getProperty(RDFS.subClassOf) == null) {
- toAdd.add(c);
- }
- }
- }
- return toAdd;
- }
-
-
- /**
- * Gets the system ontology (a shared copy).
- * @return the system ontology
- */
- public static synchronized Model getVocabularyModel() {
- if (vocabulary == null) {
- vocabulary = JenaUtil.createDefaultModel();
- org.topbraid.jenax.util.JenaUtil.initNamespaces(vocabulary.getGraph());
- vocabulary.setNsPrefix("xsd", XSD.getURI());
- InputStream ttl = SystemTriples.class.getResourceAsStream("/rdf/system-triples.ttl");
- vocabulary.read(ttl, "urn:x:dummy", FileUtils.langTurtle);
- ensureSuperClasses(RDFS.Class, RDFS.Resource);
- ensureSuperClasses(OWL.Class, OWL.Thing);
-
- // Remove owl imports rdfs which only causes trouble
- vocabulary.removeAll(null, OWL.imports, null);
-
- vocabulary.add(OWL.Thing, RDFS.subClassOf, RDFS.Resource);
- vocabulary.add(OWL.inverseOf, RDF.type, OWL.SymmetricProperty);
- vocabulary.add(OWL.equivalentClass, RDF.type, OWL.SymmetricProperty);
- vocabulary.add(OWL.equivalentProperty, RDF.type, OWL.SymmetricProperty);
- vocabulary.add(OWL.equivalentProperty, RDFS.range, RDF.Property);
- vocabulary.add(OWL.differentFrom, RDF.type, OWL.SymmetricProperty);
- vocabulary.add(OWL.sameAs, RDF.type, OWL.SymmetricProperty);
- vocabulary.add(OWL.disjointWith, RDF.type, OWL.SymmetricProperty);
- Resource xml = vocabulary.getResource(XMLLiteralType.theXMLLiteralType.getURI());
- vocabulary.add(xml, RDFS.subClassOf, RDFS.Resource);
- for(String uri : JenaDatatypes.getDatatypeURIs()) {
- Resource r = vocabulary.getResource(uri);
- if (r.getProperty(RDF.type) == null) {
- vocabulary.add(r, RDF.type, RDFS.Datatype);
- vocabulary.add(r, RDFS.subClassOf, RDFS.Literal);
- }
- }
-
- // vocabulary.add(RDF.HTML, RDFS.label, "HTML");
-
- // Triples were formally in OWL 1, but dropped from OWL 2
- vocabulary.add(RDFS.comment, RDF.type, OWL.AnnotationProperty);
- vocabulary.add(RDFS.label, RDF.type, OWL.AnnotationProperty);
- vocabulary.add(RDFS.isDefinedBy, RDF.type, OWL.AnnotationProperty);
- vocabulary.add(RDFS.seeAlso, RDF.type, OWL.AnnotationProperty);
-
- // Add rdfs:labels for XSD types
- for(Resource datatype : vocabulary.listSubjectsWithProperty(RDF.type, RDFS.Datatype).toList()) {
- datatype.addProperty(RDFS.label, datatype.getLocalName());
+ private static Model vocabulary;
+
+
+ private static void ensureSuperClasses(Resource metaClass, Resource superClass) {
+ List toAdd = collectMissingSuperClasses(metaClass, superClass);
+ for (Resource c : toAdd) {
+ vocabulary.add(c, RDFS.subClassOf, superClass);
+ }
+ }
+
+
+ private static List collectMissingSuperClasses(Resource metaClass,
+ Resource superClass) {
+ List toAdd = new ArrayList<>();
+ StmtIterator it = vocabulary.listStatements(null, RDF.type, metaClass);
+ while (it.hasNext()) {
+ Resource c = it.nextStatement().getSubject();
+ if (!c.equals(superClass)) {
+ if (c.getProperty(RDFS.subClassOf) == null) {
+ toAdd.add(c);
+ }
+ }
+ }
+ return toAdd;
+ }
+
+
+ /**
+ * Gets the system ontology (a shared copy).
+ *
+ * @return the system ontology
+ */
+ public static synchronized Model getVocabularyModel() {
+ if (vocabulary == null) {
+ vocabulary = JenaUtil.createDefaultModel();
+ org.topbraid.jenax.util.JenaUtil.initNamespaces(vocabulary.getGraph());
+ vocabulary.setNsPrefix("xsd", XSD.getURI());
+ InputStream ttl = SystemTriples.class.getResourceAsStream("/rdf/system-triples.ttl");
+ vocabulary.read(ttl, "urn:x:dummy", FileUtils.langTurtle);
+ ensureSuperClasses(RDFS.Class, RDFS.Resource);
+ ensureSuperClasses(OWL.Class, OWL.Thing);
+
+ // Remove owl imports rdfs which only causes trouble
+ vocabulary.removeAll(null, OWL.imports, null);
+
+ vocabulary.add(OWL.Thing, RDFS.subClassOf, RDFS.Resource);
+ vocabulary.add(OWL.inverseOf, RDF.type, OWL.SymmetricProperty);
+ vocabulary.add(OWL.equivalentClass, RDF.type, OWL.SymmetricProperty);
+ vocabulary.add(OWL.equivalentProperty, RDF.type, OWL.SymmetricProperty);
+ vocabulary.add(OWL.equivalentProperty, RDFS.range, RDF.Property);
+ vocabulary.add(OWL.differentFrom, RDF.type, OWL.SymmetricProperty);
+ vocabulary.add(OWL.sameAs, RDF.type, OWL.SymmetricProperty);
+ vocabulary.add(OWL.disjointWith, RDF.type, OWL.SymmetricProperty);
+ Resource xml = vocabulary.getResource(RDF.dtXMLLiteral.getURI());
+ vocabulary.add(xml, RDFS.subClassOf, RDFS.Resource);
+ for (String uri : JenaDatatypes.getDatatypeURIs()) {
+ Resource r = vocabulary.getResource(uri);
+ if (r.getProperty(RDF.type) == null) {
+ vocabulary.add(r, RDF.type, RDFS.Datatype);
+ vocabulary.add(r, RDFS.subClassOf, RDFS.Literal);
+ }
+ }
+
+ // vocabulary.add(RDF.HTML, RDFS.label, "HTML");
+
+ // Triples were formally in OWL 1, but dropped from OWL 2
+ vocabulary.add(RDFS.comment, RDF.type, OWL.AnnotationProperty);
+ vocabulary.add(RDFS.label, RDF.type, OWL.AnnotationProperty);
+ vocabulary.add(RDFS.isDefinedBy, RDF.type, OWL.AnnotationProperty);
+ vocabulary.add(RDFS.seeAlso, RDF.type, OWL.AnnotationProperty);
+
+ // Add rdfs:labels for XSD types
+ for (Resource datatype : vocabulary.listSubjectsWithProperty(RDF.type, RDFS.Datatype).toList()) {
+ datatype.addProperty(RDFS.label, datatype.getLocalName());
}
- vocabulary = JenaUtil.asReadOnlyModel(vocabulary);
- }
- return vocabulary;
- }
+ vocabulary = JenaUtil.asReadOnlyModel(vocabulary);
+ }
+ return vocabulary;
+ }
}
diff --git a/src/main/java/org/topbraid/shacl/arq/SHACLARQFunction.java b/src/main/java/org/topbraid/shacl/arq/SHACLARQFunction.java
index a751ff58..e5abc50c 100644
--- a/src/main/java/org/topbraid/shacl/arq/SHACLARQFunction.java
+++ b/src/main/java/org/topbraid/shacl/arq/SHACLARQFunction.java
@@ -17,11 +17,6 @@
package org.topbraid.shacl.arq;
-import java.io.ByteArrayOutputStream;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
import org.apache.jena.atlas.io.IndentedWriter;
import org.apache.jena.graph.Graph;
import org.apache.jena.graph.Node;
@@ -54,186 +49,184 @@
import org.topbraid.shacl.model.SHParameterizable;
import org.topbraid.shacl.vocabulary.DASH;
+import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
/**
* An ARQ function that is based on a SHACL function definition.
- *
+ *
* @author Holger Knublauch
*/
public abstract class SHACLARQFunction implements org.apache.jena.sparql.function.Function, OptionalArgsFunction, DeclarativeFunctionFactory {
-
- private boolean cachable;
-
- protected List paramNames = new ArrayList();
-
- private List optional = new ArrayList();
-
- private SHFunction shFunction;
-
-
- /**
- * Constructs a new SHACLARQFunction based on a given sh:Function.
- * The shaclFunction must be associated with the Model containing
- * the triples of its definition.
- * @param shaclFunction the SHACL function
- */
- protected SHACLARQFunction(SHFunction shaclFunction) {
- this.shFunction = shaclFunction;
- if(shaclFunction != null) {
- this.cachable = shaclFunction.hasProperty(DASH.cachable, JenaDatatypes.TRUE);
- }
- }
-
-
- protected void addParameters(SHParameterizable parameterizable) {
- JenaUtil.setGraphReadOptimization(true);
- try {
- for(SHParameter param : parameterizable.getOrderedParameters()) {
- String varName = param.getVarName();
- if(varName == null) {
- throw new IllegalStateException(param + " of " + parameterizable + " does not have a valid predicate");
- }
- paramNames.add(varName);
- optional.add(param.isOptional());
- }
- }
- finally {
- JenaUtil.setGraphReadOptimization(false);
- }
- }
-
- @Override
+
+ private boolean cachable;
+
+ protected List paramNames = new ArrayList();
+
+ private List optional = new ArrayList();
+
+ private SHFunction shFunction;
+
+
+ /**
+ * Constructs a new SHACLARQFunction based on a given sh:Function.
+ * The shaclFunction must be associated with the Model containing
+ * the triples of its definition.
+ *
+ * @param shaclFunction the SHACL function
+ */
+ protected SHACLARQFunction(SHFunction shaclFunction) {
+ this.shFunction = shaclFunction;
+ if (shaclFunction != null) {
+ this.cachable = shaclFunction.hasProperty(DASH.cachable, JenaDatatypes.TRUE);
+ }
+ }
+
+
+ protected void addParameters(SHParameterizable parameterizable) {
+ JenaUtil.setGraphReadOptimization(true);
+ try {
+ for (SHParameter param : parameterizable.getOrderedParameters()) {
+ String varName = param.getVarName();
+ if (varName == null) {
+ throw new IllegalStateException(param + " of " + parameterizable + " does not have a valid predicate");
+ }
+ paramNames.add(varName);
+ optional.add(param.isOptional());
+ }
+ } finally {
+ JenaUtil.setGraphReadOptimization(false);
+ }
+ }
+
+ @Override
public org.apache.jena.sparql.function.Function create(String uri) {
- return this;
- }
+ return this;
+ }
+
-
- @Override
+ @Override
public NodeValue exec(Binding binding, ExprList args, String uri, FunctionEnv env) {
-
- Graph activeGraph = env.getActiveGraph();
- Model model = activeGraph != null ?
- ModelFactory.createModelForGraph(activeGraph) :
- ModelFactory.createDefaultModel();
-
- QuerySolutionMap bindings = new QuerySolutionMap();
-
- Node[] paramsForCache;
- if(cachable) {
- paramsForCache = new Node[args.size()];
- }
- else {
- paramsForCache = null;
- }
- for(int i = 0; i < args.size(); i++) {
- Expr expr = args.get(i);
- if(expr != null && (!expr.isVariable() || binding.contains(expr.asVar()))) {
- NodeValue x = expr.eval(binding, env);
- if(x != null) {
- String paramName;
- if(i < paramNames.size()) {
- paramName = paramNames.get(i);
- }
- else {
- paramName = "arg" + (i + 1);
- }
- bindings.add(paramName, model.asRDFNode(x.asNode()));
- if(cachable) {
- paramsForCache[i] = x.asNode();
- }
- }
- else if(!optional.get(i)) {
- throw new ExprEvalException("Missing SHACL function argument");
- }
- }
- }
-
- Dataset dataset = DatasetFactory.wrap(env.getDataset());
-
- if(ExecStatisticsManager.get().isRecording() && ExecStatisticsManager.get().isRecordingDeclarativeFunctions()) {
- StringBuffer sb = new StringBuffer();
- sb.append("SHACL Function ");
- sb.append(SSE.str(NodeFactory.createURI(uri), model));
- sb.append("(");
- for(int i = 0; i < args.size(); i++) {
- if(i > 0) {
- sb.append(", ");
- }
- Expr expr = args.get(i);
- expr = Substitute.substitute(expr, binding);
- if(expr == null) {
- sb.append("?unbound");
- }
- else {
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- IndentedWriter iOut = new IndentedWriter(bos);
- ExprUtils.fmtSPARQL(iOut, expr, new SerializationContext(model));
- iOut.flush();
- sb.append(bos.toString());
- }
- }
- sb.append(")");
- long startTime = System.currentTimeMillis();
- NodeValue result;
- try {
- if(cachable) {
- result = SHACLFunctionsCache.get().execute(this, dataset, model, bindings, paramsForCache);
- }
- else {
- result = executeBody(dataset, model, bindings);
- }
- sb.append(" = ");
- sb.append(FmtUtils.stringForNode(result.asNode(), model));
- }
- catch(ExprEvalException ex) {
- sb.append(" : ");
- sb.append(ex.getLocalizedMessage());
- throw ex;
- }
- finally {
- long endTime = System.currentTimeMillis();
- ExecStatistics stats = new ExecStatistics(sb.toString(), getQueryString(), endTime - startTime, startTime, NodeFactory.createURI(uri));
- ExecStatisticsManager.get().addSilently(Collections.singleton(stats));
- }
- return result;
- }
- else {
- if(cachable) {
- return SHACLFunctionsCache.get().execute(this, dataset, model, bindings, paramsForCache);
- }
- else {
- return executeBody(dataset, model, bindings);
- }
- }
- }
-
-
- public abstract NodeValue executeBody(Dataset dataset, Model model, QuerySolution bindings);
-
-
- protected abstract String getQueryString();
-
-
- /**
- * Gets the underlying sh:Function Model object for this ARQ function.
- * @return the sh:Function (may be null)
- */
- public SHFunction getSHACLFunction() {
- return shFunction;
- }
-
-
- /**
- * Gets the names of the declared parameters, in order from left to right.
- * @return the parameter names
- */
- public String[] getParamNames() {
- return paramNames.toArray(new String[0]);
- }
-
-
- @Override
- public boolean isOptionalArg(int index) {
- return optional.get(index);
- }
+
+ Graph activeGraph = env.getActiveGraph();
+ Model model = activeGraph != null ?
+ ModelFactory.createModelForGraph(activeGraph) :
+ ModelFactory.createDefaultModel();
+
+ QuerySolutionMap bindings = new QuerySolutionMap();
+
+ Node[] paramsForCache;
+ if (cachable) {
+ paramsForCache = new Node[args.size()];
+ } else {
+ paramsForCache = null;
+ }
+ for (int i = 0; i < args.size(); i++) {
+ Expr expr = args.get(i);
+ if (expr != null && (!expr.isVariable() || binding.contains(expr.asVar()))) {
+ NodeValue x = expr.eval(binding, env);
+ if (x != null) {
+ String paramName;
+ if (i < paramNames.size()) {
+ paramName = paramNames.get(i);
+ } else {
+ paramName = "arg" + (i + 1);
+ }
+ bindings.add(paramName, model.asRDFNode(x.asNode()));
+ if (cachable) {
+ paramsForCache[i] = x.asNode();
+ }
+ } else if (!optional.get(i)) {
+ throw new ExprEvalException("Missing SHACL function argument");
+ }
+ }
+ }
+
+ Dataset dataset = DatasetFactory.wrap(env.getDataset());
+
+ if (ExecStatisticsManager.get().isRecording() && ExecStatisticsManager.get().isRecordingDeclarativeFunctions()) {
+ StringBuffer sb = new StringBuffer();
+ sb.append("SHACL Function ");
+ sb.append(SSE.str(NodeFactory.createURI(uri), model));
+ sb.append("(");
+ for (int i = 0; i < args.size(); i++) {
+ if (i > 0) {
+ sb.append(", ");
+ }
+ Expr expr = args.get(i);
+ expr = Substitute.substitute(expr, binding);
+ if (expr == null) {
+ sb.append("?unbound");
+ } else {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ IndentedWriter iOut = new IndentedWriter(bos);
+ ExprUtils.fmtSPARQL(iOut, expr, new SerializationContext(model));
+ iOut.flush();
+ sb.append(bos);
+ }
+ }
+ sb.append(")");
+ long startTime = System.currentTimeMillis();
+ NodeValue result;
+ try {
+ if (cachable) {
+ result = SHACLFunctionsCache.get().execute(this, dataset, model, bindings, paramsForCache);
+ } else {
+ result = executeBody(dataset, model, bindings);
+ }
+ sb.append(" = ");
+ sb.append(FmtUtils.stringForNode(result.asNode(), model));
+ } catch (ExprEvalException ex) {
+ sb.append(" : ");
+ sb.append(ex.getLocalizedMessage());
+ throw ex;
+ } finally {
+ long endTime = System.currentTimeMillis();
+ ExecStatistics stats = new ExecStatistics(sb.toString(), getQueryString(), endTime - startTime, startTime, NodeFactory.createURI(uri));
+ ExecStatisticsManager.get().addSilently(Collections.singleton(stats));
+ }
+ return result;
+ } else {
+ if (cachable) {
+ return SHACLFunctionsCache.get().execute(this, dataset, model, bindings, paramsForCache);
+ } else {
+ return executeBody(dataset, model, bindings);
+ }
+ }
+ }
+
+
+ public abstract NodeValue executeBody(Dataset dataset, Model model, QuerySolution bindings);
+
+
+ protected abstract String getQueryString();
+
+
+ /**
+ * Gets the underlying sh:Function Model object for this ARQ function.
+ *
+ * @return the sh:Function (may be null)
+ */
+ public SHFunction getSHACLFunction() {
+ return shFunction;
+ }
+
+
+ /**
+ * Gets the names of the declared parameters, in order from left to right.
+ *
+ * @return the parameter names
+ */
+ public String[] getParamNames() {
+ return paramNames.toArray(new String[0]);
+ }
+
+
+ @Override
+ public boolean isOptionalArg(int index) {
+ return optional.get(index);
+ }
}
diff --git a/src/main/java/org/topbraid/shacl/arq/SHACLFunctionsCache.java b/src/main/java/org/topbraid/shacl/arq/SHACLFunctionsCache.java
index a15b8cd0..adaca999 100644
--- a/src/main/java/org/topbraid/shacl/arq/SHACLFunctionsCache.java
+++ b/src/main/java/org/topbraid/shacl/arq/SHACLFunctionsCache.java
@@ -16,11 +16,6 @@
*/
package org.topbraid.shacl.arq;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.Map.Entry;
-
import org.apache.jena.graph.Node;
import org.apache.jena.query.Dataset;
import org.apache.jena.query.QuerySolution;
@@ -28,145 +23,145 @@
import org.apache.jena.sparql.expr.ExprEvalException;
import org.apache.jena.sparql.expr.NodeValue;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
/**
* A cache that remembers previous calls to SHACL functions marked with sh:cachable.
- *
+ *
* @author Holger Knublauch
*/
public class SHACLFunctionsCache {
- private static SHACLFunctionsCache singleton = new SHACLFunctionsCache();
-
- public static SHACLFunctionsCache get() {
- return singleton;
- }
-
- public static void set(SHACLFunctionsCache value) {
- SHACLFunctionsCache.singleton = value;
- }
-
-
- private static final int capacity = 10000;
-
- @SuppressWarnings("serial")
- private static class MyCache extends LinkedHashMap {
-
- MyCache() {
- super(capacity + 1, 1.1f, true);
- }
-
- @Override
- protected boolean removeEldestEntry(Entry eldest) {
- if(size() > capacity) {
- return true;
- }
- else {
- return false;
- }
- }
- };
-
- private Map cache = Collections.synchronizedMap(new MyCache());
-
-
- public void clear() {
- cache.clear();
- }
-
-
- public NodeValue execute(SHACLARQFunction function, Dataset dataset, Model defaultModel, QuerySolution bindings, Node[] args) {
- Key key = new Key(function.getSHACLFunction().getURI(), args);
- Result result = cache.get(key);
- if(result == null) {
- result = new Result();
- try {
- result.nodeValue = function.executeBody(dataset, defaultModel, bindings);
- }
- catch(ExprEvalException ex) {
- result.ex = ex;
- }
- cache.put(key, result);
- }
- if(result.ex != null) {
- throw new ExprEvalException(result.ex.getMessage());
- }
- else {
- return result.nodeValue;
- }
- }
-
-
- private static class Key {
-
- private int hashCode;
-
- private Node[] args;
-
- private String functionURI;
-
-
- Key(String functionURI, Node[] args) {
- this.args = args;
- this.functionURI = functionURI;
- hashCode = functionURI.hashCode();
- for(Node arg : args) {
- if(arg != null) {
- hashCode += arg.hashCode();
- }
- }
- }
-
-
- private boolean argEquals(Node arg1, Node arg2) {
- if(arg1 == null) {
- return arg2 == null;
- }
- else if(arg2 == null) {
- return false;
- }
- else {
- return arg1.equals(arg2);
- }
- }
-
-
- @Override
- public boolean equals(Object obj) {
-
- if(!(obj instanceof Key)) {
- return false;
- }
-
- Key other = (Key) obj;
- if(!functionURI.equals(other.functionURI)) {
- return false;
- }
-
- if(args.length != other.args.length) {
- return false;
- }
-
- for(int i = 0; i < args.length; i++) {
- if(!argEquals(args[i], other.args[i])) {
- return false;
- }
- }
-
- return true;
- }
-
-
- @Override
- public int hashCode() {
- return hashCode;
- }
- }
-
-
- private static class Result {
-
- ExprEvalException ex;
-
- NodeValue nodeValue;
- }
+ private static SHACLFunctionsCache singleton = new SHACLFunctionsCache();
+
+ public static SHACLFunctionsCache get() {
+ return singleton;
+ }
+
+ public static void set(SHACLFunctionsCache value) {
+ SHACLFunctionsCache.singleton = value;
+ }
+
+
+ private static final int capacity = 10000;
+
+ @SuppressWarnings("serial")
+ private static class MyCache extends LinkedHashMap {
+
+ MyCache() {
+ super(capacity + 1, 1.1f, true);
+ }
+
+ @Override
+ protected boolean removeEldestEntry(Entry eldest) {
+ if (size() > capacity) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ private Map cache = Collections.synchronizedMap(new MyCache());
+
+
+ public void clear() {
+ cache.clear();
+ }
+
+
+ public NodeValue execute(SHACLARQFunction function, Dataset dataset, Model defaultModel, QuerySolution bindings, Node[] args) {
+ Key key = new Key(function.getSHACLFunction().getURI(), args);
+ Result result = cache.get(key);
+ if (result == null) {
+ result = new Result();
+ try {
+ result.nodeValue = function.executeBody(dataset, defaultModel, bindings);
+ } catch (ExprEvalException ex) {
+ result.ex = ex;
+ }
+ cache.put(key, result);
+ }
+ if (result.ex != null) {
+ throw new ExprEvalException(result.ex.getMessage());
+ } else {
+ return result.nodeValue;
+ }
+ }
+
+
+ private static class Key {
+
+ private int hashCode;
+
+ private Node[] args;
+
+ private String functionURI;
+
+
+ Key(String functionURI, Node[] args) {
+ this.args = args;
+ this.functionURI = functionURI;
+ hashCode = functionURI.hashCode();
+ for (Node arg : args) {
+ if (arg != null) {
+ hashCode += arg.hashCode();
+ }
+ }
+ }
+
+
+ private boolean argEquals(Node arg1, Node arg2) {
+ if (arg1 == null) {
+ return arg2 == null;
+ } else if (arg2 == null) {
+ return false;
+ } else {
+ return arg1.equals(arg2);
+ }
+ }
+
+
+ @Override
+ public boolean equals(Object obj) {
+
+ if (!(obj instanceof Key)) {
+ return false;
+ }
+
+ Key other = (Key) obj;
+ if (!functionURI.equals(other.functionURI)) {
+ return false;
+ }
+
+ if (args.length != other.args.length) {
+ return false;
+ }
+
+ for (int i = 0; i < args.length; i++) {
+ if (!argEquals(args[i], other.args[i])) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+ }
+
+
+ private static class Result {
+
+ ExprEvalException ex;
+
+ NodeValue nodeValue;
+ }
}
diff --git a/src/main/java/org/topbraid/shacl/engine/ShapesGraph.java b/src/main/java/org/topbraid/shacl/engine/ShapesGraph.java
index 31c509d9..4e23b31b 100644
--- a/src/main/java/org/topbraid/shacl/engine/ShapesGraph.java
+++ b/src/main/java/org/topbraid/shacl/engine/ShapesGraph.java
@@ -16,21 +16,8 @@
*/
package org.topbraid.shacl.engine;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.function.Predicate;
-
import org.apache.jena.graph.Node;
-import org.apache.jena.rdf.model.Model;
-import org.apache.jena.rdf.model.Property;
-import org.apache.jena.rdf.model.RDFNode;
-import org.apache.jena.rdf.model.Resource;
-import org.apache.jena.rdf.model.StmtIterator;
+import org.apache.jena.rdf.model.*;
import org.apache.jena.shared.PrefixMapping;
import org.apache.jena.sparql.graph.PrefixMappingMem;
import org.apache.jena.sparql.util.FmtUtils;
@@ -49,284 +36,291 @@
import org.topbraid.shacl.vocabulary.DASH;
import org.topbraid.shacl.vocabulary.SH;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Predicate;
+
/**
* Represents a shapes graph as input to an engine (e.g. validation or inferencing).
* This is basically a collection of Shapes with some data structures that avoid repetitive computation.
- *
+ *
* @author Holger Knublauch
*/
public class ShapesGraph {
-
- private final static Map EMPTY = new HashMap<>();
-
- // May be defined to skip certain constraints (which are computed on demand)
- private Predicate constraintFilter;
-
- // Map of sh:defaultValue expressions. Outer keys are sh:path predicates, inner keys are (node) shapes.
- private Map> defaultValueMap = new ConcurrentHashMap<>();
-
- // Can be used to bypass TDB's slow prefix mapping
- private PrefixMapping fastPrefixMapping;
-
- // Cache of shapeFilter results
- private Map ignoredShapes = new ConcurrentHashMap<>();
-
- // Mapping of properties (e.g., sh:datatype) to their constraint components (e.g., sh:DatatypeConstraintComponent)
- private Map parametersMap = new ConcurrentHashMap<>();
-
- // The root shapes where whole-graph validation and inferencing would start
- private List rootShapes;
-
- // Can be used to skip certain shapes
- private Predicate shapeFilter;
-
- // Map of Jena Nodes to their Shape instances, computed on demand
- private Map shapesMap = new ConcurrentHashMap<>();
-
- // The Jena Model of the shape definitions
- private Model shapesModel;
-
- // Map of sh:values expressions. Outer keys are sh:path predicates, inner keys are (node) shapes.
- private Map> valuesMap = new ConcurrentHashMap<>();
-
-
- /**
- * Constructs a new ShapesGraph.
- * This should not be called directly, only from ShapesGraphFactory.
- * @param shapesModel the Model containing the shape definitions
- */
- public ShapesGraph(Model shapesModel) {
- this.shapesModel = shapesModel;
- }
-
-
- public ShapesGraph clone() {
- ShapesGraph clone = new ShapesGraph(shapesModel);
- clone.constraintFilter = this.constraintFilter;
- clone.shapeFilter = this.shapeFilter;
- return clone;
- }
-
-
- public Constraint createConstraint(Shape shape, SHConstraintComponent component, List params, RDFNode parameterValue) {
- return new Constraint(shape, component, params, parameterValue);
- }
-
-
- public SHConstraintComponent getComponentWithParameter(Property parameter) {
- return parametersMap.computeIfAbsent(parameter, p -> {
- StmtIterator it = shapesModel.listStatements(null, SH.path, parameter);
- while(it.hasNext()) {
- Resource param = it.next().getSubject();
- if(!param.hasProperty(SH.optional, JenaDatatypes.TRUE)) {
- StmtIterator i2 = shapesModel.listStatements(null, SH.parameter, param);
- while(i2.hasNext()) {
- Resource r = i2.next().getSubject();
- if(JenaUtil.hasIndirectType(r, SH.ConstraintComponent)) {
- i2.close();
- it.close();
- SHConstraintComponent cc = SHFactory.asConstraintComponent(r);
- return cc;
- }
- }
- }
- }
- return null;
- });
- }
-
-
- // Added for cases where repeated access to the prefixes causes many (TDB) loads, produces a faster in-memory PrefixMapping
- public synchronized PrefixMapping getFastPrefixMapping() {
- if(fastPrefixMapping == null) {
- fastPrefixMapping = new PrefixMappingMem();
- Map pm = shapesModel.getNsPrefixMap();
- for(String prefix : pm.keySet()) {
- fastPrefixMapping.setNsPrefix(prefix, pm.get(prefix));
- }
- }
- return fastPrefixMapping;
- }
-
-
- public String getPathString(Resource path) {
- if(path.isURIResource()) {
- return FmtUtils.stringForNode(path.asNode(), getFastPrefixMapping());
- }
- else {
- return SHACLPaths.getPathString(path);
- }
- }
-
-
- /**
- * Gets all non-deactivated shapes that declare a target and pass the provided filter.
- * @return the root shapes
- */
- public synchronized List getRootShapes() {
- if(rootShapes == null) {
-
- // Collect all shapes, as identified by target and/or type
- Set candidates = new HashSet<>();
- candidates.addAll(shapesModel.listSubjectsWithProperty(SH.target).toList());
- candidates.addAll(shapesModel.listSubjectsWithProperty(SH.targetClass).toList());
- candidates.addAll(shapesModel.listSubjectsWithProperty(SH.targetNode).toList());
- candidates.addAll(shapesModel.listSubjectsWithProperty(SH.targetObjectsOf).toList());
- candidates.addAll(shapesModel.listSubjectsWithProperty(SH.targetSubjectsOf).toList());
- for(Resource shape : JenaUtil.getAllInstances(shapesModel.getResource(SH.NodeShape.getURI()))) {
- if(JenaUtil.hasIndirectType(shape, RDFS.Class)) {
- candidates.add(shape);
- }
- }
- for(Resource shape : JenaUtil.getAllInstances(shapesModel.getResource(SH.PropertyShape.getURI()))) {
- if(JenaUtil.hasIndirectType(shape, RDFS.Class)) {
- candidates.add(shape);
- }
- }
-
- // Turn the shape Resource objects into Shape instances
- this.rootShapes = new LinkedList();
- for(Resource candidate : candidates) {
- SHShape shape = SHFactory.asShape(candidate);
- if(!shape.isDeactivated() && !isIgnored(shape.asNode())) {
- this.rootShapes.add(getShape(shape.asNode()));
- }
- }
- }
- return rootShapes;
- }
-
-
- public Shape getShape(Node node) {
- return shapesMap.computeIfAbsent(node, n -> new Shape(this, SHFactory.asShape(shapesModel.asRDFNode(node))));
- }
-
-
- /**
- * Gets a Map from (node) shapes to NodeExpressions derived from sh:defaultValue statements.
- * @param predicate the predicate to infer
- * @return a Map which is empty if the predicate is not mentioned in any inferences
- */
- public Map getDefaultValueNodeExpressionsMap(Resource predicate) {
- return getExpressionsMap(defaultValueMap, predicate, SH.defaultValue);
- }
-
-
- /**
- * Gets a Map from (node) shapes to NodeExpressions derived from sh:values statements.
- * Can be used to efficiently figure out how to infer the values of a given instance, based on the rdf:types
- * of the instance.
- * @param predicate the predicate to infer
- * @return a Map which is empty if the predicate is not mentioned in any inferences
- */
- public Map getValuesNodeExpressionsMap(Resource predicate) {
- return getExpressionsMap(valuesMap, predicate, SH.values);
- }
-
-
- private Map getExpressionsMap(Map> valuesMap, Resource predicate, Property systemPredicate) {
- return valuesMap.computeIfAbsent(predicate.asNode(), p -> {
-
- Map> map = new HashMap<>();
- StmtIterator it = shapesModel.listStatements(null, SH.path, predicate);
- while(it.hasNext()) {
- Resource ps = it.next().getSubject();
- if(ps.hasProperty(systemPredicate) && !ps.hasProperty(SH.deactivated, JenaDatatypes.TRUE)) {
- StmtIterator nit = shapesModel.listStatements(null, SH.property, ps);
- while(nit.hasNext()) {
- Resource nodeShape = nit.next().getSubject();
- if(!nodeShape.hasProperty(SH.deactivated, JenaDatatypes.TRUE)) {
- Node shapeNode = nodeShape.asNode();
- addExpressions(map, ps, shapeNode, systemPredicate);
- for(Resource targetClass : JenaUtil.getResourceProperties(nodeShape, SH.targetClass)) {
- addExpressions(map, ps, targetClass.asNode(), systemPredicate);
- }
- for(Resource targetClass : JenaUtil.getResourceProperties(nodeShape, DASH.applicableToClass)) {
- addExpressions(map, ps, targetClass.asNode(), systemPredicate);
- }
- }
- }
- }
- }
-
- if(map.isEmpty()) {
- // Return a non-null but empty value to avoid re-computation (null not supported by ConcurrentHashMap)
- return EMPTY;
- }
- else {
- Map result = new HashMap<>();
- for(Node key : map.keySet()) {
- List list = map.get(key);
- if(list.size() > 1) {
- RDFNode exprNode = shapesModel.asRDFNode(key);
- result.put(key, new DistinctExpression(exprNode, new UnionExpression(exprNode, list)));
- }
- else {
- result.put(key, list.get(0));
- }
- }
- return result;
- }
- });
- }
-
-
- private void addExpressions(Map> map, Resource ps, Node shapeNode, Property systemPredicate) {
- map.computeIfAbsent(shapeNode, n -> {
- List exprs = new LinkedList<>();
- StmtIterator vit = ps.listProperties(systemPredicate);
- while(vit.hasNext()) {
- RDFNode expr = vit.next().getObject();
- NodeExpression nodeExpression = NodeExpressionFactory.get().create(expr);
- exprs.add(nodeExpression);
- }
- return exprs;
- });
- }
-
-
- public Model getShapesModel() {
- return shapesModel;
- }
-
-
- public boolean isIgnored(Node shapeNode) {
- if(shapeFilter == null) {
- return false;
- }
- else {
- return ignoredShapes.computeIfAbsent(shapeNode, node -> {
- SHShape shape = SHFactory.asShape(shapesModel.asRDFNode(shapeNode));
- return !shapeFilter.test(shape);
- });
- }
- }
-
-
- public boolean isIgnoredConstraint(Constraint constraint) {
- return constraintFilter != null && !constraintFilter.test(constraint);
- }
-
-
- /**
- * Sets a filter Predicate that can be used to ignore certain constraints.
- * See for example CoreConstraintFilter.
- * Such filters must return true if the Constraint should be used, false to ignore.
- * This method should be called immediately after the constructor only.
- * @param value the new constraint filter
- */
- public void setConstraintFilter(Predicate value) {
- this.constraintFilter = value;
- }
-
-
- /**
- * Sets a filter Predicate that can be used to ignore certain shapes.
- * Such filters must return true if the shape should be used, false to ignore.
- * This method should be called immediately after the constructor only.
- * @param value the new shape filter
- */
- public void setShapeFilter(Predicate value) {
- this.shapeFilter = value;
- }
+
+ private final static Map EMPTY = new HashMap<>();
+
+ // May be defined to skip certain constraints (which are computed on demand)
+ private Predicate constraintFilter;
+
+ // Map of sh:defaultValue expressions. Outer keys are sh:path predicates, inner keys are (node) shapes.
+ private Map> defaultValueMap = new ConcurrentHashMap<>();
+
+ // Can be used to bypass TDB's slow prefix mapping
+ private PrefixMapping fastPrefixMapping;
+
+ // Cache of shapeFilter results
+ private Map ignoredShapes = new ConcurrentHashMap<>();
+
+ // Mapping of properties (e.g., sh:datatype) to their constraint components (e.g., sh:DatatypeConstraintComponent)
+ private Map parametersMap = new ConcurrentHashMap<>();
+
+ // The root shapes where whole-graph validation and inferencing would start
+ private List rootShapes;
+
+ // Can be used to skip certain shapes
+ private Predicate shapeFilter;
+
+ // Map of Jena Nodes to their Shape instances, computed on demand
+ private Map shapesMap = new ConcurrentHashMap<>();
+
+ // The Jena Model of the shape definitions
+ private Model shapesModel;
+
+ // Map of sh:values expressions. Outer keys are sh:path predicates, inner keys are (node) shapes.
+ private Map> valuesMap = new ConcurrentHashMap<>();
+
+
+ /**
+ * Constructs a new ShapesGraph.
+ * This should not be called directly, only from ShapesGraphFactory.
+ *
+ * @param shapesModel the Model containing the shape definitions
+ */
+ public ShapesGraph(Model shapesModel) {
+ this.shapesModel = shapesModel;
+ }
+
+
+ @Override
+ public ShapesGraph clone() {
+ ShapesGraph clone = new ShapesGraph(shapesModel);
+ clone.constraintFilter = this.constraintFilter;
+ clone.shapeFilter = this.shapeFilter;
+ return clone;
+ }
+
+
+ public Constraint createConstraint(Shape shape, SHConstraintComponent component, List params, RDFNode parameterValue) {
+ return new Constraint(shape, component, params, parameterValue);
+ }
+
+
+ public SHConstraintComponent getComponentWithParameter(Property parameter) {
+ return parametersMap.computeIfAbsent(parameter, p -> {
+ StmtIterator it = shapesModel.listStatements(null, SH.path, parameter);
+ while (it.hasNext()) {
+ Resource param = it.next().getSubject();
+ if (!param.hasProperty(SH.optional, JenaDatatypes.TRUE)) {
+ StmtIterator i2 = shapesModel.listStatements(null, SH.parameter, param);
+ while (i2.hasNext()) {
+ Resource r = i2.next().getSubject();
+ if (JenaUtil.hasIndirectType(r, SH.ConstraintComponent)) {
+ i2.close();
+ it.close();
+ SHConstraintComponent cc = SHFactory.asConstraintComponent(r);
+ return cc;
+ }
+ }
+ }
+ }
+ return null;
+ });
+ }
+
+
+ // Added for cases where repeated access to the prefixes causes many (TDB) loads, produces a faster in-memory PrefixMapping
+ public synchronized PrefixMapping getFastPrefixMapping() {
+ if (fastPrefixMapping == null) {
+ fastPrefixMapping = new PrefixMappingMem();
+ Map pm = shapesModel.getNsPrefixMap();
+ for (String prefix : pm.keySet()) {
+ fastPrefixMapping.setNsPrefix(prefix, pm.get(prefix));
+ }
+ }
+ return fastPrefixMapping;
+ }
+
+
+ public String getPathString(Resource path) {
+ if (path.isURIResource()) {
+ return FmtUtils.stringForNode(path.asNode(), getFastPrefixMapping());
+ } else {
+ return SHACLPaths.getPathString(path);
+ }
+ }
+
+
+ /**
+ * Gets all non-deactivated shapes that declare a target and pass the provided filter.
+ *
+ * @return the root shapes
+ */
+ public synchronized List getRootShapes() {
+ if (rootShapes == null) {
+
+ // Collect all shapes, as identified by target and/or type
+ Set candidates = new HashSet<>();
+ candidates.addAll(shapesModel.listSubjectsWithProperty(SH.target).toList());
+ candidates.addAll(shapesModel.listSubjectsWithProperty(SH.targetClass).toList());
+ candidates.addAll(shapesModel.listSubjectsWithProperty(SH.targetNode).toList());
+ candidates.addAll(shapesModel.listSubjectsWithProperty(SH.targetObjectsOf).toList());
+ candidates.addAll(shapesModel.listSubjectsWithProperty(SH.targetSubjectsOf).toList());
+ for (Resource shape : JenaUtil.getAllInstances(shapesModel.getResource(SH.NodeShape.getURI()))) {
+ if (JenaUtil.hasIndirectType(shape, RDFS.Class)) {
+ candidates.add(shape);
+ }
+ }
+ for (Resource shape : JenaUtil.getAllInstances(shapesModel.getResource(SH.PropertyShape.getURI()))) {
+ if (JenaUtil.hasIndirectType(shape, RDFS.Class)) {
+ candidates.add(shape);
+ }
+ }
+
+ // Turn the shape Resource objects into Shape instances
+ this.rootShapes = new LinkedList();
+ for (Resource candidate : candidates) {
+ SHShape shape = SHFactory.asShape(candidate);
+ if (!shape.isDeactivated() && !isIgnored(shape.asNode())) {
+ this.rootShapes.add(getShape(shape.asNode()));
+ }
+ }
+ }
+ return rootShapes;
+ }
+
+
+ public Shape getShape(Node node) {
+ return shapesMap.computeIfAbsent(node, n -> new Shape(this, SHFactory.asShape(shapesModel.asRDFNode(node))));
+ }
+
+
+ /**
+ * Gets a Map from (node) shapes to NodeExpressions derived from sh:defaultValue statements.
+ *
+ * @param predicate the predicate to infer
+ * @return a Map which is empty if the predicate is not mentioned in any inferences
+ */
+ public Map getDefaultValueNodeExpressionsMap(Resource predicate) {
+ return getExpressionsMap(defaultValueMap, predicate, SH.defaultValue);
+ }
+
+
+ /**
+ * Gets a Map from (node) shapes to NodeExpressions derived from sh:values statements.
+ * Can be used to efficiently figure out how to infer the values of a given instance, based on the rdf:types
+ * of the instance.
+ *
+ * @param predicate the predicate to infer
+ * @return a Map which is empty if the predicate is not mentioned in any inferences
+ */
+ public Map getValuesNodeExpressionsMap(Resource predicate) {
+ return getExpressionsMap(valuesMap, predicate, SH.values);
+ }
+
+
+ private Map getExpressionsMap(Map> valuesMap, Resource predicate, Property systemPredicate) {
+ return valuesMap.computeIfAbsent(predicate.asNode(), p -> {
+
+ Map> map = new HashMap<>();
+ StmtIterator it = shapesModel.listStatements(null, SH.path, predicate);
+ while (it.hasNext()) {
+ Resource ps = it.next().getSubject();
+ if (ps.hasProperty(systemPredicate) && !ps.hasProperty(SH.deactivated, JenaDatatypes.TRUE)) {
+ StmtIterator nit = shapesModel.listStatements(null, SH.property, ps);
+ while (nit.hasNext()) {
+ Resource nodeShape = nit.next().getSubject();
+ if (!nodeShape.hasProperty(SH.deactivated, JenaDatatypes.TRUE)) {
+ Node shapeNode = nodeShape.asNode();
+ addExpressions(map, ps, shapeNode, systemPredicate);
+ for (Resource targetClass : JenaUtil.getResourceProperties(nodeShape, SH.targetClass)) {
+ addExpressions(map, ps, targetClass.asNode(), systemPredicate);
+ }
+ for (Resource targetClass : JenaUtil.getResourceProperties(nodeShape, DASH.applicableToClass)) {
+ addExpressions(map, ps, targetClass.asNode(), systemPredicate);
+ }
+ }
+ }
+ }
+ }
+
+ if (map.isEmpty()) {
+ // Return a non-null but empty value to avoid re-computation (null not supported by ConcurrentHashMap)
+ return EMPTY;
+ } else {
+ Map result = new HashMap<>();
+ for (Node key : map.keySet()) {
+ List list = map.get(key);
+ if (list.size() > 1) {
+ RDFNode exprNode = shapesModel.asRDFNode(key);
+ result.put(key, new DistinctExpression(exprNode, new UnionExpression(exprNode, list)));
+ } else {
+ result.put(key, list.get(0));
+ }
+ }
+ return result;
+ }
+ });
+ }
+
+
+ private void addExpressions(Map> map, Resource ps, Node shapeNode, Property systemPredicate) {
+ map.computeIfAbsent(shapeNode, n -> {
+ List exprs = new LinkedList<>();
+ StmtIterator vit = ps.listProperties(systemPredicate);
+ while (vit.hasNext()) {
+ RDFNode expr = vit.next().getObject();
+ NodeExpression nodeExpression = NodeExpressionFactory.get().create(expr);
+ exprs.add(nodeExpression);
+ }
+ return exprs;
+ });
+ }
+
+
+ public Model getShapesModel() {
+ return shapesModel;
+ }
+
+
+ public boolean isIgnored(Node shapeNode) {
+ if (shapeFilter == null) {
+ return false;
+ } else {
+ return ignoredShapes.computeIfAbsent(shapeNode, node -> {
+ SHShape shape = SHFactory.asShape(shapesModel.asRDFNode(shapeNode));
+ return !shapeFilter.test(shape);
+ });
+ }
+ }
+
+
+ public boolean isIgnoredConstraint(Constraint constraint) {
+ return constraintFilter != null && !constraintFilter.test(constraint);
+ }
+
+
+ /**
+ * Sets a filter Predicate that can be used to ignore certain constraints.
+ * See for example CoreConstraintFilter.
+ * Such filters must return true if the Constraint should be used, false to ignore.
+ * This method should be called immediately after the constructor only.
+ *
+ * @param value the new constraint filter
+ */
+ public void setConstraintFilter(Predicate value) {
+ this.constraintFilter = value;
+ }
+
+
+ /**
+ * Sets a filter Predicate that can be used to ignore certain shapes.
+ * Such filters must return true if the shape should be used, false to ignore.
+ * This method should be called immediately after the constructor only.
+ *
+ * @param value the new shape filter
+ */
+ public void setShapeFilter(Predicate value) {
+ this.shapeFilter = value;
+ }
}
diff --git a/src/main/java/org/topbraid/shacl/engine/filters/ExcludeMetaShapesFilter.java b/src/main/java/org/topbraid/shacl/engine/filters/ExcludeMetaShapesFilter.java
index 623dfca6..c49b030e 100644
--- a/src/main/java/org/topbraid/shacl/engine/filters/ExcludeMetaShapesFilter.java
+++ b/src/main/java/org/topbraid/shacl/engine/filters/ExcludeMetaShapesFilter.java
@@ -16,11 +16,6 @@
*/
package org.topbraid.shacl.engine.filters;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.function.Predicate;
-
import org.apache.jena.rdf.model.Resource;
import org.topbraid.jenax.util.JenaUtil;
import org.topbraid.shacl.model.SHShape;
@@ -28,30 +23,34 @@
import org.topbraid.shacl.vocabulary.SH;
import org.topbraid.shacl.vocabulary.TOSH;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Predicate;
+
/**
* A Predicate that can be used to bypass any shapes that are also constraint components
* and any shapes from the tosh namespace.
- *
+ *
* @author Holger Knublauch
*/
public class ExcludeMetaShapesFilter implements Predicate {
-
- private static Set systemShapes = new HashSet<>();
- static {
- Collections.addAll(systemShapes, DASH.Editor, DASH.GraphStoreTestCase, DASH.InferencingTestCase, DASH.QueryTestCase, DASH.ValidationTestCase, DASH.Viewer, DASH.Widget);
- }
-
-
- public static void addSystemShapes(Resource... shapes) {
- for(Resource shape : shapes) {
- systemShapes.add(shape);
- }
- }
-
-
- @Override
- public boolean test(SHShape shape) {
- return !JenaUtil.hasIndirectType(shape, SH.Parameter) && !systemShapes.contains(shape) &&
- (shape.isAnon() || !shape.getURI().startsWith(TOSH.NS));
- }
+
+ private static Set systemShapes = new HashSet<>();
+
+ static {
+ Collections.addAll(systemShapes, DASH.Editor, DASH.GraphStoreTestCase, DASH.InferencingTestCase, DASH.QueryTestCase, DASH.ValidationTestCase, DASH.Viewer, DASH.Widget);
+ }
+
+
+ public static void addSystemShapes(Resource... shapes) {
+ Collections.addAll(systemShapes, shapes);
+ }
+
+
+ @Override
+ public boolean test(SHShape shape) {
+ return !JenaUtil.hasIndirectType(shape, SH.Parameter) && !systemShapes.contains(shape) &&
+ (shape.isAnon() || !shape.getURI().startsWith(TOSH.NS));
+ }
}
diff --git a/src/main/java/org/topbraid/shacl/entailment/SHACLEntailment.java b/src/main/java/org/topbraid/shacl/entailment/SHACLEntailment.java
index ed04a062..ef592ae9 100644
--- a/src/main/java/org/topbraid/shacl/entailment/SHACLEntailment.java
+++ b/src/main/java/org/topbraid/shacl/entailment/SHACLEntailment.java
@@ -16,10 +16,6 @@
*/
package org.topbraid.shacl.entailment;
-import java.net.URI;
-import java.util.HashMap;
-import java.util.Map;
-
import org.apache.jena.query.Dataset;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.ModelFactory;
@@ -31,64 +27,65 @@
import org.topbraid.shacl.rules.RulesEntailment;
import org.topbraid.shacl.vocabulary.SH;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
/**
* Singleton to support sh:entailment.
* Extensions may install their own Engines.
- *
+ *
* @author Holger Knublauch
*/
public class SHACLEntailment {
-
- public final static Resource RDFS = ResourceFactory.createResource("http://www.w3.org/ns/entailment/RDFS");
-
- public static interface Engine {
-
- Model createModelWithEntailment(Dataset dataset, URI shapesGraphURI, ShapesGraph shapesGraph, ProgressMonitor monitor) throws InterruptedException;
- }
-
- private static SHACLEntailment singleton = new SHACLEntailment();
-
- public static SHACLEntailment get() {
- return singleton;
- }
-
- private Map engines = new HashMap<>();
-
-
- protected SHACLEntailment() {
- setEngine(RDFS.getURI(), new Engine() {
- @Override
- public Model createModelWithEntailment(Dataset dataset, URI shapesGraphURI, ShapesGraph shapesGraph, ProgressMonitor monitor) {
- return ModelFactory.createRDFSModel(dataset.getDefaultModel());
- }
- });
- setEngine(SH.Rules.getURI(), new RulesEntailment());
- }
-
-
- public Engine getEngine(String uri) {
- return engines.get(uri);
- }
-
-
- public void setEngine(String uri, Engine engine) {
- engines.put(uri, engine);
- }
-
-
- public Dataset withEntailment(Dataset dataset, URI shapesGraphURI, ShapesGraph shapesGraph, Resource entailment, ProgressMonitor monitor) throws InterruptedException {
- if(entailment == null || dataset.getDefaultModel() == null) {
- return dataset;
- }
- else {
- Engine engine = getEngine(entailment.getURI());
- if(engine != null) {
- Model newDefaultModel = engine.createModelWithEntailment(dataset, shapesGraphURI, shapesGraph, monitor);
- return new DatasetWithDifferentDefaultModel(newDefaultModel, dataset);
- }
- else {
- return null;
- }
- }
- }
+
+ public final static Resource RDFS = ResourceFactory.createResource("http://www.w3.org/ns/entailment/RDFS");
+
+ public interface Engine {
+ Model createModelWithEntailment(Dataset dataset, URI shapesGraphURI, ShapesGraph shapesGraph, ProgressMonitor monitor) throws InterruptedException;
+ }
+
+ private static final SHACLEntailment singleton = new SHACLEntailment();
+
+ public static SHACLEntailment get() {
+ return singleton;
+ }
+
+ private Map engines = new HashMap<>();
+
+
+ protected SHACLEntailment() {
+ setEngine(RDFS.getURI(), new Engine() {
+ @Override
+ public Model createModelWithEntailment(Dataset dataset, URI shapesGraphURI, ShapesGraph shapesGraph, ProgressMonitor monitor) {
+ return ModelFactory.createRDFSModel(dataset.getDefaultModel());
+ }
+ });
+ setEngine(SH.Rules.getURI(), new RulesEntailment());
+ }
+
+
+ public Engine getEngine(String uri) {
+ return engines.get(uri);
+ }
+
+
+ public void setEngine(String uri, Engine engine) {
+ engines.put(uri, engine);
+ }
+
+
+ public Dataset withEntailment(Dataset dataset, URI shapesGraphURI, ShapesGraph shapesGraph, Resource entailment, ProgressMonitor monitor) throws InterruptedException {
+ if (entailment == null || dataset.getDefaultModel() == null) {
+ return dataset;
+ } else {
+ Engine engine = getEngine(entailment.getURI());
+ if (engine != null) {
+ Model newDefaultModel = engine.createModelWithEntailment(dataset, shapesGraphURI, shapesGraph, monitor);
+ return new DatasetWithDifferentDefaultModel(newDefaultModel, dataset);
+ } else {
+ return null;
+ }
+ }
+ }
}
diff --git a/src/main/java/org/topbraid/shacl/expr/AbstractNodeExpression.java b/src/main/java/org/topbraid/shacl/expr/AbstractNodeExpression.java
index 0302f16a..5aff66e2 100644
--- a/src/main/java/org/topbraid/shacl/expr/AbstractNodeExpression.java
+++ b/src/main/java/org/topbraid/shacl/expr/AbstractNodeExpression.java
@@ -1,56 +1,56 @@
package org.topbraid.shacl.expr;
-import java.util.Collections;
-import java.util.List;
-
import org.apache.jena.rdf.model.RDFNode;
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.util.iterator.ExtendedIterator;
+import java.util.Collections;
+import java.util.List;
+
public abstract class AbstractNodeExpression implements NodeExpression {
-
- private final static List EMPTY = Collections.emptyList();
- private RDFNode expr;
-
-
- protected AbstractNodeExpression(RDFNode expr) {
- this.expr = expr;
- }
+ private final static List EMPTY = Collections.emptyList();
+
+ private final RDFNode expr;
+
+
+ protected AbstractNodeExpression(RDFNode expr) {
+ this.expr = expr;
+ }
- @Override
- public ExtendedIterator evalReverse(RDFNode valueNode, NodeExpressionContext context) {
- throw new IllegalStateException("Reverse evaluation is not supported for this node expression: " + toString());
- }
+ @Override
+ public ExtendedIterator evalReverse(RDFNode valueNode, NodeExpressionContext context) {
+ throw new IllegalStateException("Reverse evaluation is not supported for this node expression: " + this);
+ }
- @Override
- public List getInputExpressions() {
- return EMPTY;
- }
+ @Override
+ public List getInputExpressions() {
+ return EMPTY;
+ }
- @Override
- public Resource getOutputShape(Resource contextShape) {
- return null;
- }
+ @Override
+ public Resource getOutputShape(Resource contextShape) {
+ return null;
+ }
- @Override
- public RDFNode getRDFNode() {
- return expr;
- }
+ @Override
+ public RDFNode getRDFNode() {
+ return expr;
+ }
- @Override
- public boolean isReversible(NodeExpressionContext context) {
- return false;
- }
+ @Override
+ public boolean isReversible(NodeExpressionContext context) {
+ return false;
+ }
- @Override
- public String toString() {
- return getFunctionalSyntax();
- }
+ @Override
+ public String toString() {
+ return getFunctionalSyntax();
+ }
}
diff --git a/src/main/java/org/topbraid/shacl/expr/ComplexNodeExpression.java b/src/main/java/org/topbraid/shacl/expr/ComplexNodeExpression.java
index 3074aab9..5c25219c 100644
--- a/src/main/java/org/topbraid/shacl/expr/ComplexNodeExpression.java
+++ b/src/main/java/org/topbraid/shacl/expr/ComplexNodeExpression.java
@@ -16,40 +16,40 @@
*/
package org.topbraid.shacl.expr;
+import org.apache.jena.rdf.model.RDFNode;
+
import java.util.Iterator;
import java.util.List;
-import org.apache.jena.rdf.model.RDFNode;
-
public abstract class ComplexNodeExpression extends AbstractNodeExpression {
-
- protected ComplexNodeExpression(RDFNode expr) {
- super(expr);
- }
-
-
- @Override
- public String getFunctionalSyntax() {
- String str = getFunctionalSyntaxName();
- str += "(";
- List args = getFunctionalSyntaxArguments();
- Iterator it = args.iterator();
- while(it.hasNext()) {
- String next = it.next();
- str += next;
- if(it.hasNext()) {
- str += ", ";
- }
- }
- str += ")";
- return str;
- }
-
-
- protected String getFunctionalSyntaxName() {
- return getTypeId().toString();
- }
-
-
- public abstract List getFunctionalSyntaxArguments();
+
+ protected ComplexNodeExpression(RDFNode expr) {
+ super(expr);
+ }
+
+
+ @Override
+ public String getFunctionalSyntax() {
+ String str = getFunctionalSyntaxName();
+ str += "(";
+ List args = getFunctionalSyntaxArguments();
+ Iterator it = args.iterator();
+ while (it.hasNext()) {
+ String next = it.next();
+ str += next;
+ if (it.hasNext()) {
+ str += ", ";
+ }
+ }
+ str += ")";
+ return str;
+ }
+
+
+ protected String getFunctionalSyntaxName() {
+ return getTypeId();
+ }
+
+
+ public abstract List getFunctionalSyntaxArguments();
}
diff --git a/src/main/java/org/topbraid/shacl/expr/PathEvaluator.java b/src/main/java/org/topbraid/shacl/expr/PathEvaluator.java
index 907ea4bb..f99622bd 100644
--- a/src/main/java/org/topbraid/shacl/expr/PathEvaluator.java
+++ b/src/main/java/org/topbraid/shacl/expr/PathEvaluator.java
@@ -16,17 +16,8 @@
*/
package org.topbraid.shacl.expr;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-
import org.apache.jena.graph.Node;
-import org.apache.jena.rdf.model.Literal;
-import org.apache.jena.rdf.model.Model;
-import org.apache.jena.rdf.model.Property;
-import org.apache.jena.rdf.model.RDFNode;
-import org.apache.jena.rdf.model.Resource;
+import org.apache.jena.rdf.model.*;
import org.apache.jena.sparql.path.P_Inverse;
import org.apache.jena.sparql.path.P_Link;
import org.apache.jena.sparql.path.Path;
@@ -38,220 +29,218 @@
import org.topbraid.shacl.engine.ShapesGraph;
import org.topbraid.shacl.expr.lib.DistinctExpression;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
/**
* An object that computes the values of a sh:path node expression.
* This implements consistent handling of inferred values.
- *
+ *
* Inferences are limited to simple forward paths consisting of a single predicate.
- *
+ *
* @author Holger Knublauch
*/
public class PathEvaluator {
-
- private NodeExpression input;
-
- private boolean isInverse;
-
- private Path jenaPath;
-
- private Property predicate;
-
-
- /**
- * Constructs a PathEvaluator for a single "forward" property look-up.
- * @param predicate the predicate
- */
- public PathEvaluator(Property predicate) {
- this.predicate = predicate;
- }
-
-
- /**
- * Constructs a PathEvaluator for an arbitrary SPARQL path (except single forward properties).
- * @param path the path
- * @param shapesModel the shapes Model
- */
- public PathEvaluator(Path path, Model shapesModel) {
- this.jenaPath = path;
- isInverse = jenaPath instanceof P_Inverse && ((P_Inverse)jenaPath).getSubPath() instanceof P_Link;
- if(isInverse) {
- P_Link link = (P_Link) ((P_Inverse)jenaPath).getSubPath();
- predicate = shapesModel.getProperty(link.getNode().getURI());
- }
- }
+
+ private NodeExpression input;
+
+ private boolean isInverse;
+
+ private Path jenaPath;
+
+ private Property predicate;
+
+
+ /**
+ * Constructs a PathEvaluator for a single "forward" property look-up.
+ *
+ * @param predicate the predicate
+ */
+ public PathEvaluator(Property predicate) {
+ this.predicate = predicate;
+ }
+
+
+ /**
+ * Constructs a PathEvaluator for an arbitrary SPARQL path (except single forward properties).
+ *
+ * @param path the path
+ * @param shapesModel the shapes Model
+ */
+ public PathEvaluator(Path path, Model shapesModel) {
+ this.jenaPath = path;
+ isInverse = jenaPath instanceof P_Inverse && ((P_Inverse) jenaPath).getSubPath() instanceof P_Link;
+ if (isInverse) {
+ P_Link link = (P_Link) ((P_Inverse) jenaPath).getSubPath();
+ predicate = shapesModel.getProperty(link.getNode().getURI());
+ }
+ }
+
+
+ public ExtendedIterator eval(RDFNode focusNode, NodeExpressionContext context) {
+ if (input == null) {
+ ExtendedIterator asserted = evalFocusNode(focusNode, context);
+ return withDefaultValues(withInferences(asserted, focusNode, context), focusNode, context);
+ } else {
+ Iterator it = input.eval(focusNode, context);
+ if (it.hasNext()) {
+ RDFNode first = it.next();
+ ExtendedIterator result = withDefaultValues(withInferences(evalFocusNode(first, context), first, context), first, context);
+ while (it.hasNext()) {
+ RDFNode n = it.next();
+ result = result.andThen(withDefaultValues(withInferences(evalFocusNode(n, context), n, context), first, context));
+ }
+ return result;
+ } else {
+ return WrappedIterator.emptyIterator();
+ }
+ }
+ }
+
+
+ public ExtendedIterator evalReverse(RDFNode valueNode, NodeExpressionContext context) {
+ // See isReversible, this only supports trivial cases for now
+ if (isInverse) {
+ if (valueNode instanceof Literal) {
+ return WrappedIterator.emptyIterator();
+ } else {
+ return context.getDataset().getDefaultModel().listObjectsOfProperty((Resource) valueNode, predicate);
+ }
+ } else {
+ return context.getDataset().getDefaultModel().listSubjectsWithProperty(predicate, valueNode).mapWith(r -> (RDFNode) r);
+ }
+ }
+
+
+ /**
+ * Gets the executed Jena Path or null if this is just a simple forward property.
+ *
+ * @return the executed Jena Path
+ */
+ public Path getJenaPath() {
+ return jenaPath;
+ }
+
+
+ /**
+ * Gets the predicate if this is a simple forward property path.
+ * Returns null for inverse paths.
+ *
+ * @return the predicate or null
+ */
+ public Property getPredicate() {
+ if (predicate != null && !isInverse) {
+ return predicate;
+ } else {
+ return null;
+ }
+ }
+
+
+ /**
+ * Checks if the values of this may be inferred.
+ * This is the case if this uses a single forward property path and there are any sh:values or sh:defaultValue statements on
+ * that predicate in the provided shapes graph.
+ * The actual computation on whether the values are inferred depends on the actual focus node, which is why this is
+ * only a "maybe".
+ * This function may be used to exclude optimizations that are possible if we know that no inferences can exist.
+ *
+ * @param shapesGraph the ShapesGraph (which caches previous results)
+ * @return true if there may be sh:values statements
+ */
+ public boolean isMaybeInferred(ShapesGraph shapesGraph) {
+ if (predicate != null && !isInverse) {
+ return !shapesGraph.getValuesNodeExpressionsMap(predicate).isEmpty() || !shapesGraph.getDefaultValueNodeExpressionsMap(predicate).isEmpty();
+ } else {
+ return false;
+ }
+ }
+
+
+ public boolean isReversible(ShapesGraph shapesGraph) {
+ // Very conservative algorithm for now
+ return input == null && !isMaybeInferred(shapesGraph) && jenaPath == null;
+ }
+
+
+ public void setInput(NodeExpression input) {
+ this.input = input;
+ }
+
+
+ private ExtendedIterator evalFocusNode(RDFNode focusNode, NodeExpressionContext context) {
+ if (jenaPath == null) {
+ if (focusNode.isLiteral()) {
+ return WrappedIterator.emptyIterator();
+ } else {
+ return context.getDataset().getDefaultModel().listObjectsOfProperty((Resource) focusNode, predicate);
+ }
+ } else if (isInverse) {
+ return context.getDataset().getDefaultModel().listSubjectsWithProperty(predicate, focusNode).mapWith(r -> (RDFNode) r);
+ } else {
+ // This ought to do lazy evaluation too
+ List results = new LinkedList<>();
+ SHACLPaths.addValueNodes(focusNode.inModel(context.getDataset().getDefaultModel()), jenaPath, results);
+ return WrappedIterator.create(results.iterator());
+ }
+ }
- public ExtendedIterator eval(RDFNode focusNode, NodeExpressionContext context) {
- if(input == null) {
- ExtendedIterator asserted = evalFocusNode(focusNode, context);
- return withDefaultValues(withInferences(asserted, focusNode, context), focusNode, context);
- }
- else {
- Iterator it = input.eval(focusNode, context);
- if(it.hasNext()) {
- RDFNode first = it.next();
- ExtendedIterator result = withDefaultValues(withInferences(evalFocusNode(first, context), first, context), first, context);
- while(it.hasNext()) {
- RDFNode n = it.next();
- result = result.andThen(withDefaultValues(withInferences(evalFocusNode(n, context), n, context), first, context));
- }
- return result;
- }
- else {
- return WrappedIterator.emptyIterator();
- }
- }
- }
-
-
- public ExtendedIterator evalReverse(RDFNode valueNode, NodeExpressionContext context) {
- // See isReversible, this only supports trivial cases for now
- if(isInverse) {
- if(valueNode instanceof Literal) {
- return WrappedIterator.emptyIterator();
- }
- else {
- return context.getDataset().getDefaultModel().listObjectsOfProperty((Resource)valueNode, predicate);
- }
- }
- else {
- return context.getDataset().getDefaultModel().listSubjectsWithProperty(predicate, valueNode).mapWith(r -> (RDFNode)r);
- }
- }
-
-
- /**
- * Gets the executed Jena Path or null if this is just a simple forward property.
- * @return
- */
- public Path getJenaPath() {
- return jenaPath;
- }
-
-
- /**
- * Gets the predicate if this is a simple forward property path.
- * Returns null for inverse paths.
- * @return the predicate or null
- */
- public Property getPredicate() {
- if(predicate != null && !isInverse) {
- return predicate;
- }
- else {
- return null;
- }
- }
-
-
- /**
- * Checks if the values of this may be inferred.
- * This is the case if this uses a single forward property path and there are any sh:values or sh:defaultValue statements on
- * that predicate in the provided shapes graph.
- * The actual computation on whether the values are inferred depends on the actual focus node, which is why this is
- * only a "maybe".
- * This function may be used to exclude optimizations that are possible if we know that no inferences can exist.
- * @param shapesGraph the ShapesGraph (which caches previous results)
- * @return true if there may be sh:values statements
- */
- public boolean isMaybeInferred(ShapesGraph shapesGraph) {
- if(predicate != null && !isInverse) {
- return !shapesGraph.getValuesNodeExpressionsMap(predicate).isEmpty() || !shapesGraph.getDefaultValueNodeExpressionsMap(predicate).isEmpty();
- }
- else {
- return false;
- }
- }
-
-
- public boolean isReversible(ShapesGraph shapesGraph) {
- // Very conservative algorithm for now
- return input == null && !isMaybeInferred(shapesGraph) && jenaPath == null;
- }
-
-
- public void setInput(NodeExpression input) {
- this.input = input;
- }
+ private ExtendedIterator withDefaultValues(ExtendedIterator base, RDFNode focusNode, NodeExpressionContext context) {
+ if (isInverse || predicate == null || base.hasNext()) {
+ return base;
+ } else {
+ Map map = context.getShapesGraph().getDefaultValueNodeExpressionsMap(predicate);
+ if (map.isEmpty()) {
+ return base;
+ } else {
+ ExtendedIterator result = WrappedIterator.emptyIterator();
+ int count = 0;
+ for (Resource type : JenaUtil.getAllTypes((Resource) focusNode)) {
+ NodeExpression expr = map.get(type.asNode());
+ if (expr != null) {
+ result = result.andThen(expr.eval(focusNode, context));
+ count++;
+ }
+ }
+ if (count > 1) {
+ // Filter out duplicates in case multiple sh:defaultValue expressions exist
+ return DistinctExpression.distinct(result);
+ } else {
+ return result;
+ }
+ }
+ }
+ }
- private ExtendedIterator evalFocusNode(RDFNode focusNode, NodeExpressionContext context) {
- if(jenaPath == null) {
- if(focusNode.isLiteral()) {
- return WrappedIterator.emptyIterator();
- }
- else {
- return context.getDataset().getDefaultModel().listObjectsOfProperty((Resource)focusNode, predicate);
- }
- }
- else if(isInverse) {
- return context.getDataset().getDefaultModel().listSubjectsWithProperty(predicate, focusNode).mapWith(r -> (RDFNode)r);
- }
- else {
- // This ought to do lazy evaluation too
- List results = new LinkedList<>();
- SHACLPaths.addValueNodes(focusNode.inModel(context.getDataset().getDefaultModel()), jenaPath, results);
- return WrappedIterator.create(results.iterator());
- }
- }
-
-
- private ExtendedIterator withDefaultValues(ExtendedIterator base, RDFNode focusNode, NodeExpressionContext context) {
- if(isInverse || predicate == null || base.hasNext()) {
- return base;
- }
- else {
- Map map = context.getShapesGraph().getDefaultValueNodeExpressionsMap(predicate);
- if(map.isEmpty()) {
- return base;
- }
- else {
- ExtendedIterator result = WrappedIterator.emptyIterator();
- int count = 0;
- for(Resource type : JenaUtil.getAllTypes((Resource)focusNode)) {
- NodeExpression expr = map.get(type.asNode());
- if(expr != null) {
- result = result.andThen(expr.eval(focusNode, context));
- count++;
- }
- }
- if(count > 1) {
- // Filter out duplicates in case multiple sh:defaultValue expressions exist
- return DistinctExpression.distinct(result);
- }
- else {
- return result;
- }
- }
- }
- }
-
-
- private ExtendedIterator withInferences(ExtendedIterator base, RDFNode focusNode, NodeExpressionContext context) {
- if(predicate != null && !isInverse && focusNode.isResource()) {
- Map map = context.getShapesGraph().getValuesNodeExpressionsMap(predicate);
- if(!map.isEmpty()) {
- ExtendedIterator result = base;
- boolean hasInferences = false;
- // TODO: support cases like metash:Resource (if it had no target): if the type has a sh:node then the value rules should be found
- // even if declared in the super-shape
- for(Resource type : JenaUtil.getAllTypes((Resource)focusNode)) {
- NodeExpression expr = map.get(type.asNode());
- if(expr != null) {
- result = result.andThen(expr.eval(focusNode, context));
- hasInferences = true;
- }
- }
- if(!hasInferences && map.get(RDFS.Resource.asNode()) != null) {
- // This is to support cases like generic schema even if no rdf:type is present or it doesn't reach rdfs:Resource in the hierarchy
- NodeExpression expr = map.get(RDFS.Resource.asNode());
- result = result.andThen(expr.eval(focusNode, context));
- hasInferences = true;
- }
- // Filter out duplicates in case the graph contains materialized inferences and because sh:values may return lists
- return DistinctExpression.distinct(result);
- }
- }
- return base;
- }
+ private ExtendedIterator withInferences(ExtendedIterator base, RDFNode focusNode, NodeExpressionContext context) {
+ if (predicate != null && !isInverse && focusNode.isResource()) {
+ Map map = context.getShapesGraph().getValuesNodeExpressionsMap(predicate);
+ if (!map.isEmpty()) {
+ ExtendedIterator result = base;
+ boolean hasInferences = false;
+ // TODO: support cases like metash:Resource (if it had no target): if the type has a sh:node then the value rules should be found
+ // even if declared in the super-shape
+ for (Resource type : JenaUtil.getAllTypes((Resource) focusNode)) {
+ NodeExpression expr = map.get(type.asNode());
+ if (expr != null) {
+ result = result.andThen(expr.eval(focusNode, context));
+ hasInferences = true;
+ }
+ }
+ if (!hasInferences && map.get(RDFS.Resource.asNode()) != null) {
+ // This is to support cases like generic schema even if no rdf:type is present or it doesn't reach rdfs:Resource in the hierarchy
+ NodeExpression expr = map.get(RDFS.Resource.asNode());
+ result = result.andThen(expr.eval(focusNode, context));
+ hasInferences = true;
+ }
+ // Filter out duplicates in case the graph contains materialized inferences and because sh:values may return lists
+ return DistinctExpression.distinct(result);
+ }
+ }
+ return base;
+ }
}
diff --git a/src/main/java/org/topbraid/shacl/optimize/ClassMetadata.java b/src/main/java/org/topbraid/shacl/optimize/ClassMetadata.java
index e300b9c7..cea1d293 100644
--- a/src/main/java/org/topbraid/shacl/optimize/ClassMetadata.java
+++ b/src/main/java/org/topbraid/shacl/optimize/ClassMetadata.java
@@ -16,16 +16,6 @@
*/
package org.topbraid.shacl.optimize;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.function.Predicate;
-
import org.apache.jena.graph.Graph;
import org.apache.jena.graph.Node;
import org.apache.jena.graph.Triple;
@@ -35,289 +25,292 @@
import org.topbraid.jenax.util.JenaNodeUtil;
import org.topbraid.shacl.vocabulary.SH;
+import java.util.*;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
/**
* Represents some ontology-related data about a given class, so that it can be accessed
* more efficiently than through the RDF triples.
- *
+ *
* In particular this includes information about the properties attached to the class,
* as well as utilities to walk up the superclass hierarchy.
- *
+ *
* @author Holger Knublauch
*/
public class ClassMetadata {
-
- public static Object createKey(Node classNode, String graphKey) {
- return new Key(classNode, graphKey);
- }
-
-
- private Node classNode;
-
- private String graphKey;
-
- private Map> groupPaths;
-
- private Map properties = new HashMap<>();
-
- private List superClasses;
-
-
- public ClassMetadata(Node classNode, String graphKey) {
- this.classNode = classNode;
- this.graphKey = graphKey;
- }
-
-
- public synchronized Set getGroupPaths(Node group, Graph graph) {
- if(groupPaths == null) {
- groupPaths = new HashMap<>();
- if(JenaNodeUtil.isInstanceOf(classNode, SH.Shape.asNode(), graph)) {
- addGroupProperties(classNode, graph, SH.parameter.asNode());
- addGroupProperties(classNode, graph, SH.property.asNode());
- }
- ExtendedIterator it = graph.find(null, SH.targetClass.asNode(), classNode);
- while(it.hasNext()) {
- Node shape = it.next().getSubject();
- addGroupProperties(shape, graph, SH.parameter.asNode());
- addGroupProperties(shape, graph, SH.property.asNode());
- }
- }
- return groupPaths.get(group);
- }
-
-
- private void addGroupProperties(Node nodeShape, Graph graph, Node systemPredicate) {
- ExtendedIterator it = graph.find(nodeShape, systemPredicate, Node.ANY);
- while(it.hasNext()) {
- Node propertyShape = it.next().getObject();
- if(!graph.contains(propertyShape, SH.deactivated.asNode(), JenaDatatypes.TRUE.asNode())) {
- Node group = JenaNodeUtil.getObject(propertyShape, SH.group.asNode(), graph);
- if(group != null) {
- Node path = JenaNodeUtil.getObject(propertyShape, SH.path.asNode(), graph);
- if(path != null) {
- Set paths = groupPaths.get(group);
- if(paths == null) {
- paths = new HashSet<>();
- groupPaths.put(group, paths);
- }
- if(path.isURI()) {
- paths.add(new PathMetadata(path, false));
- }
- else {
- Node inverse = JenaNodeUtil.getObject(path, SH.inversePath.asNode(), graph);
- if(inverse != null && inverse.isURI()) {
- paths.add(new PathMetadata(inverse, true));
- }
- }
- }
- }
- }
- }
- }
-
-
- public Node getPropertyDescription(Node property, boolean inverse, Graph graph) {
- return nearest(graph, new Function() {
- @Override
- public Node apply(ClassMetadata cm) {
- return cm.getProperty(property, inverse, graph).getDescription();
- }
- }, null);
- }
-
-
- public Node getPropertyEditWidget(Node property, boolean inverse, Graph graph) {
- return nearest(graph, new Function() {
- @Override
- public Node apply(ClassMetadata cm) {
- return cm.getProperty(property, inverse, graph).getEditWidget();
- }
- }, null);
- }
-
-
- public Node getPropertyLocalRange(Node property, boolean inverse, Graph graph) {
- return nearest(graph, new Function() {
- @Override
- public Node apply(ClassMetadata cm) {
- return cm.getProperty(property, inverse, graph).getLocalRange();
- }
- }, null);
- }
-
-
- public Integer getPropertyMaxCount(Node property, boolean inverse, Graph graph) {
- return (Integer) nearestObject(graph, new Function() {
- @Override
- public Object apply(ClassMetadata cm) {
- return cm.getProperty(property, inverse, graph).getMaxCount();
- }
- }, new HashSet());
- }
-
-
- public Node getPropertyName(Node property, boolean inverse, Graph graph) {
- return nearest(graph, new Function() {
- @Override
- public Node apply(ClassMetadata cm) {
- return cm.getProperty(property, inverse, graph).getName();
- }
- }, null);
- }
-
-
- public Node getPropertyViewWidget(Node property, boolean inverse, Graph graph) {
- return nearest(graph, new Function() {
- @Override
- public Node apply(ClassMetadata cm) {
- return cm.getProperty(property, inverse, graph).getViewWidget();
- }
- }, null);
- }
-
-
- public synchronized Iterable getSuperClasses(Graph graph) {
- if(superClasses == null) {
- superClasses = new LinkedList<>();
- ExtendedIterator it = graph.find(classNode, RDFS.subClassOf.asNode(), Node.ANY);
- while(it.hasNext()) {
- Node superClass = it.next().getObject();
- superClasses.add(OntologyOptimizations.get().getClassMetadata(superClass, graph, graphKey));
- }
- }
- return superClasses;
- }
-
-
- public synchronized ClassPropertyMetadata getProperty(Node predicate, boolean inverse, Graph graph) {
- PathMetadata pm = new PathMetadata(predicate, inverse);
- return getProperty(pm, graph);
- }
-
-
- public synchronized ClassPropertyMetadata getProperty(PathMetadata pm, Graph graph) {
- ClassPropertyMetadata result = properties.get(pm);
- if(result == null) {
- result = new ClassPropertyMetadata(classNode, pm.getPredicate(), pm.isInverse(), graph);
- properties.put(pm, result);
- }
- return result;
- }
-
-
- /**
- * Walks this and its superclasses until it finds one where the given Supplier returns a value.
- * @param property
- * @param graph
- * @param supplier
- * @return the nearest supplied value
- */
- private Node nearest(Graph graph, Function supplier, Set visited) {
- Node result = supplier.apply(this);
- if(result != null) {
- return result;
- }
- if(visited == null) {
- visited = new HashSet<>();
- }
- visited.add(classNode);
- for(ClassMetadata superClass : getSuperClasses(graph)) {
- if(!visited.contains(superClass.classNode)) {
- result = superClass.nearest(graph, supplier, visited);
- if(result != null) {
- return result;
- }
- }
- }
- return null;
- }
-
-
- private Object nearestObject(Graph graph, Function supplier, Set visited) {
- if(!visited.contains(classNode)) {
- Object result = supplier.apply(this);
- if(result != null) {
- return result;
- }
- visited.add(classNode);
- for(ClassMetadata superClass : getSuperClasses(graph)) {
- result = superClass.nearestObject(graph, supplier, visited);
- if(result != null) {
- return result;
- }
- }
- }
- return null;
- }
-
-
- public void walkSuperClasses(Graph graph, Consumer consumer, Set visited) {
- if(!visited.contains(classNode)) {
- consumer.accept(this);
- visited.add(classNode);
- for(ClassMetadata superClassMetadata : getSuperClasses(graph)) {
- superClassMetadata.walkSuperClasses(graph, consumer, visited);
- }
- }
- }
-
-
- public boolean walkSuperClassesUntil(Graph graph, Predicate predicate, Set visited) {
- if(!visited.contains(classNode)) {
- if(predicate.test(this)) {
- return true;
- }
- else {
- visited.add(classNode);
- for(ClassMetadata superClassMetadata : getSuperClasses(graph)) {
- if(superClassMetadata.walkSuperClassesUntil(graph, predicate, visited)) {
- return true;
- }
- }
- }
- }
- return false;
- }
-
-
- @Override
+
+ public static Object createKey(Node classNode, String graphKey) {
+ return new Key(classNode, graphKey);
+ }
+
+
+ private Node classNode;
+
+ private String graphKey;
+
+ private Map> groupPaths;
+
+ private Map properties = new HashMap<>();
+
+ private List superClasses;
+
+
+ public ClassMetadata(Node classNode, String graphKey) {
+ this.classNode = classNode;
+ this.graphKey = graphKey;
+ }
+
+
+ public synchronized Set getGroupPaths(Node group, Graph graph) {
+ if (groupPaths == null) {
+ groupPaths = new HashMap<>();
+ if (JenaNodeUtil.isInstanceOf(classNode, SH.Shape.asNode(), graph)) {
+ addGroupProperties(classNode, graph, SH.parameter.asNode());
+ addGroupProperties(classNode, graph, SH.property.asNode());
+ }
+ ExtendedIterator it = graph.find(null, SH.targetClass.asNode(), classNode);
+ while (it.hasNext()) {
+ Node shape = it.next().getSubject();
+ addGroupProperties(shape, graph, SH.parameter.asNode());
+ addGroupProperties(shape, graph, SH.property.asNode());
+ }
+ }
+ return groupPaths.get(group);
+ }
+
+
+ private void addGroupProperties(Node nodeShape, Graph graph, Node systemPredicate) {
+ ExtendedIterator it = graph.find(nodeShape, systemPredicate, Node.ANY);
+ while (it.hasNext()) {
+ Node propertyShape = it.next().getObject();
+ if (!graph.contains(propertyShape, SH.deactivated.asNode(), JenaDatatypes.TRUE.asNode())) {
+ Node group = JenaNodeUtil.getObject(propertyShape, SH.group.asNode(), graph);
+ if (group != null) {
+ Node path = JenaNodeUtil.getObject(propertyShape, SH.path.asNode(), graph);
+ if (path != null) {
+ Set paths = groupPaths.get(group);
+ if (paths == null) {
+ paths = new HashSet<>();
+ groupPaths.put(group, paths);
+ }
+ if (path.isURI()) {
+ paths.add(new PathMetadata(path, false));
+ } else {
+ Node inverse = JenaNodeUtil.getObject(path, SH.inversePath.asNode(), graph);
+ if (inverse != null && inverse.isURI()) {
+ paths.add(new PathMetadata(inverse, true));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+ public Node getPropertyDescription(Node property, boolean inverse, Graph graph) {
+ return nearest(graph, new Function() {
+ @Override
+ public Node apply(ClassMetadata cm) {
+ return cm.getProperty(property, inverse, graph).getDescription();
+ }
+ }, null);
+ }
+
+
+ public Node getPropertyEditWidget(Node property, boolean inverse, Graph graph) {
+ return nearest(graph, new Function() {
+ @Override
+ public Node apply(ClassMetadata cm) {
+ return cm.getProperty(property, inverse, graph).getEditWidget();
+ }
+ }, null);
+ }
+
+
+ public Node getPropertyLocalRange(Node property, boolean inverse, Graph graph) {
+ return nearest(graph, new Function() {
+ @Override
+ public Node apply(ClassMetadata cm) {
+ return cm.getProperty(property, inverse, graph).getLocalRange();
+ }
+ }, null);
+ }
+
+
+ public Integer getPropertyMaxCount(Node property, boolean inverse, Graph graph) {
+ return (Integer) nearestObject(graph, new Function() {
+ @Override
+ public Object apply(ClassMetadata cm) {
+ return cm.getProperty(property, inverse, graph).getMaxCount();
+ }
+ }, new HashSet());
+ }
+
+
+ public Node getPropertyName(Node property, boolean inverse, Graph graph) {
+ return nearest(graph, new Function() {
+ @Override
+ public Node apply(ClassMetadata cm) {
+ return cm.getProperty(property, inverse, graph).getName();
+ }
+ }, null);
+ }
+
+
+ public Node getPropertyViewWidget(Node property, boolean inverse, Graph graph) {
+ return nearest(graph, new Function() {
+ @Override
+ public Node apply(ClassMetadata cm) {
+ return cm.getProperty(property, inverse, graph).getViewWidget();
+ }
+ }, null);
+ }
+
+
+ public synchronized Iterable getSuperClasses(Graph graph) {
+ if (superClasses == null) {
+ superClasses = new LinkedList<>();
+ ExtendedIterator it = graph.find(classNode, RDFS.subClassOf.asNode(), Node.ANY);
+ while (it.hasNext()) {
+ Node superClass = it.next().getObject();
+ superClasses.add(OntologyOptimizations.get().getClassMetadata(superClass, graph, graphKey));
+ }
+ }
+ return superClasses;
+ }
+
+
+ public synchronized ClassPropertyMetadata getProperty(Node predicate, boolean inverse, Graph graph) {
+ PathMetadata pm = new PathMetadata(predicate, inverse);
+ return getProperty(pm, graph);
+ }
+
+
+ public synchronized ClassPropertyMetadata getProperty(PathMetadata pm, Graph graph) {
+ ClassPropertyMetadata result = properties.get(pm);
+ if (result == null) {
+ result = new ClassPropertyMetadata(classNode, pm.getPredicate(), pm.isInverse(), graph);
+ properties.put(pm, result);
+ }
+ return result;
+ }
+
+
+ /**
+ * Walks this and its superclasses until it finds one where the given Supplier returns a value.
+ *
+ * @param graph
+ * @param graph
+ * @param visited
+ * @return the nearest supplied value
+ */
+ private Node nearest(Graph graph, Function supplier, Set visited) {
+ Node result = supplier.apply(this);
+ if (result != null) {
+ return result;
+ }
+ if (visited == null) {
+ visited = new HashSet<>();
+ }
+ visited.add(classNode);
+ for (ClassMetadata superClass : getSuperClasses(graph)) {
+ if (!visited.contains(superClass.classNode)) {
+ result = superClass.nearest(graph, supplier, visited);
+ if (result != null) {
+ return result;
+ }
+ }
+ }
+ return null;
+ }
+
+
+ private Object nearestObject(Graph graph, Function supplier, Set visited) {
+ if (!visited.contains(classNode)) {
+ Object result = supplier.apply(this);
+ if (result != null) {
+ return result;
+ }
+ visited.add(classNode);
+ for (ClassMetadata superClass : getSuperClasses(graph)) {
+ result = superClass.nearestObject(graph, supplier, visited);
+ if (result != null) {
+ return result;
+ }
+ }
+ }
+ return null;
+ }
+
+
+ public void walkSuperClasses(Graph graph, Consumer consumer, Set visited) {
+ if (!visited.contains(classNode)) {
+ consumer.accept(this);
+ visited.add(classNode);
+ for (ClassMetadata superClassMetadata : getSuperClasses(graph)) {
+ superClassMetadata.walkSuperClasses(graph, consumer, visited);
+ }
+ }
+ }
+
+
+ public boolean walkSuperClassesUntil(Graph graph, Predicate predicate, Set visited) {
+ if (!visited.contains(classNode)) {
+ if (predicate.test(this)) {
+ return true;
+ } else {
+ visited.add(classNode);
+ for (ClassMetadata superClassMetadata : getSuperClasses(graph)) {
+ if (superClassMetadata.walkSuperClassesUntil(graph, predicate, visited)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+
+ @Override
public String toString() {
- return "ClassMetadata for " + classNode + " with " + properties.size() + " properties";
- }
-
-
- private static class Key {
-
- private Node classNode;
-
- private String graphKey;
-
-
- Key(Node classNode, String graphKey) {
- this.classNode = classNode;
- this.graphKey = graphKey;
- }
-
-
- @Override
- public boolean equals(Object obj) {
- if(obj instanceof Key) {
- return classNode.equals(((Key)obj).classNode) && graphKey.equals(((Key)obj).graphKey);
- }
- else {
- return false;
- }
- }
-
- @Override
- public int hashCode() {
- return classNode.hashCode() + graphKey.hashCode();
- }
-
-
- @Override
- public String toString() {
- return graphKey + ".classMetadata." + classNode;
- }
- }
+ return "ClassMetadata for " + classNode + " with " + properties.size() + " properties";
+ }
+
+
+ private static class Key {
+
+ private Node classNode;
+
+ private String graphKey;
+
+
+ Key(Node classNode, String graphKey) {
+ this.classNode = classNode;
+ this.graphKey = graphKey;
+ }
+
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof Key) {
+ return classNode.equals(((Key) obj).classNode) && graphKey.equals(((Key) obj).graphKey);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return classNode.hashCode() + graphKey.hashCode();
+ }
+
+
+ @Override
+ public String toString() {
+ return graphKey + ".classMetadata." + classNode;
+ }
+ }
}
diff --git a/src/main/java/org/topbraid/shacl/optimize/ClassPropertyMetadata.java b/src/main/java/org/topbraid/shacl/optimize/ClassPropertyMetadata.java
index d412c69a..c6301d86 100644
--- a/src/main/java/org/topbraid/shacl/optimize/ClassPropertyMetadata.java
+++ b/src/main/java/org/topbraid/shacl/optimize/ClassPropertyMetadata.java
@@ -16,9 +16,6 @@
*/
package org.topbraid.shacl.optimize;
-import java.util.LinkedList;
-import java.util.List;
-
import org.apache.jena.graph.Graph;
import org.apache.jena.graph.Node;
import org.apache.jena.graph.Triple;
@@ -29,202 +26,201 @@
import org.topbraid.shacl.vocabulary.SH;
import org.topbraid.shacl.vocabulary.TOSH;
+import java.util.LinkedList;
+import java.util.List;
+
/**
* Metadata about a property at a given class, possibly in the inverse direction.
* Populated from SHACL constraints and plugins (currently including OWL restrictions
* and - within the TopBraid ecosystem - SPIN constraints).
- *
+ *
* @author Holger Knublauch
*/
public class ClassPropertyMetadata {
-
- private Node description;
-
- private Node editWidget;
-
- private boolean inverse;
-
- private Node localRange;
-
- private Integer maxCount;
-
- private Node name;
-
- private Node order;
-
- private Node predicate;
-
- private Node viewWidget;
-
-
- ClassPropertyMetadata(Node classNode, Node predicate, boolean inverse, Graph graph) {
-
- this.inverse = inverse;
- this.predicate = predicate;
-
- // Init from SHACL shapes
- if(SHACLUtil.exists(graph)) {
- if(JenaNodeUtil.isInstanceOf(classNode, SH.Shape.asNode(), graph)) {
- initFromShape(classNode, graph);
- }
- ExtendedIterator it = graph.find(null, SH.targetClass.asNode(), classNode);
- while(it.hasNext()) {
- Node shape = it.next().getSubject();
- initFromShape(shape, graph);
- }
- }
-
- if(!inverse) {
- for(Plugin plugin : plugins) {
- plugin.init(this, classNode, graph);
- }
- }
- }
-
-
- public Node getDescription() {
- return description;
- }
-
-
- public Node getEditWidget() {
- return editWidget;
- }
-
-
- // Currently not supported for inverse properties (not used yet)
- public Node getLocalRange() {
- return localRange;
- }
-
-
- public Integer getMaxCount() {
- return maxCount;
- }
-
-
- public Node getName() {
- return name;
- }
-
-
- public Node getOrder() {
- return order;
- }
-
-
- public Node getPredicate() {
- return predicate;
- }
-
-
- public Node getViewWidget() {
- return viewWidget;
- }
-
-
- private void initFromShape(Node shape, Graph graph) {
- if(!graph.contains(shape, SH.deactivated.asNode(), JenaDatatypes.TRUE.asNode())) {
- initFromShape(shape, SH.property.asNode(), graph);
- initFromShape(shape, SH.parameter.asNode(), graph);
- }
- }
-
-
- private void initFromShape(Node shape, Node systemPredicate, Graph graph) {
- ExtendedIterator it = graph.find(shape, systemPredicate, Node.ANY);
- while(it.hasNext()) {
- Node propertyShape = it.next().getObject();
- if(!propertyShape.isLiteral()) {
- if(hasMatchingPath(propertyShape, graph)) {
- if(!graph.contains(propertyShape, SH.deactivated.asNode(), JenaDatatypes.TRUE.asNode())) {
- if(description == null) {
- description = JenaNodeUtil.getObject(propertyShape, SH.description.asNode(), graph);
- }
- if(localRange == null) {
- if(inverse) {
- // Maybe: support inverse ranges
- }
- else {
- localRange = SHACLUtil.walkPropertyShapesHelper(propertyShape, graph);
- }
- }
- if(maxCount == null) {
- Node maxCountNode = JenaNodeUtil.getObject(propertyShape, SH.maxCount.asNode(), graph);
- if(maxCountNode != null && maxCountNode.isLiteral()) {
- Object value = maxCountNode.getLiteralValue();
- if(value instanceof Number) {
- maxCount = ((Number) value).intValue();
- }
- }
- }
- if(name == null) {
- name = JenaNodeUtil.getObject(propertyShape, SH.name.asNode(), graph);
- }
- if(order == null) {
- order = JenaNodeUtil.getObject(propertyShape, SH.order.asNode(), graph);
- }
- if(viewWidget == null) {
- viewWidget = JenaNodeUtil.getObject(propertyShape, TOSH.viewWidget.asNode(), graph);
- }
- }
- }
- }
- }
- }
-
-
- public boolean hasMatchingPath(Node propertyShape, Graph graph) {
- if(inverse) {
- Node path = JenaNodeUtil.getObject(propertyShape, SH.path.asNode(), graph);
- if(path != null && path.isBlank()) {
- return predicate.equals(JenaNodeUtil.getObject(path, SH.inversePath.asNode(), graph));
- }
- else {
- return false;
- }
- }
- else {
- return graph.contains(propertyShape, SH.path.asNode(), predicate);
- }
- }
-
-
- public boolean isInverse() {
- return inverse;
- }
-
-
- public void setLocalRange(Node value) {
- this.localRange = value;
- }
-
-
- public void setMaxCount(int value) {
- this.maxCount = value;
- }
-
-
- @Override
- public String toString() {
- return "ClassPropertyMetadata for " + (inverse ? "^" : "") + predicate;
- }
-
-
- // Abstraction layer for OWL and SPIN
-
- private static List plugins = new LinkedList<>();
-
- public static void register(Plugin plugin) {
- plugins.add(plugin);
- }
-
- static {
- register(new OWLClassPropertyMetadataPlugin());
- }
-
- public static interface Plugin {
-
- void init(ClassPropertyMetadata cpm, Node classNode, Graph graph);
- }
+
+ private Node description;
+
+ private Node editWidget;
+
+ private boolean inverse;
+
+ private Node localRange;
+
+ private Integer maxCount;
+
+ private Node name;
+
+ private Node order;
+
+ private Node predicate;
+
+ private Node viewWidget;
+
+
+ ClassPropertyMetadata(Node classNode, Node predicate, boolean inverse, Graph graph) {
+
+ this.inverse = inverse;
+ this.predicate = predicate;
+
+ // Init from SHACL shapes
+ if (SHACLUtil.exists(graph)) {
+ if (JenaNodeUtil.isInstanceOf(classNode, SH.Shape.asNode(), graph)) {
+ initFromShape(classNode, graph);
+ }
+ ExtendedIterator it = graph.find(null, SH.targetClass.asNode(), classNode);
+ while (it.hasNext()) {
+ Node shape = it.next().getSubject();
+ initFromShape(shape, graph);
+ }
+ }
+
+ if (!inverse) {
+ for (Plugin plugin : plugins) {
+ plugin.init(this, classNode, graph);
+ }
+ }
+ }
+
+
+ public Node getDescription() {
+ return description;
+ }
+
+
+ public Node getEditWidget() {
+ return editWidget;
+ }
+
+
+ // Currently not supported for inverse properties (not used yet)
+ public Node getLocalRange() {
+ return localRange;
+ }
+
+
+ public Integer getMaxCount() {
+ return maxCount;
+ }
+
+
+ public Node getName() {
+ return name;
+ }
+
+
+ public Node getOrder() {
+ return order;
+ }
+
+
+ public Node getPredicate() {
+ return predicate;
+ }
+
+
+ public Node getViewWidget() {
+ return viewWidget;
+ }
+
+
+ private void initFromShape(Node shape, Graph graph) {
+ if (!graph.contains(shape, SH.deactivated.asNode(), JenaDatatypes.TRUE.asNode())) {
+ initFromShape(shape, SH.property.asNode(), graph);
+ initFromShape(shape, SH.parameter.asNode(), graph);
+ }
+ }
+
+
+ private void initFromShape(Node shape, Node systemPredicate, Graph graph) {
+ ExtendedIterator it = graph.find(shape, systemPredicate, Node.ANY);
+ while (it.hasNext()) {
+ Node propertyShape = it.next().getObject();
+ if (!propertyShape.isLiteral()) {
+ if (hasMatchingPath(propertyShape, graph)) {
+ if (!graph.contains(propertyShape, SH.deactivated.asNode(), JenaDatatypes.TRUE.asNode())) {
+ if (description == null) {
+ description = JenaNodeUtil.getObject(propertyShape, SH.description.asNode(), graph);
+ }
+ if (localRange == null) {
+ if (inverse) {
+ // Maybe: support inverse ranges
+ } else {
+ localRange = SHACLUtil.walkPropertyShapesHelper(propertyShape, graph);
+ }
+ }
+ if (maxCount == null) {
+ Node maxCountNode = JenaNodeUtil.getObject(propertyShape, SH.maxCount.asNode(), graph);
+ if (maxCountNode != null && maxCountNode.isLiteral()) {
+ Object value = maxCountNode.getLiteralValue();
+ if (value instanceof Number) {
+ maxCount = ((Number) value).intValue();
+ }
+ }
+ }
+ if (name == null) {
+ name = JenaNodeUtil.getObject(propertyShape, SH.name.asNode(), graph);
+ }
+ if (order == null) {
+ order = JenaNodeUtil.getObject(propertyShape, SH.order.asNode(), graph);
+ }
+ if (viewWidget == null) {
+ viewWidget = JenaNodeUtil.getObject(propertyShape, TOSH.viewWidget.asNode(), graph);
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+ public boolean hasMatchingPath(Node propertyShape, Graph graph) {
+ if (inverse) {
+ Node path = JenaNodeUtil.getObject(propertyShape, SH.path.asNode(), graph);
+ if (path != null && path.isBlank()) {
+ return predicate.equals(JenaNodeUtil.getObject(path, SH.inversePath.asNode(), graph));
+ } else {
+ return false;
+ }
+ } else {
+ return graph.contains(propertyShape, SH.path.asNode(), predicate);
+ }
+ }
+
+
+ public boolean isInverse() {
+ return inverse;
+ }
+
+
+ public void setLocalRange(Node value) {
+ this.localRange = value;
+ }
+
+
+ public void setMaxCount(int value) {
+ this.maxCount = value;
+ }
+
+
+ @Override
+ public String toString() {
+ return "ClassPropertyMetadata for " + (inverse ? "^" : "") + predicate;
+ }
+
+
+ // Abstraction layer for OWL and SPIN
+
+ private static List plugins = new LinkedList<>();
+
+ public static void register(Plugin plugin) {
+ plugins.add(plugin);
+ }
+
+ static {
+ register(new OWLClassPropertyMetadataPlugin());
+ }
+
+ public interface Plugin {
+ void init(ClassPropertyMetadata cpm, Node classNode, Graph graph);
+ }
}
diff --git a/src/main/java/org/topbraid/shacl/rules/RuleEngine.java b/src/main/java/org/topbraid/shacl/rules/RuleEngine.java
index 32d57197..b927dcc7 100644
--- a/src/main/java/org/topbraid/shacl/rules/RuleEngine.java
+++ b/src/main/java/org/topbraid/shacl/rules/RuleEngine.java
@@ -16,25 +16,10 @@
*/
package org.topbraid.shacl.rules;
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
import org.apache.jena.graph.Node;
import org.apache.jena.graph.Triple;
import org.apache.jena.query.Dataset;
-import org.apache.jena.rdf.model.Model;
-import org.apache.jena.rdf.model.Property;
-import org.apache.jena.rdf.model.RDFNode;
-import org.apache.jena.rdf.model.Resource;
-import org.apache.jena.rdf.model.Statement;
+import org.apache.jena.rdf.model.*;
import org.apache.jena.util.iterator.ExtendedIterator;
import org.apache.jena.vocabulary.RDF;
import org.topbraid.jenax.progress.ProgressMonitor;
@@ -54,307 +39,308 @@
import org.topbraid.shacl.vocabulary.DASH;
import org.topbraid.shacl.vocabulary.SH;
+import java.net.URI;
+import java.util.*;
+
/**
* A SHACL Rules engine with a pluggable architecture for different execution languages
* including Triple rules, SPARQL rules and JavaScript rules.
- *
+ *
* In preparation for inclusion into SHACL 1.1, this engine also supports sh:values rules,
- * see https://www.topquadrant.com/graphql/values.html and treats sh:defaultValues as inferences.
- *
+ * see Values and treats sh:defaultValues as inferences.
+ *
* @author Holger Knublauch
*/
public class RuleEngine extends AbstractEngine {
-
- // true to skip sh:values rules from property shapes marked with dash:neverMaterialize true
- private boolean excludeNeverMaterialize;
-
- // true to skip all sh:values rules
- private boolean excludeValues;
-
- private Model inferences;
-
- private Set pending = new HashSet<>();
-
- private Map> rule2Conditions = new HashMap<>();
-
- private Map> shape2Rules = new HashMap<>();
-
-
- public RuleEngine(Dataset dataset, URI shapesGraphURI, ShapesGraph shapesGraph, Model inferences) {
- super(dataset, shapesGraph, shapesGraphURI);
- this.inferences = inferences;
- }
-
-
- public void executeAll() throws InterruptedException {
- List ruleShapes = new ArrayList<>();
- for(Shape shape : shapesGraph.getRootShapes()) {
- if(shape.getShapeResource().hasProperty(SH.rule)) {
- ruleShapes.add(shape);
- }
- else {
- for(Resource ps : JenaUtil.getResourceProperties(shape.getShapeResource(), SH.property)) {
- if(ps.hasProperty(SH.values)) {
- ruleShapes.add(shape);
- break;
- }
- }
- }
- }
- executeShapes(ruleShapes, null);
- }
-
-
- public void executeAllDefaultValues() throws InterruptedException {
- // Add sh:defaultValues where applicable
- Model shapesModel = this.getShapesModel();
- Set defaultValuePredicates = new HashSet<>();
- shapesModel.listSubjectsWithProperty(SH.defaultValue).forEachRemaining(ps -> {
- Resource path = ps.getPropertyResourceValue(SH.path);
- if(path != null && path.isURIResource() && !ps.hasProperty(SH.deactivated, JenaDatatypes.TRUE)) {
- defaultValuePredicates.add(JenaUtil.asProperty(path));
- }
- });
- for(Property predicate : defaultValuePredicates) {
- Map map = shapesGraph.getDefaultValueNodeExpressionsMap(predicate);
- for(Node shapeNode : map.keySet()) {
- Shape shape = shapesGraph.getShape(shapeNode);
- if(shape != null) {
- NodeExpression expr = map.get(shapeNode);
- List targetNodes = new ArrayList<>(shape.getTargetNodes(getDataset()));
- for(RDFNode targetNode : targetNodes) {
- if(targetNode.isResource() && !targetNode.asResource().hasProperty(predicate)) {
- ExtendedIterator it = expr.eval(targetNode, this);
- if(it.hasNext()) {
- List list = it.toList();
- for(RDFNode value : list) {
- inferences.add(targetNode.asResource(), predicate, value);
- }
- }
- }
- }
- }
- }
- }
- }
-
-
- /**
- * Executes the rules attached to a given list of shapes, either for a dedicated
- * focus node or all target nodes of the shapes.
- * @param ruleShapes the shapes to execute
- * @param focusNode the (optional) focus node or null for all target nodes
- * @throws InterruptedException if the monitor has canceled this
- */
- public void executeShapes(List ruleShapes, RDFNode focusNode) throws InterruptedException {
-
- if(ruleShapes.isEmpty()) {
- return;
- }
-
- Collections.sort(ruleShapes, new Comparator() {
- @Override
- public int compare(Shape shape1, Shape shape2) {
- return shape1.getOrder().compareTo(shape2.getOrder());
- }
- });
-
- String baseMessage = null;
- if(monitor != null) {
- int rules = 0;
- for(Shape shape : ruleShapes) {
- rules += getShapeRules(shape).size();
- }
- baseMessage = "Executing " + rules + " SHACL rules from " + ruleShapes.size() + " shapes";
- monitor.beginTask(baseMessage, rules);
- }
-
- Double oldOrder = ruleShapes.get(0).getOrder();
- for(Shape shape : ruleShapes) {
- if(!oldOrder.equals(shape.getOrder())) {
- oldOrder = shape.getOrder();
- flushPending();
- }
- executeShape(shape, baseMessage, focusNode);
- }
- flushPending();
- }
-
-
- public void executeShape(Shape shape, String baseMessage, RDFNode focusNode) throws InterruptedException {
-
- if(shape.isDeactivated()) {
- return;
- }
-
- List rules = getShapeRules(shape);
- if(rules.isEmpty()) {
- return;
- }
-
- List targetNodes;
- if(focusNode != null) {
- targetNodes = Collections.singletonList(focusNode);
- }
- else {
- targetNodes = new ArrayList<>(shape.getTargetNodes(dataset));
- }
-
- if(!targetNodes.isEmpty()) {
- Number oldOrder = rules.get(0).getOrder();
- for(Rule rule : rules) {
- if(monitor != null) {
- if(monitor.isCanceled()) {
- throw new InterruptedException();
- }
- monitor.setTaskName(baseMessage + " (at " + RDFLabels.get().getLabel(shape.getShapeResource()) + " with " + targetNodes.size() + " target nodes)");
- monitor.subTask(rule.toString().replace("\n", " "));
- }
- if(!oldOrder.equals(rule.getOrder())) {
- oldOrder = rule.getOrder();
- // If new rdf:type triples have been inferred, recompute the target nodes (this is brute-force for now)
- boolean recomputeTarget = focusNode == null && pending.stream().anyMatch(triple -> RDF.type.asNode().equals(triple.getPredicate()));
- flushPending();
- if(recomputeTarget) {
- targetNodes = new ArrayList<>(shape.getTargetNodes(dataset));
- }
- }
- List conditions = rule2Conditions.get(rule);
- if(conditions != null && !conditions.isEmpty()) {
- List filtered = new LinkedList<>();
- for(RDFNode targetNode : targetNodes) {
- if(nodeConformsToAllShapes(targetNode, conditions)) {
- filtered.add(targetNode);
- }
- }
- executeRule(rule, filtered, shape);
- }
- else {
- executeRule(rule, targetNodes, shape);
- }
- if(monitor != null) {
- monitor.worked(1);
- }
- }
- }
- }
-
-
- private void executeRule(Rule rule, List focusNodes, Shape shape) {
- JenaUtil.setGraphReadOptimization(true);
- try {
- if(ExecStatisticsManager.get().isRecording()) {
- long startTime = System.currentTimeMillis();
- rule.execute(this, focusNodes, shape);
- long endTime = System.currentTimeMillis();
- long duration = (endTime - startTime);
- String queryText = rule.toString();
- ExecStatisticsManager.get().add(Collections.singletonList(
- new ExecStatistics(queryText, queryText, duration, startTime, rule.getContextNode())));
- }
- else {
- rule.execute(this, focusNodes, shape);
- }
- }
- finally {
- JenaUtil.setGraphReadOptimization(false);
- }
- }
-
-
- private void flushPending() {
- for(Triple triple : pending) {
- inferences.add(inferences.asStatement(triple));
- }
- pending.clear();
- }
-
-
- private List getShapeRules(Shape shape) {
- return shape2Rules.computeIfAbsent(shape, s2 -> {
- List rules = new LinkedList<>();
- List raws = new LinkedList<>();
- for(Statement s : shape.getShapeResource().listProperties(SH.rule).toList()) {
- if(s.getObject().isResource() && !s.getResource().hasProperty(SH.deactivated, JenaDatatypes.TRUE)) {
- raws.add(s.getResource());
- }
- }
- Collections.sort(raws, OrderComparator.get());
- for(Resource raw : raws) {
- RuleLanguage ruleLanguage = RuleLanguages.get().getRuleLanguage(raw);
- if(ruleLanguage == null) {
- throw new IllegalArgumentException("Unsupported SHACL rule type for " + raw);
- }
- Rule rule = ruleLanguage.createRule(raw);
- rules.add(rule);
- List conditions = JenaUtil.getResourceProperties(raw, SH.condition);
- rule2Conditions.put(rule, conditions);
- }
- if(!excludeValues) {
- for(Resource ps : JenaUtil.getResourceProperties(shape.getShapeResource(), SH.property)) {
- if(!ps.hasProperty(SH.deactivated, JenaDatatypes.TRUE) && (!excludeNeverMaterialize || !ps.hasProperty(DASH.neverMaterialize, JenaDatatypes.TRUE))) {
- Resource path = ps.getPropertyResourceValue(SH.path);
- if(path != null && path.isURIResource()) {
- for(Statement s : ps.listProperties(SH.values).toList()) {
- NodeExpression expr = NodeExpressionFactory.get().create(s.getObject());
- rules.add(new ValuesRule(expr, path.asNode(), false));
- }
- }
- }
- }
- }
- return rules;
- });
- }
-
-
- public Model getInferencesModel() {
- return inferences;
- }
-
-
- @Override
+
+ // true to skip sh:values rules from property shapes marked with dash:neverMaterialize true
+ private boolean excludeNeverMaterialize;
+
+ // true to skip all sh:values rules
+ private boolean excludeValues;
+
+ private Model inferences;
+
+ private Set pending = new HashSet<>();
+
+ private Map> rule2Conditions = new HashMap<>();
+
+ private Map> shape2Rules = new HashMap<>();
+
+
+ public RuleEngine(Dataset dataset, URI shapesGraphURI, ShapesGraph shapesGraph, Model inferences) {
+ super(dataset, shapesGraph, shapesGraphURI);
+ this.inferences = inferences;
+ }
+
+
+ public void executeAll() throws InterruptedException {
+ List ruleShapes = new ArrayList<>();
+ for (Shape shape : shapesGraph.getRootShapes()) {
+ if (shape.getShapeResource().hasProperty(SH.rule)) {
+ ruleShapes.add(shape);
+ } else {
+ for (Resource ps : JenaUtil.getResourceProperties(shape.getShapeResource(), SH.property)) {
+ if (ps.hasProperty(SH.values)) {
+ ruleShapes.add(shape);
+ break;
+ }
+ }
+ }
+ }
+ executeShapes(ruleShapes, null);
+ }
+
+
+ public void executeAllDefaultValues() throws InterruptedException {
+ // Add sh:defaultValues where applicable
+ Model shapesModel = this.getShapesModel();
+ Set defaultValuePredicates = new HashSet<>();
+ shapesModel.listSubjectsWithProperty(SH.defaultValue).forEachRemaining(ps -> {
+ Resource path = ps.getPropertyResourceValue(SH.path);
+ if (path != null && path.isURIResource() && !ps.hasProperty(SH.deactivated, JenaDatatypes.TRUE)) {
+ defaultValuePredicates.add(JenaUtil.asProperty(path));
+ }
+ });
+ for (Property predicate : defaultValuePredicates) {
+ Map map = shapesGraph.getDefaultValueNodeExpressionsMap(predicate);
+ for (Node shapeNode : map.keySet()) {
+ Shape shape = shapesGraph.getShape(shapeNode);
+ if (shape != null) {
+ NodeExpression expr = map.get(shapeNode);
+ List targetNodes = new ArrayList<>(shape.getTargetNodes(getDataset()));
+ for (RDFNode targetNode : targetNodes) {
+ if (targetNode.isResource() && !targetNode.asResource().hasProperty(predicate)) {
+ ExtendedIterator it = expr.eval(targetNode, this);
+ if (it.hasNext()) {
+ List list = it.toList();
+ for (RDFNode value : list) {
+ inferences.add(targetNode.asResource(), predicate, value);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Executes the rules attached to a given list of shapes, either for a dedicated
+ * focus node or all target nodes of the shapes.
+ *
+ * @param ruleShapes the shapes to execute
+ * @param focusNode the (optional) focus node or null for all target nodes
+ * @throws InterruptedException if the monitor has canceled this
+ */
+ public void executeShapes(List ruleShapes, RDFNode focusNode) throws InterruptedException {
+
+ if (ruleShapes.isEmpty()) {
+ return;
+ }
+
+ Collections.sort(ruleShapes, new Comparator() {
+ @Override
+ public int compare(Shape shape1, Shape shape2) {
+ return shape1.getOrder().compareTo(shape2.getOrder());
+ }
+ });
+
+ String baseMessage = null;
+ if (monitor != null) {
+ int rules = 0;
+ for (Shape shape : ruleShapes) {
+ rules += getShapeRules(shape).size();
+ }
+ baseMessage = "Executing " + rules + " SHACL rules from " + ruleShapes.size() + " shapes";
+ monitor.beginTask(baseMessage, rules);
+ }
+
+ Double oldOrder = ruleShapes.get(0).getOrder();
+ for (Shape shape : ruleShapes) {
+ if (!oldOrder.equals(shape.getOrder())) {
+ oldOrder = shape.getOrder();
+ flushPending();
+ }
+ executeShape(shape, baseMessage, focusNode);
+ }
+ flushPending();
+ }
+
+
+ public void executeShape(Shape shape, String baseMessage, RDFNode focusNode) throws InterruptedException {
+
+ if (shape.isDeactivated()) {
+ return;
+ }
+
+ List rules = getShapeRules(shape);
+ if (rules.isEmpty()) {
+ return;
+ }
+
+ List targetNodes;
+ if (focusNode != null) {
+ targetNodes = Collections.singletonList(focusNode);
+ } else {
+ targetNodes = new ArrayList<>(shape.getTargetNodes(dataset));
+ }
+
+ if (!targetNodes.isEmpty()) {
+ Number oldOrder = rules.get(0).getOrder();
+ for (Rule rule : rules) {
+ if (monitor != null) {
+ if (monitor.isCanceled()) {
+ throw new InterruptedException();
+ }
+ monitor.setTaskName(baseMessage + " (at " + RDFLabels.get().getLabel(shape.getShapeResource()) + " with " + targetNodes.size() + " target nodes)");
+ monitor.subTask(rule.toString().replace("\n", " "));
+ }
+ if (!oldOrder.equals(rule.getOrder())) {
+ oldOrder = rule.getOrder();
+ // If new rdf:type triples have been inferred, recompute the target nodes (this is brute-force for now)
+ boolean recomputeTarget = focusNode == null && pending.stream().anyMatch(triple -> RDF.type.asNode().equals(triple.getPredicate()));
+ flushPending();
+ if (recomputeTarget) {
+ targetNodes = new ArrayList<>(shape.getTargetNodes(dataset));
+ }
+ }
+ List conditions = rule2Conditions.get(rule);
+ if (conditions != null && !conditions.isEmpty()) {
+ List filtered = new LinkedList<>();
+ for (RDFNode targetNode : targetNodes) {
+ if (nodeConformsToAllShapes(targetNode, conditions)) {
+ filtered.add(targetNode);
+ }
+ }
+ executeRule(rule, filtered, shape);
+ } else {
+ executeRule(rule, targetNodes, shape);
+ }
+ if (monitor != null) {
+ monitor.worked(1);
+ }
+ }
+ }
+ }
+
+
+ private void executeRule(Rule rule, List focusNodes, Shape shape) {
+ JenaUtil.setGraphReadOptimization(true);
+ try {
+ if (ExecStatisticsManager.get().isRecording()) {
+ long startTime = System.currentTimeMillis();
+ rule.execute(this, focusNodes, shape);
+ long endTime = System.currentTimeMillis();
+ long duration = (endTime - startTime);
+ String queryText = rule.toString();
+ ExecStatisticsManager.get().add(Collections.singletonList(
+ new ExecStatistics(queryText, queryText, duration, startTime, rule.getContextNode())));
+ } else {
+ rule.execute(this, focusNodes, shape);
+ }
+ } finally {
+ JenaUtil.setGraphReadOptimization(false);
+ }
+ }
+
+
+ private void flushPending() {
+ for (Triple triple : pending) {
+ inferences.add(inferences.asStatement(triple));
+ }
+ pending.clear();
+ }
+
+
+ private List getShapeRules(Shape shape) {
+ return shape2Rules.computeIfAbsent(shape, s2 -> {
+ List rules = new LinkedList<>();
+ List raws = new LinkedList<>();
+ for (Statement s : shape.getShapeResource().listProperties(SH.rule).toList()) {
+ if (s.getObject().isResource() && !s.getResource().hasProperty(SH.deactivated, JenaDatatypes.TRUE)) {
+ raws.add(s.getResource());
+ }
+ }
+ Collections.sort(raws, OrderComparator.get());
+ for (Resource raw : raws) {
+ RuleLanguage ruleLanguage = RuleLanguages.get().getRuleLanguage(raw);
+ if (ruleLanguage == null) {
+ throw new IllegalArgumentException("Unsupported SHACL rule type for " + raw);
+ }
+ Rule rule = ruleLanguage.createRule(raw);
+ rules.add(rule);
+ List conditions = JenaUtil.getResourceProperties(raw, SH.condition);
+ rule2Conditions.put(rule, conditions);
+ }
+ if (!excludeValues) {
+ for (Resource ps : JenaUtil.getResourceProperties(shape.getShapeResource(), SH.property)) {
+ if (!ps.hasProperty(SH.deactivated, JenaDatatypes.TRUE) && (!excludeNeverMaterialize || !ps.hasProperty(DASH.neverMaterialize, JenaDatatypes.TRUE))) {
+ Resource path = ps.getPropertyResourceValue(SH.path);
+ if (path != null && path.isURIResource()) {
+ for (Statement s : ps.listProperties(SH.values).toList()) {
+ NodeExpression expr = NodeExpressionFactory.get().create(s.getObject());
+ rules.add(new ValuesRule(expr, path.asNode(), false));
+ }
+ }
+ }
+ }
+ }
+ return rules;
+ });
+ }
+
+
+ public Model getInferencesModel() {
+ return inferences;
+ }
+
+
+ @Override
public Model getShapesModel() {
- return dataset.getNamedModel(shapesGraphURI.toString());
- }
-
-
- public void infer(Triple triple, Rule rule, Shape shape) {
- pending.add(triple);
- }
-
-
- private boolean nodeConformsToAllShapes(RDFNode focusNode, Iterable shapes) {
- for(Resource shape : shapes) {
- ValidationEngine engine = ValidationEngineFactory.get().create(dataset, shapesGraphURI, shapesGraph, null);
- if(!engine.nodesConformToShape(Collections.singletonList(focusNode), shape.asNode())) {
- return false;
- }
- }
- return true;
- }
-
-
- /**
- * If set to true then all sh:values rules in property shapes marked with dash:neverMaterialize will be skipped.
- * @param value the new flag (defaults to false)
- */
- public void setExcludeNeverMaterialize(boolean value) {
- this.excludeNeverMaterialize = value;
- }
-
-
- /**
- * If set to true then all sh:values rules will be skipped.
- * @param value the new flag (defaults to false)
- */
- public void setExcludeValues(boolean value) {
- this.excludeValues = value;
- }
-
-
- @Override
+ return dataset.getNamedModel(shapesGraphURI.toString());
+ }
+
+
+ public void infer(Triple triple, Rule rule, Shape shape) {
+ pending.add(triple);
+ }
+
+
+ private boolean nodeConformsToAllShapes(RDFNode focusNode, Iterable shapes) {
+ for (Resource shape : shapes) {
+ ValidationEngine engine = ValidationEngineFactory.get().create(dataset, shapesGraphURI, shapesGraph, null);
+ if (!engine.nodesConformToShape(Collections.singletonList(focusNode), shape.asNode())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+ /**
+ * If set to true then all sh:values rules in property shapes marked with dash:neverMaterialize will be skipped.
+ *
+ * @param value the new flag (defaults to false)
+ */
+ public void setExcludeNeverMaterialize(boolean value) {
+ this.excludeNeverMaterialize = value;
+ }
+
+
+ /**
+ * If set to true then all sh:values rules will be skipped.
+ *
+ * @param value the new flag (defaults to false)
+ */
+ public void setExcludeValues(boolean value) {
+ this.excludeValues = value;
+ }
+
+
+ @Override
public void setProgressMonitor(ProgressMonitor value) {
- this.monitor = value;
- }
+ this.monitor = value;
+ }
}
diff --git a/src/main/java/org/topbraid/shacl/targets/Target.java b/src/main/java/org/topbraid/shacl/targets/Target.java
index 0c8b64a0..d2351c4a 100644
--- a/src/main/java/org/topbraid/shacl/targets/Target.java
+++ b/src/main/java/org/topbraid/shacl/targets/Target.java
@@ -1,30 +1,32 @@
package org.topbraid.shacl.targets;
-import java.util.Set;
-
import org.apache.jena.query.Dataset;
import org.apache.jena.rdf.model.RDFNode;
+import java.util.Set;
+
/**
- * Interface for the various target types supported by SHACL, including https://www.w3.org/TR/shacl/#targets
+ * Interface for the various target types supported by SHACL, including SHACL Targets
* but also SHACL-AF and SHACL-JS extensions.
- *
+ *
* @author Holger Knublauch
*/
public interface Target {
- /**
- * Adds target nodes to a given result collection.
- * @param dataset the Dataset with the potential target nodes in the default graph
- * @param results the collection to add the results to
- */
- void addTargetNodes(Dataset dataset, Set results);
+ /**
+ * Adds target nodes to a given result collection.
+ *
+ * @param dataset the Dataset with the potential target nodes in the default graph
+ * @param results the collection to add the results to
+ */
+ void addTargetNodes(Dataset dataset, Set results);
- /**
- * Checks whether a given node is in the target.
- * @param dataset the Dataset with the potential target node in the default graph
- * @param node the potential target node
- * @return true if node is in this target
- */
- boolean contains(Dataset dataset, RDFNode node);
+ /**
+ * Checks whether a given node is in the target.
+ *
+ * @param dataset the Dataset with the potential target node in the default graph
+ * @param node the potential target node
+ * @return true if node is in this target
+ */
+ boolean contains(Dataset dataset, RDFNode node);
}
diff --git a/src/main/java/org/topbraid/shacl/testcases/W3CTestRunner.java b/src/main/java/org/topbraid/shacl/testcases/W3CTestRunner.java
index 93403279..e3545086 100644
--- a/src/main/java/org/topbraid/shacl/testcases/W3CTestRunner.java
+++ b/src/main/java/org/topbraid/shacl/testcases/W3CTestRunner.java
@@ -16,26 +16,10 @@
*/
package org.topbraid.shacl.testcases;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.net.URI;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.UUID;
-
import org.apache.jena.graph.Graph;
import org.apache.jena.graph.compose.MultiUnion;
import org.apache.jena.query.Dataset;
-import org.apache.jena.rdf.model.Model;
-import org.apache.jena.rdf.model.ModelFactory;
-import org.apache.jena.rdf.model.RDFList;
-import org.apache.jena.rdf.model.RDFNode;
-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.rdf.model.*;
import org.apache.jena.sparql.vocabulary.DOAP;
import org.apache.jena.sparql.vocabulary.EARL;
import org.apache.jena.util.FileUtils;
@@ -52,260 +36,255 @@
import org.topbraid.shacl.validation.ValidationEngine;
import org.topbraid.shacl.validation.ValidationEngineConfiguration;
import org.topbraid.shacl.validation.ValidationEngineFactory;
-import org.topbraid.shacl.vocabulary.DASH;
-import org.topbraid.shacl.vocabulary.MF;
-import org.topbraid.shacl.vocabulary.SH;
-import org.topbraid.shacl.vocabulary.SHT;
-import org.topbraid.shacl.vocabulary.TOSH;
+import org.topbraid.shacl.vocabulary.*;
+
+import java.io.*;
+import java.net.URI;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
/**
* Helper object for executing the W3C test cases for SHACL.
* The tests are assumed to be in a folder structure mirroring
- *
+ *
* https://github.com/w3c/data-shapes/tree/gh-pages/data-shapes-test-suite/tests
- *
+ *
* @author Holger Knublauch
*/
public class W3CTestRunner {
-
- private final static Resource EARL_AUTHOR = ResourceFactory.createResource("http://knublauch.com");
-
- private final static Resource EARL_SUBJECT = ResourceFactory.createResource("http://topquadrant.com/shacl/api");
-
- private Model earl;
-
- private List- items = new LinkedList<>();
-
-
- public W3CTestRunner(File rootManifest) throws IOException {
-
- earl = JenaUtil.createMemoryModel();
- JenaUtil.initNamespaces(earl.getGraph());
- earl.setNsPrefix("doap", DOAP.NS);
- earl.setNsPrefix("earl", EARL.NS);
-
- earl.add(EARL_SUBJECT, RDF.type, DOAP.Project);
- earl.add(EARL_SUBJECT, RDF.type, EARL.Software);
- earl.add(EARL_SUBJECT, RDF.type, EARL.TestSubject);
- earl.add(EARL_SUBJECT, DOAP.developer, EARL_AUTHOR);
- earl.add(EARL_SUBJECT, DOAP.name, "TopBraid SHACL API");
-
- collectItems(rootManifest, "urn:x:root/");
- }
-
-
- private void collectItems(File manifestFile, String baseURI) throws IOException {
-
- String filePath = manifestFile.getAbsolutePath().replaceAll("\\\\", "/");
- int coreIndex = filePath.indexOf("core/");
- if(coreIndex > 0 && !filePath.contains("sparql/core")) {
- filePath = filePath.substring(coreIndex);
- }
- else {
- int sindex = filePath.indexOf("sparql/");
- if(sindex > 0) {
- filePath = filePath.substring(sindex);
- }
- }
-
- Model model = JenaUtil.createMemoryModel();
- model.read(new FileInputStream(manifestFile), baseURI, FileUtils.langTurtle);
-
- for(Resource manifest : model.listSubjectsWithProperty(RDF.type, MF.Manifest).toList()) {
- for(Resource include : JenaUtil.getResourceProperties(manifest, MF.include)) {
- String path = include.getURI().substring(baseURI.length());
- File includeFile = new File(manifestFile.getParentFile(), path);
- if(path.contains("/")) {
- String addURI = path.substring(0, path.indexOf('/'));
- collectItems(includeFile, baseURI + addURI + "/");
- }
- else {
- collectItems(includeFile, baseURI + path);
- }
- }
- for(Resource entries : JenaUtil.getResourceProperties(manifest, MF.entries)) {
- for(RDFNode entry : entries.as(RDFList.class).iterator().toList()) {
- items.add(new Item(entry.asResource(), filePath, manifestFile));
- }
- }
- }
- }
-
-
- public Model getEARLModel() {
- return earl;
- }
-
-
- public List
- getItems() {
- return items;
- }
-
-
- public void run(PrintStream out) throws InterruptedException {
- long startTime = System.currentTimeMillis();
- out.println("Running " + items.size() + " W3C Test Cases...");
- int count = 0;
- for(Item item : items) {
- if(!item.run(out)) {
- count++;
- }
- }
- out.println("Completed: " + count + " test failures (Duration: " + (System.currentTimeMillis() - startTime) + " ms)");
- }
-
-
- public class Item {
-
- // The sht:Validate in its defining Model
- Resource entry;
-
- String filePath;
-
- File manifestFile;
-
-
- Item(Resource entry, String filePath, File manifestFile) {
- this.entry = entry;
- this.filePath = filePath;
- this.manifestFile = manifestFile;
- }
-
-
- public Resource getEARLResource() {
- return ResourceFactory.createResource("urn:x-shacl-test:" + entry.getURI().substring("urn:x:root".length()));
- }
-
-
- public String getFilePath() {
- return filePath;
- }
-
-
- public String getLabel() {
- return JenaUtil.getStringProperty(entry, RDFS.label);
- }
-
-
- public Resource getStatus() {
- return entry.getPropertyResourceValue(MF.status);
- }
-
-
- public boolean run(PrintStream out) throws InterruptedException {
-
- Resource assertion = earl.createResource(EARL.Assertion);
- assertion.addProperty(EARL.assertedBy, EARL_AUTHOR);
- assertion.addProperty(EARL.subject, EARL_SUBJECT);
- assertion.addProperty(EARL.test, getEARLResource());
- Resource result = earl.createResource(EARL.TestResult);
- assertion.addProperty(EARL.result, result);
- result.addProperty(EARL.mode, EARL.automatic);
-
- Resource action = entry.getPropertyResourceValue(MF.action);
- Resource shapesGraphResource = action.getPropertyResourceValue(SHT.shapesGraph);
- Graph shapesBaseGraph = entry.getModel().getGraph();
- if(!(entry.getURI() + ".ttl").equals(shapesGraphResource.getURI())) {
- int last = shapesGraphResource.getURI().lastIndexOf('/');
- File shapesFile = new File(manifestFile.getParentFile(), shapesGraphResource.getURI().substring(last + 1));
- Model shapesModel = JenaUtil.createMemoryModel();
- try {
- shapesModel.read(new FileInputStream(shapesFile), "urn:x:dummy", FileUtils.langTurtle);
- shapesBaseGraph = shapesModel.getGraph();
- } catch (FileNotFoundException e) {
- ExceptionUtil.throwUnchecked(e);
- }
- }
-
- MultiUnion multiUnion = new MultiUnion(new Graph[] {
- shapesBaseGraph,
- ARQFactory.getNamedModel(TOSH.BASE_URI).getGraph(),
- ARQFactory.getNamedModel(DASH.BASE_URI).getGraph(),
- ARQFactory.getNamedModel(SH.BASE_URI).getGraph()
- });
- Model shapesModel = ModelFactory.createModelForGraph(multiUnion);
-
- Model dataModel = entry.getModel();
- Resource dataGraph = action.getPropertyResourceValue(SHT.dataGraph);
- if(!(entry.getURI() + ".ttl").equals(dataGraph.getURI())) {
- int last = dataGraph.getURI().lastIndexOf('/');
- File dataFile = new File(manifestFile.getParentFile(), dataGraph.getURI().substring(last + 1));
- dataModel = JenaUtil.createMemoryModel();
- try {
- dataModel.read(new FileInputStream(dataFile), "urn:x:dummy", FileUtils.langTurtle);
- } catch (FileNotFoundException e) {
- ExceptionUtil.throwUnchecked(e);
- }
- }
-
- URI shapesGraphURI = URI.create("urn:x-shacl-shapes-graph:" + UUID.randomUUID().toString());
- Dataset dataset = ARQFactory.get().getDataset(dataModel);
- dataset.addNamedModel(shapesGraphURI.toString(), shapesModel);
-
- ShapesGraph shapesGraph = ShapesGraphFactory.get().createShapesGraph(shapesModel);
- ValidationEngineConfiguration configuration = new ValidationEngineConfiguration().setValidateShapes(false);
- if(entry.hasProperty(ResourceFactory.createProperty(MF.NS + "requires"), SHT.CoreOnly)) {
- shapesGraph.setConstraintFilter(new CoreConstraintFilter());
- }
- ValidationEngine engine = ValidationEngineFactory.get().create(dataset, shapesGraphURI, shapesGraph, null);
- engine.setConfiguration(configuration);
- try {
- Resource actualReport = engine.validateAll();
- Model actualResults = actualReport.getModel();
- actualResults.setNsPrefix(SH.PREFIX, SH.NS);
- actualResults.setNsPrefix("rdf", RDF.getURI());
- actualResults.setNsPrefix("rdfs", RDFS.getURI());
- Model expectedModel = JenaUtil.createDefaultModel();
- Resource expectedReport = entry.getPropertyResourceValue(MF.result);
- for(Statement s : expectedReport.listProperties().toList()) {
- expectedModel.add(s);
- }
- for(Statement s : expectedReport.listProperties(SH.result).toList()) {
- for(Statement t : s.getResource().listProperties().toList()) {
- if(t.getPredicate().equals(DASH.suggestion)) {
- GraphValidationTestCaseType.addStatements(expectedModel, t);
- }
- else if(SH.resultPath.equals(t.getPredicate())) {
- expectedModel.add(t.getSubject(), t.getPredicate(),
- SHACLPaths.clonePath(t.getResource(), expectedModel));
- }
- else {
- expectedModel.add(t);
- }
- }
- }
- actualResults.removeAll(null, SH.message, (RDFNode)null);
- for(Statement s : actualResults.listStatements(null, SH.resultMessage, (RDFNode)null).toList()) {
- if(!expectedModel.contains(null, SH.resultMessage, s.getObject())) {
- actualResults.remove(s);
- }
- }
- if(expectedModel.getGraph().isIsomorphicWith(actualResults.getGraph())) {
- out.println("PASSED: " + entry);
- result.addProperty(EARL.outcome, EARL.passed);
- return true;
- }
- else {
- out.println("FAILED: " + entry);
- result.addProperty(EARL.outcome, EARL.failed);
- expectedModel.setNsPrefixes(actualResults);
- System.out.println("Expected\n" + ModelPrinter.get().print(expectedModel));
- System.out.println("Actual\n" + ModelPrinter.get().print(actualResults));
- return false;
- }
- }
- catch(Exception ex) {
- if(entry.hasProperty(MF.result, SHT.Failure)) {
- out.println("PASSED: " + entry);
- result.addProperty(EARL.outcome, EARL.passed);
- return true;
- }
- else {
- out.println("EXCEPTION: " + entry + " " + ex.getMessage());
- result.addProperty(EARL.outcome, EARL.failed);
- return false;
- }
- }
- }
- }
+
+ private final static Resource EARL_AUTHOR = ResourceFactory.createResource("http://knublauch.com");
+
+ private final static Resource EARL_SUBJECT = ResourceFactory.createResource("http://topquadrant.com/shacl/api");
+
+ private Model earl;
+
+ private List
- items = new LinkedList<>();
+
+
+ public W3CTestRunner(File rootManifest) throws IOException {
+
+ earl = JenaUtil.createMemoryModel();
+ JenaUtil.initNamespaces(earl.getGraph());
+ earl.setNsPrefix("doap", DOAP.NS);
+ earl.setNsPrefix("earl", EARL.NS);
+
+ earl.add(EARL_SUBJECT, RDF.type, DOAP.Project);
+ earl.add(EARL_SUBJECT, RDF.type, EARL.Software);
+ earl.add(EARL_SUBJECT, RDF.type, EARL.TestSubject);
+ earl.add(EARL_SUBJECT, DOAP.developer, EARL_AUTHOR);
+ earl.add(EARL_SUBJECT, DOAP.name, "TopBraid SHACL API");
+
+ collectItems(rootManifest, "urn:x:root/");
+ }
+
+
+ private void collectItems(File manifestFile, String baseURI) throws IOException {
+
+ String filePath = manifestFile.getAbsolutePath().replaceAll("\\\\", "/");
+ int coreIndex = filePath.indexOf("core/");
+ if (coreIndex > 0 && !filePath.contains("sparql/core")) {
+ filePath = filePath.substring(coreIndex);
+ } else {
+ int sindex = filePath.indexOf("sparql/");
+ if (sindex > 0) {
+ filePath = filePath.substring(sindex);
+ }
+ }
+
+ Model model = JenaUtil.createMemoryModel();
+ model.read(new FileInputStream(manifestFile), baseURI, FileUtils.langTurtle);
+
+ for (Resource manifest : model.listSubjectsWithProperty(RDF.type, MF.Manifest).toList()) {
+ for (Resource include : JenaUtil.getResourceProperties(manifest, MF.include)) {
+ String path = include.getURI().substring(baseURI.length());
+ File includeFile = new File(manifestFile.getParentFile(), path);
+ if (path.contains("/")) {
+ String addURI = path.substring(0, path.indexOf('/'));
+ collectItems(includeFile, baseURI + addURI + "/");
+ } else {
+ collectItems(includeFile, baseURI + path);
+ }
+ }
+ for (Resource entries : JenaUtil.getResourceProperties(manifest, MF.entries)) {
+ for (RDFNode entry : entries.as(RDFList.class).iterator().toList()) {
+ items.add(new Item(entry.asResource(), filePath, manifestFile));
+ }
+ }
+ }
+ }
+
+
+ public Model getEARLModel() {
+ return earl;
+ }
+
+
+ public List
- getItems() {
+ return items;
+ }
+
+
+ public void run(PrintStream out) throws InterruptedException {
+ long startTime = System.currentTimeMillis();
+ out.println("Running " + items.size() + " W3C Test Cases...");
+ int count = 0;
+ for (Item item : items) {
+ if (!item.run(out)) {
+ count++;
+ }
+ }
+ out.println("Completed: " + count + " test failures (Duration: " + (System.currentTimeMillis() - startTime) + " ms)");
+ }
+
+
+ public class Item {
+
+ // The sht:Validate in its defining Model
+ Resource entry;
+
+ String filePath;
+
+ File manifestFile;
+
+
+ Item(Resource entry, String filePath, File manifestFile) {
+ this.entry = entry;
+ this.filePath = filePath;
+ this.manifestFile = manifestFile;
+ }
+
+
+ public Resource getEARLResource() {
+ return ResourceFactory.createResource("urn:x-shacl-test:" + entry.getURI().substring("urn:x:root".length()));
+ }
+
+
+ public String getFilePath() {
+ return filePath;
+ }
+
+
+ public String getLabel() {
+ return JenaUtil.getStringProperty(entry, RDFS.label);
+ }
+
+
+ public Resource getStatus() {
+ return entry.getPropertyResourceValue(MF.status);
+ }
+
+
+ public boolean run(PrintStream out) throws InterruptedException {
+
+ Resource assertion = earl.createResource(EARL.Assertion);
+ assertion.addProperty(EARL.assertedBy, EARL_AUTHOR);
+ assertion.addProperty(EARL.subject, EARL_SUBJECT);
+ assertion.addProperty(EARL.test, getEARLResource());
+ Resource result = earl.createResource(EARL.TestResult);
+ assertion.addProperty(EARL.result, result);
+ result.addProperty(EARL.mode, EARL.automatic);
+
+ Resource action = entry.getPropertyResourceValue(MF.action);
+ Resource shapesGraphResource = action.getPropertyResourceValue(SHT.shapesGraph);
+ Graph shapesBaseGraph = entry.getModel().getGraph();
+ if (!(entry.getURI() + ".ttl").equals(shapesGraphResource.getURI())) {
+ int last = shapesGraphResource.getURI().lastIndexOf('/');
+ File shapesFile = new File(manifestFile.getParentFile(), shapesGraphResource.getURI().substring(last + 1));
+ Model shapesModel = JenaUtil.createMemoryModel();
+ try {
+ shapesModel.read(new FileInputStream(shapesFile), "urn:x:dummy", FileUtils.langTurtle);
+ shapesBaseGraph = shapesModel.getGraph();
+ } catch (FileNotFoundException e) {
+ ExceptionUtil.throwUnchecked(e);
+ }
+ }
+
+ MultiUnion multiUnion = new MultiUnion(new Graph[]{
+ shapesBaseGraph,
+ ARQFactory.getNamedModel(TOSH.BASE_URI).getGraph(),
+ ARQFactory.getNamedModel(DASH.BASE_URI).getGraph(),
+ ARQFactory.getNamedModel(SH.BASE_URI).getGraph()
+ });
+ Model shapesModel = ModelFactory.createModelForGraph(multiUnion);
+
+ Model dataModel = entry.getModel();
+ Resource dataGraph = action.getPropertyResourceValue(SHT.dataGraph);
+ if (!(entry.getURI() + ".ttl").equals(dataGraph.getURI())) {
+ int last = dataGraph.getURI().lastIndexOf('/');
+ File dataFile = new File(manifestFile.getParentFile(), dataGraph.getURI().substring(last + 1));
+ dataModel = JenaUtil.createMemoryModel();
+ try {
+ dataModel.read(new FileInputStream(dataFile), "urn:x:dummy", FileUtils.langTurtle);
+ } catch (FileNotFoundException e) {
+ ExceptionUtil.throwUnchecked(e);
+ }
+ }
+
+ URI shapesGraphURI = URI.create("urn:x-shacl-shapes-graph:" + UUID.randomUUID());
+ Dataset dataset = ARQFactory.get().getDataset(dataModel);
+ dataset.addNamedModel(shapesGraphURI.toString(), shapesModel);
+
+ ShapesGraph shapesGraph = ShapesGraphFactory.get().createShapesGraph(shapesModel);
+ ValidationEngineConfiguration configuration = new ValidationEngineConfiguration().setValidateShapes(false);
+ if (entry.hasProperty(ResourceFactory.createProperty(MF.NS + "requires"), SHT.CoreOnly)) {
+ shapesGraph.setConstraintFilter(new CoreConstraintFilter());
+ }
+ ValidationEngine engine = ValidationEngineFactory.get().create(dataset, shapesGraphURI, shapesGraph, null);
+ engine.setConfiguration(configuration);
+ try {
+ Resource actualReport = engine.validateAll();
+ Model actualResults = actualReport.getModel();
+ actualResults.setNsPrefix(SH.PREFIX, SH.NS);
+ actualResults.setNsPrefix("rdf", RDF.getURI());
+ actualResults.setNsPrefix("rdfs", RDFS.getURI());
+ Model expectedModel = JenaUtil.createDefaultModel();
+ Resource expectedReport = entry.getPropertyResourceValue(MF.result);
+ for (Statement s : expectedReport.listProperties().toList()) {
+ expectedModel.add(s);
+ }
+ for (Statement s : expectedReport.listProperties(SH.result).toList()) {
+ for (Statement t : s.getResource().listProperties().toList()) {
+ if (t.getPredicate().equals(DASH.suggestion)) {
+ GraphValidationTestCaseType.addStatements(expectedModel, t);
+ } else if (SH.resultPath.equals(t.getPredicate())) {
+ expectedModel.add(t.getSubject(), t.getPredicate(),
+ SHACLPaths.clonePath(t.getResource(), expectedModel));
+ } else {
+ expectedModel.add(t);
+ }
+ }
+ }
+ actualResults.removeAll(null, SH.message, (RDFNode) null);
+ for (Statement s : actualResults.listStatements(null, SH.resultMessage, (RDFNode) null).toList()) {
+ if (!expectedModel.contains(null, SH.resultMessage, s.getObject())) {
+ actualResults.remove(s);
+ }
+ }
+ if (expectedModel.getGraph().isIsomorphicWith(actualResults.getGraph())) {
+ out.println("PASSED: " + entry);
+ result.addProperty(EARL.outcome, EARL.passed);
+ return true;
+ } else {
+ out.println("FAILED: " + entry);
+ result.addProperty(EARL.outcome, EARL.failed);
+ expectedModel.setNsPrefixes(actualResults);
+ System.out.println("Expected\n" + ModelPrinter.get().print(expectedModel));
+ System.out.println("Actual\n" + ModelPrinter.get().print(actualResults));
+ return false;
+ }
+ } catch (Exception ex) {
+ if (entry.hasProperty(MF.result, SHT.Failure)) {
+ out.println("PASSED: " + entry);
+ result.addProperty(EARL.outcome, EARL.passed);
+ return true;
+ } else {
+ out.println("EXCEPTION: " + entry + " " + ex.getMessage());
+ result.addProperty(EARL.outcome, EARL.failed);
+ return false;
+ }
+ }
+ }
+ }
}
diff --git a/src/main/java/org/topbraid/shacl/util/ModelPrinter.java b/src/main/java/org/topbraid/shacl/util/ModelPrinter.java
index 623064fc..4fccb179 100644
--- a/src/main/java/org/topbraid/shacl/util/ModelPrinter.java
+++ b/src/main/java/org/topbraid/shacl/util/ModelPrinter.java
@@ -16,41 +16,34 @@
*/
package org.topbraid.shacl.util;
-import java.io.StringWriter;
-
import org.apache.jena.rdf.model.Model;
-import org.apache.jena.rdf.model.RDFWriterI;
-import org.apache.jena.util.FileUtils;
+import org.apache.jena.riot.Lang;
+import org.apache.jena.riot.RDFDataMgr;
+
+import java.io.StringWriter;
/**
* A singleton that takes a Jena Model and prints it into a string.
* Used to create comparable renderings of Models produced by test cases.
- *
+ *
* @author Holger Knublauch
*/
public class ModelPrinter {
- private static ModelPrinter singleton = new ModelPrinter();
-
-
- public static ModelPrinter get() {
- return singleton;
- }
-
- public static void set(ModelPrinter value) {
- singleton = value;
- }
-
-
- protected RDFWriterI createRDFWriter(Model model) {
- return model.getWriter(FileUtils.langTurtle);
- }
-
-
- public String print(Model model) {
- StringWriter writer = new StringWriter();
- RDFWriterI w = createRDFWriter(model);
- w.write(model, writer, null);
- return writer.toString();
- }
+ private static ModelPrinter singleton = new ModelPrinter();
+
+ public static ModelPrinter get() {
+ return singleton;
+ }
+
+ public static void set(ModelPrinter value) {
+ singleton = value;
+ }
+
+ public String print(Model model) {
+ StringWriter writer = new StringWriter();
+ RDFDataMgr.write(writer, model, Lang.TURTLE);
+ return writer.toString();
+ }
+
}
diff --git a/src/main/java/org/topbraid/shacl/validation/ExpressionConstraintExecutor.java b/src/main/java/org/topbraid/shacl/validation/ExpressionConstraintExecutor.java
index 4fb09f5c..0bcafb0d 100644
--- a/src/main/java/org/topbraid/shacl/validation/ExpressionConstraintExecutor.java
+++ b/src/main/java/org/topbraid/shacl/validation/ExpressionConstraintExecutor.java
@@ -16,9 +16,6 @@
*/
package org.topbraid.shacl.validation;
-import java.util.Collection;
-import java.util.List;
-
import org.apache.jena.rdf.model.RDFNode;
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.rdf.model.Statement;
@@ -28,31 +25,34 @@
import org.topbraid.shacl.expr.NodeExpressionFactory;
import org.topbraid.shacl.vocabulary.SH;
+import java.util.Collection;
+import java.util.List;
+
/**
- * Validator for sh:expression constraints, see https://w3c.github.io/shacl/shacl-af/#ExpressionConstraintComponent
- *
+ * Validator for sh:expression constraints, see ExpressionConstraintComponent
+ *
* @author Holger Knublauch
*/
public class ExpressionConstraintExecutor implements ConstraintExecutor {
- @Override
- public void executeConstraint(Constraint constraint, ValidationEngine engine, Collection focusNodes) {
- // TODO: optimize, currently produces a new NodeExpression each time
- NodeExpression expr = NodeExpressionFactory.get().create(constraint.getParameterValue());
- for(RDFNode focusNode : focusNodes) {
- engine.checkCanceled();
- for(RDFNode valueNode : engine.getValueNodes(constraint, focusNode)) {
- List results = expr.eval(valueNode, engine).toList();
- if(results.size() != 1 || !JenaDatatypes.TRUE.equals(results.get(0))) {
- Resource result = engine.createValidationResult(constraint, focusNode, valueNode, () -> "Expression does not evaluate to true");
- result.addProperty(SH.sourceConstraint, constraint.getParameterValue());
- if(constraint.getParameterValue() instanceof Resource && ((Resource)constraint.getParameterValue()).hasProperty(SH.message)) {
- for(Statement s : ((Resource)constraint.getParameterValue()).listProperties(SH.message).toList()) {
- result.addProperty(SH.resultMessage, s.getObject());
- }
- }
- }
- }
- }
- }
+ @Override
+ public void executeConstraint(Constraint constraint, ValidationEngine engine, Collection focusNodes) {
+ // TODO: optimize, currently produces a new NodeExpression each time
+ NodeExpression expr = NodeExpressionFactory.get().create(constraint.getParameterValue());
+ for (RDFNode focusNode : focusNodes) {
+ engine.checkCanceled();
+ for (RDFNode valueNode : engine.getValueNodes(constraint, focusNode)) {
+ List results = expr.eval(valueNode, engine).toList();
+ if (results.size() != 1 || !JenaDatatypes.TRUE.equals(results.get(0))) {
+ Resource result = engine.createValidationResult(constraint, focusNode, valueNode, () -> "Expression does not evaluate to true");
+ result.addProperty(SH.sourceConstraint, constraint.getParameterValue());
+ if (constraint.getParameterValue() instanceof Resource && ((Resource) constraint.getParameterValue()).hasProperty(SH.message)) {
+ for (Statement s : ((Resource) constraint.getParameterValue()).listProperties(SH.message).toList()) {
+ result.addProperty(SH.resultMessage, s.getObject());
+ }
+ }
+ }
+ }
+ }
+ }
}
diff --git a/src/main/java/org/topbraid/shacl/validation/MaximumNumberViolations.java b/src/main/java/org/topbraid/shacl/validation/MaximumNumberViolations.java
index 79e00910..4cea4845 100644
--- a/src/main/java/org/topbraid/shacl/validation/MaximumNumberViolations.java
+++ b/src/main/java/org/topbraid/shacl/validation/MaximumNumberViolations.java
@@ -1,9 +1,8 @@
package org.topbraid.shacl.validation;
-@SuppressWarnings("serial")
public class MaximumNumberViolations extends RuntimeException {
-
- public MaximumNumberViolations(int violationCount) {
- super("Maximum number of violations (" + violationCount + ") reached");
+
+ public MaximumNumberViolations(int violationCount) {
+ super("Maximum number of violations (" + violationCount + ") reached");
}
}
diff --git a/src/main/java/org/topbraid/shacl/validation/SHACLException.java b/src/main/java/org/topbraid/shacl/validation/SHACLException.java
index 699d6a9e..325fe57a 100644
--- a/src/main/java/org/topbraid/shacl/validation/SHACLException.java
+++ b/src/main/java/org/topbraid/shacl/validation/SHACLException.java
@@ -18,13 +18,12 @@
/**
* An Exception signaling invalid input to the constraint validation engine.
- *
+ *
* @author Holger Knublauch
*/
-@SuppressWarnings("serial")
public class SHACLException extends RuntimeException {
- public SHACLException(String message) {
- super(message);
- }
+ public SHACLException(String message) {
+ super(message);
+ }
}
diff --git a/src/main/java/org/topbraid/shacl/validation/ValidationEngine.java b/src/main/java/org/topbraid/shacl/validation/ValidationEngine.java
index d5c4b992..bb890c57 100644
--- a/src/main/java/org/topbraid/shacl/validation/ValidationEngine.java
+++ b/src/main/java/org/topbraid/shacl/validation/ValidationEngine.java
@@ -16,37 +16,13 @@
*/
package org.topbraid.shacl.validation;
-import java.net.URI;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.WeakHashMap;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.function.Function;
-import java.util.function.Predicate;
-import java.util.function.Supplier;
-
import org.apache.jena.graph.Graph;
import org.apache.jena.graph.Node;
import org.apache.jena.graph.Triple;
import org.apache.jena.graph.compose.MultiUnion;
import org.apache.jena.query.Dataset;
import org.apache.jena.query.QuerySolution;
-import org.apache.jena.rdf.model.Literal;
-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.RDFNode;
-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.rdf.model.StmtIterator;
+import org.apache.jena.rdf.model.*;
import org.apache.jena.sparql.path.P_Inverse;
import org.apache.jena.sparql.path.P_Link;
import org.apache.jena.sparql.path.Path;
@@ -58,11 +34,7 @@
import org.topbraid.jenax.util.JenaUtil;
import org.topbraid.jenax.util.RDFLabels;
import org.topbraid.shacl.arq.SHACLPaths;
-import org.topbraid.shacl.engine.AbstractEngine;
-import org.topbraid.shacl.engine.Constraint;
-import org.topbraid.shacl.engine.SHACLScriptEngineManager;
-import org.topbraid.shacl.engine.Shape;
-import org.topbraid.shacl.engine.ShapesGraph;
+import org.topbraid.shacl.engine.*;
import org.topbraid.shacl.engine.filters.ExcludeMetaShapesFilter;
import org.topbraid.shacl.expr.NodeExpression;
import org.topbraid.shacl.expr.NodeExpressionFactory;
@@ -77,610 +49,607 @@
import org.topbraid.shacl.vocabulary.DASH;
import org.topbraid.shacl.vocabulary.SH;
+import java.net.URI;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
/**
* A ValidationEngine uses a given shapes graph (represented via an instance of ShapesGraph)
* and performs SHACL validation on a given Dataset.
- *
+ *