Merge pull request #764 from afs/test4

JENA-1922: Runners for test sets using W3C test manifests
diff --git a/jena-arq/src/main/java/org/apache/jena/riot/out/NodeFmtLib.java b/jena-arq/src/main/java/org/apache/jena/riot/out/NodeFmtLib.java
index ecc1393..cb0fca6 100644
--- a/jena-arq/src/main/java/org/apache/jena/riot/out/NodeFmtLib.java
+++ b/jena-arq/src/main/java/org/apache/jena/riot/out/NodeFmtLib.java
@@ -40,17 +40,17 @@
  * <p>
  * Methods <tt>str</tt> generate a reparsable string.
  * <p>
- * Methods <tt>displayStr</tt> do not guarantee a reparsable string 
- * e.g. may use abbreviations or common prefixes.   
+ * Methods <tt>displayStr</tt> do not guarantee a reparsable string
+ * e.g. may use abbreviations or common prefixes.
  */
 public class NodeFmtLib
 {
     // Replaces FmtUtils
     // See OutputLangUtils.
     // See and use EscapeStr
-    
+
     private static final NodeFormatter plainFormatter = new NodeFormatterNT();
-    
+
     private static PrefixMap dftPrefixMap = PrefixMapFactory.create();
     static {
         PrefixMapping pm = ARQConstants.getGlobalPrefixMap();
@@ -105,9 +105,9 @@
 
     private static String displayStrNodes(Node...nodes) {
         StringJoiner sj = new StringJoiner(" ");
-        for ( Node n : nodes ) 
+        for ( Node n : nodes )
             sj.add(displayStr(n));
-        return sj.toString(); 
+        return sj.toString();
     }
 
     public static void str(IndentedWriter w, Node n) {
@@ -118,6 +118,10 @@
         return str(n, prologue.getBaseURI(), prologue.getPrefixMap());
     }
 
+    public static String str(Node n, PrefixMap prefixMap) {
+        return str(n, null, prefixMap);
+    }
+
     public static String str(Node n, String base, PrefixMap prefixMap) {
         IndentedLineBuffer sw = new IndentedLineBuffer();
         serialize(sw, n, base, prefixMap);
@@ -136,21 +140,21 @@
             formatter = new NodeFormatterTTL(base, prefixMap);
         formatter.format(w, n);
     }
-    
+
     // ---- Blank node labels.
-    
+
     // Strict N-triples only allows [A-Za-z][A-Za-z0-9]
     static char encodeMarkerChar = 'X';
 
-    // These two form a pair to convert bNode labels to a safe (i.e. legal N-triples form) and back agains. 
-    
+    // These two form a pair to convert bNode labels to a safe (i.e. legal N-triples form) and back agains.
+
     // Encoding is:
-    // 1 - Add a Letter 
+    // 1 - Add a Letter
     // 2 - Hexify, as Xnn, anything outside ASCII A-Za-z0-9
     // 3 - X is encoded as XX
-    
-    private static char LabelLeadingLetter = 'B'; 
-    
+
+    private static char LabelLeadingLetter = 'B';
+
     public static String encodeBNodeLabel(String label) {
         StringBuilder buff = new StringBuilder();
         // Must be at least one char and not a digit.
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/algebra/optimize/TransformPathFlattern.java b/jena-arq/src/main/java/org/apache/jena/sparql/algebra/optimize/TransformPathFlattern.java
index 6915233..2d08a71 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/algebra/optimize/TransformPathFlattern.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/algebra/optimize/TransformPathFlattern.java
@@ -29,7 +29,7 @@
  * Perform the equivalent of the transactions in the SPARQL 1.1 spec. ARQ
  * regards path transformation as an optimization. ARQ does not execute the
  * exact transformation as per spec as there are better ways to do it for ARQ.
- * For example, 
+ * For example,
  * <ul>
  * <li>Path seq {@literal ->} BGPs or a (sequence)
  * <li>"|" is not expanded into a union.
@@ -38,18 +38,18 @@
 
 public class TransformPathFlattern extends TransformCopy
 {
-    // This transform is also used so programmtically built queries also get converted.  
+    // This transform is also used so programmatically built queries also get converted.
     // Need previous BGP for merging?  Do as a separate pass (sequence, BGP collapse)
-    
+
     private PathCompiler pathCompiler ;
 
     public TransformPathFlattern() { this(new PathCompiler()) ; }
-    
+
     public TransformPathFlattern(PathCompiler pathCompiler)
     {
         this.pathCompiler = pathCompiler ;
     }
-    
+
     @Override
     public Op transform(OpPath opPath)
     {
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/path/PathCompiler.java b/jena-arq/src/main/java/org/apache/jena/sparql/path/PathCompiler.java
index ab84fd6..e4d1a50 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/path/PathCompiler.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/path/PathCompiler.java
@@ -23,6 +23,7 @@
 import org.apache.jena.sparql.ARQConstants ;
 import org.apache.jena.sparql.core.PathBlock ;
 import org.apache.jena.sparql.core.TriplePath ;
+import org.apache.jena.sparql.core.Var;
 import org.apache.jena.sparql.core.VarAlloc ;
 
 public class PathCompiler
@@ -32,6 +33,9 @@
     
     private static VarAlloc varAlloc = new VarAlloc(ARQConstants.allocPathVariables) ;
     
+    /** Testing use only. */
+    public static void resetForTest() {  varAlloc = new VarAlloc(ARQConstants.allocPathVariables) ; }
+    
     // Move to AlgebraCompiler and have a per-transaction scoped var generator 
     
     // ---- Syntax-based
@@ -95,8 +99,14 @@
         {
             P_Seq ps = (P_Seq)path ;
             Node v = varAlloc.allocVar() ;
-            reduce(x, varAlloc, startNode, ps.getLeft(), v) ;
-            reduce(x, varAlloc, v, ps.getRight(), endNode) ;
+            if ( Var.isVar(startNode) && ! Var.isVar(endNode) ) {
+                // start at the grounded term.
+                reduce(x, varAlloc, v, ps.getRight(), endNode) ;
+                reduce(x, varAlloc, startNode, ps.getLeft(), v) ;
+            } else {
+                reduce(x, varAlloc, startNode, ps.getLeft(), v) ;
+                reduce(x, varAlloc, v, ps.getRight(), endNode) ;
+            }
             return ;
         }
 
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/sse/lang/SSE_Parser.java b/jena-arq/src/main/java/org/apache/jena/sparql/sse/lang/SSE_Parser.java
index 6eeca0f..5a1f71e 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/sse/lang/SSE_Parser.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/sse/lang/SSE_Parser.java
@@ -29,47 +29,35 @@
 
 public class SSE_Parser
 {
-    public static void term(Reader reader, ParseHandler handler)
-    {
-        SSE_ParserCore p = new SSE_ParserCore(reader) ;
-        p.setHandler(handler) ;
-        try
-        {
-            p.term() ;
-            // Checks for EOF
-//            //<EOF> test : EOF is always token 0.
-//            if ( p.token_source.getNextToken().kind != 0 )
-//                throw new SSEParseException("Trailing characters after "+item, item.getLine(), item.getColumn()) ;
-       }
-       catch (ParseException ex)
-       { throw new SSEParseException(ex.getMessage(), ex.currentToken.beginLine, ex.currentToken.beginColumn) ; }
-       catch (TokenMgrError tErr)
-       {
-           // Last valid token : not the same as token error message - but this should not happen
-           int col = p.token.endColumn ;
-           int line = p.token.endLine ;
-           throw new SSEParseException(tErr.getMessage(), line, col) ;
-       }
-       //catch (JenaException ex)  { throw new TurtleParseException(ex.getMessage(), ex) ; }
+    @FunctionalInterface
+    private interface ParserEntry { void entry(SSE_ParserCore parser) throws ParseException; }
+
+    public static void term(Reader reader, ParseHandler handler) {
+        parse$(reader, handler, SSE_ParserCore::term);
     }
 
-    public static void parse(Reader reader, ParseHandler handler)
-    {
+    public static void parse(Reader reader, ParseHandler handler) {
+        parse$(reader, handler, SSE_ParserCore::parse);
+    }
+
+    private static void parse$(Reader reader, ParseHandler handler, ParserEntry parserStep) {
         SSE_ParserCore p = new SSE_ParserCore(reader) ;
         p.setHandler(handler) ;
-        try
+        try {
+            parserStep.entry(p);
+//        Token tok = p.token_source.getNextToken();
+//        if ( tok.kind != 0 )
+//            throw new SSEParseException("Trailing characters at "+tok, tok.beginLine, tok.beginColumn) ;
+        }
+        catch (ParseException ex)
+        { throw new SSEParseException(ex.getMessage(), ex.currentToken.beginLine, ex.currentToken.beginColumn) ; }
+        catch (TokenMgrError tErr)
         {
-            p.parse() ;
-       }
-       catch (ParseException ex)
-       { throw new SSEParseException(ex.getMessage(), ex.currentToken.beginLine, ex.currentToken.beginColumn) ; }
-       catch (TokenMgrError tErr)
-       {
-           // Last valid token : not the same as token error message - but this should not happen
-           int col = p.token.endColumn ;
-           int line = p.token.endLine ;
-           throw new SSEParseException(tErr.getMessage(), line, col) ;
-       }
-       //catch (JenaException ex)  { throw new TurtleParseException(ex.getMessage(), ex) ; }
+            // Last valid token : not the same as token error message - but this should not happen
+            int col = p.token.endColumn ;
+            int line = p.token.endLine ;
+            throw new SSEParseException(tErr.getMessage(), line, col) ;
+        }
     }
+
 }
diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/algebra/optimize/TS_Optimization.java b/jena-arq/src/test/java/org/apache/jena/sparql/algebra/optimize/TS_Optimization.java
index e32bf9d..a6716f5 100644
--- a/jena-arq/src/test/java/org/apache/jena/sparql/algebra/optimize/TS_Optimization.java
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/algebra/optimize/TS_Optimization.java
@@ -31,6 +31,7 @@
     , TestTransformFilters.class
     , TestTransformFilterEquality.class
     , TestTransformFilterPlacement.class
+    , TestTransformPathFlatten.class
     , TestTransformMergeBGPs.class
     , TestTransformPromoteTableEmpty.class
     , TestTransformEliminateAssignments.class
diff --git a/jena-arq/src/test/java/org/apache/jena/sparql/algebra/optimize/TestTransformPathFlatten.java b/jena-arq/src/test/java/org/apache/jena/sparql/algebra/optimize/TestTransformPathFlatten.java
new file mode 100644
index 0000000..6cd77bf
--- /dev/null
+++ b/jena-arq/src/test/java/org/apache/jena/sparql/algebra/optimize/TestTransformPathFlatten.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.sparql.algebra.optimize;
+
+import static org.apache.jena.atlas.lib.StrUtils.strjoinNL;
+import static org.junit.Assert.assertEquals;
+
+import org.apache.jena.sparql.algebra.Op;
+import org.apache.jena.sparql.algebra.Transformer;
+import org.apache.jena.sparql.algebra.op.OpPath;
+import org.apache.jena.sparql.core.Prologue;
+import org.apache.jena.sparql.core.TriplePath;
+import org.apache.jena.sparql.path.Path;
+import org.apache.jena.sparql.path.PathCompiler;
+import org.apache.jena.sparql.path.PathParser;
+import org.apache.jena.sparql.sse.SSE;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class TestTransformPathFlatten {
+    private static String pre = "(prefix ((: <http://example/>))" ;
+    private static String post =  ")" ;
+    
+    private static Prologue prologue;
+
+    @BeforeClass public static void beforeClass() {
+        prologue = new Prologue();
+        prologue.getPrefixMapping().setNsPrefix("", "http://example/");
+    }
+
+
+    @Before public void before() {
+        // Reset the variable allocator. 
+        PathCompiler.resetForTest();
+    }
+    
+    @Test public void pathFlatten_00() {
+        Op op1 = path(":x0", ":p0", ":T0");
+        Op op2 = op("(bgp (triple :x0 :p0 :T0))");
+        test(op1, op2);
+    }
+    
+    @Test public void pathFlatten_01() {
+        Op op1 = path(":x1", ":q1/:p1*", ":T1");
+        Op op2 = op("(sequence"
+                   ,"  (bgp (triple :x1 :q1 ??P0))"
+                   ,"  (path ??P0 (path* :p1) :T1))"
+                   );
+        test(op1, op2);
+    }
+    
+    // JENA-1918 : order of sequence is grounded first.
+    @Test public void pathFlatten_02() { 
+        Op op1 = path("?x", ":q1/:p1*", ":T1");
+        Op op2 = op("(sequence"
+                   ,"  (path ??P0 (path* :p1) :T1)"
+                   ,"  (bgp (triple ?x :q1 ??P0)) )"
+                   );
+        test(op1, op2);
+    }
+
+    // JENA-1918 : order of sequence is grounded first.
+    @Test public void pathFlatten_10() { 
+        Op op1 = path("?x", ":p1{2}", ":T1");
+        Op op2 = op("(bgp"
+            ,"  (triple ?x :p1 ??P0)"
+            ,"  (triple ??P0 :p1 :T1)"
+            ,")"
+          );
+        test(op1, op2);
+    }
+
+    @Test public void pathFlatten_11() { 
+        Op op1 = path("?x", ":p1{2,}", ":T1");
+        Op op2 = op
+            ("(sequence"
+            ,"    (path ??P0 (pathN* :p1) :T1)"
+            ,"    (bgp"
+            ,"      (triple ?x :p1 ??P1)"
+            ,"      (triple ??P1 :p1 ??P0)"
+            ,"   ))");
+        test(op1, op2);
+    }
+    
+    private static Op path(String s, String pathStr, String o) {
+        Path path = PathParser.parse(pathStr, prologue);
+        TriplePath tp = new TriplePath(SSE.parseNode(s), path, SSE.parseNode(o));
+        return new OpPath(tp);
+    }
+    
+    private static Op op(String...opStr) {
+        String s = strjoinNL(opStr);
+        String input = pre + s + post;
+        return SSE.parseOp(input);
+    }
+    
+    private static void test(Op opInput, Op opExpected) {
+        Op op = Transformer.transform(new TransformPathFlattern(), opInput);
+        if ( opExpected == null ) {
+            System.out.print(opInput);
+            System.out.println("  ==>");
+            System.out.print(op);
+            System.out.println();
+            return;
+        }
+        
+        assertEquals(opExpected, op);
+    }
+}
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/io/BufferingWriter.java b/jena-base/src/main/java/org/apache/jena/atlas/io/BufferingWriter.java
index 347707a..343c737 100644
--- a/jena-base/src/main/java/org/apache/jena/atlas/io/BufferingWriter.java
+++ b/jena-base/src/main/java/org/apache/jena/atlas/io/BufferingWriter.java
@@ -16,10 +16,10 @@
  * limitations under the License.
  */
 
-package org.apache.jena.atlas.io ;
+package org.apache.jena.atlas.io;
 
-import java.io.IOException ;
-import java.io.Writer ;
+import java.io.IOException;
+import java.io.Writer;
 
 /**
  * A buffering writer. Like BufferedWriter but with no synchronization. A
@@ -30,35 +30,34 @@
  * </p>
  * This class is not thread safe.
  */
-
 public final class BufferingWriter extends Writer {
     // Default sizes
-    private static final int SIZE      = 8 * 1024 ;      // Unit size in bytes.
-    private static final int BLOB_SIZE = SIZE / 2 ;      // Large object size,
-                                                          // worse case, bytes
+    private static final int SIZE      = 8 * 1024;      // Unit size in bytes.
+    private static final int BLOB_SIZE = SIZE / 2;      // Large object size, worse case, bytes
     // Sizes for this instance
-    private final int        blockSize ;
-    private final int        blobSize ;
+    private final int        blockSize;
+    private final int        blobSize;
 
-    private char[]           buffer    = new char[SIZE] ;
-    private int              idx       = 0 ;
-    private Writer           out ;
+    private final char[]     buffer;
+    private int              idx       = 0;
+    private final Writer     out;
 
     /** Create a buffering writer */
     public BufferingWriter(Writer dest) {
-        this(dest, SIZE, BLOB_SIZE) ;
+        this(dest, SIZE, BLOB_SIZE);
     }
 
     /** Create a buffering writer */
     public BufferingWriter(Writer dest, int size) {
-        this(dest, size, size/2) ;
+        this(dest, size, size/2);
     }
 
     /** Create a buffering writer */
     public BufferingWriter(Writer dest, int size, int blobSize) {
-        this.out = dest ;
-        this.blockSize = size ;
-        this.blobSize = blobSize ;
+        this.out = dest;
+        this.buffer = new char[size];
+        this.blockSize = size;
+        this.blobSize = blobSize;
     }
 
     /**
@@ -66,113 +65,113 @@
      * @param string Characters
      */
     public void output(String string) {
-        output(string, 0, string.length()) ;
+        output(string, 0, string.length());
     }
-    
+
     /**
      * Output a string
-     * 
+     *
      * @param string Characters
      * @param off    Starting point in the string
      * @param length Length
      */
     public void output(String string, int off, int length) {
-        boolean largeBlob = (length > blobSize) ;
+        boolean largeBlob = (length > blobSize);
 
         // There is no space or too big
         if ( largeBlob || (blockSize - idx) < length )
-            flushBuffer() ;
+            flushBuffer();
         // If too big, do directly.
         if ( largeBlob /* too big */) {
-            try { out.write(string, off, length) ; }
-            catch (IOException ex) { IO.exception(ex) ; }
-            return ;
+            try { out.write(string, off, length); }
+            catch (IOException ex) { IO.exception(ex); }
+            return;
         }
-        int n = string.length() ;
-        string.getChars(off, (n + off), buffer, idx) ;
-        idx += n ;
+        int n = string.length();
+        string.getChars(off, (n + off), buffer, idx);
+        idx += n;
     }
 
     /** Output an array of characters */
     public void output(char chars[]) {
-        output(chars, 0, chars.length) ;
+        output(chars, 0, chars.length);
     }
 
     /**
      * Output an array of characters
-     * 
+     *
      * @param chars Characters
      * @param start Start
      * @param length Length
      */
     public void output(char chars[], int start, int length) {
-        boolean largeBlob = (length > blobSize) ;
+        boolean largeBlob = (length > blobSize);
 
         // There is no space or too big
         if ( largeBlob || (blockSize - idx) < length )
-            flushBuffer() ;
+            flushBuffer();
         // If too big, do directly.
         if ( largeBlob /* too big */) {
-            try { out.write(chars) ; }
-            catch (IOException ex) { IO.exception(ex) ; }
-            return ;
+            try { out.write(chars, start, length); }
+            catch (IOException ex) { IO.exception(ex); }
+            return;
         }
-        System.arraycopy(chars, start, buffer, idx, length) ;
-        idx += length ;
+        System.arraycopy(chars, start, buffer, idx, length);
+        idx += length;
     }
 
     /** Output a single character */
     public void output(char ch) {
         if ( blockSize == idx )
-            flushBuffer() ;
-        buffer[idx++] = ch ;
+            flushBuffer();
+        buffer[idx++] = ch;
     }
 
     private void flushBuffer() {
         if ( idx > 0 ) {
-            try { out.write(buffer, 0, idx) ; }
-            catch (IOException ex) { IO.exception(ex) ; }
-            idx = 0 ;
+            try { out.write(buffer, 0, idx); }
+            catch (IOException ex) { IO.exception(ex); }
+            idx = 0;
         }
 
     }
-    
+
     // ---- Writer
 
     @Override
     public void close() {
-        flushBuffer() ;
-        IO.close(out) ;
+        flushBuffer();
+        IO.close(out);
     }
 
     @Override
     public void flush() {
-        flushBuffer() ;
-        IO.flush(out) ;
+        flushBuffer();
+        IO.flush(out);
     }
 
     @Override
     public void write(char[] cbuf, int off, int len) throws IOException {
-        output(cbuf, off, len) ;
+        output(cbuf, off, len);
     }
 
     @Override
     public void write(char[] cbuf) throws IOException {
-        write(cbuf, 0, cbuf.length) ;
+        write(cbuf, 0, cbuf.length);
     }
 
     @Override
     public void write(String string, int off, int len) throws IOException {
-        output(string, off, len) ;
+        output(string, off, len);
     }
 
     @Override
     public void write(String string) throws IOException {
-        output(string, 0, string.length()) ;
+        output(string, 0, string.length());
     }
 
     @Override
     public void write(int ch) throws IOException {
-        output((char)ch) ;
+        output((char)ch);
     }
 }
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/io/TestBufferingWriter.java b/jena-base/src/test/java/org/apache/jena/atlas/io/TestBufferingWriter.java
index 1414f3e..ba26f6a 100644
--- a/jena-base/src/test/java/org/apache/jena/atlas/io/TestBufferingWriter.java
+++ b/jena-base/src/test/java/org/apache/jena/atlas/io/TestBufferingWriter.java
@@ -95,4 +95,29 @@
         String x = string() ;
         assertEquals("test", x) ;
     }
+
+    @Test // JENA-19219
+    public void write_07() {
+        create(8194, 4098);
+        for (int i = 0; i < 8194;i++) {
+            w.output('a');
+        }
+        w.close();
+        String x = string();
+        assertEquals(x.length(), 8194);
+    }
+
+    @Test // JENA-1920
+    public void write_08() {
+        create(8192, 4096);
+        char[] chars = new char[8192];
+        //define enough to make it a 'large blob'
+        for(int i = 0; i < 5000; i++){
+            chars[i] = '1';
+        }
+        w.output(chars, 0, 5000);
+        w.close();
+        String x = string();
+        assertEquals(5000, x.length());
+    }
 }
diff --git a/jena-text/src/main/java/org/apache/jena/query/text/TextIndexTDB1.java b/jena-text/src/main/java/org/apache/jena/query/text/TextIndexTDB1.java
index f713826..f7c6a3d 100644
--- a/jena-text/src/main/java/org/apache/jena/query/text/TextIndexTDB1.java
+++ b/jena-text/src/main/java/org/apache/jena/query/text/TextIndexTDB1.java
@@ -18,7 +18,6 @@
 
 package org.apache.jena.query.text;
 
-import org.apache.jena.query.text.TextIndex;
 import org.apache.jena.tdb.transaction.Transaction;
 import org.apache.jena.tdb.transaction.TransactionLifecycle;
 
@@ -56,5 +55,4 @@
 
     @Override
     public void clearupCommitted(Transaction txn) {}
-    
 }