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 2014/01/05 19:05:51 UTC

svn commit: r1555594 [1/2] - in /jena/Experimental/rdfpatch: ./ src/ src/main/ src/main/java/ src/main/java/dev/ src/main/java/org/ src/main/java/org/apache/ src/main/java/org/apache/jena/ src/main/java/org/apache/jena/rdfpatch/ src/main/java/org/apach...

Author: andy
Date: Sun Jan  5 18:05:50 2014
New Revision: 1555594

URL: http://svn.apache.org/r1555594
Log:
RDF Patch processor.
Fully transactional (serializable) in-memory datasets.

Added:
    jena/Experimental/rdfpatch/pom.xml
    jena/Experimental/rdfpatch/src/
    jena/Experimental/rdfpatch/src/main/
    jena/Experimental/rdfpatch/src/main/java/
    jena/Experimental/rdfpatch/src/main/java/dev/
    jena/Experimental/rdfpatch/src/main/java/dev/MainRec.java
    jena/Experimental/rdfpatch/src/main/java/dev/PROJECT_Patch.java
    jena/Experimental/rdfpatch/src/main/java/org/
    jena/Experimental/rdfpatch/src/main/java/org/apache/
    jena/Experimental/rdfpatch/src/main/java/org/apache/jena/
    jena/Experimental/rdfpatch/src/main/java/org/apache/jena/rdfpatch/
    jena/Experimental/rdfpatch/src/main/java/org/apache/jena/rdfpatch/DatasetChangesTuples.java
    jena/Experimental/rdfpatch/src/main/java/org/apache/jena/rdfpatch/DatasetGraphPatchTransaction.java
    jena/Experimental/rdfpatch/src/main/java/org/apache/jena/rdfpatch/DatasetGraphPlayer.java
    jena/Experimental/rdfpatch/src/main/java/org/apache/jena/rdfpatch/PatchAssembler.java
    jena/Experimental/rdfpatch/src/main/java/org/apache/jena/rdfpatch/RDFPatch.java
    jena/Experimental/rdfpatch/src/main/java/org/apache/jena/rdfpatch/Vocab.java
    jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/
    jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/
    jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/CommsException.java
    jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/TokenInputStream.java
    jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/TokenInputStreamBase.java
    jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/TokenInputStreamWrapper.java
    jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/TokenOutputStream.java
    jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/TokenOutputStreamWrapper.java
    jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/TokenOutputStreamWriter.java
    jena/Experimental/rdfpatch/src/test/
    jena/Experimental/rdfpatch/src/test/java/
    jena/Experimental/rdfpatch/src/test/java/org/
    jena/Experimental/rdfpatch/src/test/java/org/apache/
    jena/Experimental/rdfpatch/src/test/java/org/apache/jena/
    jena/Experimental/rdfpatch/src/test/java/org/apache/jena/rdfpatch/
    jena/Experimental/rdfpatch/src/test/java/org/apache/jena/rdfpatch/TS_RDFPatch.java
    jena/Experimental/rdfpatch/src/test/java/org/apache/jena/rdfpatch/TestPatchPlayer.java
    jena/Experimental/rdfpatch/src/test/java/org/apache/jena/rdfpatch/TestRDFPatch.java
    jena/Experimental/rdfpatch/src/test/java/org/apache/jena/riot/
    jena/Experimental/rdfpatch/src/test/java/org/apache/jena/riot/tio/
    jena/Experimental/rdfpatch/src/test/java/org/apache/jena/riot/tio/TestTokenIO.java

Added: jena/Experimental/rdfpatch/pom.xml
URL: http://svn.apache.org/viewvc/jena/Experimental/rdfpatch/pom.xml?rev=1555594&view=auto
==============================================================================
--- jena/Experimental/rdfpatch/pom.xml (added)
+++ jena/Experimental/rdfpatch/pom.xml Sun Jan  5 18:05:50 2014
@@ -0,0 +1,180 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+   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.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.apache.jena</groupId>
+  <artifactId>jena-rdfpatch</artifactId>
+  <packaging>jar</packaging>
+  <name>Apache Jena - RDF Patch</name>
+  <version>0.0.1-SNAPSHOT</version>
+
+  <parent>
+    <groupId>org.apache.jena</groupId>
+    <artifactId>jena-parent</artifactId>
+    <version>8-SNAPSHOT</version>
+    <relativePath>../jena-parent</relativePath>
+  </parent> 
+
+  <!-- Need if the parent is a snapshot -->
+  <repositories>
+    <repository>
+      <id>apache.snapshots</id>
+      <name>Apache Snapshot Repository</name>
+      <url>http://repository.apache.org/snapshots</url>
+      <releases>
+	<enabled>false</enabled>
+      </releases>
+    </repository>
+  </repositories>
+
+  <description>RDF Patch http://afs.github.io/rdf-patch/</description>
+  <scm>
+    <connection>scm:svn:http://svn.apache.org/repos/asf/jena/tags/jena-2.11.1-SNAPSHOT/jena-rdfpatch</connection>
+    <developerConnection>scm:svn:http://svn.apache.org/repos/asf/jena/tags/jena-2.11.1-SNAPSHOT/jena-rdfpatch</developerConnection>
+    <url>https://svn.apache.org/repos/asf/jena/tags/jena-2.11.1-SNAPSHOT/jena-rdfpatch</url>
+  </scm>
+  
+  <dependencies>
+
+    <dependency>
+      <groupId>org.apache.jena</groupId>
+      <artifactId>jena-arq</artifactId>
+      <version>2.11.1-SNAPSHOT</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.jena</groupId>
+      <artifactId>jena-arq</artifactId>
+      <classifier>tests</classifier>
+      <version>2.11.1-SNAPSHOT</version>
+      <scope>test</scope>
+    </dependency>
+
+     <dependency>
+      <groupId>org.apache.jena</groupId>
+      <artifactId>jena-core</artifactId>
+      <version>2.11.1-SNAPSHOT</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.jena</groupId>
+      <artifactId>jena-core</artifactId>
+      <classifier>tests</classifier>
+      <version>2.11.1-SNAPSHOT</version>
+      <scope>test</scope>
+    </dependency>
+
+  </dependencies>
+
+  <build>
+    <!--
+    <resources>
+      <resource>
+        <filtering>false</filtering>
+        <directory>src/main/resources</directory>
+        <excludes>
+          <exclude>org/apache/jena/patch/patch-properties.xml</exclude>
+        </excludes>
+      </resource>
+      <resource>
+        <filtering>true</filtering>
+        <directory>src/main/resources</directory>
+        <includes>
+          <include>org/apache/jena/patch/patch-properties.xml</include>
+        </includes>
+      </resource>
+    </resources>
+    -->
+
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <includes>
+            <include>**/TS_*.java</include>
+          </includes>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-source-plugin</artifactId>
+        <executions> 
+          <execution>
+            <id>attach-sources</id>
+	    <!-- <phase>package</phase> package is the default -->
+            <goals>
+              <goal>jar-no-fork</goal> 
+            </goals>
+          </execution>
+          <execution>
+            <id>attach-sources-test</id>
+            <goals>
+              <goal>test-jar-no-fork</goal>
+            </goals>
+          </execution>
+        </executions>
+      
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <executions>
+          <execution>
+            <goals>
+              <goal>test-jar</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-javadoc-plugin</artifactId>
+        <configuration>
+          <version>true</version>
+          <show>public</show>
+          <quiet>true</quiet>
+          <encoding>UTF-8</encoding>
+          <windowtitle>Apache Jena - RDF Patch</windowtitle>
+          <doctitle>Apache Jena - RDF Patch ${project.version}</doctitle>
+          <bottom>Licenced under the Apache License, Version 2.0</bottom>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-resources-plugin</artifactId>
+      </plugin>
+
+    </plugins>
+  </build>
+
+  <reporting>
+    <!-- <outputDirectory>${project.build.directory}/site</outputDirectory> -->
+  </reporting>
+
+</project>

