All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.jena.shacl.engine.ShaclPaths Maven / Gradle / Ivy

The newest version!
/*
 * 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.io.IndentedWriter;
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.riot.out.NodeFormatter;
import org.apache.jena.riot.system.StreamRDF;
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;
import org.apache.jena.system.G;

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 valueNodes(Graph graph, Node node, Path path) {
        if ( path instanceof P_Link plink) {
            // Fast path common case.
            Node p = plink.getNode();
            return G.allSP(graph, node, p);
        }
        // Value nodes are a set.
        return Iter.toSet(pathReachIter(graph, node, path));
    }

    private static Iterator pathReachIter(Graph graph, Node node, Path path) {
        if ( path instanceof P_Link pLink ) {
            // Fast path common case.
            Node p = pLink.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 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 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 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 void write(IndentedWriter out, Path path, Prologue prologue) {
        PathWriter.write(out, path, prologue);
    }

    public static void write(IndentedWriter out, Path path, NodeFormatter nodeFmt) {
        Node n = pathNode(path);
        if ( n == null )
            out.print(PathWriter.asString(path));
        else
            nodeFmt.format(out, n);
    }


    public static Node pathNode(Path path) {
        if ( path instanceof P_Link pLink ) {
            return pLink.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,Node> pathToRDF(Path p) {
//        Set 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;
    }

    /** Create triples for path, send to a StreamRDF */
    public static Node pathToRDF(Path p, StreamRDF dest) {
        Node n = pathToRDF(dest::triple, p);
        return n;
    }

    public static Node pathToRDF(Consumer acc, Path p) {
        PathToRDF proc = new PathToRDF(acc);
        p.visit(proc);
        return proc.point;
    }

    static class PathToRDF implements PathVisitor {

        private final Consumer acc;
        private Node point;

        PathToRDF(Consumer 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 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); }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy