/* * 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.sparql.util.graph; import java.util.ArrayList ; import java.util.Collection ; import java.util.HashSet ; import java.util.Iterator ; import java.util.List ; import java.util.Set ; import org.apache.jena.atlas.iterator.Iter ; import org.apache.jena.atlas.iterator.IteratorConcat ; import org.apache.jena.atlas.logging.Log ; 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.sparql.core.BasicPattern ; import org.apache.jena.sparql.vocabulary.ListPFunction ; import org.apache.jena.util.iterator.NiceIterator ; import org.apache.jena.vocabulary.RDF ; // Support function for RDF collections (graph level) public class GraphList { // ---------------------- /** Starting at a list element, find the heads of lists it is in */ public static List<Node> listFromMember(GNode gnode) { List<Node> x = new ArrayList<>() ; listFromMember(gnode, x) ; return x ; } public static void listFromMember(GNode gnode, final Collection<Node> acc) { // Get the list nodes for this value. Set<GNode> lists = findCellsWithMember(gnode) ; for ( GNode gn : lists ) { // For each, Reverse to the head while( gn != null ) { GNode gn2 = previous(gn) ; if ( gn2 == null ) { acc.add(gn.node) ; // Finish inner loop break ; } gn = gn2 ; } } } private static Set<GNode> findCellsWithMember(GNode gnode) { Set<GNode> x = new HashSet<>() ; Iterator<Triple> iter = gnode.findable.find(Node.ANY, CAR, gnode.node) ; for ( ; iter.hasNext() ; ) { Triple t = iter.next() ; x.add(new GNode(gnode, t.getSubject())) ; } NiceIterator.close(iter) ; return x ; } private static GNode previous(GNode gnode) { // reverse Node n = getNodeReverse(gnode, CDR) ; if ( n == null ) return null ; return new GNode(gnode, n) ; } private static Node getNodeReverse(GNode gnode, Node arc) { Triple t = getTripleReverse(gnode, arc) ; if ( t == null ) return null ; return t.getSubject() ; } private static Triple getTripleReverse(GNode gnode, Node arc) { Iterator<Triple> iter = gnode.findable.find(Node.ANY, arc, gnode.node) ; if ( ! iter.hasNext() ) return null ; Triple t = iter.next() ; if ( iter.hasNext() ) Log.warn(GraphList.class, "Unusual list: two arcs with same property ("+arc+")") ; NiceIterator.close(iter) ; return t ; } // --------------------------------------------- /** Calculate ?s list:member ?o as if it were a graph.find */ public static Iterator<Triple> listMember(Graph graph, final Node s, Node o) { if ( isAny(s) ) { Set<Node> x = findAllLists(graph) ; IteratorConcat<Triple> iterConcat = new IteratorConcat<>() ; for ( Node s2 : x ) { Iterator<Triple> iter = listMember(graph, s2, o) ; if ( iter.hasNext() ) iterConcat.add(iter); } return iterConcat ; } GNode gn = new GNode(graph, s) ; if ( ! isAny(o) ) { if ( contains(gn, o) ) return Iter.singleton(Triple.create(s, ListPFunction.nListMember, o)) ; else return Iter.nullIterator() ; } List<Node> x = members(gn) ; return Iter.map(x.iterator(), t -> Triple.create(s, ListPFunction.nListMember, t)) ; } private static boolean isAny(Node x) { return x == null || Node.ANY.equals(x) ; } public static List<Node> members(GNode gnode) { List<Node> x = new ArrayList<>() ; members(gnode, x) ; return x ; } public static void members(GNode gnode, final Collection<Node> acc) { if ( ! isListNode(gnode) ) return ; while( ! listEnd(gnode) ) { Node n = car(gnode) ; if ( n != null ) acc.add(n) ; gnode = next(gnode) ; } } public static int length(GNode gnode) { if ( ! isListNode(gnode) ) return -1 ; int len = 0 ; while ( ! listEnd(gnode) ) { len++ ; gnode = next(gnode) ; } return len ; } public static int occurs(GNode gnode, Node item) { return indexes(gnode, item).size() ; } public static boolean contains(GNode gnode, Node item) { return index(gnode, item) >= 0 ; } public static Node get(GNode gnode, int idx) { // if ( idx == 0 ) // return car(gnode) ; // Node n = next(gnode) ; // return get(graph, n, idx-1) ; if ( ! isListNode(gnode) ) return null ; while ( ! listEnd(gnode) ) { if ( idx == 0 ) return car(gnode) ; gnode = next(gnode) ; idx -- ; } return null ; } public static int index(GNode gnode, Node value) { if ( ! isListNode(gnode) ) return -1 ; int idx = 0 ; while ( ! listEnd(gnode) ) { Node v = car(gnode) ; if ( v != null && v.equals(value) ) return idx ; gnode = next(gnode) ; idx++ ; } return -1 ; } public static List<Integer> indexes(GNode gnode, Node value) { List<Integer> x = new ArrayList<>() ; if ( ! isListNode(gnode) ) return x ; int idx = 0 ; while ( ! listEnd(gnode) ) { Node v = car(gnode) ; if ( v != null && v.equals(value) ) x.add(new Integer(idx)) ; gnode = next(gnode) ; idx++ ; } return x ; } public static void triples(GNode gnode, Collection<Triple> acc) { if ( listEnd(gnode) ) return ; Triple t = null ; t = getTriple(gnode, CAR) ; if ( t != null ) acc.add(t) ; t = getTriple(gnode, CDR) ; if ( t != null ) acc.add(t) ; } public static List<Triple> allTriples(GNode gnode) { List<Triple> x = new ArrayList<>() ; allTriples(gnode, x) ; return x ; } public static void allTriples(GNode gnode, Collection<Triple> acc) { if ( ! isListNode(gnode) ) return ; while( ! listEnd(gnode) ) { triples(gnode, acc) ; gnode = next(gnode) ; } } /** Expensive operation to find all the likely looking list heads in a model */ public static Set<Node> findAllLists(Graph graph) { // All except rdf:nil. Set<Node> acc = new HashSet<>() ; // A list head is a node with a rdf:rest from it, not but rdf:rest to it. Iterator<Triple> iter = graph.find(Node.ANY, CDR, Node.ANY) ; try { for ( ; iter.hasNext() ; ) { Triple t = iter.next(); Node node = t.getSubject() ; if ( ! graph.contains(Node.ANY, CDR, node) ) acc.add(node) ; } } finally { NiceIterator.close(iter) ; } // Find any rdf:nil lists (which are not pure tails) iter = graph.find(Node.ANY, Node.ANY, NIL) ; try { for ( ; iter.hasNext() ; ) { Triple t = iter.next(); if ( ! t.getPredicate().equals(CDR) ) { acc.add(NIL) ; break ; } } } finally { NiceIterator.close(iter) ; } if ( graph.contains(NIL, Node.ANY, Node.ANY) ) acc.add(NIL) ; return acc ; } /** Convert a list of nodes into triples, placing them in BPG, returning the head of the list*/ public static Node listToTriples(List<Node> list, BasicPattern bgp) { // List ... if ( list.size() == 0 ) return RDF.Nodes.nil ; Node head = NodeFactory.createBlankNode() ; Node n = head ; for ( Node elt : list ) { // Cell: Node consCell = NodeFactory.createBlankNode() ; // Last cell to this one. Triple t = new Triple(n, RDF.Nodes.rest, consCell) ; Triple t1 = new Triple(consCell, RDF.Nodes.first, elt) ; n = consCell ; bgp.add(t) ; bgp.add(t1) ; } // Finish list. Triple t = new Triple(n, RDF.Nodes.rest, RDF.Nodes.nil) ; bgp.add(t) ; return head ; } private static final Node CAR = RDF.first.asNode() ; private static final Node CDR = RDF.rest.asNode() ; private static final Node NIL = RDF.nil.asNode() ; private static GNode next(GNode gnode) { return new GNode(gnode, cdr(gnode)) ; } public static boolean isListNode (GNode gnode) { return gnode.node.equals(NIL) || isCons(gnode) ; } private static boolean isCons (GNode gnode) { return gnode.findable.contains(gnode.node, CDR, null) ; } private static boolean listEnd (GNode gnode) { return gnode.node == null || gnode.node.equals(NIL) ; } private static Node car(GNode gnode) { return getNode(gnode, CAR) ; } private static Node cdr(GNode gnode) { return getNode(gnode, CDR) ; } private static Node getNode(GNode gnode, Node arc) { if ( listEnd(gnode) ) return null ; Triple t = getTriple(gnode, arc) ; if ( t == null ) return null ; return t.getObject() ; } private static Triple getTriple(GNode gnode, Node arc) { if ( listEnd(gnode) ) return null ; Iterator<Triple> iter = gnode.findable.find(gnode.node, arc, Node.ANY) ; if ( ! iter.hasNext() ) return null ; Triple t = iter.next() ; if ( iter.hasNext() ) Log.warn(GraphList.class, "Unusual list: two arcs with same property ("+arc+")") ; NiceIterator.close(iter) ; return t ; } }