Added: jena/Experimental/rdfpatch/src/main/java/dev/MainRec.java
URL: http://svn.apache.org/viewvc/jena/Experimental/rdfpatch/src/main/java/dev/MainRec.java?rev=1555594&view=auto
==============================================================================
--- jena/Experimental/rdfpatch/src/main/java/dev/MainRec.java (added)
+++ jena/Experimental/rdfpatch/src/main/java/dev/MainRec.java Sun Jan  5 18:05:50 2014
@@ -0,0 +1,124 @@
+/**
+ * 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 dev;
+
+import java.io.StringWriter ;
+
+import org.apache.jena.atlas.iterator.Iter ;
+import org.apache.jena.atlas.logging.LogCtl ;
+import org.apache.jena.rdfpatch.DatasetChangesTuples ;
+import org.apache.jena.rdfpatch.DatasetGraphPatchTransaction ;
+import org.apache.jena.rdfpatch.DatasetGraphPlayer ;
+import org.apache.jena.rdfpatch.RDFPatch ;
+import org.apache.jena.riot.tio.TokenInputStream ;
+import org.apache.jena.riot.tio.TokenInputStreamBase ;
+import org.apache.jena.riot.tio.TokenOutputStreamWriter ;
+import org.apache.jena.riot.tokens.Tokenizer ;
+import org.apache.jena.riot.tokens.TokenizerFactory ;
+
+import com.hp.hpl.jena.graph.Graph ;
+import com.hp.hpl.jena.graph.Node ;
+import com.hp.hpl.jena.graph.NodeFactory ;
+import com.hp.hpl.jena.query.ReadWrite ;
+import com.hp.hpl.jena.sparql.core.* ;
+import com.hp.hpl.jena.sparql.sse.SSE ;
+
+public class MainRec
+{
+    static { LogCtl.setCmdLogging() ; }
+    
+    public static void main(String[] args) {
+        DatasetGraph dsg1 = DatasetGraphFactory.createMem() ;
+        DatasetGraphPatchTransaction dsg = new  DatasetGraphPatchTransaction(dsg1) ;
+        Quad q = SSE.parseQuad("(:g <s> <p> _:a)") ;
+        dsg.begin(ReadWrite.WRITE);
+        dsg.add(q) ;
+        SSE.write(dsg) ;
+        dsg.abort() ;
+        dsg.end();
+        SSE.write(dsg) ;
+        
+        dsg.begin(ReadWrite.WRITE);
+        dsg.add(q) ;
+        dsg.commit() ;
+        dsg.end();
+        SSE.write(dsg) ;
+        
+        dsg.begin(ReadWrite.READ);
+        Iter.toList(dsg.find()) ;
+        dsg.end();
+        System.out.println("DONE") ; 
+    }
+    
+    public static void main1(String[] args)
+    {
+        DatasetGraph dsg = DatasetGraphFactory.createMem() ;
+        if ( false )
+            dsg = new DatasetGraphSimpleMem() ;
+        
+        StringWriter sw = new StringWriter() ;
+        TokenOutputStreamWriter out = new TokenOutputStreamWriter(null, sw) ;
+        DatasetChanges changeLogger = new DatasetChangesTuples(out) ;
+        
+        dsg = RDFPatch.record(dsg, changeLogger) ;
+        out.setPrefixMapping("", "http://example/") ;
+        
+        //changeLogger.begin(ReadWrite.WRITE) ;
+        changeLogger.start() ;
+        Quad q = SSE.parseQuad("(:g <s> <p> _:a)") ;
+        Node b = q.getObject() ;
+        dsg.add(q) ;
+        dsg.delete(q) ;
+        dsg.delete(q) ;
+        
+//        changeLogger.commit() ;
+//        changeLogger.end() ;
+        
+        Graph g = SSE.parseGraph("(graph (<s1> <p1> 3) (<s2> <p2> 2))") ;
+        dsg.addGraph(NodeFactory.createURI("graph"), g) ;
+        changeLogger.finish(); 
+        out.flush();
+
+        SSE.write(dsg) ;
+
+        System.out.println("------------------------------") ;
+        System.out.print(sw.toString()) ;
+
+        Tokenizer t = TokenizerFactory.makeTokenizerString(sw.toString()) ;
+        TokenInputStream in = new TokenInputStreamBase(null, t) ;
+//        while(in.hasNext())
+//        {
+//            List<Token> line = in.next() ;
+//            System.out.println(line) ;
+//        }
+        
+        System.out.println("------------------------------") ;
+        
+        DatasetGraph dsg2 = DatasetGraphFactory.createMem() ;
+        DatasetGraphPlayer.play(in, dsg2) ;
+        SSE.write(dsg2) ;
+        
+        Node x = dsg2.getGraph(NodeFactory.createURI("http://example/g")).find(null, null, null).next().getObject() ;
+        System.out.println(x.getBlankNodeLabel()) ;
+        System.out.println(x.equals(b)) ;
+        System.out.println("DONE") ; 
+    }
+
+}
+

Added: jena/Experimental/rdfpatch/src/main/java/dev/PROJECT_Patch.java
URL: http://svn.apache.org/viewvc/jena/Experimental/rdfpatch/src/main/java/dev/PROJECT_Patch.java?rev=1555594&view=auto
==============================================================================
--- jena/Experimental/rdfpatch/src/main/java/dev/PROJECT_Patch.java (added)
+++ jena/Experimental/rdfpatch/src/main/java/dev/PROJECT_Patch.java Sun Jan  5 18:05:50 2014
@@ -0,0 +1,33 @@
+/**
+ * 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 dev;
+
+
+public class PROJECT_Patch {
+    // How to end changes and flush.
+    
+    // Is it time to have a universal event handled for add/delete?
+    //  DatasetGraph -> DatasetGraph+txn 
+    
+    // Consider query engine registries.
+    // How to have wrappers yet still pass down to concrete DSGs.
+    // Just a case of a Plan that passwses to the wrapped DSG?
+    
+    // Jena3: dsg.exec(op) ?
+}

Added: jena/Experimental/rdfpatch/src/main/java/org/apache/jena/rdfpatch/DatasetChangesTuples.java
URL: http://svn.apache.org/viewvc/jena/Experimental/rdfpatch/src/main/java/org/apache/jena/rdfpatch/DatasetChangesTuples.java?rev=1555594&view=auto
==============================================================================
--- jena/Experimental/rdfpatch/src/main/java/org/apache/jena/rdfpatch/DatasetChangesTuples.java (added)
+++ jena/Experimental/rdfpatch/src/main/java/org/apache/jena/rdfpatch/DatasetChangesTuples.java Sun Jan  5 18:05:50 2014
@@ -0,0 +1,118 @@
+/**
+ * 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.rdfpatch;
+
+import java.io.PrintStream ;
+
+import org.apache.jena.atlas.lib.Sync ;
+import org.apache.jena.riot.tio.TokenInputStream ;
+import org.apache.jena.riot.tio.TokenOutputStream ;
+
+import com.hp.hpl.jena.graph.Node ;
+import com.hp.hpl.jena.query.ReadWrite ;
+import com.hp.hpl.jena.sparql.core.DatasetChanges ;
+import com.hp.hpl.jena.sparql.core.DatasetGraph ;
+import com.hp.hpl.jena.sparql.core.QuadAction ;
+import com.hp.hpl.jena.sparql.core.Transactional ;
+import com.hp.hpl.jena.sparql.util.FmtUtils ;
+
+/** Write changes to a TokenOutputStream, often going to a file.
+ * The reverse operation of applying a record stream is provided by
+ * {@linkplain DatasetGraphPlayer#play(TokenInputStream, DatasetGraph)}.
+ */
+
+public class DatasetChangesTuples implements DatasetChanges, Transactional, Sync
+{
+    private TokenOutputStream out ;
+    
+    public DatasetChangesTuples(TokenOutputStream out) { this.out = out; }
+    
+    @Override
+    public void start()
+    {}
+
+    @Override
+    public void finish()
+    { out.flush() ; } 
+
+    @Override
+    public void change(QuadAction action, Node g, Node s, Node p, Node o)
+    {
+        record(action, g, s, p, o) ;
+    }
+
+    static final String SEP1 = ", " ;    // TAB is good. 
+    static final String SEP2 = "\n" ; 
+    
+    private void record(QuadAction action, Node g, Node s, Node p, Node o)
+    {
+        out.startTuple() ;
+        out.sendWord(action.label) ;
+        out.sendNode(g) ;
+        out.sendNode(s) ;
+        out.sendNode(p) ;
+        out.sendNode(o) ;
+        out.endTuple() ;
+    }
+    
+    private void print(PrintStream out, Node x)
+    {
+        String str = FmtUtils.stringForNode(x) ;
+        out.print(str) ;
+    }
+
+    private void outOneWord(String word) {
+        out.startTuple() ;
+        out.sendWord(word) ;
+        out.endTuple() ;
+    }
+    
+    // Need to record at success/failure of transaction.
+    
+    boolean isInTransaction = false ;
+    
+    @Override
+    public void begin(ReadWrite readWrite)
+    { outOneWord("BEGIN/"+readWrite.name()) ; isInTransaction = true ; }
+
+    @Override
+    public void commit()
+    { outOneWord("COMMIT") ; isInTransaction = false ;}
+
+    @Override
+    public void abort()
+    { outOneWord("ABORT") ; isInTransaction = false ;}
+
+    @Override
+    public boolean isInTransaction()
+    {
+        return isInTransaction ;
+    }
+
+    @Override
+    public void end()
+    { outOneWord("END") ; }
+
+    @Override
+    public void sync() {
+        out.flush() ;
+    }
+
+}
+

