You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jena.apache.org by an...@apache.org on 2021/09/09 19:43:23 UTC

[jena] branch main updated: JENA-1903: Encode RDF-star into RDF Reification

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

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


The following commit(s) were added to refs/heads/main by this push:
     new f662345  JENA-1903: Encode RDF-star into RDF Reification
     new 8794a93  Merge pull request #1065 from afs/rdf-star-translate
f662345 is described below

commit f6623458e47559c965299512b4053ed0f8c47e5f
Author: Andy Seaborne <an...@apache.org>
AuthorDate: Mon Sep 6 14:47:30 2021 +0100

    JENA-1903: Encode RDF-star into RDF Reification
---
 .../main/java/org/apache/jena/system/RDFStar.java  | 491 +++++++++++++++++++++
 .../java/org/apache/jena/system/TS_System.java     |   1 +
 .../apache/jena/system/TestRDFStarTranslation.java | 281 ++++++++++++
 3 files changed, 773 insertions(+)

diff --git a/jena-arq/src/main/java/org/apache/jena/system/RDFStar.java b/jena-arq/src/main/java/org/apache/jena/system/RDFStar.java
new file mode 100644
index 0000000..df05acd
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/system/RDFStar.java
@@ -0,0 +1,491 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.system;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jena.atlas.lib.Cache;
+import org.apache.jena.atlas.lib.CacheFactory;
+import org.apache.jena.atlas.lib.Lib;
+import org.apache.jena.graph.*;
+import org.apache.jena.riot.other.G;
+import org.apache.jena.riot.out.NodeFmtLib;
+import org.apache.jena.riot.system.*;
+import org.apache.jena.shared.JenaException;
+import org.apache.jena.sparql.core.Quad;
+import org.apache.jena.sparql.graph.GraphFactory;
+import org.apache.jena.vocabulary.RDF;
+
+/**
+ * Library for RDF-star translation to and from reification form.
+ * There is one reification for each unique quoted triple term.
+ * This is especially important when decoding.
+ */
+public class RDFStar {
+
+    private static final Node rdfSubject   = RDF.Nodes.subject;
+    private static final Node rdfPredicate = RDF.Nodes.predicate;
+    private static final Node rdfObject    = RDF.Nodes.object;
+
+    /**
+     * Returns a copy of the argument graph with any triple terms encoded as
+     * reification.
+     * <p>
+     * See {@link #decodeRDFStar(Graph)} for the reverse operation.
+     * <p>
+     * See {@link #encodeRDFStarInPlace(Graph)} {@link #decodeRDFStarInPlace(Graph)}
+     * for operations that alters the argument graph in-place.
+     * <p>
+     * Returns a new graph with triples involving triple terms replaced with
+     * reification.
+     */
+    public static Graph encodeRDFStar(Graph graph) {
+        Graph output = GraphFactory.createDefaultGraph();
+        StreamRDF dest = StreamRDFLib.graph(output);
+        StreamRDF process = encodeRDFStar(dest);
+        StreamRDFOps.graphToStream(graph, process);
+        output.getPrefixMapping().samePrefixMappingAs(graph.getPrefixMapping());
+        output.getPrefixMapping().setNsPrefix("rdf", RDF.getURI());
+        return output;
+    }
+
+    /**
+     * Copy to a {@link StreamRDF}, encoding RDF-star Triple terms by replacing them with
+     * RDF Reification.
+     */
+    public static void encodeRDFStar(Graph graph, StreamRDF dest) {
+        StreamRDFOps.sendPrefixesToStream(graph.getPrefixMapping(), dest);
+        // Ensure set because this process uses vocabulary from RDF.
+        dest.prefix("rdf", RDF.getURI());
+        StreamRDF process = encodeRDFStar(dest);
+        StreamRDFOps.graphToStream(graph, process);
+    }
+
+    /**
+     * Returns a copy of the argument graph with any reifications for triple terms
+     * translated to triple terms.
+     * <p>
+     * See {@link #decodeRDFStar(Graph)} for the reverse operation.
+     * <p>
+     * See {@link #encodeRDFStarInPlace(Graph)} {@link #decodeRDFStarInPlace(Graph)}
+     * for operations that alters the argument graph in-place.
+     */
+    public static Graph decodeRDFStar(Graph graph) {
+        Graph gx = GraphFactory.createDefaultGraph();
+        decodeRDFStar(graph, StreamRDFLib.graph(gx));
+        gx.getPrefixMapping().setNsPrefixes(graph.getPrefixMapping());
+        return gx;
+
+    }
+
+    /**
+     * Copy the argument graph to a {@link StreamRDF}, replacing reifications with for triple terms.
+     * Caution: this operation uses space proportional to the number of triple terms present.
+     */
+    public static void decodeRDFStar(Graph graph, StreamRDF dest) {
+        // Two pass
+        // 1: Generate <<>>
+        // 2: Process graph
+
+        Map<Node, Node> map = decodeBuildTerms(graph);
+        decodeProcessGraph(graph, map, dest);
+    }
+
+    /** Return a {@link StreamRDF} that encodes RDF-star triples as reification. */
+    private static StreamRDF encodeRDFStar(StreamRDF dest) {
+        return new ConvertToReified(dest);
+    }
+
+    private static class ConvertToReified extends StreamRDFWrapper {
+
+            public ConvertToReified(StreamRDF other) {
+                super(other);
+            }
+
+            // Cache of the Node of the reification of seen Node_Triple.
+            // Reduce the number of triples in repeated reifications.
+            //  <<>> :p 1 ; :q 2 ; ....
+            private Cache<Node_Triple, Node> cache = CacheFactory.createCache(1000);
+
+            @Override
+            public void triple(Triple triple) {
+                Triple replacement = encode(triple, cache, other);
+                if ( replacement == null )
+                    other.triple(triple);
+                else
+                    other.triple(replacement);
+            }
+
+            @Override
+            public void quad(Quad quad) {
+                Quad replacement = encode(quad, cache, other);
+                if ( replacement == null )
+                    other.quad(quad);
+                else
+                    other.quad(replacement);
+            }
+        }
+
+    /**
+     * Encode RDF-star Triple terms by replacing them with RDF Reification.
+     * <p>
+     * Changes the argument graph in-place.
+     * @see #decodeRDFStar
+     */
+    public static Graph encodeRDFStarInPlace(Graph graph) {
+        Graph gx = graph;
+
+        // Accumulate changes so that ConcurrentModificationExceptions don't happen.
+        // graph and gx being the same will work.
+        List<Triple> inserts = new ArrayList<>();
+        List<Triple> deletes = new ArrayList<>();
+
+        StreamRDF insertStream = new StreamRDFApply(inserts::add, null);
+
+        StreamRDF process = new StreamRDFWrapper(insertStream) {
+            private Cache<Node_Triple, Node> cache = CacheFactory.createCache(1000);
+            @Override
+            public void triple(Triple triple) {
+                Triple triple2 = encode(triple, cache, insertStream);
+                if ( triple2 != null ) {
+                    insertStream.triple(triple2);
+                    deletes.add(triple);
+                }
+            }
+        };
+
+        StreamRDFOps.sendTriplesToStream(graph, process);
+        update(gx, deletes, inserts);
+        return gx;
+    }
+
+    // Assuming good encoding as reification, reverse the process on a graph.
+    /**
+     * Replace reification encoding of RDF-star terms with RDF-star triples.
+     * <p>
+     * This function assuming any reification use in the graph is for RDF-star terms.
+     */
+
+    // [RDF-star] Streamify. Buffering Graph
+    public static Graph decodeRDFStarInPlace(Graph graph) {
+        Graph gx = copyGraph(graph);
+
+        graph.find(null, rdfPredicate, null).toList().forEach((t)->{
+            List<Triple> inserts = new ArrayList<>();
+            List<Triple> deletes = new ArrayList<>();
+            decode(gx, t, deletes, inserts);
+            update(gx, deletes, inserts);
+        });
+        return gx;
+    }
+
+    /**
+     * Generate reification triples the input triple has an {@link Node_Triple} term
+     * (i.e. RDF-star, triple used as subject or object).
+     *
+     * Return true if this quad was converted.
+     *
+     * @param triple
+     * @param cache used to suppress duplicate reifications
+     * @param stream output stream for reification triples.
+     * @return Triple
+     */
+    private static Triple encode(Triple triple, Cache<Node_Triple, Node> cache, StreamRDF stream) {
+        Node s = triple.getSubject();
+        Node p = triple.getPredicate();
+        Node o = triple.getObject();
+        Node s1 = nodeReif(s, cache, stream);
+        Node o1 = nodeReif(o, cache, stream);
+
+        // Replace triple if changes.
+        if ( s1 == s && o1 == o ) {
+            // No change - do nothing
+            //stream.triple(triple);
+            return null;
+        }
+        // Change. Replace original.
+        if ( s1 == null )
+            s1 = s ;
+        if ( o1 == null )
+            o1 = o ;
+        return Triple.create(s1, p, o1);
+    }
+
+    /**
+     * Generate reification triples when the input quad has an {@link Node_Triple} term
+     * (i.e. RDF-star, triple used as subject or object).
+     *
+     * Return true if this triple was converted.
+     *
+     * @param quad
+     * @param cache used to suppress duplicate reifications
+     * @param stream output stream for reification triples.
+     * @return Quad
+     */
+    private static Quad encode(Quad quad, Cache<Node_Triple, Node> cache, StreamRDF stream) {
+        Node g = quad.getGraph();
+        Node s = quad.getSubject();
+        Node p = quad.getPredicate();
+        Node o = quad.getObject();
+        Node s1 = nodeReif(s, cache, stream);
+        Node o1 = nodeReif(o, cache, stream);
+
+        // Replace triple if changes.
+        if ( s1 == s && o1 == o ) {
+            // No change - do nothing
+            return null;
+        }
+
+        // Change. Replace original.
+        if ( s1 == null )
+            s1 = s ;
+        if ( o1 == null )
+            o1 = o ;
+        return Quad.create(g, s1, p, o1);
+    }
+
+    private static Node nodeReif(Node x, Cache<Node_Triple, Node> cache, StreamRDF output) {
+        if ( ! x.isNodeTriple() )
+            return x;
+        Triple t = x.getTriple();
+        // Reify any nested triple terms. Reifications sent to stream.
+        Triple t2 = encode(t, cache, output);
+        // If its a new triple, this node is based on the replacement t2.
+        Node_Triple nt = ( t2 == null )
+            ? (Node_Triple)x
+            : (Node_Triple)NodeFactory.createTripleNode(t2);
+        return cache.getOrFill(nt, ()->genReif(nt, output));
+    }
+
+    /** Build a mapping of reification terms to RDF-startriple terms. */
+    private static Map<Node, Node> decodeBuildTerms(Graph graph) {
+        Map<Node, Node> map = new HashMap<>();
+        StreamRDF builder = new StreamRDFBase() {
+            @Override
+            public void triple(Triple triple) {
+                // Vocabulary.
+                if ( ! rdfPredicate.equals(triple.getPredicate()) )
+                    return;
+                Node reif = triple.getSubject();
+                Node s = G.getOneSP(graph, reif, rdfSubject);
+                Node p = G.getOneSP(graph, reif, rdfPredicate);
+                Node o = G.getOneSP(graph, reif, rdfObject);
+                Node tripleTerm = NodeFactory.createTripleNode(s,p,o);
+                map.put(reif, tripleTerm);
+            }
+        };
+        StreamRDFOps.sendTriplesToStream(graph, builder);
+        return map;
+    }
+
+    /** Given a mapping of reification terms, convert graph to RDF-star graph */
+    private static void decodeProcessGraph(Graph graph, Map<Node, Node> map, StreamRDF dest) {
+        StreamRDF translate = new StreamRDFWrapper(dest) {
+            @Override
+            public void triple(Triple triple) {
+              //Node g = quad.getGraph();
+              Node s = triple.getSubject();
+              Node p = triple.getPredicate();
+              Node o = triple.getObject();
+              // Filter out reifications
+              if ( p.equals(rdfSubject) || p.equals(rdfPredicate) || p.equals(rdfObject) )
+                  return;
+
+              Node s1 = translate(s, map);
+              Node o1 = translate(o, map);
+              if ( s == s1 && o == o1 ) {
+                  // No change.
+                  super.triple(triple);
+              }
+              else {
+                  Triple t = Triple.create(s1, p, o1);
+                  super.triple(t);
+              }
+            }
+
+            /** Recursively search for the replacement for a Node */
+            private Node translate(Node x, Map<Node, Node> map) {
+                // x is in the map if it is a reification URI.
+                Node x1 = map.get(x);
+                if ( x1 == null )
+                    return x;
+                // Recursively translate
+                if ( x1.isNodeTriple() ) {
+                    Triple triple = x1.getTriple();
+                    Node s = triple.getSubject();
+                    Node p = triple.getPredicate();
+                    Node o = triple.getObject();
+                    Node s1 = translate(s, map);
+                    Node o1 = translate(o, map);
+                    if ( s == s1 && o == o1 )
+                        return x1;
+                    x1 = NodeFactory.createTripleNode(s1, p, o1);
+                }
+                return x1;
+            }
+        };
+        // Send triples to translater
+        StreamRDFOps.sendTriplesToStream(graph, translate);
+    }
+
+    /**
+     * Test whether a triple has an triple term as one of its components.
+     */
+    static boolean tripleHasNodeTriple(Triple triple) {
+        return triple.getSubject().isNodeTriple()
+               /*|| triple.getPredicate().isNodeTriple()*/
+               || triple.getObject().isNodeTriple();
+    }
+
+    private static Graph copyGraph(Graph graph) {
+        if ( false )
+            return graph;
+        Graph gx = GraphFactory.createDefaultGraph();
+        gx.getPrefixMapping().setNsPrefixes(graph.getPrefixMapping());
+        graph.find().forEachRemaining(gx::add);
+        return gx;
+    }
+
+    private static void update(Graph graph, List<Triple> deletes, List<Triple> inserts) {
+        deletes.forEach(graph::delete);
+        inserts.forEach(graph::add);
+    }
+
+    /**
+     * Generate the reification for a Node_Triple. Return the subject of the
+     * reification triples.
+     */
+    private static Node genReif(Node_Triple nt, StreamRDF output) {
+        Triple t = nt.getTriple();
+        Node n = reificationSubject(nt);
+        output.triple(Triple.create(n, rdfSubject, t.getSubject()));
+        output.triple(Triple.create(n, rdfPredicate, t.getPredicate()));
+        output.triple(Triple.create(n, rdfObject, t.getObject()));
+        return n;
+    }
+
+    /**
+     * Calculate and accumulate changes to the graph for one reification.
+     * Does not make the changes.
+     */
+    private static void decode(Graph graph, Triple pReifTriple, List<Triple> deletes, List<Triple> inserts) {
+        // Get all triples?
+        Node reif = pReifTriple.getSubject();
+        System.out.println("    reif = "+NodeFmtLib.str(reif));
+
+        Triple sReifTriple = G.getOne(graph, reif, rdfSubject, null);
+        Triple oReifTriple = G.getOne(graph, reif, rdfObject, null);
+
+        Node s = sReifTriple.getObject();
+        Node p = pReifTriple.getObject();
+        Node o = oReifTriple.getObject();
+
+        Node_Triple nodeTriple = new Node_Triple(s, p, o);
+        if ( false )
+            inserts.add(Triple.create(s, p, o));
+
+        deletes.add(sReifTriple);
+        deletes.add(pReifTriple);
+        deletes.add(oReifTriple);
+
+        // What about <<reif1>> :p <<reif2>>
+        // [RDF-star] BUG : Does not see <<reif1>> changes
+
+        // Find mentions, accumulate deletions and insertions.
+        // BOTH!! Uncommon (??!) so don't worry about using a list for "both"
+        G.find(graph, reif, null, reif).forEachRemaining(t->{
+            Triple tx = Triple.create(nodeTriple, t.getPredicate(), nodeTriple);
+            deletes.add(t);
+            inserts.add(tx);
+        });
+
+        G.find(graph, null, null, reif).forEachRemaining(t->{
+            if (! t.getSubject().equals(reif) ) {
+                Triple tx = Triple.create(t.getSubject(), t.getPredicate(), nodeTriple);
+                deletes.add(t);
+                inserts.add(tx);
+            }
+        });
+
+        // Will find reification triples.
+        G.find(graph, reif, null, null).forEachRemaining(t->{
+            Node pred = t.getPredicate();
+            if ( ! pred.equals(rdfSubject) && ! pred.equals(rdfPredicate) && ! pred.equals(rdfObject) ) {
+                if ( ! t.getObject().equals(reif) ) {
+                    Triple tx = Triple.create(nodeTriple, pred, t.getObject());
+                    deletes.add(t);
+                    inserts.add(tx);
+                }
+            }
+        });
+    }
+
+    // Configuration of the reification subject generator.
+    private static final boolean USE_REIF_URIS = true;
+    private static String URN_TRIPLE = "urn:triple:";
+    private static String BNODE_TRIPLE = "_:T";
+
+//    public static boolean isReificationSubject(Node node) {
+//        if ( USE_REIF_URIS )
+//            return node.isURI() && node.getURI().startsWith(URN_TRIPLE);
+//        else
+//            return node.isBlank() && node.getBlankNodeLabel().startsWith(BNODE_TRIPLE);
+//    }
+
+    /**
+     * Calculate a reification subject node for a {@link Node_Triple}
+     * This must be the same node (same by value) whenever called with
+     * a {@link Node_Triple} with the same s/p/o.
+     */
+    public static Node reificationSubject(Node_Triple nodeTriple) {
+        Triple t = nodeTriple.getTriple();
+        String x = reifStr(t);
+        x = Lib.murmurHashHex(x);
+        if ( USE_REIF_URIS )
+            return NodeFactory.createURI(URN_TRIPLE+x);
+        else
+            return NodeFactory.createBlankNode(BNODE_TRIPLE+x);
+    }
+
+    // Assumes no loops!
+    private static String reifStr(Triple triple) {
+        return reifStr(triple.getSubject())+reifStr(triple.getPredicate())+reifStr(triple.getObject());
+    }
+
+    private static String reifStr(Node node) {
+        if ( node.isURI() )
+            return node.getURI();
+        if ( node.isBlank() )
+            return node.getBlankNodeLabel();
+        if ( node.isLiteral() ) {
+            if ( ! StringUtils.isEmpty(node.getLiteralLanguage()) )
+                // Langtag
+                return node.getLiteralLexicalForm()+" @"+node.getLiteralLanguage();
+            // Non-URI character to separate the URI, in case we start using the string without hashing.
+            return node.getLiteralLexicalForm()+" "+node.getLiteralDatatypeURI();
+        }
+        if ( node.isNodeTriple() )
+            return reifStr(node.getTriple());
+        throw new JenaException("Node type not supported in Node_Triple: "+node);
+    }
+}
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 90a6c7f..bb69754 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
@@ -30,6 +30,7 @@ import org.junit.runners.Suite ;
     , TestTxn.class
     , TestTxnThread.class
     , TestJenaTitanium.class
