This is an automated email from the ASF dual-hosted git repository.

afs pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/jena.git

commit 2a164f62dbda22a9a14eddc168731e3a4a7501af
Author: Andy Seaborne <[email protected]>
AuthorDate: Mon May 4 12:21:35 2026 +0100

    Tests for G
---
 .../src/main/java/org/apache/jena/system/G.java    |  54 +++---
 .../main/java/org/apache/jena/system/GList.java    |  16 +-
 .../java/org/apache/jena/system/TS_System.java     |  13 +-
 .../jena/system/{GTest.java => TestGCopy.java}     |   2 +-
 .../java/org/apache/jena/system/TestG_Basic.java   | 123 +++++++++++++
 .../java/org/apache/jena/system/TestG_Classes.java | 200 ++++++++++++++++++++
 .../test/java/org/apache/jena/system/TestG_PO.java | 201 +++++++++++++++++++++
 .../java/org/apache/jena/system/TestG_Quad.java    |  72 ++++++++
 .../test/java/org/apache/jena/system/TestG_SP.java | 201 +++++++++++++++++++++
 .../java/org/apache/jena/system/TestG_Triple.java  | 103 +++++++++++
 10 files changed, 943 insertions(+), 42 deletions(-)

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 7392a0b0c2..e9c1cab044 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.DatasetGraph;
 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 class G {
      */
     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 class G {
      */
     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 class G {
         return iterSP(graph, subject, predicate).toList();
     }
 
+    /** Return a set of all objects for subject-predicate */
+    public static Set<Node> 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 class G {
         return iterPO(graph, predicate, object).toList();
     }
 
+    /** Return a set of all subjects for predicate-object */
+    public static Set<Node> 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 class G {
     public static List<Node> 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<Node> 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<Node> 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 @@ public class G {
             );
     }
 
-    /** Return a set of all objects for subject-predicate */
-    public static Set<Node> 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<Node> 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 class G {
 
     /**
      * Creates a copy of the given graph.
-     * If the graph implements Copyable<Graph> then the copy method is called.
+     * If the graph implements {@code Copyable<Graph>} 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<Graph> copyableGraph = (Copyable<Graph>)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
index 6880e2b2f4..a6ac246971 100644
--- a/jena-arq/src/main/java/org/apache/jena/system/GList.java
+++ b/jena-arq/src/main/java/org/apache/jena/system/GList.java
@@ -316,6 +316,13 @@ public class GList {
         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.
@@ -472,15 +479,6 @@ public class GList {
 //        }
     }
 
-
-
-    private static boolean isListNode(Graph graph, Node node) {
-        if ( node.equals(NIL) )
-            return true;
-        // Well-formedness check.
-        return isCons(graph, node);
-    }
-
     private static boolean isCons (Graph graph, Node node) {
         return G.hasOneSP(graph, node, CDR) && G.hasOneSP(graph, node, CAR);
     }
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 604162c3c9..8e2732d446 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
@@ -29,12 +29,21 @@ import org.junit.platform.suite.api.Suite;
     TestPrefixes.class
     , TestPrefixLib.class
 
-    , TestTxnCounter.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
     , 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 ee3361388f..d877548cb4 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.mem.GraphMemFast;
 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/TestG_Basic.java 
b/jena-arq/src/test/java/org/apache/jena/system/TestG_Basic.java
new file mode 100644
index 0000000000..9f64b8d799
--- /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: <http://www.w3.org/2000/01/rdf-schema#>
+                       PREFIX rdf:  
<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+                       PREFIX : <http://example/>
+
+                               :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 :     <http://example/>\n" +
+                                          "PREFIX rdf:  
<http://www.w3.org/1999/02/22-rdf-syntax-ns#>\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 0000000000..89daff8246
--- /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 : <http://example/>
+                               PREFIX rdf:     
<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+                               PREFIX rdfs: 
<http://www.w3.org/2000/01/rdf-schema#>
+                               :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<Node> subs = G.listSubClasses(g, B);
+               Set<Node> 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 : <http://example/>
+                               PREFIX rdf:     
<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+                               PREFIX rdfs: 
<http://www.w3.org/2000/01/rdf-schema#>
+                               :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<Node> supers = G.listSuperClasses(g, E);
+               Set<Node> 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 : <http://example/>
+                               PREFIX rdf:     
<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+                               PREFIX rdfs: 
<http://www.w3.org/2000/01/rdf-schema#>
+                               :A rdfs:subClassOf :B .
+                               :C rdfs:subClassOf :A .
+                               """;
+               Graph g = RDFParser.fromString(body, Lang.TURTLE).toGraph();
+
+               Node B = SSE.parseNode(":B");
+               List<Node> subsList = G.listSubClasses(g, B);
+               Set<Node> subsSet = G.subClasses(g, B);
+               assertEquals(new HashSet<>(subsList), subsSet);
+       }
+
+       @Test
+       public void superClasses_return_set_equivalent_to_list() {
+               String body = "PREFIX : <http://example/>\n" +
+                                         "PREFIX rdfs: 
<http://www.w3.org/2000/01/rdf-schema#>\n" +
+                                         ":A rdfs:subClassOf :B . :C 
rdfs:subClassOf :A .";
+               Graph g = RDFParser.fromString(body, Lang.TURTLE).toGraph();
+
+               Node C = SSE.parseNode(":C");
+               List<Node> supersList = G.listSuperClasses(g, C);
+               Set<Node> supersSet = G.superClasses(g, C);
+               assertEquals(new HashSet<>(supersList), supersSet);
+       }
+
+       @Test
+       public void listSubClasses_no_relations_contains_self_only() {
+               String body = """
+                               PREFIX : <http://example/>
+                               PREFIX rdf:     
<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+                               :Z a :U .
+                               """;
+               Graph g = RDFParser.fromString(body, Lang.TURTLE).toGraph();
+               Node Z = SSE.parseNode(":Z");
+               List<Node> 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 : <http://example/>
+                               PREFIX rdf:     
<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+                               PREFIX rdfs: 
<http://www.w3.org/2000/01/rdf-schema#>
+                               :A rdfs:subClassOf :B .
+                               :x rdf:type :A .
+                               """;
+               Graph g = RDFParser.fromString(body, Lang.TURTLE).toGraph();
+               Node x = SSE.parseNode(":x");
+               List<Node> types = G.listTypesOfNodeRDFS(g, x);
+               Set<Node> 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 : <http://example/>
+                               PREFIX rdf:     
<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+                               PREFIX rdfs: 
<http://www.w3.org/2000/01/rdf-schema#>
+                               :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<Node> nodes = G.listNodesOfTypeRDFS(g, A);
+               Set<Node> 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 : <http://example/>
+                               PREFIX rdf:     
<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+                               PREFIX rdfs: 
<http://www.w3.org/2000/01/rdf-schema#>
+                               :A rdfs:subClassOf :B .
+                               :x rdf:type :A .
+                               """;
+               Graph g = RDFParser.fromString(body, Lang.TURTLE).toGraph();
+               Node x = SSE.parseNode(":x");
+               Set<Node> 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 : <http://example/>
+                               PREFIX rdf:     
<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+                               PREFIX rdfs: 
<http://www.w3.org/2000/01/rdf-schema#>
+                               :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<Node> 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 0000000000..ccba2bb0d1
--- /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<Node> iter = G.iterPO(g, p, 
SSE.parseNode(":o"));
+               try {
+                       List<Node> 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<Node> 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<Node> 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 :     <http://example/>\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 0000000000..f68a1935a8
--- /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 :     <http://example/>\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 0000000000..8941072d7c
--- /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 :     <http://example/>\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<Node> iter = G.iterSP(g, s, p);
+               try {
+                       List<Node> 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<Node> 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<Node> 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 0000000000..f3a44b1114
--- /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 :     <http://example/>\n";
+               return RDFParser.fromString(setup+ttlBody, 
Lang.TURTLE).toGraph();
+       }
+}


Reply via email to