Added: jena/Experimental/rdfpatch/src/main/java/org/apache/jena/rdfpatch/DatasetGraphPatchTransaction.java
URL: http://svn.apache.org/viewvc/jena/Experimental/rdfpatch/src/main/java/org/apache/jena/rdfpatch/DatasetGraphPatchTransaction.java?rev=1555594&view=auto
==============================================================================
--- jena/Experimental/rdfpatch/src/main/java/org/apache/jena/rdfpatch/DatasetGraphPatchTransaction.java (added)
+++ jena/Experimental/rdfpatch/src/main/java/org/apache/jena/rdfpatch/DatasetGraphPatchTransaction.java Sun Jan  5 18:05:50 2014
@@ -0,0 +1,110 @@
+/**
+ * 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.rdfpatch;
+
+import org.apache.jena.rdfpatch.DatasetGraphPlayer.Direction ;
+
+import com.hp.hpl.jena.graph.Node ;
+import com.hp.hpl.jena.query.ReadWrite ;
+import com.hp.hpl.jena.sparql.core.* ;
+
+/** Provide transactional semantics to a DatasetGraph.
+ *  Changes are made the dataset immediately and an undo log is kept.
+ *  If {@code abort()} is called, the changes are undone.
+ *  Implicitly uses MRSW locking; there is no true parallel readers
+ *  when a write transaction is active.
+ */
+
+public class DatasetGraphPatchTransaction extends DatasetGraphWithLock { 
+    private final DatasetChangesTransaction delta ;
+    // The original dataset.  Used as the replay target.  
+    private final DatasetGraph dataset ;
+    // The dataset with a change monitor. 
+    private final DatasetGraph datasetMonitor ;
+    
+    public DatasetGraphPatchTransaction(DatasetGraph dsg) {
+        // Instead of passing in the dataset, we override get()
+        super(null) ;
+        delta = new DatasetChangesTransaction() ;
+        this.dataset = dsg ;
+        DatasetGraphMonitor dsgm = new DatasetGraphMonitor(dsg, delta) ;
+        datasetMonitor = dsgm  ;
+    }
+    
+    @Override
+    protected DatasetGraph get() { return datasetMonitor ; }
+    
+    @Override
+    protected void _begin(ReadWrite readWrite) {
+        switch (readWrite) {
+            case READ : break ;
+            case WRITE :
+                delta.start() ;
+                break ;
+            default :
+        }
+        super._begin(readWrite) ; 
+    }
+
+    @Override
+    protected void _commit() {
+        delta.finish() ;
+        super._commit() ; 
+    }
+
+    @Override
+    protected void _abort() {
+        if ( isTransactionType(ReadWrite.WRITE) )
+            DatasetGraphPlayer.play(delta.get().getActions(), dataset, Direction.BACKWARDS) ;
+        // fakery for super.abort()
+        if ( isTransactionType(ReadWrite.WRITE) )
+            super._end() ;
+    }
+
+    @Override
+    protected void _end() {
+        delta.finish() ;
+        super._end() ;
+    }
+    
+    static class DatasetChangesTransaction implements DatasetChanges {
+        private DatasetChangesCapture changes ;
+        public DatasetChangesCapture get()  { return changes ; } 
+
+        public DatasetChangesTransaction() {
+        }
+
+        @Override
+        public void start() {
+            changes = new DatasetChangesCapture() ;
+            changes.start() ;
+        }
+
+        @Override
+        public void change(QuadAction qaction, Node g, Node s, Node p, Node o) {
+            changes.change(qaction, g, s, p, o) ;
+        }
+
+        @Override
+        public void finish() {
+            if ( changes != null )
+                changes.finish() ;
+        }
+    }
+}

Added: jena/Experimental/rdfpatch/src/main/java/org/apache/jena/rdfpatch/DatasetGraphPlayer.java
URL: http://svn.apache.org/viewvc/jena/Experimental/rdfpatch/src/main/java/org/apache/jena/rdfpatch/DatasetGraphPlayer.java?rev=1555594&view=auto
==============================================================================
--- jena/Experimental/rdfpatch/src/main/java/org/apache/jena/rdfpatch/DatasetGraphPlayer.java (added)
+++ jena/Experimental/rdfpatch/src/main/java/org/apache/jena/rdfpatch/DatasetGraphPlayer.java Sun Jan  5 18:05:50 2014
@@ -0,0 +1,195 @@
+/**
+ * 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.rdfpatch;
+
+import java.util.Iterator ;
+import java.util.List ;
+import java.util.ListIterator ;
+
+import org.apache.jena.atlas.lib.Pair ;
+import org.apache.jena.riot.system.RiotLib ;
+import org.apache.jena.riot.tio.TokenInputStream ;
+import org.apache.jena.riot.tokens.Token ;
+
+import com.hp.hpl.jena.graph.Node ;
+import com.hp.hpl.jena.sparql.core.DatasetChangesCapture ;
+import com.hp.hpl.jena.sparql.core.DatasetGraph ;
+import com.hp.hpl.jena.sparql.core.Quad ;
+import com.hp.hpl.jena.sparql.core.QuadAction ;
+
+public class DatasetGraphPlayer
+{
+    enum Direction { FORWARDS, BACKWARDS }
+    
+    /**
+     * @see DatasetChangesCapture
+     */
+    public static void play(List<Pair<QuadAction, Quad>> actions, DatasetGraph dsg, Direction direction)
+    {
+        Iterator<Pair<QuadAction, Quad>> iter = 
+            (Direction.BACKWARDS == direction) 
+            ? new ListIteratorReverse<Pair<QuadAction, Quad>>(actions.listIterator(actions.size())) 
+            : actions.listIterator() ;
+
+        loop: 
+        while ( iter.hasNext() ) {
+            Pair<QuadAction, Quad> p = iter.next() ;
+            QuadAction action = p.getLeft() ;
+            Quad quad = p.getRight() ;
+            
+            boolean addOp = true ;
+            switch (action) {
+                case ADD :
+                    break ;
+                case DELETE :
+                    addOp = false ; 
+                    break ;
+                case NO_ADD :
+                case NO_DELETE :
+                default :
+                    continue loop ;
+            }
+            
+            if ( direction == Direction.BACKWARDS )
+                addOp = ! addOp ;
+            
+            if ( addOp )
+                dsg.add(quad) ;
+            else
+                dsg.delete(quad) ;
+        }
+    }
+
+    /** Reverse the mainings of next/previous on a ListIterator */
+    static class ListIteratorReverse<T> implements ListIterator<T> {
+        private final ListIterator<T> iter ;
+
+        ListIteratorReverse(ListIterator<T> iter) {
+             this.iter= iter ; 
+        }
+        
+        @Override
+        public boolean hasNext() {
+            return iter.hasPrevious() ;
+        }
+
+        @Override
+        public T next() {
+            return iter.previous() ;
+        }
+
+        @Override
+        public boolean hasPrevious() {
+            return iter.hasNext() ;
+        }
+
+        @Override
+        public T previous() {
+            return iter.next() ;
+        }
+
+        @Override
+        public int nextIndex() {
+            return iter.previousIndex() ;
+        }
+
+        @Override
+        public int previousIndex() {
+            return iter.nextIndex() ;
+        }
+
+        @Override
+        public void remove() { iter.remove() ; }
+
+        @Override
+        public void set(T e) { iter.set(e) ; } 
+
+        @Override
+        public void add(T e) { iter.add(e) ; }
+    }
+    
+    public static void play(TokenInputStream input, DatasetGraph dsg)
+    {
+        long count = 0 ; 
+        long errorCount = 0 ;
+        while(input.hasNext())
+        {
+            List<Token> line = input.next() ;
+            count++ ;
+            if ( line.size() == 0 )
+                continue ;
+            if ( line.size() == 1 )
+            {
+                // Maybe a word
+                continue ;
+            }
+            
+            if ( line.size() != 5 ) {
+                errorCount++ ;
+                error("[%d] Bad tuple - length %d", count, line.size() ) ;
+//                if ( errorCount > MaxErrors ) {
+//                    error("Too many errors" ) ;
+//                    return ;
+//                }
+            }
+
+            Token ctl = line.get(0) ;
+            if ( ! ctl.isWord() )
+                error("[%d] No start word",count) ; 
+            String str = ctl.getImage() ;
+            if ( str.startsWith("#") )
+                continue ;
+            Node g = asNode(line.get(1)) ;
+            Node s = asNode(line.get(2)) ;
+            Node p = asNode(line.get(3)) ;
+            Node o = asNode(line.get(4)) ;
+            
+            boolean addOp = true ;
+            
+            if ( str.equals("A") )
+                addOp = true ;
+            else if ( str.equals("D") )
+                addOp = false ;
+            else
+                error("Bad word: "+str) ;
+            
+//            if ( direction == Direction.BACKWARDS )
+//                addOp = ! addOp ;
+            
+            if ( addOp )
+                dsg.add(g,s,p,o) ;
+            else
+                dsg.delete(g,s,p,o) ;
+        }
+    }
+
+    private static Node asNode(Token t) {
+        // <_:...> bnodes.
+        if ( t.isIRI() )
+            return RiotLib.createIRIorBNode(t.getImage()) ;
+        return t.asNode() ;
+    }
+    
+    private static void error(String fmt, Object...args)
+    {
+        throw new RuntimeException(String.format(fmt,args)) ;
+    }
+    
+}
+

