blob: 437d85c0057ce089557b013b30ecb48c662e4452 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jena.shacl.engine;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import org.apache.jena.atlas.iterator.Iter;
import org.apache.jena.atlas.lib.NotImplemented;
import org.apache.jena.graph.Graph;
import org.apache.jena.graph.Node;
import org.apache.jena.graph.NodeFactory;
import org.apache.jena.graph.Triple;
import org.apache.jena.shacl.lib.G;
import org.apache.jena.shacl.lib.ShLib;
import org.apache.jena.shacl.parser.ShaclParseException;
import org.apache.jena.shacl.sys.C;
import org.apache.jena.shacl.vocabulary.SHACL;
import org.apache.jena.sparql.core.Prologue;
import org.apache.jena.sparql.path.*;
import org.apache.jena.sparql.path.eval.PathEval;
public class ShaclPaths {
// SPARQL Property path: ex:parent
// SHACL Property path: ex:parent
//
// SPARQL Property path: ^ex:parent
// SHACL Property path: [ sh:inversePath ex:parent ]
//
// SPARQL Property path: ex:parent/ex:firstName
// SHACL Property path: ( ex:parent ex:firstName )
//
// SPARQL Property path: rdf:type/rdfs:subClassOf*
// SHACL Property path: ( rdf:type [ sh:zeroOrMorePath rdfs:subClassOf ] )
//
// SPARQL Property path: ex:father|ex:mother
// SHACL Property path: [ sh:alternativePath ( ex:father ex:mother ) ]
// 2.3.1 SHACL Property Paths
// 2.3.1.1 Predicate Paths
// 2.3.1.2 Sequence Paths
// 2.3.1.3 Alternative Paths
// 2.3.1.4 Inverse Paths
// 2.3.1.5 Zero-Or-More Paths
// 2.3.1.6 One-Or-More Paths
// 2.3.1.7 Zero-Or-One Paths
public static Set<Node> valueNodes(Graph graph, Node node, Path path) {
if ( path instanceof P_Link ) {
// Fast path common case.
Node p = ((P_Link)path).getNode();
return G.setSP(graph, node, p);
}
// Value nodes are a set.
return Iter.toSet(pathReachIter(graph, node, path));
}
private static Iterator<Node> pathReachIter(Graph graph, Node node, Path path) {
if ( path instanceof P_Link ) {
// Fast path common case.
Node p = ((P_Link)path).getNode();
// Not an extended iterator.
return G.iterSP(graph, node, p);
}
return PathEval.eval(graph, node, path, null);
}
private static void toRDF(Graph graph, Node node, Path path) {
throw new NotImplemented();
}
public static Path parsePath(Graph graph, Node node) {
return path(graph, node);
}
// XXX Better error checking.
private static Path path(Graph graph, Node node) {
if ( node.isURI() && ! C.NIL.equals(node) )
return PathFactory.pathLink(node);
if ( isList(graph, node) ) {
List<Node> nodes = G.rdfList(graph, node);
if ( nodes.isEmpty() )
throw new ShaclParseException("Empty list for path sequence");
Path path = null;
// Left deep. Like the jena path parser.
for ( Node n : nodes ) {
Path p = path(graph, n);
if ( path == null )
path = p;
else
path = PathFactory.pathSeq(path, p);
}
// // Nicer - right deep.
// ListIterator<Node> iter = nodes.listIterator(nodes.size());
// while(iter.hasPrevious()) {
// Node n = iter.previous();
// Path p = path(graph, n);
// if ( path == null )
// path = p;
// else
// path = PathFactory.pathSeq(p, path);
// }
return path;
}
if ( node.isBlank() ) {
if ( G.hasProperty(graph, node, SHACL.inversePath) ) {
Node x = G.getSP(graph, node, SHACL.inversePath);
Path p = path(graph, x);
return PathFactory.pathInverse(p);
}
if ( G.hasProperty(graph, node, SHACL.zeroOrMorePath) ) {
Node x = G.getSP(graph, node, SHACL.zeroOrMorePath);
Path p = path(graph, x);
return PathFactory.pathZeroOrMore1(p);
}
if ( G.hasProperty(graph, node, SHACL.oneOrMorePath) ) {
Node x = G.getSP(graph, node, SHACL.oneOrMorePath);
Path p = path(graph, x);
return PathFactory.pathOneOrMore1(p);
}
if ( G.hasProperty(graph, node, SHACL.zeroOrOnePath) ) {
Node x = G.getSP(graph, node, SHACL.zeroOrOnePath);
Path p = path(graph, x);
return PathFactory.pathZeroOrOne(p);
}
if ( G.hasProperty(graph, node, SHACL.alternativePath) ) {
Node x = G.getSP(graph, node, SHACL.alternativePath);
if ( ! isList(graph, x) ) {
throw new ShaclParseException("Not a list for path alternativePath");
}
List<Node> nodes = G.rdfList(graph, x);
Path path = null;
for ( Node n : nodes ) {
Path p = path(graph, n);
if ( path == null )
path = p;
else
path = PathFactory.pathAlt(path, p);
}
return path;
}
}
throw new ShaclParseException("Bad list: "+ShLib.displayStr(node));
}
private static boolean isList(Graph graph, Node node) {
return C.NIL.equals(node) || G.contains(graph, node, C.FIRST, null);
}
/**
* Copy a SHACL path from one graph to another.
* Return the path head to be connected to the rest of the graph.
* This function does not preserve blank nodes in the SHACL path.
*/
public static Node copyPath(Graph srcGraph, Graph dstGraph, Node start) {
Path resultPath = ShaclPaths.parsePath(srcGraph, start);
Node pn = ShaclPaths.pathToRDF(resultPath, dstGraph);
return pn;
}
public static String pathToString(Path path) {
return PathWriter.asString(path);
}
public static String pathToString(Graph graph, Path path) {
Prologue prologue = new Prologue(graph.getPrefixMapping());
return PathWriter.asString(path, prologue);
}
public static Node pathNode(Path path) {
if ( path instanceof P_Link ) {
return ((P_Link)path).getNode();
}
return null;
}
/*
* P_Link
* P_Inverse
* P_Alt
* P_Seq
* P_OneOrMore1, P_OneOrMoreN
* P_ZeroOrMore1, P_ZeroOrMoreN
* P_ZeroOrOne
*/
/*
SPARQL Property path: ex:parent
SHACL Property path: ex:parent
SPARQL Property path: ^ex:parent
SHACL Property path: [ sh:inversePath ex:parent ]
SPARQL Property path: ex:parent/ex:firstName
SHACL Property path: ( ex:parent ex:firstName )
SPARQL Property path: rdf:type/rdfs:subClassOf*
SHACL Property path: ( rdf:type [ sh:zeroOrMorePath rdfs:subClassOf ] )
SPARQL Property path: ex:father|ex:mother
SHACL Property path: [ sh:alternativePath ( ex:father ex:mother ) ]
*/
// public static Pair<Set<Triple>,Node> pathToRDF(Path p) {
// Set<Triple> acc = new HashSet<>();
// Node n = pathToRDF(acc::add, p);
// return Pair.create(acc, n);
// }
/** Create triples for path in dstGraph */
public static Node pathToRDF(Path p, Graph dstGraph) {
Node n = pathToRDF(dstGraph::add, p);
return n;
}
public static Node pathToRDF(Consumer<Triple> acc, Path p) {
PathToRDF proc = new PathToRDF(acc);
p.visit(proc);
return proc.point;
}
static class PathToRDF implements PathVisitor {
private final Consumer<Triple> acc;
private Node point;
PathToRDF(Consumer<Triple> acc) {
this.acc = acc;
}
private Node pathToRDF$(Path p) {
p.visit(this);
return point;
}
@Override
public void visit(P_Link pathNode) {
point = pathNode.getNode();
}
@Override
public void visit(P_ReverseLink pathNode) {
// [ sh:inversePath ex:parent ]
Node n = pathNode.getNode();
point = NodeFactory.createBlankNode();
Triple t = Triple.create(point, SHACL.alternativePath, n);
acc.accept(t);
}
@Override
public void visit(P_Alt pathAlt) {
// [ sh:alternativePath ( elt1 elt2 ) ]
Node n1 = pathToRDF$(pathAlt.getLeft());
Node n2 = pathToRDF$(pathAlt.getRight());
Node list = list(acc, n1, n2);
point = NodeFactory.createBlankNode();
Triple t = Triple.create(point, SHACL.alternativePath, list);
acc.accept(t);
}
private static Node list(Consumer<Triple> acc, Node...elts) {
Node list = C.NIL;
for ( int i = elts.length-1; i >= 0 ; i--) {
Node elt = elts[i];
Node cell = NodeFactory.createBlankNode();
Triple t1 = Triple.create(cell, C.REST, list);
Triple t2 = Triple.create(cell, C.FIRST, elt);
acc.accept(t1);
acc.accept(t2);
list = cell;
}
return list;
}
@Override
public void visit(P_Seq pathSeq) {
// Flatten left and right.
Node n1 = pathToRDF$(pathSeq.getLeft());
Node n2 = pathToRDF$(pathSeq.getRight());
Node list = list(acc, n1, n2);
point = list;
}
private Node step(Node predicate, Path path) {
Node p = pathToRDF$(path);
Node n = NodeFactory.createBlankNode();
Triple t = Triple.create(n, predicate, p);
acc.accept(t);
return n;
}
@Override
public void visit(P_OneOrMore1 path) {
point = step(SHACL.oneOrMorePath, path.getSubPath());
}
@Override
public void visit(P_ZeroOrOne path) {
point = step(SHACL.zeroOrOnePath, path.getSubPath());
}
@Override
public void visit(P_ZeroOrMore1 path) {
point = step(SHACL.zeroOrMorePath, path.getSubPath());
}
@Override
public void visit(P_OneOrMoreN path) {
throw new ShaclParseException("Not part of SHACL: "+path);
}
@Override
public void visit(P_ZeroOrMoreN path) {
throw new ShaclParseException("Not part of SHACL: "+path);
}
@Override
public void visit(P_Inverse inversePath) {
point = step(SHACL.inversePath, inversePath.getSubPath());
}
@Override
public void visit(P_Shortest pathShortest) { throw new ShaclParseException("Not part of SHACL: "+pathShortest); }
@Override
public void visit(P_Multi pathMulti) { throw new ShaclParseException("Not part of SHACL: "+pathMulti); }
@Override
public void visit(P_Distinct pathDistinct) { throw new ShaclParseException("Not part of SHACL: "+pathDistinct); }
@Override
public void visit(P_FixedLength pFixedLength) { throw new ShaclParseException("Not part of SHACL: "+pFixedLength); }
@Override
public void visit(P_Mod pathMod) { throw new ShaclParseException("Not part of SHACL: "+pathMod); }
@Override
public void visit(P_NegPropSet pathNotOneOf) { throw new ShaclParseException("Not part of SHACL: "+pathNotOneOf); }
}
}