diff --git a/jena-arq/src/main/java/org/apache/jena/system/G.java b/jena-arq/src/main/java/org/apache/jena/system/G.java index 7392a0b0c24..e9c1cab044e 100644 --- a/jena-arq/src/main/java/org/apache/jena/system/G.java +++ b/jena-arq/src/main/java/org/apache/jena/system/G.java @@ -40,8 +40,6 @@ import org.apache.jena.sparql.core.Quad; import org.apache.jena.sparql.engine.iterator.IterAbortable; import org.apache.jena.sparql.graph.NodeConst; -import org.apache.jena.sparql.util.graph.GNode; -import org.apache.jena.sparql.util.graph.GraphList; import org.apache.jena.util.iterator.ExtendedIterator; import org.apache.jena.util.iterator.WrappedIterator; @@ -270,7 +268,7 @@ public static Node getZeroOrOneSP(Graph graph, Node subject, Node predicate) { */ public static Node getPO(Graph graph, Node predicate, Node object) { Objects.requireNonNull(graph, "graph"); - return object(first(find(graph, Node.ANY, predicate, object))); + return subject(first(find(graph, Node.ANY, predicate, object))); } /** @@ -287,7 +285,7 @@ public static Node getOnePO(Graph graph, Node predicate, Node object) { */ public static boolean hasOnePO(Graph graph, Node predicate, Node object) { Objects.requireNonNull(graph, "graph"); - return findUniqueTriple(graph, Node.ANY, predicate, object) != null; + return findZeroOneTriple(graph, Node.ANY, predicate, object) != null; } /** @@ -374,6 +372,12 @@ public static List listSP(Graph graph, Node subject, Node predicate) { return iterSP(graph, subject, predicate).toList(); } + /** Return a set of all objects for subject-predicate */ + public static Set allSP(Graph graph, Node subject, Node predicate) { + Objects.requireNonNull(graph, "graph"); + return find(graph, subject, predicate, null).mapWith(Triple::getObject).toSet(); + } + /** Count matches of subject-predicate (which can be wildcards). */ public static long countSP(Graph graph, Node subject, Node predicate) { Objects.requireNonNull(graph, "graph"); @@ -399,6 +403,12 @@ public static List listPO(Graph graph, Node predicate, Node object) { return iterPO(graph, predicate, object).toList(); } + /** Return a set of all subjects for predicate-object */ + public static Set allPO(Graph graph, Node predicate, Node object) { + Objects.requireNonNull(graph, "graph"); + return find(graph, null, predicate, object).mapWith(Triple::getSubject).toSet(); + } + /** Count matches of predicate-object (which can be wildcards). */ public static long countPO(Graph graph, Node predicate, Node object) { Objects.requireNonNull(graph, "graph"); @@ -499,34 +509,30 @@ public static Set typesOfNodeAsSet(Graph graph, Node node) { public static List rdfList(Graph graph, Node node) { Objects.requireNonNull(graph, "graph"); Objects.requireNonNull(node, "node"); - GNode gNode = GNode.create(graph, node); - if ( ! GraphList.isListNode(gNode) ) - return null; - return GraphList.members(gNode); + List nodes = GList.members(graph, node); + return nodes; } /** Return a the length of an RDF list. */ public static int listLength(Graph graph, Node node) { Objects.requireNonNull(graph, "graph"); Objects.requireNonNull(node, "node"); - GNode gNode = GNode.create(graph, node); - if ( ! GraphList.isListNode(gNode) ) + if ( ! GList.isListNode(graph, node) ) return -1; - return GraphList.length(gNode); + return (int)GList.listLength(graph, node); } /** - * Return a java list where the {@code node} is an RDF list of nodes or a single - * node (returned a singleton list). + * Return a java list where the {@code node} is an RDF list of nodes, + * of if the node is not a list, return the node as a list of one. */ public static List getOneOrList(Graph graph, Node node) { Objects.requireNonNull(graph, "graph"); Objects.requireNonNull(node, "node"); - GNode gNode = GNode.create(graph, node); // An element on its own is a list of one - if ( ! GraphList.isListNode(gNode) ) + if ( ! GList.isListNode(graph, node) ) return List.of(node); - return GraphList.members(gNode); + return GList.members(graph, node); } // Sub-class / super-class @@ -647,18 +653,6 @@ private static void accNodesOfTypes(Collection acc, Graph graph, Collectio ); } - /** Return a set of all objects for subject-predicate */ - public static Set allSP(Graph graph, Node subject, Node predicate) { - Objects.requireNonNull(graph, "graph"); - return find(graph, subject, predicate, null).mapWith(Triple::getObject).toSet(); - } - - /** Return a set of all subjects for predicate-object */ - public static Set allPO(Graph graph, Node predicate, Node object) { - Objects.requireNonNull(graph, "graph"); - return find(graph, null, predicate, object).mapWith(Triple::getSubject).toSet(); - } - // --- Graph walking. /** Count the number of in-arc to an object */ @@ -841,15 +835,15 @@ public static void copyGraphSrcToDst(Graph src, Graph dst) { /** * Creates a copy of the given graph. - * If the graph implements Copyable then the copy method is called. + * If the graph implements {@code Copyable} then the copy method is called. * Otherwise, a new system default memory-based graph is created and the triples are copied * into it. * @param src the graph to copy * @return a copy of the graph */ - @SuppressWarnings("unchecked") public static Graph copy(Graph src) { if(src instanceof Copyable copyable) { + @SuppressWarnings("unchecked") Copyable copyableGraph = (Copyable)copyable; return copyableGraph.copy(); } diff --git a/jena-arq/src/main/java/org/apache/jena/system/GList.java b/jena-arq/src/main/java/org/apache/jena/system/GList.java new file mode 100644 index 00000000000..a6ac2469718 --- /dev/null +++ b/jena-arq/src/main/java/org/apache/jena/system/GList.java @@ -0,0 +1,499 @@ +/* + * 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 + * + * https://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. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.apache.jena.system; + +import java.util.*; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.apache.jena.graph.Graph; +import org.apache.jena.graph.Node; +import org.apache.jena.graph.Triple; +import org.apache.jena.sparql.util.graph.GNode; +import org.apache.jena.sparql.util.graph.GraphList; +import org.apache.jena.sys.JenaSystem; +import org.apache.jena.util.iterator.ExtendedIterator; +import org.apache.jena.vocabulary.RDF; + +/** + * Operations on RDF Collections (RDF Lists). + *

+ * Operations may throw {@link RDFDataException} if the list is not well-formed. + *

+ * Operations check for exactly {@code rdf:first}, and exactly one {@code rdf:rest}, + * but do not check for cycles unless noted in their javadoc. + *

+ * To get a list of all the items in a list, use {@link #members} operations, which + * does check for cycles, or if the list is known to be well-formed, one of the {@link #elements} + * operations, which do not check for cycles. + *

+ * The {@link #contains} operation does not check for cycles so that repeated use on + * a list does not perform duplicate checking work repeatedly. + *

+ * If a list is potentially mal-formed by having a cycle, check first with + * {@link #isWellformedList(Graph, Node)} or + * {@link #isWellformedListEx(Graph, Node)}. + *

+ * List validation only needs to be performed once. + *

+ * List arising from parsing Turtle or TriG syntax for lists will be cycle-free. + *

+ * This class is not public API. + * + * @see GraphList - uses a findable abstaction ({@link GNode}to work on a graph or lists of triples. + */ +public class GList { + + static { JenaSystem.init(); } + + private static final Node CAR = RDF.Nodes.first; + private static final Node CDR = RDF.Nodes.rest; + private static final Node NIL = RDF.Nodes.nil; + private static final Node RDF_TYPE = RDF.Nodes.type; + private static final long BAD_LIST = -1; + private static final Function stdExceptionMaker = RDFDataException::new; + + // forEach (unchecked). + +// occurs(GNode, Node) +// contains(GNode, Node) +// get(GNode, int) +// index(GNode, Node) +// indexes(GNode, Node) +// triples(GNode, Collection) +// allTriples(GNode) +// allTriples(GNode, Collection) +// findAllLists(Graph) +// listToTriples(List, BasicPattern) + + + /** + * Check for a valid list; throw a {@link RDFDataException} if the list is bad in some way. + * The {@link RDFDataException} message indicates the problem with the list. + * @param graph + * @param list + */ + public static void isWellformedListEx(Graph graph, Node list) { + Objects.requireNonNull(graph); + Objects.requireNonNull(list); + forEachMember(graph, list, false, null, stdExceptionMaker); + } + + /** Check for a valid list; throw a {@link RDFDataException} if the list is bad in some way. + * The {@link RDFDataException} message indicates the problem with the list. + * @param graph + * @param list + * @param closedCells - whether to check that only {@code rdf:first} and {@code rdf:next} are used on a list cell + */ + public static void isWellformedListEx(Graph graph, Node list, boolean closedCells) { + Objects.requireNonNull(graph); + Objects.requireNonNull(list); + forEachMember(graph, list, closedCells, null, stdExceptionMaker); + } + + /** + * Check for a valid list; return true if well-formed else return false. + * Use {@link #isWellformedListEx(Graph, Node)} to get an except with an + * informational message about the problem detected. + * + * @param graph + * @param list + */ + public static boolean isWellformedList(Graph graph, Node list) { + Objects.requireNonNull(graph); + Objects.requireNonNull(list); + return forEachMember(graph, list, false, null, null) != BAD_LIST; + } + + /** Check for a valid list; return true if well-formed else return false. + * Use {@link #isWellformedListEx(Graph, Node, boolean)} to get an except with an + * informational message about the problem detected. + * @param graph + * @param list + * @param closedCells - whether to check that only rdf:first and rdf:next are used on a list cell + */ + public static boolean isWellformedList(Graph graph, Node list, boolean closedCells) { + Objects.requireNonNull(graph); + Objects.requireNonNull(list); + return forEachMember(graph, list, closedCells, null, null) != BAD_LIST; + } + + /** + * Return the members of a well-formed RDF list. + * @throws RDFDataException if the list is not well-formed, including if it has a cycle. + */ + public static List members(Graph graph, Node list) { + Objects.requireNonNull(graph); + Objects.requireNonNull(list); + if ( list.equals(NIL) ) + return List.of(); + List acc = new ArrayList<>(); + forEachMember(graph, list, false, acc::add, stdExceptionMaker); + return acc; + } + + /** + * Accumulate the members of a well-formed RDF list. + * @throws RDFDataException if the list is not well-formed, including if it has a cycle. + */ + public static void members(Graph graph, Node list, Collection acc) { + Objects.requireNonNull(graph); + Objects.requireNonNull(list); + if ( list.equals(NIL) ) + return; + forEachMember(graph, list, false, acc::add, stdExceptionMaker); + } + + /** + * Return the elements of a well-formed RDF list. + * This operation does not check for cycles. + * {@link #members} is has the same result and incorporates a well-formness cycle check. + */ + public static List elements(Graph graph, Node list) { + Objects.requireNonNull(graph); + Objects.requireNonNull(list); + + if ( list.equals(NIL) ) + return List.of(); + List acc = new ArrayList<>(); + elements(graph, list, acc); + return acc; + } + + /** + * Accumulate the elements of a well-formed RDF list. + * This operation does not check for cycles. + */ + public static void elements(Graph graph, Node list, Collection acc) { + Objects.requireNonNull(graph); + Objects.requireNonNull(list); + Objects.requireNonNull(acc); + if ( list.equals(NIL) ) + return; + Node cell = list; + while ( ! listEnd(graph, cell) ) { + Node elt = G.getOneSP(graph, cell, CAR); + Node next = G.getOneSP(graph, cell, CDR); + acc.add(elt); + cell = next; + } + } + + /** + * Run an action on each element of a list, in order. + * @see #iterator(Graph, Node) + */ + public static void forEach(Graph graph, Node list, Consumer action) { + Objects.requireNonNull(graph); + Objects.requireNonNull(list); + Objects.requireNonNull(action); + if ( list.equals(NIL) ) + return; + // No cycle checking. + long countElts = 0; + Node cell = list; + while(!listEnd(graph, cell) ) { + Node elt = G.getOneSP(graph, cell, CAR); + Node next = G.getOneSP(graph, cell, CDR); + cell = next; + action.accept(elt); + } + } + + /** + * Return the length of a well-formed list. + * This operations assumes the list is well-formed. + * See {@link #isWellformedList(Graph, Node)} for check a list. + */ + public static long listLength(Graph graph, Node list) { + Objects.requireNonNull(graph); + Objects.requireNonNull(list); + if ( list.equals(NIL) ) + return 0; + // No cycle checking. + long countElts = 0; + Node cell = list; + while(!listEnd(graph, cell) ) { + // Check integrity. + G.hasOneSP(graph, cell, CDR); + Node next = G.getOneSP(graph, cell, CDR); + cell = next; + countElts++; + } + return countElts; + } + + // -------- + + /** + * Return an iterator over the list. + * The iterator does not check for well-formed lists. + * Ensure the list is well-formed - {@link #isWellformedListEx(Graph, Node)}. + * + * @see #forEach + */ + public static Iterator iterator(Graph graph, Node list) { + Objects.requireNonNull(graph); + Objects.requireNonNull(list); + return new RDFListIterator(graph, list); + } + + /** + * Return the first index of an occurrence of a node in a list. + * Return -1 for not in list. + * Indexes start at 0. + * @param graph + * @param list + * @param item to check + */ + public static int indexOf(Graph graph, Node list, Node item) { + Objects.requireNonNull(graph); + Objects.requireNonNull(list); + Objects.requireNonNull(item); + Node cell = list; + int index = 0; + while( !listEnd(graph, cell) ) { + Node elt = G.getOneSP(graph, cell, CAR); + Node next = G.getOneSP(graph, cell, CDR); + cell = next; + if ( item.sameTermAs(elt) ) + return index; + index++; + } + return -1; + } + + /** + * Return whether an item is in the list. + * Return for not in list. + * @param graph + * @param list + * @param item to check for + */ + public static boolean contains(Graph graph, Node list, Node item) { + return indexOf(graph, list, item) >= 0; + } + + /** + * Return the list element at an index (indexes start at 0). + *

+ * Do not use this to iterate over a list - consider using {@link #members} to + * collect the list in a single pass. + */ + public static Node get(Graph graph, Node list, int index) { + Objects.requireNonNull(graph); + Objects.requireNonNull(list); + if ( index < 0 ) + return null; + Node cell = list; + Node elt = null ; + for ( int i = 0 ; i <= index ; i++ ) { + if ( listEnd(graph, cell) ) + return null; + elt = G.getOneSP(graph, cell, CAR); + Node next = G.getOneSP(graph, cell, CDR); + cell = next; + } + return elt; + } + + public static boolean isListNode(Graph graph, Node node) { + if ( node.equals(NIL) ) + return true; + // Well-formedness check. + return isCons(graph, node); + } + + /** + * Run over a list, checking for cycles and well-formed list cons cells. + * Call an action on each element if {@ocde elementAction} is not null. + * For a bad list, throw an exception or return -1 if {@code exceptionMaker} is null. + */ + private static long forEachMember(Graph graph, Node node, + boolean closedCells, + Consumer elementAction, + Function exceptionMaker) { + Node cell = node; + Set visited = new HashSet<>(); + long numListElements = 0 ; + while (!listEnd(graph, cell)) { + if ( visited.contains(cell) ) { + badListEx("Cyclic list", cell, exceptionMaker); + return -1; + } + visited.add(cell); + // rdf:first elt; rdf:rest next; + Node elt = null; + Node next = null; + ExtendedIterator iterSP = G.find(graph, cell, null, null); + try { + while(iterSP.hasNext()) { + Triple t = iterSP.next(); + Node predicate = t.getPredicate(); + + if ( CAR.equals(predicate) ) { + if ( elt != null ) { + badListEx("List contains an element with two rdf:first", cell, exceptionMaker); + return -1; + } + elt = t.getObject(); + continue; + } + if ( CDR.equals(predicate) ) { + if ( next != null ) { + badListEx("List contains an element with two rdf:next", cell, exceptionMaker); + return -1; + } + next = t.getObject(); + continue; + } + if ( RDF_TYPE.equals(predicate) ) { + // Allow rdf:type on a list cons cell. + continue; + } + + if ( closedCells ) { + badListEx("List contains non-list triples", cell, exceptionMaker); + return -1; + } + } + + if ( elt == null ) { + badListEx("List contains an element with no rdf:first", node, exceptionMaker); + return -1; + } + if ( next == null ) { + badListEx("List contains an element with no rdf:next", node, exceptionMaker); + return -1; + } + // Valid list element + numListElements++; + if ( elementAction != null ) + elementAction.accept(elt); + cell = next; + } finally { iterSP.close(); } + } + return numListElements; + } + + private static class RDFListIterator implements Iterator { + + private Function exceptionMaker = RDFDataException::new; + private final Graph graph; + private Node current = null; + + RDFListIterator(Graph graph, Node node) { + this.graph = graph; + this.current = node; + //isWellformedListEx(graph, node); + } + + @Override + public boolean hasNext() { + return ! listEnd(graph, current); + } + + @Override + public Node next() { + // Assume well-formed. + Node elt = G.getOneSP(graph, current, CAR); + Node next = G.getOneSP(graph, current, CDR); + current = next; + return elt; + } + + // Very complicated for the sake of walking the list once. + // Instead, expect the caller to have validated the list once before list operations. +// @Override +// public Node next() { +// if ( listEnd(graph, current) ) +// throw new NoSuchElementException(); +// if ( true ) { +// // Checking. +// ExtendedIterator iterSP = G.find(graph, current, null, null); +// try { +// Node elt = null; +// Node next = null; +// boolean closedCells = true; +// +// while(iterSP.hasNext()) { +// Triple t = iterSP.next(); +// Node predicate = t.getPredicate(); +// +// if ( CAR.equals(predicate) ) { +// if ( elt != null ) { +// badListEx("List contains an element with two rdf:first", null, exceptionMaker); +// throw new NoSuchElementException(); +// } +// elt = t.getObject(); +// continue; +// } +// if ( CDR.equals(predicate) ) { +// if ( next != null ) { +// badListEx("List contains an element with two rdf:next", null, exceptionMaker); +// throw new NoSuchElementException(); +// } +// next = t.getObject(); +// continue; +// } +// if ( RDF_TYPE.equals(predicate) ) { +// // Allow rdf:type on a list cons cell. +// continue; +// } +// +// if ( closedCells ) { +// badListEx("List contains non-list triples", null, exceptionMaker); +// throw new NoSuchElementException(); +// } +// } +// current = next; +// return elt; +// } finally { iterSP.close(); } +// +// } else { +// // Assume well-formed. +// Node elt = G.getOneSP(graph, current, CAR); +// Node next = G.getOneSP(graph, current, CDR); +// current = next; +// return elt; +// } +// } + } + + private static boolean isCons (Graph graph, Node node) { + return G.hasOneSP(graph, node, CDR) && G.hasOneSP(graph, node, CAR); + } + + private static boolean listEnd(Graph graph, Node node) { + return node == null || node.equals(NIL); + } + + /** Check for a valid list; return true if the list is well-formed else return false + * @param graph + * @param node + * @param closedCells - whether to check that only rdf:first and rdf:next are used on a list cell + */ + private static void badListEx(String msg, Node node, Function exceptionMaker ) { + if ( exceptionMaker != null ) + throw exceptionMaker.apply(msg); + } +} diff --git a/jena-arq/src/main/java/org/apache/jena/system/ThreadTxn.java b/jena-arq/src/main/java/org/apache/jena/system/ThreadTxn.java index d9f10cdb5b3..e6dfb2507b4 100644 --- a/jena-arq/src/main/java/org/apache/jena/system/ThreadTxn.java +++ b/jena-arq/src/main/java/org/apache/jena/system/ThreadTxn.java @@ -33,18 +33,17 @@ * forked transaction sees is outside the creating thread which may itself be in a * transaction. Warning: creating a write transaction inside a write transaction * will cause deadlock. - */ + */ public class ThreadTxn { - /** Create a thread-backed delayed transaction action. + /** Create a thread-backed delayed transaction action. * Call {@link ThreadAction#run} to perform the read transaction. */ public static ThreadAction threadTxn(Transactional trans, TxnType txnType, Runnable action) { return create(trans, txnType, action, true, true) ; } - - /** Create a thread-backed delayed READ transaction action. + /** Create a thread-backed delayed READ transaction action. * Call {@link ThreadAction#run} to perform the read transaction. */ public static ThreadAction threadTxnRead(Transactional trans, Runnable action) { @@ -59,7 +58,7 @@ public static ThreadAction threadTxnRead(Transactional trans, Runnable action) { public static ThreadAction threadTxnWrite(Transactional trans, Runnable action) { return threadTxn(trans, TxnType.WRITE, action) ; } - + /** Create a thread-backed delayed WRITE-abort action (mainly for testing). */ public static ThreadAction threadTxnWriteAbort(Transactional trans, Runnable action) { return create(trans, TxnType.WRITE, action, true, false) ; @@ -71,11 +70,11 @@ private static ThreadAction create(Transactional trans, TxnType txnType, Runnabl , action , afterAction(trans, txnType, isCommitAfter) ) ; } - + private static Runnable beforeAction(Transactional trans, TxnType txnType, boolean isCommit) { return ()-> trans.begin(txnType) ; } - + private static Runnable afterAction(Transactional trans, TxnType txnType, boolean isCommit) { return () -> { // Finish transaction (if no throwable) diff --git a/jena-arq/src/main/java/org/apache/jena/system/Vocab.java b/jena-arq/src/main/java/org/apache/jena/system/Vocab.java index 514eed015fe..6f0ee37d6df 100644 --- a/jena-arq/src/main/java/org/apache/jena/system/Vocab.java +++ b/jena-arq/src/main/java/org/apache/jena/system/Vocab.java @@ -21,24 +21,20 @@ package org.apache.jena.system; -import org.apache.jena.rdf.model.Property ; -import org.apache.jena.rdf.model.Resource ; -import org.apache.jena.rdf.model.ResourceFactory ; +import org.apache.jena.rdf.model.Property; +import org.apache.jena.rdf.model.Resource; +import org.apache.jena.rdf.model.ResourceFactory; -public class Vocab -{ - public static Resource type(String namespace, String localName) - { - return ResourceFactory.createResource(namespace+localName) ; +public class Vocab { + public static Resource type(String namespace, String localName) { + return ResourceFactory.createResource(namespace + localName); } - - public static Resource resource(String namespace, String localName) - { - return ResourceFactory.createResource(namespace+localName) ; + + public static Resource resource(String namespace, String localName) { + return ResourceFactory.createResource(namespace + localName); } - public static Property property(String namespace, String localName) - { - return ResourceFactory.createProperty(namespace+localName) ; + public static Property property(String namespace, String localName) { + return ResourceFactory.createProperty(namespace + localName); } } diff --git a/jena-arq/src/test/java/org/apache/jena/system/TS_System.java b/jena-arq/src/test/java/org/apache/jena/system/TS_System.java index 292ea12e20c..8e2732d4468 100644 --- a/jena-arq/src/test/java/org/apache/jena/system/TS_System.java +++ b/jena-arq/src/test/java/org/apache/jena/system/TS_System.java @@ -26,15 +26,25 @@ @Suite @SelectClasses({ - TestCounter.class + TestPrefixes.class + , TestPrefixLib.class + + , TestG_Basic.class + , TestG_SP .class + , TestG_PO .class + , TestG_Triple.class + , TestG_Quad.class + , TestG_Classes.class + + , TestGCopy.class + + , TestTxn.class , TestThreadAction.class , TestTxnLifecycle.class , TestTxnOp.class - , TestTxn.class , TestTxnThread.class + , TestTxnCounter.class , TestReadXML.class - , TestPrefixes.class - , TestPrefixLib.class , TestRDFStarTranslation.class , TestFindNamespaces.class }) diff --git a/jena-arq/src/test/java/org/apache/jena/system/GTest.java b/jena-arq/src/test/java/org/apache/jena/system/TestGCopy.java similarity index 99% rename from jena-arq/src/test/java/org/apache/jena/system/GTest.java rename to jena-arq/src/test/java/org/apache/jena/system/TestGCopy.java index ee3361388fa..d877548cb43 100644 --- a/jena-arq/src/test/java/org/apache/jena/system/GTest.java +++ b/jena-arq/src/test/java/org/apache/jena/system/TestGCopy.java @@ -33,7 +33,7 @@ import org.apache.jena.memvalue.GraphMemValue; import org.apache.jena.sparql.sse.SSE; -public class GTest { +public class TestGCopy { @Test public void copy() { diff --git a/jena-arq/src/test/java/org/apache/jena/system/TestGList.java b/jena-arq/src/test/java/org/apache/jena/system/TestGList.java new file mode 100644 index 00000000000..6c62691132a --- /dev/null +++ b/jena-arq/src/test/java/org/apache/jena/system/TestGList.java @@ -0,0 +1,469 @@ +/* + * 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 + * + * https://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. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.apache.jena.system; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; +import java.util.Iterator; +import java.util.ArrayList; +import java.util.function.BiConsumer; + +import org.junit.jupiter.api.Test; + +import org.apache.jena.graph.Graph; +import org.apache.jena.graph.Node; +import org.apache.jena.graph.NodeFactory; +import org.apache.jena.riot.Lang; +import org.apache.jena.riot.RDFParser; +import org.apache.jena.sparql.graph.GraphZero; +import org.apache.jena.sparql.sse.SSE; +import org.apache.jena.sys.JenaSystem; +import org.apache.jena.vocabulary.RDF; + +public class TestGList { + + static { JenaSystem.init(); } + + private static Node x = SSE.parseNode(":x"); + private static Node p = SSE.parseNode(":p"); + private static Node elt1 = SSE.parseNode("'A'"); + private static Node elt2 = SSE.parseNode("'B'"); + + @Test public void glist_members_01() { + Graph graph = GraphZero.instance(); + List members = members(graph, RDF.Nodes.nil); + assertTrue(members.isEmpty()); + } + + @Test public void glist_members_02() { + test("()", (graph, list)->{ + List members = members(graph, list); + assertTrue(members.isEmpty()); + }); + } + + @Test public void glist_members_03() { + test("('A')", (graph, list)->{ + List members = members(graph, list); + assertEquals(1, members.size()); + assertEquals(elt1, members.getFirst()); + }); + } + + @Test public void glist_members_04() { + test("('A' 'B')", (graph, list)->{ + List members = members(graph, list); + assertEquals(2, members.size()); + assertEquals(elt1, members.getFirst()); + assertEquals(elt2, members.getLast()); + }); + } + + @Test public void glist_members_05() { + test("((:a :b :c))", (graph, list)->{ + List members = members(graph, list); + assertEquals(1, members.size()); + assertTrue(members.getFirst().isBlank()); + + Node listInner = members.getFirst(); + List membersInner = members(graph, listInner); + assertEquals(3, membersInner.size()); + }); + } + + @Test public void glist_members_bad_01() { + testEx("[ rdf:first 1 ]", (graph, list)->members(graph, list)); + } + + @Test public void glist_members_bad_02() { + testEx("[ rdf:rest rdf:nil ]", (graph, list)->members(graph, list)); + } + + @Test public void glist_members_bad_03() { + testWellformed("[ rdf:first 1 ; rdf:rest () ]"); + testEx("[ rdf:first 1 ; rdf:first 2 ; rdf:rest () ]", (graph, list)->members(graph, list)); + } + + @Test public void glist_members_bad_04() { + testWellformed("[ rdf:first 1 ; rdf:rest ('A') ]"); + testEx("[ rdf:first 1 ; rdf:rest ('A') ; rdf:rest () ]", (graph, list)->members(graph, list)); + } + + @Test public void glist_wellformed_01() { + testWellformed("()"); + } + + @Test public void glist_wellformed_02() { + testWellformed("('A')"); + } + + // Allow rdf:type + @Test public void glist_wellformed_03() { + testWellformed("[ rdf:first 1 ; rdf:rest rdf:nil; rdf:type :Cell]"); + } + + // Allow rdf:type + @Test public void glist_wellformed_04() { + testWellformed(""" + [ rdf:first 1 ; + rdf:rest [ + rdf:first 1 ; + rdf:rest rdf:nil ; + rdf:type :Cell + ] + ] + """); + } + + @Test public void glist_wellformed_bad_01() { + testNotWellformed("[ rdf:first 1 ]"); + } + + @Test public void glist_wellformed_bad_02() { + testNotWellformed("[ rdf:rest rdf:nil ]"); + } + + @Test public void glist_wellformed_bad_03() { + testWellformed("[ rdf:first 1 ; rdf:rest () ]"); + testNotWellformed("[ rdf:first 1 ; rdf:first 2 ; rdf:rest () ]"); + } + + @Test public void glist_wellformed_bad_04() { + testWellformed("[ rdf:first 1 ; rdf:rest ('A') ]"); + testNotWellformed("[ rdf:first 1 ; rdf:rest ('A') ; rdf:rest () ]"); + } + + @Test public void glist_wellformed_bad_10() { + testWellformed("[ rdf:first 1 ; rdf:rest [ rdf:first 'A' ; rdf:rest rdf:nil ] ]"); + testNotWellformed(""" + [ rdf:first 1 ; + rdf:rest [ + rdf:first 'A' ; + rdf:first 'B' ; + rdf:rest rdf:nil + ] + ] + """); + } + + @Test public void glist_wellformed_bad_11() { + testWellformed(""" + [ rdf:first 1 ; + rdf:rest [ + rdf:first 'A' ; + rdf:rest rdf:nil + ] + ] + """); + testNotWellformed(""" + [ rdf:first 1 ; + rdf:rest [ + rdf:first 'A' ; + rdf:rest 'B' ; + rdf:rest rdf:nil ] + ] + """); + } + + @Test public void glist_wellformed_bad_12() { + testWellformed("[ rdf:first 1 ; rdf:rest [ rdf:first 'A' ; rdf:rest rdf:nil ] ]"); + testNotWellformed("[ rdf:first 1 ; rdf:rest [ rdf:first 'A' ; ] ]"); + } + + @Test public void glist_wellformed_bad_13() { + testWellformed("[ rdf:first 1 ; rdf:rest [ rdf:first 'A' ; rdf:rest rdf:nil ] ]"); + testNotWellformed("[ rdf:first 1 ; rdf:rest [ rdf:rest rdf:nil ] ]"); + } + + @Test public void glist_wellformed_bad_14() { + testWellformed("[ rdf:first 1 ; rdf:rest [ rdf:first 'A' ; rdf:rest rdf:nil ] ]"); + testNotWellformed("[ rdf:first 1 ; rdf:rest [ ] ]"); + } + + @Test public void glist_wellformed_cyclic_01() { + String graphStr = """ + :head rdf:first 0 ; rdf:rest :x . + :x rdf:first 1 ; rdf:rest :y . + :y rdf:first 1 ; rdf:rest :x . + """; + Graph graph = graph(graphStr); + Node list = x; + boolean b = GList.isWellformedList(graph, list); + assertFalse(b); + assertThrows(RDFDataException.class, ()->GList.isWellformedListEx(graph, list)); + } + + @Test public void glist_isWellformedEx_message_no_rest() { + // A cell with rdf:first but no rdf:rest should cause the "no rdf:next" error message + String s = "[ rdf:first 1 ]"; + String g = """ + PREFIX : + PREFIX rdf: + :x :p """ + s; + Graph graph = RDFParser.fromString(g, Lang.TURTLE).toGraph(); + Node list = G.getOneSP(graph, SSE.parseNode(":x"), SSE.parseNode(":p")); + RDFDataException ex = assertThrows(RDFDataException.class, ()->GList.isWellformedListEx(graph, list)); + String msg = ex.getMessage(); + assertTrue(msg != null && msg.contains("no rdf:next"), "Expected exception message to mention 'no rdf:next', got: " + msg); + } + + @Test public void glist_length_01() { + testlength("()", 0); + } + + @Test public void glist_length_02() { + testlength("(1 2 3)", 3); + } + + @Test public void glist_length_03() { + testlength("( () )", 1); + } + + @Test public void glist_length_04() { + testlength("( (:a :b) )", 1); + } + + @Test public void glist_length_05() { + testlength("( :A (:a :b) )", 2); + } + + @Test public void glist_length_06() { + testlength("( (:a :b) :Z)", 2); + } + + @Test public void glist_length_bad_01() { + assertThrows(RDFDataException.class, ()->testlength("[ rdf:first 1 ; rdf:rest [ rdf:first 'A' ; ] ]", -1)); + } + + @Test public void glist_indexOf_01() { + test("('A')", (graph, list)->{ + Node elt = NodeFactory.createLiteralString("A"); + int idx = GList.indexOf(graph, list, elt); + assertEquals(0, idx); + }); + } + + @Test public void glist_indexOf_02() { + test("('A')", (graph, list)->{ + Node elt = NodeFactory.createLiteralString("B"); + int idx = GList.indexOf(graph, list, elt); + assertEquals(-1, idx); + }); + } + + @Test public void glist_indexOf_03() { + test("('A' 'A' 'A')", (graph, list)->{ + Node elt = NodeFactory.createLiteralString("A"); + int idx = GList.indexOf(graph, list, elt); + assertEquals(0, idx); + }); + } + + + @Test public void glist_indexOf_04() { + // Does not recurse in lists in lists. + test("(('A'))", (graph, list)->{ + Node elt = NodeFactory.createLiteralString("A"); + int idx = GList.indexOf(graph, list, elt); + assertEquals(-1, idx); + }); + } + + @Test public void glist_indexOf_05() { + test("()", (graph, list)->{ + Node elt = NodeFactory.createLiteralString("A"); + int idx = GList.indexOf(graph, list, elt); + assertEquals(-1, idx); + }); + } + + @Test public void glist_get_01() { + test("('A')", (graph, list)->{ + Node x = GList.get(graph, list, 0); + assertEquals("A", x.getLiteral().getLexicalForm()); + }); + } + + @Test public void glist_get_02() { + test("('A')", (graph, list)->{ + Node x = GList.get(graph, list, 1); + assertNull(x); + }); + } + + @Test public void glist_get_03() { + test("()", (graph, list)->{ + Node x = GList.get(graph, list, 0); + assertNull(x); + }); + } + + @Test public void glist_get_04() { + test("('A' 'B' 'C')", (graph, list)->{ + Node x = GList.get(graph, list, 1); + assertEquals("B", x.getLiteral().getLexicalForm()); + }); + } + + @Test public void glist_get_negative_index() { + test("('A')", (graph, list)->{ + assertNull(GList.get(graph, list, -1)); + }); + } + + @Test public void glist_forEach_01() { + test("()", (graph, list)->{ + StringBuilder sb = new StringBuilder(); + GList.forEach(graph, list, n -> { + // numeric literals; use lexical form + sb.append(n.getLiteral().getLexicalForm()).append(","); + }); + assertEquals("", sb.toString()); + }); + } + + @Test public void glist_forEach_02() { + test("(1 2 3)", (graph, list)->{ + StringBuilder sb = new StringBuilder(); + GList.forEach(graph, list, n -> { + // numeric literals; use lexical form + sb.append(n.getLiteral().getLexicalForm()).append(","); + }); + assertEquals("1,2,3,", sb.toString()); + }); + } + + @Test public void glist_iterator_traverse() { + test("('A' 'B' 'C')", (graph, list)->{ + List seen = new ArrayList<>(); + Iterator it = GList.iterator(graph, list); + while (it.hasNext()) { + seen.add(it.next()); + } + List expected = GList.elements(graph, list); + assertEquals(expected, seen); + }); + } + + @Test public void glist_iterator_empty() { + Graph graph = graph(":s :p :o"); + Iterator it = GList.iterator(graph, RDF.Nodes.nil); + assertFalse(it.hasNext()); + } + + @Test public void glist_closedCells_reject_extra_predicate() { + // Create a list cell that has an extra non-list triple :p :o + // Use closedCells = true to exercise that branch. + test("[ rdf:first 1 ; rdf:rest rdf:nil ; :p :o ]", (graph, list)->{ + assertFalse(GList.isWellformedList(graph, list, true)); + assertThrows(RDFDataException.class, ()->GList.isWellformedListEx(graph, list, true)); + }); + } + + @Test public void glist_indexOf_blanknode_identity() { + // Outer list contains one inner list (a blank node). indexOf should handle blank-node identity. + test("((:a :b :c))", (graph, list)->{ + // get the actual inner blank node from the members method + List members = members(graph, list); + assertEquals(1, members.size()); + Node inner = members.get(0); + + // Searching for the same blank node returns 0 + assertEquals(0, GList.indexOf(graph, list, inner)); + + // Searching for a different (not-equal) blank node returns -1 + Node otherBNode = NodeFactory.createBlankNode(); + assertEquals(-1, GList.indexOf(graph, list, otherBNode)); + }); + } + + @Test public void glist_contains_01() { + test("()", (graph, list)->{ + Node c = NodeFactory.createLiteralString("C"); + assertFalse(GList.contains(graph, list, c)); + }); + } + + @Test public void glist_contains_02() { + test("('A' 'B')", (graph, list)->{ + Node a = NodeFactory.createLiteralString("A"); + Node c = NodeFactory.createLiteralString("C"); + assertTrue(GList.contains(graph, list, a)); + assertFalse(GList.contains(graph, list, c)); + }); + } + + // ---- + + private List members(Graph graph, Node list) { + // Do the checking form first in case it throws RDFDataException. + List x1 = GList.members(graph, list); + List x2 = GList.elements(graph, list); + assertEquals(x1, x2); + return x1; + } + + private static void testWellformed(String string) { + test(string, (graph, list)->{ + assertTrue(GList.isWellformedList(graph, list)); + }); + } + + private static void testNotWellformed(String string) { + test(string, (graph, list)->{ + assertFalse(GList.isWellformedList(graph, list)); + assertThrows(RDFDataException.class, ()->GList.isWellformedListEx(graph, list)); + }); + } + + private static void testlength(String string, int expected) { + test(string, (graph, list)->{ + long actual = GList.listLength(graph, list); + assertEquals(expected, actual); + }); + } + + private static void test(String string, BiConsumer action) { + String graphStr = ":x :p "+string; + Graph graph = graph(graphStr); + Node list = G.getOneSP(graph, x, p); + action.accept(graph, list); + } + + private static void testEx(String string, BiConsumer action) { + String graphStr = ":x :p "+string; + Graph graph = graph(graphStr); + Node list = G.getOneSP(graph, x, p); + assertThrows(RDFDataException.class, ()->action.accept(graph, list)); + } + + + private static Graph graph(String str) { + String setup = """ + PREFIX : + PREFIX rdf: + """; + return RDFParser.fromString(setup+str, Lang.TURTLE).toGraph(); + } + +} diff --git a/jena-arq/src/test/java/org/apache/jena/system/TestG_Basic.java b/jena-arq/src/test/java/org/apache/jena/system/TestG_Basic.java new file mode 100644 index 00000000000..9f64b8d7992 --- /dev/null +++ b/jena-arq/src/test/java/org/apache/jena/system/TestG_Basic.java @@ -0,0 +1,123 @@ +/* + * 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 + * + * https://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. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.apache.jena.system; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import org.apache.jena.graph.Graph; +import org.apache.jena.graph.Node; +import org.apache.jena.riot.Lang; +import org.apache.jena.riot.RDFParser; +import org.apache.jena.sparql.sse.SSE; +import org.apache.jena.sys.JenaSystem; + +public class TestG_Basic { + + static { JenaSystem.init(); } + + @Test + public void contains_true_and_false() { + Graph g = graph(":s :p :o ."); + Node s = SSE.parseNode(":s"); + Node p = SSE.parseNode(":p"); + Node o = SSE.parseNode(":o"); + + assertTrue(G.contains(g, s, p, o)); + // different predicate -> false + assertFalse(G.contains(g, s, SSE.parseNode(":q"), o)); + } + + @Test + public void containsNode_detects_nodes() { + Graph g = graph(":s :p :o . _:b :p :o ."); + Node s = SSE.parseNode(":s"); + Node o = SSE.parseNode(":o"); + Node missing = SSE.parseNode(":x"); + + assertTrue(G.containsNode(g, s)); + assertTrue(G.containsNode(g, o)); + assertFalse(G.containsNode(g, missing)); + } + + @Test + public void hasProperty_true_false() { + Graph g = graph(":s :p :o ."); + Node s = SSE.parseNode(":s"); + Node p = SSE.parseNode(":p"); + Node q = SSE.parseNode(":q"); + + assertTrue(G.hasProperty(g, s, p)); + assertFalse(G.hasProperty(g, s, q)); + } + + @Test + public void containsOne_single_none_multiple() { + Node s = SSE.parseNode(":s"); + Node p = SSE.parseNode(":p"); + Node o = SSE.parseNode(":o"); + + Graph gSingle = graph(":s :p :o ."); + assertTrue(G.containsOne(gSingle, s, p, o)); + + Graph gNone = graph(":s :q :o ."); + assertFalse(G.containsOne(gNone, s, p, o)); + + Graph gMulti = graph(":s :p :o1 . :s :p :o2 ."); + assertFalse(G.containsOne(gMulti, s, p, Node.ANY)); + } + + @Test + public void hasType_direct() { + Graph g = graph(":x rdf:type :A ."); + Node x = SSE.parseNode(":x"); + Node A = SSE.parseNode(":A"); + assertTrue(G.hasType(g, x, A)); + assertFalse(G.hasType(g, x, SSE.parseNode(":B"))); + } + + @Test + public void isOfType_with_subclass() { + String body = """ + PREFIX rdfs: + PREFIX rdf: + PREFIX : + + :A rdfs:subClassOf :B . + :x rdf:type :A . + """; + Graph g = RDFParser.fromString(body, Lang.TURTLE).toGraph(); + Node x = SSE.parseNode(":x"); + Node B = SSE.parseNode(":B"); + Node C = SSE.parseNode(":C"); + assertTrue(G.isOfType(g, x, B)); + assertFalse(G.isOfType(g, x, C)); + } + + private static Graph graph(String ttlBody) { + String setup = "PREFIX : \n" + + "PREFIX rdf: \n"; + return RDFParser.fromString(setup+ttlBody, Lang.TURTLE).toGraph(); + } +} diff --git a/jena-arq/src/test/java/org/apache/jena/system/TestG_Classes.java b/jena-arq/src/test/java/org/apache/jena/system/TestG_Classes.java new file mode 100644 index 00000000000..89daff82461 --- /dev/null +++ b/jena-arq/src/test/java/org/apache/jena/system/TestG_Classes.java @@ -0,0 +1,200 @@ +/* + * 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 + * + * https://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. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.apache.jena.system; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; +import java.util.Set; +import java.util.HashSet; + +import org.junit.jupiter.api.Test; + +import org.apache.jena.graph.Graph; +import org.apache.jena.graph.Node; +import org.apache.jena.sparql.sse.SSE; +import org.apache.jena.sys.JenaSystem; +import org.apache.jena.riot.Lang; +import org.apache.jena.riot.RDFParser; + +public class TestG_Classes { + + static { JenaSystem.init(); } + + @Test + public void listSubClasses_includes_transitive_subclasses_and_self() { + String body = """ + PREFIX : + PREFIX rdf: + PREFIX rdfs: + :A rdfs:subClassOf :B . + :C rdfs:subClassOf :A . + :D rdfs:subClassOf :B . + :E rdfs:subClassOf :C . + """; + Graph g = RDFParser.fromString(body, Lang.TURTLE).toGraph(); + + Node B = SSE.parseNode(":B"); + List subs = G.listSubClasses(g, B); + Set set = new HashSet<>(subs); + assertEquals(5, set.size()); + assertTrue(set.contains(SSE.parseNode(":B"))); + assertTrue(set.contains(SSE.parseNode(":A"))); + assertTrue(set.contains(SSE.parseNode(":C"))); + assertTrue(set.contains(SSE.parseNode(":D"))); + assertTrue(set.contains(SSE.parseNode(":E"))); + } + + @Test + public void listSuperClasses_includes_transitive_superclasses_and_self() { + String body = """ + PREFIX : + PREFIX rdf: + PREFIX rdfs: + :A rdfs:subClassOf :B . + :C rdfs:subClassOf :A . + :E rdfs:subClassOf :C . + """; + Graph g = RDFParser.fromString(body, Lang.TURTLE).toGraph(); + + Node E = SSE.parseNode(":E"); + List supers = G.listSuperClasses(g, E); + Set set = new HashSet<>(supers); + assertEquals(4, set.size()); + assertTrue(set.contains(SSE.parseNode(":E"))); + assertTrue(set.contains(SSE.parseNode(":C"))); + assertTrue(set.contains(SSE.parseNode(":A"))); + assertTrue(set.contains(SSE.parseNode(":B"))); + } + + @Test + public void subClasses_return_set_equivalent_to_list() { + String body = """ + PREFIX : + PREFIX rdf: + PREFIX rdfs: + :A rdfs:subClassOf :B . + :C rdfs:subClassOf :A . + """; + Graph g = RDFParser.fromString(body, Lang.TURTLE).toGraph(); + + Node B = SSE.parseNode(":B"); + List subsList = G.listSubClasses(g, B); + Set subsSet = G.subClasses(g, B); + assertEquals(new HashSet<>(subsList), subsSet); + } + + @Test + public void superClasses_return_set_equivalent_to_list() { + String body = "PREFIX : \n" + + "PREFIX rdfs: \n" + + ":A rdfs:subClassOf :B . :C rdfs:subClassOf :A ."; + Graph g = RDFParser.fromString(body, Lang.TURTLE).toGraph(); + + Node C = SSE.parseNode(":C"); + List supersList = G.listSuperClasses(g, C); + Set supersSet = G.superClasses(g, C); + assertEquals(new HashSet<>(supersList), supersSet); + } + + @Test + public void listSubClasses_no_relations_contains_self_only() { + String body = """ + PREFIX : + PREFIX rdf: + :Z a :U . + """; + Graph g = RDFParser.fromString(body, Lang.TURTLE).toGraph(); + Node Z = SSE.parseNode(":Z"); + List subs = G.listSubClasses(g, Z); + assertEquals(1, subs.size()); + assertEquals(Z, subs.get(0)); + } + + @Test + public void listTypesOfNodeRDFS_includes_direct_and_superclasses() { + String body = """ + PREFIX : + PREFIX rdf: + PREFIX rdfs: + :A rdfs:subClassOf :B . + :x rdf:type :A . + """; + Graph g = RDFParser.fromString(body, Lang.TURTLE).toGraph(); + Node x = SSE.parseNode(":x"); + List types = G.listTypesOfNodeRDFS(g, x); + Set set = new HashSet<>(types); + assertTrue(set.contains(SSE.parseNode(":A"))); + assertTrue(set.contains(SSE.parseNode(":B"))); + } + + @Test + public void listNodesOfTypeRDFS_includes_nodes_of_subclasses() { + String body = """ + PREFIX : + PREFIX rdf: + PREFIX rdfs: + :C rdfs:subClassOf :A . + :x rdf:type :A . + :y rdf:type :C . + """; + Graph g = RDFParser.fromString(body, Lang.TURTLE).toGraph(); + Node A = SSE.parseNode(":A"); + List nodes = G.listNodesOfTypeRDFS(g, A); + Set set = new HashSet<>(nodes); + assertTrue(set.contains(SSE.parseNode(":x"))); + assertTrue(set.contains(SSE.parseNode(":y"))); + } + + @Test + public void allTypesOfNodeRDFS_returns_set_of_types_including_superclasses() { + String body = """ + PREFIX : + PREFIX rdf: + PREFIX rdfs: + :A rdfs:subClassOf :B . + :x rdf:type :A . + """; + Graph g = RDFParser.fromString(body, Lang.TURTLE).toGraph(); + Node x = SSE.parseNode(":x"); + Set types = G.allTypesOfNodeRDFS(g, x); + assertTrue(types.contains(SSE.parseNode(":A"))); + assertTrue(types.contains(SSE.parseNode(":B"))); + } + + @Test + public void allNodesOfTypeRDFS_returns_set_of_nodes_including_subclass_instances() { + String body = """ + PREFIX : + PREFIX rdf: + PREFIX rdfs: + :C rdfs:subClassOf :A . + :x rdf:type :A . + :y rdf:type :C . + """; + Graph g = RDFParser.fromString(body, Lang.TURTLE).toGraph(); + Node A = SSE.parseNode(":A"); + Set nodes = G.allNodesOfTypeRDFS(g, A); + assertTrue(nodes.contains(SSE.parseNode(":x"))); + assertTrue(nodes.contains(SSE.parseNode(":y"))); + } +} diff --git a/jena-arq/src/test/java/org/apache/jena/system/TestG_PO.java b/jena-arq/src/test/java/org/apache/jena/system/TestG_PO.java new file mode 100644 index 00000000000..ccba2bb0d10 --- /dev/null +++ b/jena-arq/src/test/java/org/apache/jena/system/TestG_PO.java @@ -0,0 +1,201 @@ +/* + * 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 + * + * https://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. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.apache.jena.system; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import org.apache.jena.graph.Graph; +import org.apache.jena.graph.Node; +import org.apache.jena.riot.Lang; +import org.apache.jena.riot.RDFParser; +import org.apache.jena.sparql.sse.SSE; +import org.apache.jena.sys.JenaSystem; +import org.apache.jena.util.iterator.ExtendedIterator; + +public class TestG_PO { + + static { JenaSystem.init(); } + + private static final Node s1 = SSE.parseNode(":s1"); + private static final Node s2 = SSE.parseNode(":s2"); + private static final Node x = SSE.parseNode(":x"); + private static final Node p = SSE.parseNode(":p"); + private static final Node o1 = SSE.parseNode(":o1"); + private static final Node o2 = SSE.parseNode(":o2"); + + @Test + public void getPO_many_returns_one_of_subjects() { + Graph g = graph(":s1 :p :o . :s2 :p :o ."); + Node got = G.getPO(g, p, SSE.parseNode(":o")); + assertTrue(s1.sameTermAs(got) || s2.sameTermAs(got)); + } + + @Test + public void getOnePO_single_ok() { + Graph g = graph(":s :p :o1 ."); + Node got = G.getOnePO(g, p, o1); + assertEquals(SSE.parseNode(":s"), got); + } + + @Test + public void getOnePO_none_throws() { + Graph g = graph(":s :q :o1 ."); + assertThrows(RDFDataException.class, ()->G.getOnePO(g, p, o1)); + } + + @Test + public void getOnePO_multiple_throws() { + Graph g = graph(":s1 :p :o1 . :s2 :p :o1 ."); + assertThrows(RDFDataException.class, ()->G.getOnePO(g, p, o1)); + } + + @Test + public void getZeroOrOnePO_none_null() { + Graph g = graph(":s :q :o1 ."); + Node got = G.getZeroOrOnePO(g, p, o1); + assertNull(got); + } + + @Test + public void getZeroOrOnePO_one_ok() { + Graph g = graph(":s :p :o1 ."); + Node got = G.getZeroOrOnePO(g, p, o1); + assertEquals(SSE.parseNode(":s"), got); + } + + @Test + public void getZeroOrOnePO_multiple_throws() { + Graph g = graph(":s1 :p :o1 . :s2 :p :o1 ."); + assertThrows(RDFDataException.class, ()->G.getZeroOrOnePO(g, p, o1)); + } + + @Test + public void hasOnePO_none_false() { + Graph g = graph(":s :q :o1 ."); + assertFalse(G.hasOnePO(g, p, o1)); + } + + @Test + public void hasOnePO_one_true() { + Graph g = graph(":s :p :o1 ."); + assertTrue(G.hasOnePO(g, p, o1)); + } + + @Test + public void hasOnePO_multiple_throws() { + Graph g = graph(":s1 :p :o1 . :s2 :p :o1 ."); + assertThrows(RDFDataException.class, ()->G.hasOnePO(g, p, o1)); + } + + // Wildcard tests similar to TestG_SP + @Test + public void getPO_objectAny_returns_one_of_subjects() { + Graph g = graph(":s1 :p :o1 . :s2 :p :o2 ."); + Node got = G.getPO(g, p, Node.ANY); + assertTrue(SSE.parseNode(":s1").sameTermAs(got) || SSE.parseNode(":s2").sameTermAs(got)); + } + + @Test + public void getOnePO_objectAny_single_ok() { + Graph g = graph(":s1 :p :o1 ."); + Node got = G.getOnePO(g, p, Node.ANY); + assertEquals(SSE.parseNode(":s1"), got); + } + + @Test + public void getOnePO_objectAny_multiple_throws() { + Graph g = graph(":s1 :p :o1 . :s2 :p :o2 ."); + assertThrows(RDFDataException.class, ()->G.getOnePO(g, p, Node.ANY)); + } + + @Test + public void getZeroOrOnePO_objectAny_single_ok() { + Graph g = graph(":s1 :p :o1 ."); + Node got = G.getZeroOrOnePO(g, p, Node.ANY); + assertEquals(SSE.parseNode(":s1"), got); + } + + @Test + public void getZeroOrOnePO_objectAny_multiple_throws() { + Graph g = graph(":s1 :p :o1 . :s2 :p :o2 ."); + assertThrows(RDFDataException.class, ()->G.getZeroOrOnePO(g, p, Node.ANY)); + } + + @Test + public void hasOnePO_objectAny_true_false_and_throws() { + // none -> false + Graph g0 = graph(":s :q :o1 ."); + assertFalse(G.hasOnePO(g0, p, Node.ANY)); + + // single -> true + Graph g1 = graph(":s1 :p :o1 ."); + assertTrue(G.hasOnePO(g1, p, Node.ANY)); + + // multiple -> throws + Graph g2 = graph(":s1 :p :o1 . :s2 :p :o2 ."); + assertThrows(RDFDataException.class, ()->G.hasOnePO(g2, p, Node.ANY)); + } + + @Test + public void iterPO_returns_subjects() { + Graph g = graph(":s1 :p :o . :s2 :p :o . :s3 :q :o ."); + ExtendedIterator iter = G.iterPO(g, p, SSE.parseNode(":o")); + try { + List seen = iter.toList(); + assertEquals(2, seen.size()); + assertTrue(seen.contains(SSE.parseNode(":s1")) && seen.contains(SSE.parseNode(":s2"))); + } finally { iter.close(); } + } + + @Test + public void listPO_returns_list_of_subjects() { + Graph g = graph(":s1 :p :o . :s2 :p :o . :s3 :q :o ."); + List list = G.listPO(g, p, SSE.parseNode(":o")); + assertEquals(2, list.size()); + assertTrue(list.contains(SSE.parseNode(":s1")) && list.contains(SSE.parseNode(":s2"))); + } + + @Test + public void countPO_counts_matches() { + Graph g = graph(":s1 :p :o . :s2 :p :o . :s3 :q :o ."); + long count = G.countPO(g, p, SSE.parseNode(":o")); + assertEquals(2, count); + } + + @Test + public void allPO_returns_set_of_subjects() { + Graph g = graph(":s1 :p :o . :s2 :p :o . :s3 :q :o ."); + Set all = G.allPO(g, p, SSE.parseNode(":o")); + assertEquals(2, all.size()); + assertTrue(all.contains(SSE.parseNode(":s1")) && all.contains(SSE.parseNode(":s2"))); + } + + private static Graph graph(String ttlBody) { + String setup = "PREFIX : \n"; + return RDFParser.fromString(setup+ttlBody, Lang.TURTLE).toGraph(); + } +} diff --git a/jena-arq/src/test/java/org/apache/jena/system/TestG_Quad.java b/jena-arq/src/test/java/org/apache/jena/system/TestG_Quad.java new file mode 100644 index 00000000000..f68a1935a88 --- /dev/null +++ b/jena-arq/src/test/java/org/apache/jena/system/TestG_Quad.java @@ -0,0 +1,72 @@ +/* + * 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 + * + * https://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. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.apache.jena.system; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +import org.apache.jena.graph.Node; +import org.apache.jena.riot.Lang; +import org.apache.jena.riot.RDFParser; +import org.apache.jena.sparql.core.DatasetGraph; +import org.apache.jena.sparql.core.Quad; +import org.apache.jena.sparql.sse.SSE; +import org.apache.jena.sys.JenaSystem; + +public class TestG_Quad { + + static { JenaSystem.init(); } + + private static final Node s = SSE.parseNode(":s"); + private static final Node p = SSE.parseNode(":p"); + private static final Node o = SSE.parseNode(":o"); + + // DatasetGraph getOne (quads) + @Test + public void getOne_datasetgraph_single_ok() { + DatasetGraph dsg = dataset(":g { :s :p :o } "); + Node gname = SSE.parseNode(":g"); + Quad q = G.getOne(dsg, gname, s, p, o); + assertEquals(Quad.create(gname, s, p, o), q); + } + + @Test + public void getOne_datasetgraph_none_throws() { + DatasetGraph dsg = dataset(""); + Node gname = SSE.parseNode(":g"); + assertThrows(RDFDataException.class, ()->G.getOne(dsg, gname, s, p, o)); + } + + @Test + public void getOne_datasetgraph_multiple_throws_with_wildcard() { + DatasetGraph dsg = dataset(":g1 { :s :p :o } :g2 { :s :p :o } "); + // Use graph wildcard to match multiple quad locations + assertThrows(RDFDataException.class, ()->G.getOne(dsg, Node.ANY, s, p, o)); + } + + private static DatasetGraph dataset(String trigBody) { + String setup = "PREFIX : \n"; + return RDFParser.fromString(setup+trigBody, Lang.TRIG).toDatasetGraph(); + } +} diff --git a/jena-arq/src/test/java/org/apache/jena/system/TestG_SP.java b/jena-arq/src/test/java/org/apache/jena/system/TestG_SP.java new file mode 100644 index 00000000000..8941072d7c0 --- /dev/null +++ b/jena-arq/src/test/java/org/apache/jena/system/TestG_SP.java @@ -0,0 +1,201 @@ +/* + * 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 + * + * https://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. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.apache.jena.system; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import org.apache.jena.graph.Graph; +import org.apache.jena.graph.Node; +import org.apache.jena.riot.Lang; +import org.apache.jena.riot.RDFParser; +import org.apache.jena.sparql.sse.SSE; +import org.apache.jena.sys.JenaSystem; +import org.apache.jena.util.iterator.ExtendedIterator; + +public class TestG_SP { + + static { JenaSystem.init(); } + + private static final Node s = SSE.parseNode(":s"); + private static final Node x = SSE.parseNode(":x"); + private static final Node p = SSE.parseNode(":p"); + private static final Node o1 = SSE.parseNode(":o1"); + private static final Node o2 = SSE.parseNode(":o2"); + + @Test + public void getSP_many_returns_one_of_objects() { + Graph g = graph(":s :p :o1 . :s :p :o2 ."); + Node got = G.getSP(g, s, p); + assertTrue(o1.sameTermAs(got) || o2.sameTermAs(got), "getSP should return one of the objects when multiple exist"); + } + + @Test + public void getOneSP_single_ok() { + Graph g = graph(":s :p :o1 ."); + Node got = G.getOneSP(g, s, p); + assertEquals(o1, got); + } + + @Test + public void getOneSP_none_throws() { + Graph g = graph(":s :q :o1 ."); + assertThrows(RDFDataException.class, ()->G.getOneSP(g, s, p)); + } + + @Test + public void getOneSP_multiple_throws() { + Graph g = graph(":s :p :o1 . :s :p :o2 ."); + assertThrows(RDFDataException.class, ()->G.getOneSP(g, s, p)); + } + + @Test + public void getZeroOrOneSP_none_null() { + Graph g = graph(":s :q :o1 ."); + Node got = G.getZeroOrOneSP(g, s, p); + assertNull(got); + } + + @Test + public void getZeroOrOneSP_one_ok() { + Graph g = graph(":s :p :o1 ."); + Node got = G.getZeroOrOneSP(g, s, p); + assertEquals(o1, got); + } + + @Test + public void getZeroOrOneSP_multiple_throws() { + Graph g = graph(":s :p :o1 . :s :p :o2 ."); + assertThrows(RDFDataException.class, ()->G.getZeroOrOneSP(g, s, p)); + } + + @Test + public void hasOneSP_none_false() { + Graph g = graph(":s :q :o1 ."); + assertFalse(G.hasOneSP(g, s, p)); + } + + @Test + public void hasOneSP_one_true() { + Graph g = graph(":s :p :o1 ."); + assertTrue(G.hasOneSP(g, s, p)); + } + + @Test + public void hasOneSP_multiple_throws() { + Graph g = graph(":s :p :o1 . :s :p :o2 ."); + assertThrows(RDFDataException.class, ()->G.hasOneSP(g, s, p)); + } + + private static Graph graph(String ttlBody) { + String setup = "PREFIX : \n"; + return RDFParser.fromString(setup+ttlBody, Lang.TURTLE).toGraph(); + } + + // --- Wildcard (Node.ANY) handling tests + + @Test + public void getSP_subjectAny_returns_one_of_objects() { + Graph g = graph(":s1 :p :o1 . :s2 :p :o2 ."); + Node got = G.getSP(g, Node.ANY, p); + assertTrue(o1.sameTermAs(got) || o2.sameTermAs(got)); + } + + @Test + public void getOneSP_subjectAny_single_ok() { + Graph g = graph(":s1 :p :o1 ."); + Node got = G.getOneSP(g, Node.ANY, p); + assertEquals(o1, got); + } + + @Test + public void getOneSP_subjectAny_multiple_throws() { + Graph g = graph(":s1 :p :o1 . :s2 :p :o2 ."); + assertThrows(RDFDataException.class, ()->G.getOneSP(g, Node.ANY, p)); + } + + @Test + public void getZeroOrOneSP_predicateAny_single_ok() { + Graph g = graph(":s :p1 :o1 ."); + Node got = G.getZeroOrOneSP(g, s, Node.ANY); + assertEquals(o1, got); + } + + @Test + public void getZeroOrOneSP_predicateAny_multiple_throws() { + Graph g = graph(":s :p1 :o1 . :s :p2 :o2 ."); + assertThrows(RDFDataException.class, ()->G.getZeroOrOneSP(g, s, Node.ANY)); + } + + @Test + public void hasOneSP_predicateAny_true_false_and_throws() { + // none -> false + Graph g0 = graph(":s :q :o1 ."); + assertFalse(G.hasOneSP(g0, x, Node.ANY)); + + // single -> true + Graph g1 = graph(":s :p1 :o1 ."); + assertTrue(G.hasOneSP(g1, s, Node.ANY)); + + // multiple -> throws + Graph g2 = graph(":s :p1 :o1 . :s :p2 :o2 ."); + assertThrows(RDFDataException.class, ()->G.hasOneSP(g2, s, Node.ANY)); + } + + @Test + public void iterSP_returns_objects() { + Graph g = graph(":s :p :o1 . :s :p :o2 . :s2 :p :o2 ."); + ExtendedIterator iter = G.iterSP(g, s, p); + try { + List seen = iter.toList(); + assertEquals(2, seen.size()); + assertTrue(seen.contains(o1) && seen.contains(o2)); + } finally { iter.close(); } + } + + @Test + public void listSP_returns_list_of_objects() { + Graph g = graph(":s :p :o1 . :s :p :o2 . :s2 :p :o2 ."); + List list = G.listSP(g, s, p); + assertEquals(2, list.size()); + assertTrue(list.contains(o1) && list.contains(o2)); + } + + @Test + public void countSP_counts_matches() { + Graph g = graph(":s :p :o1 . :s :p :o2 . :s2 :p :o2 ."); + long count = G.countSP(g, s, p); + assertEquals(2, count); + } + + @Test + public void allSP_returns_set_of_objects() { + Graph g = graph(":s :p :o1 . :s :p :o2 . :s2 :p :o2 ."); + Set all = G.allSP(g, s, p); + assertEquals(2, all.size()); + assertTrue(all.contains(o1) && all.contains(o2)); + } +} diff --git a/jena-arq/src/test/java/org/apache/jena/system/TestG_Triple.java b/jena-arq/src/test/java/org/apache/jena/system/TestG_Triple.java new file mode 100644 index 00000000000..f3a44b1114d --- /dev/null +++ b/jena-arq/src/test/java/org/apache/jena/system/TestG_Triple.java @@ -0,0 +1,103 @@ +/* + * 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 + * + * https://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. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.apache.jena.system; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +import org.apache.jena.graph.Graph; +import org.apache.jena.graph.Node; +import org.apache.jena.graph.Triple; +import org.apache.jena.riot.Lang; +import org.apache.jena.riot.RDFParser; +import org.apache.jena.sparql.sse.SSE; +import org.apache.jena.sys.JenaSystem; + +public class TestG_Triple { + + static { JenaSystem.init(); } + + private static final Node s = SSE.parseNode(":s"); + private static final Node p = SSE.parseNode(":p"); + private static final Node o = SSE.parseNode(":o"); + + @Test + public void getOne_single_ok() { + Graph g = graph(":s :p :o ."); + Triple t = G.getOne(g, s, p, o); + assertEquals(SSE.parseTriple("(:s :p :o)"), t); + } + + @Test + public void getOne_none_throws() { + Graph g = graph(":s :q :o ."); + assertThrows(RDFDataException.class, ()->G.getOne(g, s, p, o)); + } + + @Test + public void getOne_multiple_throws_with_wildcard() { + Graph g = graph(":s1 :p :o1 . :s2 :p :o2 ."); + // Use wildcard for object to match multiple objects + assertThrows(RDFDataException.class, ()->G.getOne(g, s, p, Node.ANY)); + } + + @Test + public void getZeroOrOne_none_null() { + Graph g = graph(":s :q :o ."); + Triple t = G.getZeroOrOne(g, s, p, o); + assertNull(t); + } + + @Test + public void getZeroOrOne_one_ok() { + Graph g = graph(":s :p :o ."); + Triple t = G.getZeroOrOne(g, s, p, o); + assertEquals(SSE.parseTriple("(:s :p :o)"), t); + } + + @Test + public void getZeroOrOne_multiple_throws() { + Graph g = graph(":s :p :o1 . :s :p :o2 ."); + assertThrows(RDFDataException.class, ()->G.getZeroOrOne(g, s, p, Node.ANY)); + } + + @Test + public void getOneOrNull_none_null() { + Graph g = graph(":s :q :o ."); + Triple t = G.getOneOrNull(g, s, p, o); + assertNull(t); + } + + @Test + public void getOneOrNull_multiple_null() { + Graph g = graph(":s :p :o1 . :s :p :o2 ."); + Triple t = G.getOneOrNull(g, s, p, Node.ANY); + assertNull(t); + } + private static Graph graph(String ttlBody) { + String setup = "PREFIX : \n"; + return RDFParser.fromString(setup+ttlBody, Lang.TURTLE).toGraph(); + } +} diff --git a/jena-arq/src/test/java/org/apache/jena/system/TestCounter.java b/jena-arq/src/test/java/org/apache/jena/system/TestTxnCounter.java similarity index 99% rename from jena-arq/src/test/java/org/apache/jena/system/TestCounter.java rename to jena-arq/src/test/java/org/apache/jena/system/TestTxnCounter.java index 2859e663e9d..21f847682cb 100644 --- a/jena-arq/src/test/java/org/apache/jena/system/TestCounter.java +++ b/jena-arq/src/test/java/org/apache/jena/system/TestTxnCounter.java @@ -31,7 +31,7 @@ import org.apache.jena.sparql.JenaTransactionException ; // TestTxn also tests counters. -public class TestCounter { +public class TestTxnCounter { private TxnCounter counter = new TxnCounter(0) ; @Test