Added: jena/Experimental/rdfpatch/src/main/java/org/apache/jena/rdfpatch/PatchAssembler.java
URL: http://svn.apache.org/viewvc/jena/Experimental/rdfpatch/src/main/java/org/apache/jena/rdfpatch/PatchAssembler.java?rev=1555594&view=auto
==============================================================================
--- jena/Experimental/rdfpatch/src/main/java/org/apache/jena/rdfpatch/PatchAssembler.java (added)
+++ jena/Experimental/rdfpatch/src/main/java/org/apache/jena/rdfpatch/PatchAssembler.java Sun Jan  5 18:05:50 2014
@@ -0,0 +1,68 @@
+/**
+ * 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.rdfpatch;
+
+import static com.hp.hpl.jena.sparql.util.graph.GraphUtils.exactlyOneProperty ;
+
+import java.io.FileOutputStream ;
+import java.io.IOException ;
+import java.io.OutputStream ;
+
+import org.apache.jena.atlas.io.IO ;
+
+import com.hp.hpl.jena.assembler.Assembler ;
+import com.hp.hpl.jena.assembler.Mode ;
+import com.hp.hpl.jena.assembler.exceptions.AssemblerException ;
+import com.hp.hpl.jena.query.Dataset ;
+import com.hp.hpl.jena.query.DatasetFactory ;
+import com.hp.hpl.jena.rdf.model.Property ;
+import com.hp.hpl.jena.rdf.model.Resource ;
+import com.hp.hpl.jena.sparql.core.DatasetGraph ;
+import com.hp.hpl.jena.sparql.core.assembler.DatasetAssembler ;
+import com.hp.hpl.jena.sparql.core.assembler.DatasetAssemblerException ;
+
+public class PatchAssembler extends DatasetAssembler
+{
+    private static final String NS = "http://jena.apache.org/rdfpatch#" ;
+    
+    static Property pDataset = Vocab.property(NS, "dataset") ; 
+    static Property pChangeLog = Vocab.property(NS, "changelog") ;
+    
+    @Override
+    public Dataset createDataset(Assembler a, Resource root, Mode mode) {
+        if ( !exactlyOneProperty(root, pDataset) )
+            throw new AssemblerException(root, "No dataset to wrap") ;
+
+        Resource other = getRequiredResource(root, pDataset) ;
+     
+        Object obj = super.open(a, other, mode) ;
+        if ( ! ( obj instanceof Dataset ) )
+            throw new DatasetAssemblerException(root, "Not a dataset: "+other) ;
+        DatasetGraph dsg = ((Dataset)obj).asDatasetGraph() ;
+        
+        // And where to route the changes.
+        
+        String logfile = getUniqueString(root, pChangeLog) ;
+        try {
+            OutputStream outs = new FileOutputStream(logfile) ;
+            DatasetGraph dsgx = RDFPatch.recordChanges(dsg, outs) ;
+            return DatasetFactory.create(dsgx) ;    
+        } catch (IOException ex) { IO.exception(ex); return null ; }
+    }
+}

Added: jena/Experimental/rdfpatch/src/main/java/org/apache/jena/rdfpatch/RDFPatch.java
URL: http://svn.apache.org/viewvc/jena/Experimental/rdfpatch/src/main/java/org/apache/jena/rdfpatch/RDFPatch.java?rev=1555594&view=auto
==============================================================================
--- jena/Experimental/rdfpatch/src/main/java/org/apache/jena/rdfpatch/RDFPatch.java (added)
+++ jena/Experimental/rdfpatch/src/main/java/org/apache/jena/rdfpatch/RDFPatch.java Sun Jan  5 18:05:50 2014
@@ -0,0 +1,78 @@
+/**
+ * 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.rdfpatch ;
+
+import java.io.InputStream ;
+import java.io.OutputStream ;
+import java.io.Writer ;
+
+import org.apache.jena.atlas.io.IO ;
+import org.apache.jena.riot.tio.TokenInputStream ;
+import org.apache.jena.riot.tio.TokenInputStreamBase ;
+import org.apache.jena.riot.tio.TokenOutputStream ;
+import org.apache.jena.riot.tio.TokenOutputStreamWriter ;
+import org.apache.jena.riot.tokens.Tokenizer ;
+import org.apache.jena.riot.tokens.TokenizerFactory ;
+
+import com.hp.hpl.jena.sparql.core.DatasetChanges ;
+import com.hp.hpl.jena.sparql.core.DatasetGraph ;
+import com.hp.hpl.jena.sparql.core.DatasetGraphMonitor ;
+
+public class RDFPatch {
+    /**
+     * Write changes to an OutputStream. Chnages are written in 
+     * <a href="http://afs.github.io/rdf-patch/">RDF Patch</a> format.
+     * When finished, call <code>.sync()<</code> to ensure
+     * buffered changes have been written to the <code>OutputStream</code>.
+     * 
+     * @param dsg
+     * @param output Destination
+     * @return DatasetGraph
+     */
+    public static DatasetGraphMonitor recordChanges(DatasetGraph dsg, OutputStream output) {
+        Writer w = IO.asBufferedUTF8(output) ;
+        TokenOutputStream out = new TokenOutputStreamWriter(null, w) ;
+        DatasetChanges changes = new DatasetChangesTuples(out) ;
+        return record(dsg, changes) ;
+    }
+
+    /**
+     * Record changes, sending a record of them to a DatasetChanges processor.
+     * 
+     * @param dsg DatasetGraph
+     * @param changes A DatasetChanges processor.
+     * @return DatasetGraph
+     */
+    public static DatasetGraphMonitor record(DatasetGraph dsg, DatasetChanges changes) {
+        DatasetGraphMonitor monitor = new DatasetGraphMonitor(dsg, changes) ;
+        return monitor ;
+    }
+
+    /**
+     * Replay a stream in <a href="http://afs.github.io/rdf-patch/">RDF
+     * Patch</a> format, as might be produced by {@linkplain #recordChanges}
+     * @param dsg
+     * @param input
+     */
+    public static void replayChanges(DatasetGraph dsg, InputStream input) {
+        Tokenizer t = TokenizerFactory.makeTokenizerUTF8(input) ;
+        TokenInputStream in = new TokenInputStreamBase(null, t) ;
+        DatasetGraphPlayer.play(in, dsg) ;
+    }
+}

Added: jena/Experimental/rdfpatch/src/main/java/org/apache/jena/rdfpatch/Vocab.java
URL: http://svn.apache.org/viewvc/jena/Experimental/rdfpatch/src/main/java/org/apache/jena/rdfpatch/Vocab.java?rev=1555594&view=auto
==============================================================================
--- jena/Experimental/rdfpatch/src/main/java/org/apache/jena/rdfpatch/Vocab.java (added)
+++ jena/Experimental/rdfpatch/src/main/java/org/apache/jena/rdfpatch/Vocab.java Sun Jan  5 18:05:50 2014
@@ -0,0 +1,41 @@
+/*
+ * 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.rdfpatch;
+
+import com.hp.hpl.jena.rdf.model.Property;
+import com.hp.hpl.jena.rdf.model.Resource;
+import com.hp.hpl.jena.rdf.model.ResourceFactory;
+
+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 Property property(String namespace, String localName)
+    {
+        return ResourceFactory.createProperty(namespace+localName) ;
+    }
+}

Added: jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/CommsException.java
URL: http://svn.apache.org/viewvc/jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/CommsException.java?rev=1555594&view=auto
==============================================================================
--- jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/CommsException.java (added)
+++ jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/CommsException.java Sun Jan  5 18:05:50 2014
@@ -0,0 +1,34 @@
+/*
+ * 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.riot.tio;
+
+public class CommsException extends RuntimeException
+{
+    public CommsException()                 
+    { super(); }
+
+    public CommsException(String message)   
+    { super(message) ; }
+
+    public CommsException(String message, Throwable cause)  
+    { super(message, cause) ; }
+
+    public CommsException(Throwable cause)  
+    { super(cause) ; }
+}

Added: jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/TokenInputStream.java
URL: http://svn.apache.org/viewvc/jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/TokenInputStream.java?rev=1555594&view=auto
==============================================================================
--- jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/TokenInputStream.java (added)
+++ jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/TokenInputStream.java Sun Jan  5 18:05:50 2014
@@ -0,0 +1,31 @@
+/*
+ * 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.riot.tio;
+
+import java.util.Iterator ;
+import java.util.List ;
+
+import org.apache.jena.atlas.lib.Closeable ;
+import org.apache.jena.riot.tokens.Token ;
+
+/** A stream of tokens */
+public interface TokenInputStream extends Iterator<List<Token>>, Iterable<List<Token>>, Closeable
+{
+    
+}