+    , TestRDFStarTranslation.class
 })
 
 public class TS_System {}
diff --git a/jena-arq/src/test/java/org/apache/jena/system/TestRDFStarTranslation.java b/jena-arq/src/test/java/org/apache/jena/system/TestRDFStarTranslation.java
new file mode 100644
index 0000000..d640b60
--- /dev/null
+++ b/jena-arq/src/test/java/org/apache/jena/system/TestRDFStarTranslation.java
@@ -0,0 +1,281 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.system;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.util.function.Function;
+
+import org.apache.jena.atlas.lib.StrUtils;
+import org.apache.jena.graph.Graph;
+import org.apache.jena.graph.Node;
+import org.apache.jena.riot.RDFDataMgr;
+import org.apache.jena.riot.RDFFormat;
+import org.apache.jena.riot.other.G;
+import org.apache.jena.sparql.sse.SSE;
+import org.apache.jena.sparql.util.IsoMatcher;
+import org.apache.jena.vocabulary.RDF;
+import org.junit.Test;
+
+public class TestRDFStarTranslation {
+
+    private static final Node s = SSE.parseNode(":s");
+    private static final Node p = SSE.parseNode(":p");
+    private static final Node o = SSE.parseNode(":o");
+    private static final Node q = SSE.parseNode(":q");
+    private static final Node z = SSE.parseNode(":z");
+    private static final Node a = SSE.parseNode(":a");
+
+    private static final Node rdfSubject = RDF.Nodes.subject;
+    private static final Node rdfPredicate = RDF.Nodes.predicate;
+    private static final Node rdfObject = RDF.Nodes.object;
+
+    private static boolean isomorphic(Graph g1, Graph g2) {
+        // Copes with embedded triples.
+        return IsoMatcher.isomorphic(g1, g2);
+    }
+
+    @Test public void rdfx_basic() {
+        // No RDF-star triple terms - no change.
+        Graph g = data("(graph (:s :p :o))");
+
+        Graph g1 = RDFStar.encodeRDFStar(g);
+        assertTrue(isomorphic(g, g1));
+        assertEquals(1, g1.size());
+
+        Graph g2 = RDFStar.encodeRDFStar(g1);
+        assertTrue(isomorphic(g, g2));
+        assertEquals(1, g2.size());
+    }
+
+    @Test public void rdfx_01() {
+        // One term
+        Graph graph = data("(graph (<<:s :p :o>> :q :z) (:s1 :p1 :o1) )");
+        testEncode(graph, 5, (g)-> G.getOnePO(g, q, z));
+    }
+
+    @Test public void rdfx_02() {
+        // One term, used twice, subj/subj
+        Graph graph = data("(graph (<<:s :p :o>> :q :z) (<<:s :p :o>> :q2 :z2) )");
+        testEncode(graph, 5, (g)-> G.getOnePO(g, q, z));
+    }
+
+    @Test public void rdfx_03() {
+        // One term, used twice, subj/obj
+        Graph graph = data("(graph (<<:s :p :o>> :q :z) (:a :q <<:s :p :o>>) )");
+        testEncode(graph, 5, (g)-> G.getOnePO(g, q, z));
+    }
+
+    @Test public void rdfx_04() {
+        // One term, used subj/obj same triple.
+        Graph graph = data("(graph (<<:s :p :o>> :q <<:s :p :o>>) (<<:s :p :o>> :q :z) )");
+        testEncode(graph, 5, (g)-> G.getOnePO(g, q, z));
+    }
+
+    @Test public void rdfx_05() {
+        // Two terms
+        Graph graph = data("(graph (<<:s :p :o>> :q <<:s1 :p1 :o1>>) (<<:s :p :o>> :q :z) )");
+        testEncode(graph, 8, (g)-> G.getOnePO(g, q, z));
+    }
+
+    @Test public void rdfx_10() {
+        // One term
+        testEncodeDecode("(graph (<<:s :p :o>> :q :z) (:s1 :p1 :o1) )");
+    }
+
+    @Test public void rdfx_11() {
+        // One term, used twice, subj/subj
+        testEncodeDecode("(graph (<<:s :p :o>> :q :z) (<<:s :p :o>> :q2 :z2) )");
+    }
+
+    @Test public void rdfx_12() {
+        // One term, used twice, subj/obj
+        testEncodeDecode("(graph (<<:s :p :o>> :q :z) (:a :q <<:s :p :o>>) )");
+    }
+
+    @Test public void rdfx_13() {
+        // One term, used subj/obj same triple.
+        testEncodeDecode("(graph (<<:s :p :o>> :q <<:s :p :o>>) (<<:s :p :o>> :q :z) )");
+    }
+
+    @Test public void rdfx_14() {
+        // Two terms, used subj/obj same triple.
+        testEncodeDecode("(graph (<<:s :p :o>> :q <<:s1 :p1 :o1>>) (<<:s :p :o>> :q :z) )");
+    }
+
+    @Test public void rdfx_15() {
+        testEncodeDecode("(graph (<< <<:s :p :o>> :r :z>> :q :a) )");
+    }
+
+    @Test public void rdfx_18() {
+        String data = StrUtils.strjoinNL
+            ("(graph"
+            ,"  (<<:s :p :o>> :q :z)"
+            ,"  (<<:s :p :o>> :r <<:s :p :o>>)"
+            ,"  (:a :q <<:s :p :o>>)"
+            ,"  (:s :p :o)"
+            ,")");
+        testEncodeDecode(data);
+    }
+
+    @Test public void rdfx_19() {
+        testEncodeDecode("(graph (<< <<:s :p :o>> :r <<:s1 :p1 :o1>>>> :q <<:s :p :o>>) )");
+    }
+
+    static Graph data(String dataStr) {
+        Graph g = SSE.parseGraph(dataStr);
+        g.getPrefixMapping().setNsPrefix("", "http://example/");
+        g.getPrefixMapping().setNsPrefix("rdf", RDF.getURI());
+        return g;
+    }
+
+    private void testEncodeDecode(String str) {
+        Graph graph = data(str);
+        Graph g1 = RDFStar.encodeRDFStar(graph);
+        testNoTripleTerms(g1);
+        Graph g2 = RDFStar.decodeRDFStar(g1);
+        // check for no triple terms.
+        boolean b = isomorphic(graph, g2);
+        if ( ! b ) {
+            System.out.println("-- Mismatch");
+            RDFDataMgr.write(System.out, graph, RDFFormat.TURTLE_BLOCKS);
+            System.out.println("-- Encode");
+            RDFDataMgr.write(System.out, g1, RDFFormat.TURTLE_BLOCKS);
+            System.out.println("-- Decode");
+            RDFDataMgr.write(System.out, g2, RDFFormat.TURTLE_BLOCKS);
+            System.out.println("-----");
+        }
+
+        assertTrue(b);
+    }
+
+    private void testNoTripleTerms(Graph graph) {
+        assertFalse(G.find(graph, null, null, null).filterKeep(RDFStar::tripleHasNodeTriple).hasNext());
+    }
+
+    private void testEncode(Graph graph, int expectedSize, Function<Graph, Node> getReif) {
+        Graph g1 = RDFStar.encodeRDFStar(graph);
+        testNoTripleTerms(g1);
+        //RDFDataMgr.write(System.out, g1, RDFFormat.TURTLE_BLOCKS);
+        assertEquals("Encoded", expectedSize, g1.size());
+
+        // Check there is the expected reification.
+        Node reif = getReif.apply(g1);
+        assertTrue(G.containsOne(g1, reif, rdfSubject, s));
+        assertTrue(G.containsOne(g1, reif, rdfPredicate, p));
+        assertTrue(G.containsOne(g1, reif, rdfObject, o));
+    }
+
+    private void testInPlace(String str) {
+        Graph graph = data(str);
+
+        Graph g1a = RDFStar.encodeRDFStar(graph);
+        Graph g1 = RDFStar.encodeRDFStarInPlace(graph);
+
+        // Decode inplace.
+
+        assertSame(graph, g1);
+        testNoTripleTerms(g1);
+
+        boolean b = isomorphic(g1,g1a);
+        if ( ! b ) {
+            System.out.println("-- Mismatch");
+            RDFDataMgr.write(System.out, data(str), RDFFormat.TURTLE_BLOCKS);
+            System.out.println("-- Encode");
+            RDFDataMgr.write(System.out, g1, RDFFormat.TURTLE_BLOCKS);
+//            System.out.println("-- Decode");
+//            RDFDataMgr.write(System.out, g2, RDFFormat.TURTLE_BLOCKS);
+            System.out.println("-----");
+        }
+        assertTrue(b);
+    }
+
+    @Test public void rdfx_inplace_01() {
+        // One term
+        testInPlace("(graph (<<:s :p :o>> :q :z) (:s1 :p1 :o1) )");
+    }
+
+    @Test public void rdfx_inplace_02() {
+        // One term, used twice, subj/subj
+        testInPlace("(graph (<<:s :p :o>> :q :z) (<<:s :p :o>> :q2 :z2) )");
+    }
+
+    @Test public void rdfx_inplace_03() {
+        // One term, used twice, subj/obj
+        testInPlace("(graph (<<:s :p :o>> :q :z) (:a :q <<:s :p :o>>) )");
+    }
+
+    @Test public void rdfx_inplace_04() {
+        // One term, used subj/obj same triple.
+        testInPlace("(graph (<<:s :p :o>> :q <<:s :p :o>>) (<<:s :p :o>> :q :z) )");
+    }
+
+    @Test public void rdfx_inplace_05() {
+        // Two terms
+        testInPlace("(graph (<<:s :p :o>> :q <<:s1 :p1 :o1>>) (<<:s :p :o>> :q :z) )");
+    }
+
+    @Test public void rdfx_inplace_10() {
+        // One term
+        testInPlace("(graph (<<:s :p :o>> :q :z) (:s1 :p1 :o1) )");
+    }
+
+    @Test public void rdfx_inplace_11() {
+        // One term, used twice, subj/subj
+        testInPlace("(graph (<<:s :p :o>> :q :z) (<<:s :p :o>> :q2 :z2) )");
+    }
+
+    @Test public void rdfx_inplace_12() {
+        // One term, used twice, subj/obj
+        testInPlace("(graph (<<:s :p :o>> :q :z) (:a :q <<:s :p :o>>) )");
+    }
+
+    @Test public void rdfx_inplace_13() {
+        // One term, used subj/obj same triple.
+        testInPlace("(graph (<<:s :p :o>> :q <<:s :p :o>>) (<<:s :p :o>> :q :z) )");
+    }
+
+    @Test public void rdfx_inplace_14() {
+        // Two terms, used subj/obj same triple.
+        testInPlace("(graph (<<:s :p :o>> :q <<:s1 :p1 :o1>>) (<<:s :p :o>> :q :z) )");
+    }
+
+    @Test public void rdfx_inplace_15() {
+        testInPlace("(graph (<< <<:s :p :o>> :r :z>> :q :a) )");
+    }
+
+    @Test public void rdfx_inplace_18() {
+        String data = StrUtils.strjoinNL
+            ("(graph"
+            ,"  (<<:s :p :o>> :q :z)"
+            ,"  (<<:s :p :o>> :r <<:s :p :o>>)"
+            ,"  (:a :q <<:s :p :o>>)"
+            ,"  (:s :p :o)"
+            ,")");
+        testInPlace(data);
+    }
+
+    @Test public void rdfx_inplace_19() {
+        testEncodeDecode("(graph (<< <<:s :p :o>> :r <<:s1 :p1 :o1>>>> :q <<:s :p :o>>) )");
+    }
+}
+