Added: jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/TokenInputStreamBase.java
URL: http://svn.apache.org/viewvc/jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/TokenInputStreamBase.java?rev=1555594&view=auto
==============================================================================
--- jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/TokenInputStreamBase.java (added)
+++ jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/TokenInputStreamBase.java Sun Jan  5 18:05:50 2014
@@ -0,0 +1,167 @@
+/*
+ * 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.riot.tio ;
+
+import java.util.ArrayList ;
+import java.util.HashMap ;
+import java.util.Iterator ;
+import java.util.List ;
+import java.util.Map ;
+import java.util.NoSuchElementException ;
+
+import org.apache.jena.riot.tokens.PrintTokenizer ;
+import org.apache.jena.riot.tokens.Token ;
+import org.apache.jena.riot.tokens.Tokenizer ;
+import static org.apache.jena.riot.tokens.TokenType.* ;
+import org.slf4j.Logger ;
+import org.slf4j.LoggerFactory ;
+
+/** Tokenizer that sorts out prefixes and groups into sequences of token */
+public class TokenInputStreamBase implements TokenInputStream {
+    private static Logger       log      = LoggerFactory.getLogger(TokenInputStreamBase.class) ;
+    private boolean             finished = false ;
+    private final Tokenizer     tokens ;
+    private List<Token>         list ;
+    private Map<String, String> map      = new HashMap<String, String>() ;
+    private String              label ;
+
+    public TokenInputStreamBase(String label, Tokenizer tokens) {
+        if ( false )
+            tokens = new PrintTokenizer("InputStream: ", tokens) ;
+        this.tokens = tokens ;
+        this.label = label ;
+    }
+
+    @Override
+    public boolean hasNext() {
+        if ( finished )
+            return false ;
+
+        if ( list != null ) // Already got the reply.
+            return true ;
+
+        try {
+            if ( !tokens.hasNext() ) {
+                finished = true ;
+                return false ;
+            }
+            list = buildOneLine() ;
+            if ( false && log.isDebugEnabled() )
+                log.debug("Tokens: " + list) ;
+            if ( list == null )
+                finished = true ;
+            return list != null ;
+        } catch (Exception ex) {
+            finished = true ;
+            return false ;
+        }
+    }
+
+    private List<Token> buildOneLine() {
+        List<Token> tuple = new ArrayList<Token>() ;
+        boolean isDirective = false ;
+        for (; tokens.hasNext();) {
+            Token token = tokens.next() ;
+
+            if ( token.hasType(DIRECTIVE) )
+                isDirective = true ;
+
+            if ( token.hasType(DOT) ) {
+                if ( tuple.size() > 0 && tuple.get(0).hasType(DIRECTIVE) ) {
+                    directive(tuple) ;
+                    tuple.clear() ;
+                    isDirective = false ;
+                    // Start again.
+                    continue ;
+                }
+                return tuple ;
+            }
+
+            // Fixup prefix names.
+            if ( !isDirective && token.hasType(PREFIXED_NAME) ) {
+                String ns = map.get(token.getImage()) ;
+                String iri ;
+                if ( ns == null ) {
+                    log.warn("Can't resolve '" + token.toString(false) + "'", ns) ;
+                    iri = "unresolved:" + token.getImage() + ":" + token.getImage2() ;
+                } else
+                    iri = ns + token.getImage2() ;
+                token.setType(IRI) ;
+                token.setImage(iri) ;
+                token.setImage2(null) ;
+            }
+
+            tuple.add(token) ;
+        }
+
+        // No final DOT
+        return tuple ;
+    }
+
+    private void directive(List<Token> tuple) {
+        if ( tuple.size() != 3 )
+            throw new CommsException("Bad directive: " + tuple) ;
+
+        String x = tuple.get(0).getImage() ;
+
+        if ( x.equals("prefix") ) {
+            // Raw - unresolved prefix name.
+            if ( !tuple.get(1).hasType(PREFIXED_NAME) )
+                throw new CommsException("@prefix requires a prefix (found '" + tuple.get(1) + "')") ;
+            if ( tuple.get(1).getImage2().length() != 0 )
+                throw new CommsException("@prefix requires a prefix and no suffix (found '" + tuple.get(1) + "')") ;
+            String prefix = tuple.get(1).getImage() ;
+
+            if ( !tuple.get(2).hasType(IRI) )
+                throw new CommsException("@prefix requires an IRI (found '" + tuple.get(1) + "')") ;
+            String iriStr = tuple.get(2).getImage() ;
+            map.put(prefix, iriStr) ;
+            return ;
+        }
+        throw new CommsException("Unregcognized directive: " + x) ;
+    }
+
+    @Override
+    public List<Token> next() {
+        if ( !hasNext() )
+            throw new NoSuchElementException() ;
+        List<Token> r = list ;
+        if ( log.isDebugEnabled() ) {
+            if ( label != null )
+                log.debug("<< " + label + ": " + r) ;
+            else
+                log.debug("<< " + r.toString()) ;
+        }
+        list = null ;
+        return r ;
+    }
+
+    @Override
+    public void remove() {
+        throw new UnsupportedOperationException() ;
+    }
+
+    @Override
+    public Iterator<List<Token>> iterator() {
+        return this ;
+    }
+
+    @Override
+    public void close() {}
+}

Added: jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/TokenInputStreamWrapper.java
URL: http://svn.apache.org/viewvc/jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/TokenInputStreamWrapper.java?rev=1555594&view=auto
==============================================================================
--- jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/TokenInputStreamWrapper.java (added)
+++ jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/TokenInputStreamWrapper.java Sun Jan  5 18:05:50 2014
@@ -0,0 +1,47 @@
+/*
+ * 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.riot.tio;
+
+import java.util.Iterator ;
+import java.util.List ;
+
+import org.apache.jena.atlas.lib.Closeable ;
+import org.apache.jena.riot.tokens.Token ;
+
+public class TokenInputStreamWrapper implements Iterator<List<Token>>, Iterable<List<Token>>, Closeable
+{
+    private TokenInputStream stream ;
+    
+    public TokenInputStreamWrapper(TokenInputStream stream) { this.stream = stream ; }
+
+    @Override
+    public boolean hasNext()                    { return stream.hasNext() ; }
+
+    @Override
+    public List<Token> next()                   { return stream.next() ; }
+
+    @Override
+    public void remove()                        { stream.remove() ; }
+
+    @Override
+    public Iterator<List<Token>> iterator()     { return stream.iterator() ; }
+
+    @Override
+    public void close()                         { stream.close(); }
+}

Added: jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/TokenOutputStream.java
URL: http://svn.apache.org/viewvc/jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/TokenOutputStream.java?rev=1555594&view=auto
==============================================================================
--- jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/TokenOutputStream.java (added)
+++ jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/TokenOutputStream.java Sun Jan  5 18:05:50 2014
@@ -0,0 +1,40 @@
+/*
+ * 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.riot.tio;
+
+import org.apache.jena.atlas.lib.Closeable ;
+import org.apache.jena.atlas.lib.Sync ;
+import org.apache.jena.riot.tokens.Token ;
+
+import com.hp.hpl.jena.graph.Node ;
+
+public interface TokenOutputStream extends Closeable, Sync
+{
+    public void sendToken(Token token) ;
+    public void sendNode(Node node) ;
+    public void sendString(String string) ;
+    public void sendWord(String word) ;
+    public void sendControl(char character) ;
+    public void sendNumber(long number) ;
+
+    public void startTuple() ;
+    public void endTuple() ;
+    
+    public void flush() ;
+}

Added: jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/TokenOutputStreamWrapper.java
URL: http://svn.apache.org/viewvc/jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/TokenOutputStreamWrapper.java?rev=1555594&view=auto
==============================================================================
--- jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/TokenOutputStreamWrapper.java (added)
+++ jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/TokenOutputStreamWrapper.java Sun Jan  5 18:05:50 2014
@@ -0,0 +1,67 @@
+/*
+ * 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.riot.tio;
+
+import org.apache.jena.riot.tokens.Token ;
+
+import com.hp.hpl.jena.graph.Node ;
+
+
+public class TokenOutputStreamWrapper implements TokenOutputStream
+{
+    private TokenOutputStream stream ;
+
+    public TokenOutputStreamWrapper(TokenOutputStream stream)
+    {
+        this.stream = stream ;
+    }
+    
+    @Override
+    public void startTuple()                { stream.startTuple() ; }
+
+    @Override
+    public void endTuple()                  { stream.endTuple() ; }
+
+    @Override
+    public void sendToken(Token token)      { stream.sendToken(token)  ; }
+
+    @Override
+    public void sendControl(char character) { stream.sendControl(character) ; }
+
+    @Override
+    public void sendNode(Node node)         { stream.sendNode(node) ; }
+
+    @Override
+    public void sendNumber(long number)     { stream.sendNumber(number) ; }
+
+    @Override
+    public void sendString(String string)   { stream.sendString(string) ; }
+
+    @Override
+    public void sendWord(String word)       { stream.sendWord(word) ; }
+
+    @Override
+    public void close()                     { stream.close(); }
+
+    @Override
+    public void flush()                     { stream.flush(); }
+
+    @Override
+    public void sync()                      { stream.sync() ; }
+}

Added: jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/TokenOutputStreamWriter.java
URL: http://svn.apache.org/viewvc/jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/TokenOutputStreamWriter.java?rev=1555594&view=auto
==============================================================================
--- jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/TokenOutputStreamWriter.java (added)
+++ jena/Experimental/rdfpatch/src/main/java/org/apache/jena/riot/tio/TokenOutputStreamWriter.java Sun Jan  5 18:05:50 2014
@@ -0,0 +1,414 @@
+/*
+ * 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.riot.tio;
+
+import java.io.IOException ;
+import java.io.Writer ;
+import java.util.ArrayList ;
+import java.util.List ;
+
+import org.apache.jena.atlas.io.AWriter ;
+import org.apache.jena.atlas.io.BufferingWriter ;
+import org.apache.jena.atlas.io.IO ;
+import org.apache.jena.riot.RiotException ;
+import org.apache.jena.riot.out.NodeFormatter ;
+import org.apache.jena.riot.out.NodeFormatterTTL ;
+import org.apache.jena.riot.system.PrefixMap ;
+import org.apache.jena.riot.system.PrefixMapFactory ;
+import org.apache.jena.riot.tokens.Token ;
+import org.apache.jena.riot.tokens.TokenizerText ;
+import org.slf4j.Logger ;
+import org.slf4j.LoggerFactory ;
+
+import com.hp.hpl.jena.graph.Node ;
+import com.hp.hpl.jena.sparql.util.FmtUtils ;
+
+public class TokenOutputStreamWriter implements TokenOutputStream
+{
+    // Both token output stream and tuple abbreviation rules.
+    // Separate out abbreviation/LastTuple support?
+    
+    // Will need rework to improve performance.
+    // XXX TokenWriter. See tokenToString
+    
+    private static Logger log = LoggerFactory.getLogger(TokenOutputStreamWriter.class) ;
+    
+    // Whether to space out the tuples a bit for readability.
+    private static final boolean GAPS = true ;
+    
+    private final AWriter out ;
+    private List<Object> lastTuple = null ;
+    private boolean inTuple = false ;
+    private boolean inSection = false ;
+    private List<Object> thisTuple = new ArrayList<Object> () ;
+    
+    private final PrefixMap pmap = PrefixMapFactory.create() ;
+    private final NodeFormatter fmt ;
+
+    private String label ;
+    
+    /** Create a TokenOutputStreamWriter going to a Writer,
+     * ideally one that buffers (e.g. {@linkplain BufferingWriter}).  
+     * @param out
+     */
+    public TokenOutputStreamWriter(Writer out) {
+        this(null, null, out) ;
+    }
+    
+    /** Create a TokenOutputStreamWriter going to a Writer,
+     * ideally one that buffers (e.g. {@linkplain BufferingWriter}).  
+     * @param out
+     */
+    public TokenOutputStreamWriter(AWriter out) {
+        this(null, null, out) ;
+    }
+
+    /** Create a TokenOutputStreamWriter going to a Writer,
+     * with a given NodeFormatter policy 
+     * ideally one that buffers (e.g. {@linkplain BufferingWriter}).  
+     * @param out
+     */
+    public TokenOutputStreamWriter(NodeFormatter formatter, Writer out) {
+        this(null, formatter, out) ;
+    }
+    
+    /** Create a TokenOutputStreamWriter going to a Writer,
+     * with a given NodeFormatter policy 
+     * ideally one that buffers (e.g. {@linkplain BufferingWriter}).  
+     * @param out
+     */
+    public TokenOutputStreamWriter(NodeFormatter formatter, AWriter out) {
+        this(null, formatter, out) ;
+    }
+
+    public TokenOutputStreamWriter(String label, NodeFormatter formatter, Writer out)
+    { this(label, formatter, IO.wrap(out)) ; }
+    
+    public TokenOutputStreamWriter(String label, NodeFormatter formatter, AWriter out)
+    {
+        if ( formatter == null )
+            // XXX Must write bNodes as <_:....>
+            formatter = new NodeFormatterTTL(null, pmap) ;
+        formatter = new NodeFormatterBNode(formatter) ;
+        
+        this.fmt = formatter ;
+        this.out = out ;
+        this.label = label ;
+    }
+    
+    static class NodeFormatterBNode extends NodeFormatterWrapper {
+        public NodeFormatterBNode(NodeFormatter other) {
+            super(other) ;
+        }
+        @Override
+        public void format(AWriter w, Node n)
+        { 
+            if ( n.isBlank() )
+                formatBNode(w, n) ;
+            else
+                super.format(w, n);
+        }
+        
+        @Override
+        public void formatBNode(AWriter w, Node n)
+        { formatBNode(w, n.getBlankNodeLabel()); }
+
+        @Override
+        public void formatBNode(AWriter w, String label)
+        { 
+            w.print("<_:");
+            w.print(label) ;
+            w.print(">");
+        }
+    }
+    
+    static class NodeFormatterWrapper implements NodeFormatter {
+        private final NodeFormatter fmt ;
+
+        public NodeFormatterWrapper(NodeFormatter other)
+        { this.fmt = other ; }
+        
+        @Override
+        public void format(AWriter w, Node n)
+        { fmt.format(w, n); }
+
+        @Override
+        public void formatURI(AWriter w, Node n)
+        { fmt.formatURI(w, n); }
+
+        @Override
+        public void formatURI(AWriter w, String uriStr)
+        { fmt.formatURI(w, uriStr); }
+
+        @Override
+        public void formatVar(AWriter w, Node n) 
+        { fmt.formatVar(w, n); }
+
+        @Override
+        public void formatVar(AWriter w, String name)
+        { fmt.formatVar(w, name); }
+
+        @Override
+        public void formatBNode(AWriter w, Node n)
+        { fmt.formatBNode(w, n); }
+
+        @Override
+        public void formatBNode(AWriter w, String label)
+        { fmt.formatBNode(w, label); }
+
+        @Override
+        public void formatLiteral(AWriter w, Node n)
+        { fmt.formatLiteral(w, n); }
+
+        @Override
+        public void formatLitString(AWriter w, String lex)
+        { fmt.formatLitString(w, lex); }
+
+        @Override
+        public void formatLitLang(AWriter w, String lex, String langTag)
+        { fmt.formatLitLang(w, lex, langTag); }
+
+        @Override
+        public void formatLitDT(AWriter w, String lex, String datatypeURI)
+        { fmt.formatLitDT(w, lex, datatypeURI); }
+        
+    }
+    // Really want to directly insert in to the output byte buffer.
+    // Optimization for later - get something working for now
+    // (and it may well be fast enough anyway).
+    
+    public void setPrefixMapping(String prefix, String uri)
+    {
+        String pf = prefix ;
+        if ( pf.endsWith(":") )
+            pf = pf.substring(0, pf.length()-1) ;
+        else
+            prefix = prefix + ":" ;
+        if ( pmap != null )
+            pmap.add(pf, uri) ;
+        startTuple() ;
+        lastTuple = null ;
+        write("@prefix") ;
+        gap(true) ;
+        write(prefix) ;
+        gap(true) ;
+        write("<") ;
+        write(uri) ;
+        write(">") ;
+        gap(false) ;
+        endTuple() ;
+    }
+    
+    @Override
+    public void sendToken(Token token)
+    {
+        remember(token) ;
+        String string = tokenToString(token) ;
+        write(string) ;
+        gap(true) ;
+    }
+    
+    @Override
+    public void sendNode(Node node)
+    {
+        remember(node) ;
+        fmt.format(out, node);
+        gap(false) ;
+    }
+
+    @Override
+    public void sendString(String string)
+    {
+        remember(string) ;
+        fmt.formatLitString(out, string); 
+        gap(false) ;
+    }
+    
+    @Override
+    public void sendWord(String string)
+    {
+        remember(string) ;
+        write(string) ; // no escapes, no quotes
+        gap(true) ;
+    }
+    
+    private static String cntrlAsString(char cntrl)
+    {
+      return Character.toString((char)TokenizerText.CTRL_CHAR)+Character.toString(cntrl);
+    }
+   
+    @Override
+    public void sendControl(char controlChar)
+    {
+        String x = cntrlAsString(controlChar) ;
+        remember(x) ;
+        write(x) ; 
+        gap(false) ;
+    }
+
+    @Override
+    public void sendNumber(long number)
+    {
+        remember(number) ;
+        write(Long.toString(number)) ;
+        gap(true) ;
+    }
+    
+    @Override
+    public void startTuple()
+    {
+        // Ensure endTuple.
+        if ( log.isDebugEnabled() ) log.debug("Start tuple") ;
+    }
+
+    @Override
+    public void endTuple()
+    {
+        if ( ! inTuple ) return ;
+        write(".") ;
+        write("\n") ;
+        if ( log.isDebugEnabled() )
+        {
+            log.debug("End tuple") ;
+            if ( label != null )
+                log.debug(">> "+label+": "+thisTuple) ;
+            else
+                log.debug(">> "+thisTuple.toString()) ;
+        }
+        
+        // Compression.
+        lastTuple = thisTuple ;
+        thisTuple = new ArrayList<Object>(lastTuple.size()) ;
+        inTuple = false ; 
+    }
+    
+    @Override
+    public void close()
+    {
+        if ( inTuple ) {}
+        IO.close(out);
+    }
+    
+    @Override
+    public void sync()
+    { flush() ; }
+    
+    @Override
+    public void flush() { 
+        out.flush() ; 
+    }
+    
+    // --------
+    
+    private String tokenToString(Token token)
+    {
+        switch ( token.getType() )
+        {
+            // superclass case NODE:
+            case IRI:
+                return "<"+token.getImage()+">" ;
+            case PREFIXED_NAME: 
+                notImplemented(token) ;
+                return null ;
+            case BNODE:
+                return "_:"+token.getImage() ;
+            //BOOLEAN,
+            // One kind of string?
+            case STRING:
+            case STRING1:
+            case STRING2:
+            case LONG_STRING1:
+            case LONG_STRING2:
+                // XXX
+                //return "'"+NodeFmtLib.esc(token.getImage())+"'" ;
+                return "\""+FmtUtils.stringEsc(token.getImage())+"\"" ;
+            case LITERAL_LANG:
+                return "\""+FmtUtils.stringEsc(token.getImage())+"\"@"+token.getImage2() ;
+            case LITERAL_DT:
+                return "\""+FmtUtils.stringEsc(token.getImage())+"\"^^"+tokenToString(token.getSubToken2()) ;
+            case INTEGER:
+            case DECIMAL:
+            case DOUBLE:
+                return token.getImage() ;
+                
+            // Not RDF
+            case KEYWORD:
+                return token.getImage() ;
+            case CNTRL:
+                if ( token.getCntrlCode() == -1 )
+                    return "*" ; 
+                return "*"+Character.toString((char)token.getCntrlCode()) ;
+            case VAR:
+            case HEX:
+                
+            // Syntax
+            // COLON is only visible if prefix names are not being processed.
+            case DOT:
+            case COMMA:
+            case SEMICOLON:
+            case COLON: 
+            case DIRECTIVE:
+            // LT, GT, LE, GE are only visible if IRI processing is not enabled.
+            case LT:
+            case GT:
+            case LE:
+            case GE:
+            // In RDF, UNDERSCORE is only visible if BNode processing is not enabled.
+            case UNDERSCORE: 
+            case LBRACE:    case RBRACE:    // {} 
+            case LPAREN:    case RPAREN:    // ()
+            case LBRACKET:  case RBRACKET:  // []
+                
+            case PLUS:
+            case MINUS:
+            case STAR:
+            case SLASH:
+            case RSLASH:
+            default:
+                notImplemented(token) ;
+                return null ;
+            //case EOF:
+        }
+    }
+
+    private void remember(Object obj)
+    { thisTuple.add(obj) ; }
+    
+    // A gap is always necessary for items that are not endLog-limited.
+    // For example, numbers adjacent to numbers must have a gap but
+    // quoted string then quoted string does not require a gap.  
+    private void gap(boolean required)
+    {
+        if ( required || GAPS ) write(" ") ;
+    }
+
+    // Beware of multiple stringing.
+    private void write(String string)
+    { 
+        //System.out.println("Send: "+string) ;
+        inTuple = true ; 
+        out.write(string) ; 
+    }
+    
+    private static void exception(IOException ex)
+    { throw new CommsException(ex) ; }
+
+    private void notImplemented(Token token)
+    { throw new RiotException("Unencodable token: "+token) ; }
+}

Added: jena/Experimental/rdfpatch/src/test/java/org/apache/jena/rdfpatch/TS_RDFPatch.java
URL: http://svn.apache.org/viewvc/jena/Experimental/rdfpatch/src/test/java/org/apache/jena/rdfpatch/TS_RDFPatch.java?rev=1555594&view=auto
==============================================================================
--- jena/Experimental/rdfpatch/src/test/java/org/apache/jena/rdfpatch/TS_RDFPatch.java (added)
+++ jena/Experimental/rdfpatch/src/test/java/org/apache/jena/rdfpatch/TS_RDFPatch.java Sun Jan  5 18:05:50 2014
@@ -0,0 +1,30 @@
+/**
+ * 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.rdfpatch;
+
+import org.junit.runner.RunWith ;
+import org.junit.runners.Suite ;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses( {
+    TestRDFPatch.class
+    , TestPatchPlayer.class
+})
+
+public class TS_RDFPatch { }

Added: jena/Experimental/rdfpatch/src/test/java/org/apache/jena/rdfpatch/TestPatchPlayer.java
URL: http://svn.apache.org/viewvc/jena/Experimental/rdfpatch/src/test/java/org/apache/jena/rdfpatch/TestPatchPlayer.java?rev=1555594&view=auto
==============================================================================
--- jena/Experimental/rdfpatch/src/test/java/org/apache/jena/rdfpatch/TestPatchPlayer.java (added)
+++ jena/Experimental/rdfpatch/src/test/java/org/apache/jena/rdfpatch/TestPatchPlayer.java Sun Jan  5 18:05:50 2014
@@ -0,0 +1,140 @@
+/**
+ * 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.rdfpatch;
+
+import java.io.ByteArrayInputStream ;
+import java.io.ByteArrayOutputStream ;
+import java.util.List ;
+
+import org.apache.jena.atlas.io.IO ;
+import org.apache.jena.atlas.iterator.Iter ;
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.junit.After ;
+import org.junit.Before ;
+import org.junit.Test ;
+
+import com.hp.hpl.jena.sparql.core.DatasetGraph ;
+import com.hp.hpl.jena.sparql.core.DatasetGraphFactory ;
+import com.hp.hpl.jena.sparql.core.DatasetGraphMonitor ;
+import com.hp.hpl.jena.sparql.core.Quad ;
+import com.hp.hpl.jena.sparql.sse.SSE ;
+
+public class TestPatchPlayer extends BaseTest {
+    
+    DatasetGraph dsg1 = DatasetGraphFactory.createMem() ;
+    ByteArrayOutputStream bout = new ByteArrayOutputStream() ;
+    DatasetGraphMonitor dsg = RDFPatch.recordChanges(dsg1, bout) ;
+
+    @Before public void beforeTest() {}
+    @After public void afterTest() {}    
+    
+    private static Quad quad1 = SSE.parseQuad("(:g _:s <p> 1)") ;
+    private static Quad quad2 = SSE.parseQuad("(:g _:s <p> 2)") ;
+    
+    private DatasetGraph replay() {
+        DatasetChangesTuples tuples = (DatasetChangesTuples)dsg.getMonitor() ;
+        IO.close(bout) ;
+        ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray()) ;
+        DatasetGraph dsg2 = DatasetGraphFactory.createMem() ;
+        RDFPatch.replayChanges(dsg2, bin);
+        return dsg2 ;
+    }
+
+    private static void check(DatasetGraph dsg, Quad...quads) {
+        if ( quads.length == 0 ) {
+            assertTrue(dsg.isEmpty()) ;
+            return ;
+        }
+        
+        List<Quad> list = Iter.toList(dsg.find()) ;
+        assertEquals(list.size(), quads.length) ;
+        
+        for (Quad q : quads ) {
+            assertTrue(dsg.contains(q)) ;
+        }
+    }
+
+    @Test public void record_00() {
+        DatasetGraph dsg2 = replay() ;
+        check(dsg2) ;
+    }
+    
+    @Test public void record_add() {
+        dsg.getMonitor().start() ; 
+        dsg.add(quad1) ;
+        dsg.getMonitor().finish() ;
+        DatasetGraph dsg2 = replay() ;
+        check(dsg2, quad1) ;
+    }
+
+    @Test public void record_add_add_1() {
+        dsg.getMonitor().start() ; 
+        dsg.add(quad1) ;
+        dsg.add(quad2) ;
+        dsg.getMonitor().finish() ;
+        DatasetGraph dsg2 = replay() ;
+        check(dsg2, quad1, quad2) ;
+    }
+
+    @Test public void record_add_add_2() {
+        dsg.getMonitor().start() ; 
+        dsg.add(quad1) ;
+        dsg.add(quad1) ;
+        dsg.getMonitor().finish() ;
+        DatasetGraph dsg2 = replay() ;
+        check(dsg2, quad1) ;
+    }
+    
+    @Test public void record_add_delete_1() {
+        dsg.getMonitor().start() ; 
+        dsg.add(quad1) ;
+        dsg.delete(quad1) ;
+        dsg.getMonitor().finish() ;
+        DatasetGraph dsg2 = replay() ;
+        check(dsg2) ;
+    }
+
+    @Test public void record_add_delete_2() {
+        dsg.getMonitor().start() ; 
+        dsg.add(quad1) ;
+        dsg.delete(quad2) ;
+        dsg.getMonitor().finish() ;
+        DatasetGraph dsg2 = replay() ;
+        check(dsg2, quad1) ;
+    }
+
+    @Test public void record_add_delete_3() {
+        dsg.getMonitor().start() ; 
+        dsg.delete(quad2) ;
+        dsg.add(quad1) ;
+        dsg.getMonitor().finish() ;
+        DatasetGraph dsg2 = replay() ;
+        check(dsg2, quad1) ;
+    }
+    
+    @Test public void record_add_delete_4() {
+        dsg.getMonitor().start() ; 
+        dsg.delete(quad1) ;
+        dsg.add(quad1) ;
+        dsg.getMonitor().finish() ;
+        DatasetGraph dsg2 = replay() ;
+        check(dsg2, quad1) ;
+    }
+
+}

Added: jena/Experimental/rdfpatch/src/test/java/org/apache/jena/rdfpatch/TestRDFPatch.java
URL: http://svn.apache.org/viewvc/jena/Experimental/rdfpatch/src/test/java/org/apache/jena/rdfpatch/TestRDFPatch.java?rev=1555594&view=auto
==============================================================================
--- jena/Experimental/rdfpatch/src/test/java/org/apache/jena/rdfpatch/TestRDFPatch.java (added)
+++ jena/Experimental/rdfpatch/src/test/java/org/apache/jena/rdfpatch/TestRDFPatch.java Sun Jan  5 18:05:50 2014
@@ -0,0 +1,301 @@
+/**
+ * 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.rdfpatch;
+
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.junit.Test ;
+
+import com.hp.hpl.jena.query.ReadWrite ;
+import com.hp.hpl.jena.sparql.core.DatasetGraph ;
+import com.hp.hpl.jena.sparql.core.DatasetGraphFactory ;
+import com.hp.hpl.jena.sparql.core.Quad ;
+import com.hp.hpl.jena.sparql.sse.SSE ;
+
+public class TestRDFPatch extends BaseTest
+{
+    private static DatasetGraphPatchTransaction create(Quad...quads) {
+        DatasetGraph dsg1 = DatasetGraphFactory.createMem() ;
+        for ( Quad quad : quads )
+            dsg1.add(quad) ;
+        DatasetGraphPatchTransaction dsg = new  DatasetGraphPatchTransaction(dsg1) ;
+        return dsg ;
+    }
+    
+    private static Quad quad1 = SSE.parseQuad("(:g <s> <p> _:a)") ;
+
+    @Test
+    public void patch_01() {
+        DatasetGraphPatchTransaction dsg = create() ;
+        dsg.begin(ReadWrite.WRITE) ;
+        try {
+            dsg.commit() ;
+        } finally {
+            dsg.end() ;
+        }
+        dsg.begin(ReadWrite.READ) ;
+        try {
+            assertTrue(dsg.isEmpty()) ;
+        } finally { dsg.end() ; }
+    }
+
+    @Test
+    public void patch_02() {
+        DatasetGraphPatchTransaction dsg = create() ;
+        dsg.begin(ReadWrite.WRITE) ;
+        try {
+            dsg.abort() ;
+        } finally {
+            dsg.end() ;
+        }
+        dsg.begin(ReadWrite.READ) ;
+        try {
+            assertTrue(dsg.isEmpty()) ;
+        } finally { dsg.end() ; }
+    }
+
+    @Test public void patch_add_commit() {
+        DatasetGraphPatchTransaction dsg = create() ;
+        dsg.begin(ReadWrite.WRITE);
+        dsg.add(quad1) ;
+        assertFalse(dsg.isEmpty()) ;
+        dsg.commit() ;
+        dsg.end();
+        
+        dsg.begin(ReadWrite.READ) ;
+        try {
+            assertFalse(dsg.isEmpty()) ;
+            assertTrue(dsg.contains(quad1)) ;
+        } finally { dsg.end() ; }
+    }
+    
+    @Test public void patch_add_abort() {
+        DatasetGraphPatchTransaction dsg = create() ;
+        dsg.begin(ReadWrite.WRITE);
+        dsg.add(quad1) ;
+        assertFalse(dsg.isEmpty()) ;
+        dsg.abort() ;
+        dsg.end();
+
+        dsg.begin(ReadWrite.READ) ;
+        try {
+            assertTrue(dsg.isEmpty()) ;
+            assertFalse(dsg.contains(quad1)) ;
+        } finally { dsg.end() ; }
+    }
+    
+    @Test public void patch_add_add_commit() {
+        DatasetGraphPatchTransaction dsg = create() ;
+        dsg.begin(ReadWrite.WRITE);
+        dsg.add(quad1) ;
+        dsg.add(quad1) ;
+        assertFalse(dsg.isEmpty()) ;
+        dsg.commit() ;
+        dsg.end();
+        
+        dsg.begin(ReadWrite.READ) ;
+        try {
+            assertFalse(dsg.isEmpty()) ;
+            assertTrue(dsg.contains(quad1)) ;
+        } finally { dsg.end() ; }
+    }
+    
+    @Test public void patch_add_add_abort() {
+        DatasetGraphPatchTransaction dsg = create() ;
+        dsg.begin(ReadWrite.WRITE);
+        dsg.add(quad1) ;
+        dsg.add(quad1) ;
+        assertFalse(dsg.isEmpty()) ;
+        dsg.abort() ;
+        dsg.end();
+
+        dsg.begin(ReadWrite.READ) ;
+        try {
+            assertTrue(dsg.isEmpty()) ;
+            assertFalse(dsg.contains(quad1)) ;
+        } finally { dsg.end() ; }
+    }
+    
+
+    @Test public void patch_add_delete_commit() {
+        DatasetGraphPatchTransaction dsg = create() ;
+        dsg.begin(ReadWrite.WRITE);
+        dsg.add(quad1) ;
+        dsg.delete(quad1) ;
+        assertTrue(dsg.isEmpty()) ;
+        dsg.commit() ;
+        dsg.end();
+        
+        dsg.begin(ReadWrite.READ) ;
+        try {
+            assertTrue(dsg.isEmpty()) ;
+            assertFalse(dsg.contains(quad1)) ;
+        } finally { dsg.end() ; }
+    }
+    
+    @Test public void patch_add_delete_abort() {
+        DatasetGraphPatchTransaction dsg = create() ;
+        dsg.begin(ReadWrite.WRITE);
+        dsg.add(quad1) ;
+        dsg.delete(quad1) ;
+        assertTrue(dsg.isEmpty()) ;
+        dsg.abort() ;
+        dsg.end();
+
+        dsg.begin(ReadWrite.READ) ;
+        try {
+            assertTrue(dsg.isEmpty()) ;
+            assertFalse(dsg.contains(quad1)) ;
+        } finally { dsg.end() ; }
+    }
+
+    @Test public void patch_delete_add_commit() {
+        DatasetGraphPatchTransaction dsg = create() ;
+        dsg.begin(ReadWrite.WRITE);
+        dsg.delete(quad1) ;
+        dsg.add(quad1) ;
+        assertFalse(dsg.isEmpty()) ;
+        dsg.commit() ;
+        dsg.end();
+        
+        dsg.begin(ReadWrite.READ) ;
+        try {
+            assertFalse(dsg.isEmpty()) ;
+            assertTrue(dsg.contains(quad1)) ;
+        } finally { dsg.end() ; }
+    }
+    
+    @Test public void patch_delete_add_abort() {
+        DatasetGraphPatchTransaction dsg = create() ;
+        dsg.begin(ReadWrite.WRITE);
+        dsg.delete(quad1) ;
+        dsg.add(quad1) ;
+        assertFalse(dsg.isEmpty()) ;
+        dsg.abort() ;
+        dsg.end();
+
+        dsg.begin(ReadWrite.READ) ;
+        try {
+            assertTrue(dsg.isEmpty()) ;
+            assertFalse(dsg.contains(quad1)) ;
+        } finally { dsg.end() ; }
+    }
+
+    @Test public void patch_add_delete_add_commit() {
+        DatasetGraphPatchTransaction dsg = create() ;
+        dsg.begin(ReadWrite.WRITE);
+        dsg.add(quad1) ;
+        dsg.delete(quad1) ;
+        dsg.add(quad1) ;
+        assertFalse(dsg.isEmpty()) ;
+        dsg.commit() ;
+        dsg.end();
+        
+        dsg.begin(ReadWrite.READ) ;
+        try {
+            assertFalse(dsg.isEmpty()) ;
+            assertTrue(dsg.contains(quad1)) ;
+        } finally { dsg.end() ; }
+    }
+    
+    @Test public void patch_add_delete_add_abort() {
+        DatasetGraphPatchTransaction dsg = create() ;
+        dsg.begin(ReadWrite.WRITE);
+        dsg.add(quad1) ;
+        dsg.delete(quad1) ;
+        dsg.add(quad1) ;
+        assertFalse(dsg.isEmpty()) ;
+        dsg.abort() ;
+        dsg.end();
+
+        dsg.begin(ReadWrite.READ) ;
+        try {
+            assertTrue(dsg.isEmpty()) ;
+            assertFalse(dsg.contains(quad1)) ;
+        } finally { dsg.end() ; }
+    }
+    
+    
+    @Test public void patch_delete_01() {
+        DatasetGraphPatchTransaction dsg = create(quad1) ;
+        assertTrue(dsg.contains(quad1)) ;
+        
+        dsg.begin(ReadWrite.WRITE);
+        dsg.delete(quad1) ;
+        assertTrue(dsg.isEmpty()) ;
+        dsg.commit() ;
+        dsg.end();
+        
+        dsg.begin(ReadWrite.READ) ;
+        try {
+            assertTrue(dsg.isEmpty()) ;
+            assertFalse(dsg.contains(quad1)) ;
+        } finally { dsg.end() ; }
+    }
+    
+    @Test public void patch_delete_02() {
+        DatasetGraphPatchTransaction dsg = create(quad1) ;
+        assertTrue(dsg.contains(quad1)) ;
+        
+        dsg.begin(ReadWrite.WRITE);
+        dsg.delete(quad1) ;
+        assertTrue(dsg.isEmpty()) ;
+        dsg.commit() ;
+        dsg.end();
+        
+        dsg.begin(ReadWrite.READ) ;
+        try { 
+            assertTrue(dsg.isEmpty()) ;
+            assertFalse(dsg.contains(quad1)) ;
+        } finally { dsg.end() ; }
+    }
+        
+    @Test public void patch_delete_03() {
+        DatasetGraphPatchTransaction dsg = create() ;
+        assertFalse(dsg.contains(quad1)) ;
+        
+        dsg.begin(ReadWrite.WRITE);
+        dsg.delete(quad1) ;
+        assertTrue(dsg.isEmpty()) ;
+        dsg.commit() ;
+        dsg.end();
+        
+        dsg.begin(ReadWrite.READ) ;
+        try {
+            assertTrue(dsg.isEmpty()) ;
+            assertFalse(dsg.contains(quad1)) ;
+        } finally { dsg.end() ; }
+    }
+    
+    @Test public void patch_delete_04() {
+        DatasetGraphPatchTransaction dsg = create() ;
+        assertFalse(dsg.contains(quad1)) ;
+        
+        dsg.begin(ReadWrite.WRITE);
+        dsg.delete(quad1) ;
+        assertTrue(dsg.isEmpty()) ;
+        dsg.commit() ;
+        dsg.end();
+        
+        dsg.begin(ReadWrite.READ) ;
+        try { 
+            assertTrue(dsg.isEmpty()) ;
+            assertFalse(dsg.contains(quad1)) ;
+        } finally { dsg.end() ; }
+    }
+}