http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/io/InputStreamBuffered.java
----------------------------------------------------------------------
diff --git 
a/jena-base/src/main/java/org/apache/jena/atlas/io/InputStreamBuffered.java 
b/jena-base/src/main/java/org/apache/jena/atlas/io/InputStreamBuffered.java
new file mode 100644
index 0000000..9b03d43
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/io/InputStreamBuffered.java
@@ -0,0 +1,104 @@
+/*
+ * 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.atlas.io;
+
+import static org.apache.jena.atlas.io.IO.EOF ;
+
+import java.io.IOException ;
+import java.io.InputStream ;
+
+/** InputStream optimizing for one byte at a time operation.
+ *  BufferedInputStream operations have synchronization making 
+ *  reading one byte at a time expensive.
+ *  
+ *  @see java.io.InputStream
+ *  @see java.io.BufferedInputStream
+ */
+public final class InputStreamBuffered extends InputStream 
+{
+    public static int DFT_BUFSIZE = 16*1024 ;
+    private InputStream source ;
+    private byte[] buffer ;
+    private int buffLen = 0 ;
+    private int idx = 0 ;
+    private long count = 0 ;
+    
+    public InputStreamBuffered(InputStream input)
+    {
+        this(input, DFT_BUFSIZE) ;
+    }
+    
+    public InputStreamBuffered(InputStream input, int bufsize)
+    {
+        super() ;
+        this.source = input ;
+        this.buffer = new byte[bufsize] ;
+        this.idx = 0 ;
+        this.buffLen = 0 ;
+    }
+
+//    @Override
+//    public int read(byte b[], int off, int len) throws IOException
+//    {
+//    }
+    
+    @Override
+    public int read() throws IOException
+    {
+        return advance() ;
+    }
+    
+    @Override
+    public void close() throws IOException
+    {
+        source.close() ;
+    }
+    
+    public final int advance()
+    {
+        if ( idx >= buffLen )
+            // Points outside the array.  Refill it 
+            fillArray() ;
+        
+        // Advance one character.
+        if ( buffLen >= 0 )
+        {
+            byte ch = buffer[idx] ;
+            // Advance the lookahead character
+            idx++ ;
+            count++ ;
+            return ch & 0xFF ;
+        }  
+        else
+            // Buffer empty, end of stream.
+            return EOF ;
+    }
+
+    private int fillArray()
+    {
+        try
+        {
+            int x = source.read(buffer) ;
+            idx = 0 ;
+            buffLen = x ;   // Maybe -1
+            return x ;
+        } 
+        catch (IOException ex) { IO.exception(ex) ; return -1 ; }
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/io/NullOutputStream.java
----------------------------------------------------------------------
diff --git 
a/jena-base/src/main/java/org/apache/jena/atlas/io/NullOutputStream.java 
b/jena-base/src/main/java/org/apache/jena/atlas/io/NullOutputStream.java
new file mode 100644
index 0000000..8986f9e
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/io/NullOutputStream.java
@@ -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.atlas.io;
+
+import java.io.OutputStream ;
+
+/** /dev/null */
+public class NullOutputStream extends OutputStream {
+
+    private static final NullOutputStream DEV_NULL = new NullOutputStream();
+
+    public static OutputStream sink() {
+        return DEV_NULL ;
+    }
+    
+    public NullOutputStream() {}
+    
+    @Override public void write(int b) {}
+
+    @Override public void write(byte b[]) {}
+
+    @Override public void write(byte b[], int off, int len) { }
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/io/OutStreamUTF8.java
----------------------------------------------------------------------
diff --git 
a/jena-base/src/main/java/org/apache/jena/atlas/io/OutStreamUTF8.java 
b/jena-base/src/main/java/org/apache/jena/atlas/io/OutStreamUTF8.java
new file mode 100644
index 0000000..d4d15d4
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/io/OutStreamUTF8.java
@@ -0,0 +1,179 @@
+/*
+ * 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.atlas.io;
+
+import java.io.IOException ;
+import java.io.OutputStream ;
+import java.io.Writer ;
+
+/** Output UTF-8 encoded data.
+ *  This class implements the "Modified UTF8" encoding rules (null -> C0 80)
+ *  It will encode any 16 bit value.  
+ *  It can be used as a pure UTf-8 encoder. 
+ * 
+ *  @see InStreamUTF8
+ */
+public final class OutStreamUTF8 extends Writer
+{
+    private OutputStream out ;
+
+    public OutStreamUTF8(OutputStream out)
+    {
+        // Buffer?
+        this.out = out ;
+    }
+    
+    @Override
+    public void write(char[] cbuf, int off, int len) throws IOException
+    {
+        for ( int i = 0 ; i < len; i++ )
+            write(cbuf[off+i]) ;
+    }
+    
+    @Override
+    public void write(int ch) throws IOException
+    { output(out, ch) ; }
+    
+    @Override
+    public void write(char[] b) throws IOException
+    { write(b, 0, b.length) ; }
+    
+    @Override
+    public void write(String str) throws IOException
+    { write(str,0, str.length()) ; }
+    
+    @Override
+    public void write(String str, int idx, int len) throws IOException
+    {
+        for ( int i = 0 ; i < len; i++ )
+            write(str.charAt(idx+i)) ;
+    }
+
+    public void output(int x)
+    { 
+        try { 
+            output(out, x) ;
+        } catch (IOException ex) { IO.exception(ex) ; }
+    }
+    
+    /*
+     * Bits 
+     * 7    U+007F      1 to 127              0xxxxxxx 
+     * 11   U+07FF      128 to 2,047          110xxxxx 10xxxxxx
+     * 16   U+FFFF      2,048 to 65,535       1110xxxx 10xxxxxx 10xxxxxx
+     * 21   U+1FFFFF    65,536 to 1,114,111   11110xxx 10xxxxxx 10xxxxxx 
10xxxxxx
+     * 26   U+3FFFFFF                         111110xx 10xxxxxx 10xxxxxx 
10xxxxxx 10xxxxxx
+     * 31   U+7FFFFFFF                        1111110x 10xxxxxx 10xxxxxx 
10xxxxxx 10xxxxxx 10xxxxxx
+     */
+    public static void output(OutputStream out, int ch) throws IOException
+    {
+        if ( ch != 0 && ch <= 127 )
+        {
+            // 7 bits
+            out.write(ch) ;
+            return ;
+        }
+        
+        if ( ch == 0 )
+        {
+            // Modified UTF-8.
+            out.write(0xC0) ;
+            out.write(0x80) ;
+            return ;
+        }
+        
+        // Better? output(int HiMask, int byte length, int value) 
+        
+        if ( ch <= 0x07FF )
+        {
+            // 11 bits : 110yyyyy 10xxxxxx
+            // int x1 = ( ((ch>>(11-5))&0x7) | 0xC0 ) ; outputBytes(out, x1, 
2, ch) ; return ;
+            int x1 = ( ((ch>>(11-5))&0x01F ) | 0xC0 ) ; 
+            int x2 = ( (ch&0x3F)  | 0x80 ) ;
+            out.write(x1) ;
+            out.write(x2) ;
+            return ;
+        }
+        if ( ch <= 0xFFFF )
+        {
+            // 16 bits : 1110aaaa  10bbbbbb  10cccccc
+            // int x1 = ( ((ch>>(16-4))&0x7) | 0xE0 ) ; outputBytes(out, x1, 
3, ch) ; return ;
+            int x1 = ( ((ch>>(16-4))&0x0F) | 0xE0 ) ;
+            int x2 = ( ((ch>>6)&0x3F) | 0x80 ) ;
+            int x3 = ( (ch&0x3F) | 0x80 ) ;
+            out.write(x1) ;
+            out.write(x2) ;
+            out.write(x3) ;
+            return ;
+        }
+        
+//        if ( Character.isDefined(ch) )
+//            throw new AtlasException("not a character") ;
+        
+        //if ( true ) throw new InternalErrorException("Valid code point for 
Java but not encodable") ;
+        
+        // Not java, where chars are 16 bit.
+        if ( ch <= 0x1FFFFF )
+        {
+            // 21 bits : 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+            int x1 = ( ((ch>>(21-3))&0x7) | 0xF0 ) ;
+            outputBytes(out, x1, 4, ch) ;
+            return ;
+        }
+        if ( ch <= 0x3FFFFFF )
+        {
+            // 26 bits : 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+            int x1 = ( ((ch>>(26-2))&0x3) | 0xF8 ) ;
+            outputBytes(out, x1, 5, ch) ;
+            return ;
+        }
+
+        if ( ch <= 0x7FFFFFFF )
+        {
+            // 32 bits : 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+            int x1 = ( ((ch>>(32-1))&0x1) | 0xFC ) ;
+            outputBytes(out, x1, 6, ch) ;
+            return ;
+        }
+    }
+    
+    private static void outputBytes(OutputStream out, int x1, int byteLength, 
int ch) throws IOException
+    {
+        // ByteLength = 3 => 2 byteLenth => shift=6 and shift=0  
+        out.write(x1) ;
+        byteLength-- ; // remaining bytes
+        for ( int i = 0 ; i < byteLength ; i++ )
+        {
+            // 6 Bits, loop from high to low  
+            int shift = 6*(byteLength-i-1) ;
+            int x =  (ch>>shift) & 0x3F ;
+            x = x | 0x80 ;  // 10xxxxxx
+            out.write(x) ;
+        }
+    }
+
+    @Override
+    public void flush() throws IOException
+    { out.flush(); }
+
+    @Override
+    public void close() throws IOException
+    { out.close() ; }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/io/OutputUtils.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/io/OutputUtils.java 
b/jena-base/src/main/java/org/apache/jena/atlas/io/OutputUtils.java
new file mode 100644
index 0000000..9659643
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/io/OutputUtils.java
@@ -0,0 +1,77 @@
+/*
+ * 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.atlas.io;
+
+import java.io.IOException ;
+import java.io.Writer ;
+
+import org.apache.jena.atlas.lib.BitsInt ;
+import org.apache.jena.atlas.lib.Chars ;
+
+
+public class OutputUtils
+{
+    /** Print the number x in width hex chars.  x must fit */
+    public static void printHex(StringBuilder out, int x, int width)
+    {
+        for ( int i = width-1 ; i >= 0 ; i-- )
+            x = oneHex(out, x, i) ;
+    }
+
+    /** Print one hex digit of the number */
+    public static int oneHex(StringBuilder out, int x, int i)
+    {
+        int y = BitsInt.unpack(x, 4*i, 4*i+4) ;
+        char charHex = Chars.hexDigitsUC[y] ;
+        out.append(charHex) ; 
+        return BitsInt.clear(x, 4*i, 4*i+4) ;
+    }
+    
+    /** Print the number x in width hex chars.  x must fit */
+    public static void printHex(Writer out, int x, int width)
+    {
+        for ( int i = width-1 ; i >= 0 ; i-- )
+            x = oneHex(out, x, i) ;
+    }
+
+    /** Print one hex digit of the number */
+    public static int oneHex(Writer out, int x, int i)
+    {
+        int y = BitsInt.unpack(x, 4*i, 4*i+4) ;
+        char charHex = Chars.hexDigitsUC[y] ;
+        try { out.write(charHex) ; } catch (IOException ex) {} 
+        return BitsInt.clear(x, 4*i, 4*i+4) ;
+    }
+
+    /** Print the number x in width hex chars.  x must fit */
+    public static void printHex(AWriter out, int x, int width)
+    {
+        for ( int i = width-1 ; i >= 0 ; i-- )
+            x = oneHex(out, x, i) ;
+    }
+
+    /** Print one hex digit of the number */
+    public static int oneHex(AWriter out, int x, int i)
+    {
+        int y = BitsInt.unpack(x, 4*i, 4*i+4) ;
+        char charHex = Chars.hexDigitsUC[y] ;
+        out.print(charHex) ; 
+        return BitsInt.clear(x, 4*i, 4*i+4) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/io/PeekInputStream.java
----------------------------------------------------------------------
diff --git 
a/jena-base/src/main/java/org/apache/jena/atlas/io/PeekInputStream.java 
b/jena-base/src/main/java/org/apache/jena/atlas/io/PeekInputStream.java
new file mode 100644
index 0000000..4a6403c
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/io/PeekInputStream.java
@@ -0,0 +1,233 @@
+/*
+ * 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.atlas.io;
+
+import static org.apache.jena.atlas.io.IO.EOF ;
+import static org.apache.jena.atlas.io.IO.UNSET ;
+
+import java.io.FileInputStream ;
+import java.io.FileNotFoundException ;
+import java.io.IOException ;
+import java.io.InputStream ;
+
+import org.apache.jena.atlas.AtlasException ;
+import org.apache.jena.atlas.RuntimeIOException ;
+
+/** Parsing-centric input stream.
+ *  @see PeekReader
+ */ 
+
+
+public final class PeekInputStream extends InputStream
+{
+    // Change to looking at slices of a ByteBuffer and rework TokenizerBytes 
+    
+    private final InputStreamBuffered source ;
+    
+    private static final int PUSHBACK_SIZE = 10 ; 
+    static final byte BYTE0 = (byte)0 ;
+    
+    private byte[] pushbackBytes ;
+    private int idxPushback ;                   // Index into pushbackBytes: 
points to next pushBack. -1 => none.
+    
+    private int currByte = UNSET ;              // Next byte to return when 
reading forwards.
+    private long posn ;
+    
+    public static final int INIT_LINE = 1 ;
+    public static final int INIT_COL = 1 ;
+    
+    private long colNum ;
+    private long lineNum ;
+    
+    // ---- static construction methods.
+    
+    public static PeekInputStream make(InputStream inputStream)
+    {
+        return make(inputStream, InputStreamBuffered.DFT_BUFSIZE) ;
+    }
+    
+    public static PeekInputStream make(InputStream inputStream, int bufferSize)
+    {
+        if ( inputStream instanceof PeekInputStream )
+            return (PeekInputStream)inputStream ;
+        
+        if ( inputStream instanceof InputStreamBuffered )
+            return new PeekInputStream((InputStreamBuffered)inputStream) ;
+        InputStreamBuffered in = new InputStreamBuffered(inputStream, 
bufferSize) ;
+        return new PeekInputStream(in) ;
+    }
+
+    public static PeekInputStream open(String filename) 
+    {
+        try {
+            InputStream in = new FileInputStream(filename) ;
+            return make(in) ;
+        } catch (FileNotFoundException ex){ throw new AtlasException("File not 
found: "+filename) ; }
+    }
+    
+    private PeekInputStream(InputStreamBuffered input)
+    {
+        this.source = input ;
+        this.pushbackBytes = new byte[PUSHBACK_SIZE] ; 
+        this.idxPushback = -1 ;
+        
+        this.colNum = INIT_COL ;
+        this.lineNum = INIT_LINE ;
+        this.posn = 0 ;
+        
+        // We start at byte "-1", i.e. just before the file starts.
+        // Advance always so that the peek byte is valid (is byte 0) 
+        // Returns the byte before the file starts (i.e. UNSET).
+    }
+
+    public final InputStreamBuffered getInput()   { return source ; }
+    
+    public long getLineNum()            { return lineNum; }
+
+    public long getColNum()             { return colNum; }
+
+    public long getPosition()           { return posn; }
+
+    //---- Do not access currByte except with peekByte/setCurrByte.
+    public final int peekByte()
+    { 
+        if ( idxPushback >= 0 )
+            return pushbackBytes[idxPushback] ;
+        
+        // If not started ... delayed initialization.
+        if ( currByte == UNSET )
+            init() ;
+        return currByte ;
+    }
+    
+    // And the correct way to read the currByte is to call peekByte
+    private final void setCurrByte(int b)
+    {
+        currByte = b ;
+    }
+    
+    public final int readByte()               { return nextByte() ; }
+    
+    /** push back a byte : does not alter underlying position, line or column 
counts*/  
+    public final void pushbackByte(int b)    { unreadByte(b) ; }
+    
+    @Override
+    public final void close() throws IOException
+    {
+        source.close() ;
+    }
+
+    @Override
+    public final int read() throws IOException
+    {
+        if ( eof() )
+            return EOF ;
+        int x = readByte() ;
+        return x ;
+    }
+    
+    @Override
+    public final int read(byte[] buf, int off, int len) throws IOException
+    {
+        if ( eof() )
+            return EOF ;
+        for ( int i = 0 ; i < len ; i++ )
+        {
+            int ch = readByte() ;
+            if ( ch == EOF )
+                return (i==0)? EOF : i ;
+            buf[i+off] = (byte)ch ;
+        }
+        return len ;
+    }
+
+    public final boolean eof()   { return peekByte() == EOF ; }
+
+    // ----------------
+    // The methods below are the only ones to manipulate the byte buffers.
+    // Other methods may read the state of variables.
+    
+    private final void unreadByte(int b)
+    {
+        // The push back buffer is in the order where [0] is the oldest.
+        // Does not alter the line number, column number or position count. 
+        
+        if ( idxPushback >= pushbackBytes.length )
+        {
+            // Enlarge pushback buffer.
+            byte[] pushbackBytes2 = new byte[pushbackBytes.length*2] ;
+            System.arraycopy(pushbackBytes, 0, pushbackBytes2, 0, 
pushbackBytes.length) ;
+            pushbackBytes = pushbackBytes2 ;
+            //throw new JenaException("Pushback buffer overflow") ;
+        }
+        if ( b == EOF || b == UNSET )
+            throw new RuntimeIOException("Illegal byte to push back: "+b) ;
+        
+        idxPushback++ ;
+        pushbackBytes[idxPushback] = (byte)b ;
+    }
+    
+    private final void init()
+    {
+        advanceAndSet() ;
+        if ( currByte == UNSET )
+            setCurrByte(EOF) ;
+    }
+
+    private final void advanceAndSet() 
+    {
+        try {
+            int ch = source.read() ;
+            setCurrByte(ch) ;
+        } catch (IOException ex) { IO.exception(ex) ; }
+    }
+    
+    
+    // Invariants.
+    // currByte is either bytes[idx-1] or pushbackBytes[idxPushback]
+    
+    /** Return the next byte, moving on one place and resetting the peek byte 
*/ 
+    private final int nextByte()
+    {
+        int b = peekByte() ;
+        
+        if ( b == EOF )
+            return EOF ;
+        
+        if ( idxPushback >= 0 )
+        {
+            byte b2 = pushbackBytes[idxPushback] ;
+            idxPushback-- ;
+            return b2 ;
+        }
+
+        posn++ ;
+        
+        if (b == '\n')
+        {
+            lineNum++;
+            colNum = INIT_COL ;
+        } 
+        else
+            colNum++;
+        
+        advanceAndSet() ;
+        return b ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/io/PeekReader.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/io/PeekReader.java 
b/jena-base/src/main/java/org/apache/jena/atlas/io/PeekReader.java
new file mode 100644
index 0000000..31076b0
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/io/PeekReader.java
@@ -0,0 +1,270 @@
+/*
+ * 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.atlas.io ;
+
+import static org.apache.jena.atlas.io.IO.EOF ;
+import static org.apache.jena.atlas.io.IO.UNSET ;
+
+import java.io.* ;
+
+import org.apache.jena.atlas.AtlasException ;
+import org.apache.jena.atlas.RuntimeIOException ;
+import org.apache.jena.atlas.lib.Chars ;
+
+/**
+ * Parsing-centric reader. This class is not thread safe.
+ * @see PeekInputStream
+ */
+
+public final class PeekReader extends Reader {
+    // Remember to apply fixes to PeekInputStream as well.
+
+    // Buffering is done by a CharStream - does it make adifference?
+    // Yes. A lot (Java6).
+
+    // Using a Reader here seems to have zero cost or benefit but CharStream
+    // allows fast String handling.
+    private final CharStream source ;
+
+    private static final int PUSHBACK_SIZE = 10 ;
+    static final byte        CHAR0         = (char)0 ;
+
+    private char[]           pushbackChars ;
+    // Index into pushbackChars: points to next pushBack.
+    // -1 => none.
+    private int              idxPushback ;            
+
+    // Next character to return when reading forwards.
+    private int              currChar      = UNSET ;  
+    private long             posn ;
+
+    public static final int  INIT_LINE     = 1 ;
+    public static final int  INIT_COL      = 1 ;
+
+    private long             colNum ;
+    private long             lineNum ;
+
+    // ---- static construction methods.
+
+    public static PeekReader make(Reader r) {
+        if ( r instanceof PeekReader )
+            return (PeekReader)r ;
+        return make(r, CharStreamBuffered.CB_SIZE) ;
+    }
+
+    public static PeekReader make(Reader r, int bufferSize) {
+        // It is worth our own buffering even if a BufferedReader
+        // because of the synchronized on one char reads in BufferedReader.
+        return new PeekReader(new CharStreamBuffered(r, bufferSize)) ;
+    }
+
+    /** Make PeekReader where the input is UTF8 : BOM is removed */
+    public static PeekReader makeUTF8(InputStream in) {
+        // This is the best route to make a PeekReader because it avoids
+        // chances of wrong charset for a Reader say.
+        PeekReader pr ;
+        if ( true ) {
+            Reader r = IO.asUTF8(in) ;
+            // This adds reader-level buffering
+            pr = make(r) ;
+        } else {
+            // This is a bit slower - reason unknown.
+            InputStreamBuffered in2 = new InputStreamBuffered(in) ;
+            CharStream r = new InStreamUTF8(in2) ;
+            pr = new PeekReader(r) ;
+        }
+        // Skip BOM.
+        int ch = pr.peekChar() ;
+        if ( ch == Chars.BOM )
+            // Skip BOM
+            pr.readChar() ;
+        return pr ;
+    }
+
+    /** Make PeekReader where the input is ASCII */
+    public static PeekReader makeASCII(InputStream in) {
+        Reader r = IO.asASCII(in) ;
+        return make(r) ;
+    }
+
+    public static PeekReader make(CharStream r) {
+        return new PeekReader(r) ;
+    }
+
+    public static PeekReader readString(String string) {
+        return new PeekReader(new CharStreamSequence(string)) ;
+    }
+
+    public static PeekReader open(String filename) {
+        try {
+            InputStream in = new FileInputStream(filename) ;
+            return makeUTF8(in) ;
+        } catch (FileNotFoundException ex) {
+            throw new AtlasException("File not found: " + filename) ;
+        }
+    }
+
+    private PeekReader(CharStream stream) {
+        this.source = stream ;
+        this.pushbackChars = new char[PUSHBACK_SIZE] ;
+        this.idxPushback = -1 ;
+
+        this.colNum = INIT_COL ;
+        this.lineNum = INIT_LINE ;
+        this.posn = 0 ;
+    }
+
+    public long getLineNum() {
+        return lineNum ;
+    }
+
+    public long getColNum() {
+        return colNum ;
+    }
+
+    public long getPosition() {
+        return posn ;
+    }
+
+    // ---- Do not access currChar except with peekChar/setCurrChar.
+    public final int peekChar() {
+        if ( idxPushback >= 0 )
+            return pushbackChars[idxPushback] ;
+
+        // If not started ... delayed initialization.
+        if ( currChar == UNSET )
+            init() ;
+        return currChar ;
+    }
+
+    // And the correct way to read the currChar is to call peekChar.
+    private final void setCurrChar(int ch) {
+        currChar = ch ;
+    }
+
+    public final int readChar() {
+        return nextChar() ;
+    }
+
+    /**
+     * push back a character : does not alter underlying position, line or
+     * column counts
+     */
+    public final void pushbackChar(int ch) {
+        unreadChar(ch) ;
+    }
+
+    // Reader operations
+    @Override
+    public final void close() throws IOException {
+        source.closeStream() ;
+    }
+
+    @Override
+    public final int read() throws IOException {
+        if ( eof() )
+            return EOF ;
+        int x = readChar() ;
+        return x ;
+    }
+
+    @Override
+    public final int read(char[] cbuf, int off, int len) throws IOException {
+        if ( eof() )
+            return EOF ;
+        // Note - we need to preserve line count
+        // Single char ops are reasonably efficient.
+        for (int i = 0; i < len; i++) {
+            int ch = readChar() ;
+            if ( ch == EOF )
+                return (i == 0) ? EOF : i ;
+            cbuf[i + off] = (char)ch ;
+        }
+        return len ;
+    }
+
+    public final boolean eof() {
+        return peekChar() == EOF ;
+    }
+
+    // ----------------
+    // The methods below are the only ones to manipulate the character buffers.
+    // Other methods may read the state of variables.
+
+    private final void unreadChar(int ch) {
+        // The push back buffer is in the order where [0] is the oldest.
+        // Does not alter the line number, column number or position count
+        // not does reading a pushback charcater.
+
+        if ( idxPushback >= pushbackChars.length ) {
+            // Enlarge pushback buffer.
+            char[] pushbackChars2 = new char[pushbackChars.length * 2] ;
+            System.arraycopy(pushbackChars, 0, pushbackChars2, 0, 
pushbackChars.length) ;
+            pushbackChars = pushbackChars2 ;
+            // throw new JenaException("Pushback buffer overflow") ;
+        }
+        if ( ch == EOF || ch == UNSET )
+            throw new RuntimeIOException("Illegal character to push back: " + 
ch) ;
+
+        idxPushback++ ;
+        pushbackChars[idxPushback] = (char)ch ;
+    }
+
+    private final void init() {
+        advanceAndSet() ;
+        if ( currChar == UNSET )
+            setCurrChar(EOF) ;
+    }
+
+    private final void advanceAndSet() {
+        int ch = source.advance() ;
+        setCurrChar(ch) ;
+    }
+
+    // Invariants.
+    // currChar is either chars[idx-1] or pushbackChars[idxPushback]
+
+    /**
+     * Return the next character, moving on one place and resetting the peek
+     * character
+     */
+    private final int nextChar() {
+        int ch = peekChar() ;
+
+        if ( ch == EOF )
+            return EOF ;
+
+        if ( idxPushback >= 0 ) {
+            char ch2 = pushbackChars[idxPushback] ;
+            idxPushback-- ;
+            return ch2 ;
+        }
+
+        posn++ ;
+
+        if ( ch == '\n' ) {
+            lineNum++ ;
+            colNum = INIT_COL ;
+        } else
+            colNum++ ;
+
+        advanceAndSet() ;
+        return ch ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/io/PrintUtils.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/io/PrintUtils.java 
b/jena-base/src/main/java/org/apache/jena/atlas/io/PrintUtils.java
new file mode 100644
index 0000000..14e4ed1
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/io/PrintUtils.java
@@ -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.atlas.io;
+
+
+public class PrintUtils
+{
+    // ---- Printable
+    public static String toString(Printable f)
+    { 
+        IndentedLineBuffer buff = new IndentedLineBuffer() ;
+        f.output(buff) ;
+        return buff.toString() ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/io/Printable.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/io/Printable.java 
b/jena-base/src/main/java/org/apache/jena/atlas/io/Printable.java
new file mode 100644
index 0000000..69bd211
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/io/Printable.java
@@ -0,0 +1,24 @@
+/*
+ * 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.atlas.io;
+
+public interface Printable
+{
+    public void output(IndentedWriter out) ;
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/io/PrintableBase.java
----------------------------------------------------------------------
diff --git 
a/jena-base/src/main/java/org/apache/jena/atlas/io/PrintableBase.java 
b/jena-base/src/main/java/org/apache/jena/atlas/io/PrintableBase.java
new file mode 100644
index 0000000..89fbb24
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/io/PrintableBase.java
@@ -0,0 +1,26 @@
+/*
+ * 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.atlas.io;
+
+// Hmm - hardly worth it!
+public abstract class PrintableBase implements Printable
+{
+    @Override
+    public String toString() { return PrintUtils.toString(this) ; }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/io/StringWriterI.java
----------------------------------------------------------------------
diff --git 
a/jena-base/src/main/java/org/apache/jena/atlas/io/StringWriterI.java 
b/jena-base/src/main/java/org/apache/jena/atlas/io/StringWriterI.java
new file mode 100644
index 0000000..5fea4d3
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/io/StringWriterI.java
@@ -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.atlas.io;
+
+import java.io.StringWriter ;
+
+public class StringWriterI extends Writer2
+{
+    public StringWriterI()
+    {
+        super(new StringWriter()) ;
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/io/Writer2.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/io/Writer2.java 
b/jena-base/src/main/java/org/apache/jena/atlas/io/Writer2.java
new file mode 100644
index 0000000..100ec6f
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/io/Writer2.java
@@ -0,0 +1,97 @@
+/**
+ * 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.atlas.io;
+
+import java.io.BufferedWriter ;
+import java.io.IOException ;
+import java.io.Writer ;
+
+import org.apache.jena.atlas.lib.Closeable ;
+
+/** A Writer, without the checked exceptions. */
+
+public class Writer2 extends AWriterBase implements AWriter, Closeable
+{
+    protected final Writer writer ;
+
+    public static Writer2 wrap(Writer writer)
+    {
+        if ( writer instanceof BufferedWriter )
+            return new Writer2(writer) ;
+        if ( writer instanceof BufferingWriter )
+            return new Writer2(writer) ;
+        
+        writer = new BufferingWriter(writer) ;
+        return new Writer2(writer) ;
+    }
+    
+    protected Writer2(Writer writer) { this.writer = writer ; }
+
+    @Override
+    public void print(char ch)
+    { 
+        try { writer.write(ch) ; } catch (IOException ex) { IO.exception(ex) ; 
}
+    }
+
+    @Override
+    public void print(String string)
+    { 
+        try { writer.write(string) ; } catch (IOException ex) { 
IO.exception(ex) ; }
+    }
+
+    @Override
+    public void print(char[] cbuf)
+    {
+        try { writer.write(cbuf) ; } catch (IOException ex) { IO.exception(ex) 
; }
+    }
+
+    @Override
+    public void flush()
+    {
+        try { writer.flush() ; } catch (IOException ex) { IO.exception(ex) ; }
+    }
+
+    @Override
+    public void close()
+    {
+        try { writer.close() ; } catch (IOException ex) { IO.exception(ex) ; }
+    }
+
+    @Override
+    public void printf(String fmt, Object... args)
+    {
+        print(String.format(fmt, args)) ;
+    }
+
+    @Override
+    public void println(String obj)
+    {
+        print(obj) ; print("\n") ;
+    }
+
+    @Override
+    public void println()
+    {
+        print("\n") ;
+    }
+    
+    @Override
+    public String toString() { return writer.toString() ; }
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/iterator/AccString.java
----------------------------------------------------------------------
diff --git 
a/jena-base/src/main/java/org/apache/jena/atlas/iterator/AccString.java 
b/jena-base/src/main/java/org/apache/jena/atlas/iterator/AccString.java
new file mode 100644
index 0000000..7df3486
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/AccString.java
@@ -0,0 +1,66 @@
+/*
+ * 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.atlas.iterator;
+
+public class AccString<T> implements Accumulate<T, String>
+{
+    StringBuilder buffer = null ;
+    private String sep ;
+    private boolean first = true ;
+
+    // Fresh StringBuilder
+    public AccString(String sep) { this.sep = sep ; }
+    public AccString() { this(" ") ; }
+
+    @Override
+    public void accumulate(T item)
+    { 
+        if ( ! first )
+            buffer.append(sep) ;
+        if ( item != null )
+            buffer.append(toString(item)) ;
+        else
+            buffer.append("<null>") ;
+        first = false ;
+    }
+
+    /** Make into a string */
+    protected String toString(T item)
+    {
+        return item.toString() ; 
+    }
+    
+    @Override
+    public String get()
+    {
+        return buffer.toString() ;
+    }
+
+    @Override
+    public void start()
+    { 
+        // Resets on each use.
+        buffer = new StringBuilder() ; 
+        first = true ;
+    }
+    
+    @Override
+    public void finish() {}
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/iterator/Accumulate.java
----------------------------------------------------------------------
diff --git 
a/jena-base/src/main/java/org/apache/jena/atlas/iterator/Accumulate.java 
b/jena-base/src/main/java/org/apache/jena/atlas/iterator/Accumulate.java
new file mode 100644
index 0000000..2eef4fa
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/Accumulate.java
@@ -0,0 +1,27 @@
+/*
+ * 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.atlas.iterator;
+
+public interface Accumulate <T, R>
+{
+    public void start() ;
+    public void accumulate(T item) ;
+    public void finish();
+    public R get() ;
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/iterator/Action.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/iterator/Action.java 
b/jena-base/src/main/java/org/apache/jena/atlas/iterator/Action.java
new file mode 100644
index 0000000..de2630e
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/Action.java
@@ -0,0 +1,24 @@
+/*
+ * 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.atlas.iterator;
+
+public interface Action<T>
+{
+    public void apply(T item) ;
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/iterator/ActionCount.java
----------------------------------------------------------------------
diff --git 
a/jena-base/src/main/java/org/apache/jena/atlas/iterator/ActionCount.java 
b/jena-base/src/main/java/org/apache/jena/atlas/iterator/ActionCount.java
new file mode 100644
index 0000000..20f837c
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/ActionCount.java
@@ -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.atlas.iterator;
+
+
+public class ActionCount<T> implements Action<T>
+{
+    private long count = 0 ;
+    
+    @Override
+    public void apply(T item)
+    { count++ ; }
+    
+    public long getCount() { return count ; }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/iterator/ActionNothing.java
----------------------------------------------------------------------
diff --git 
a/jena-base/src/main/java/org/apache/jena/atlas/iterator/ActionNothing.java 
b/jena-base/src/main/java/org/apache/jena/atlas/iterator/ActionNothing.java
new file mode 100644
index 0000000..d05b279
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/ActionNothing.java
@@ -0,0 +1,26 @@
+/*
+ * 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.atlas.iterator;
+
+
+public class ActionNothing<T> implements Action<T>
+{
+    @Override
+    public void apply(T item)   { }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/iterator/Filter.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/iterator/Filter.java 
b/jena-base/src/main/java/org/apache/jena/atlas/iterator/Filter.java
new file mode 100644
index 0000000..5d427c5
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/Filter.java
@@ -0,0 +1,22 @@
+/*
+ * 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.atlas.iterator;
+
+@FunctionalInterface
+public interface Filter <T> { boolean accept(T item) ; }

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/iterator/FilterDistinctAdjacent.java
----------------------------------------------------------------------
diff --git 
a/jena-base/src/main/java/org/apache/jena/atlas/iterator/FilterDistinctAdjacent.java
 
b/jena-base/src/main/java/org/apache/jena/atlas/iterator/FilterDistinctAdjacent.java
new file mode 100644
index 0000000..3beb832
--- /dev/null
+++ 
b/jena-base/src/main/java/org/apache/jena/atlas/iterator/FilterDistinctAdjacent.java
@@ -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.atlas.iterator;
+
+import org.apache.jena.atlas.lib.Lib ;
+
+public class FilterDistinctAdjacent<T> implements Filter<T>
+{
+    private boolean isSet = false ;
+    private T last = null ; 
+    
+    public FilterDistinctAdjacent() { }
+    
+    @Override
+    public boolean accept(T item)
+    {
+        if ( isSet && Lib.equal(last, item) )
+            return false ;
+        last = item ;
+        isSet = true ;
+        return true ;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/iterator/FilterOutNulls.java
----------------------------------------------------------------------
diff --git 
a/jena-base/src/main/java/org/apache/jena/atlas/iterator/FilterOutNulls.java 
b/jena-base/src/main/java/org/apache/jena/atlas/iterator/FilterOutNulls.java
new file mode 100644
index 0000000..21cd471
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/FilterOutNulls.java
@@ -0,0 +1,32 @@
+/*
+ * 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.atlas.iterator;
+
+
+public class FilterOutNulls<T> implements Filter<T>
+{
+    public FilterOutNulls() { }
+    
+    @Override
+    public boolean accept(T item)
+    {
+        return item != null ;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/iterator/FilterStack.java
----------------------------------------------------------------------
diff --git 
a/jena-base/src/main/java/org/apache/jena/atlas/iterator/FilterStack.java 
b/jena-base/src/main/java/org/apache/jena/atlas/iterator/FilterStack.java
new file mode 100644
index 0000000..479d93f
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/FilterStack.java
@@ -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.atlas.iterator;
+
+/**
+ * Add a filter to a chain - the original filter is called after this new 
sub-filter.
+ */
+public abstract class FilterStack<T> implements  Filter<T>
+{
+    private final Filter<T> other ;
+    private final boolean subFilterLast ;
+    
+    public FilterStack(Filter<T> other) { this(other, false) ; }
+    
+    public FilterStack(Filter<T> other, boolean callOldFilterFirst)
+    {
+        this.other = other ;
+        this.subFilterLast = callOldFilterFirst ;
+    }
+   
+    @Override
+    public final boolean accept(T item)
+    {
+        if ( subFilterLast )
+            return acceptAdditionaOther(item) ;
+        else
+            return acceptOtherAdditional(item) ;
+    }
+    
+    private boolean acceptAdditionaOther(T item)
+    {
+        if ( ! acceptAdditional(item) )
+            return false ;
+        
+        if ( other != null && ! other.accept(item) )
+            return false ;
+        
+        return true ;
+    }
+
+    private boolean acceptOtherAdditional(T item)
+    {
+        if ( other != null && ! other.accept(item) )
+            return false ;
+        return acceptAdditional(item) ;
+    }
+
+    
+    /** Additional filter condition to apply */
+    public abstract boolean acceptAdditional(T item) ;
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/iterator/FilterUnique.java
----------------------------------------------------------------------
diff --git 
a/jena-base/src/main/java/org/apache/jena/atlas/iterator/FilterUnique.java 
b/jena-base/src/main/java/org/apache/jena/atlas/iterator/FilterUnique.java
new file mode 100644
index 0000000..2b2a957
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/FilterUnique.java
@@ -0,0 +1,39 @@
+/*
+ * 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.atlas.iterator;
+
+import java.util.HashSet ;
+import java.util.Set ;
+
+public class FilterUnique<T> implements Filter<T>
+{
+    private Set<T> seen = new HashSet<>() ;
+    
+    public FilterUnique() { }
+    
+    @Override
+    public boolean accept(T item)
+    {
+        if ( seen.contains(item) )
+            return false ;
+        seen.add(item) ;
+        return true ;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/iterator/Iter.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/iterator/Iter.java 
b/jena-base/src/main/java/org/apache/jena/atlas/iterator/Iter.java
new file mode 100644
index 0000000..8aec2ea
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/Iter.java
@@ -0,0 +1,971 @@
+/*
+ * 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.atlas.iterator ;
+
+import java.io.PrintStream ;
+import java.util.* ;
+import java.util.stream.Stream ;
+import java.util.stream.StreamSupport ;
+
+import org.apache.jena.atlas.lib.ActionKeyValue ;
+import org.apache.jena.atlas.lib.Closeable ;
+import org.apache.jena.atlas.lib.Sink ;
+
+public class Iter<T> implements Iterator<T> {
+    
+    // Most Iterable<T> operations have been removed - use streams instad.
+    
+    public static <T> Stream<T> asStream(Iterator<T> iterator) {
+        // Why isn't there a JDK operation for iterator -> (sequential) stream?
+        return asStream(iterator, false);
+    }
+
+    public static <T> Stream<T> asStream(Iterator<T> iterator, boolean 
parallel) {
+        // Why isn't there a JDK operation for iterator -> (sequential) 
stream?  
+        Iterable<T> iterable = () -> iterator;
+        return StreamSupport.stream(iterable.spliterator(), parallel);
+    }
+
+    // First part : the static function library.
+    // Often with both Iterator<? extends T> and Iterable<? extends T>
+
+    public static <T> Iterator<T> singleton(T item) {
+        return new SingletonIterator<>(item) ;
+    }
+
+    @SuppressWarnings("rawtypes")
+    private static final Iterator iter0 = new NullIterator() ;
+    @SuppressWarnings({"unchecked", "cast"})
+    public static <T> Iterator<T> nullIterator() { return 
(NullIterator<T>)iter0 ; }
+
+//    public static <T> Iterator<T> nullIterator() {
+//        return new NullIterator<T>() ;
+//    }
+    
+//    public static <T> Set<T> toSet(Iterable<? extends T> stream) {
+//        return toSet(stream.iterator()) ;
+//    }
+
+    public static <T> Set<T> toSet(Iterator<? extends T> stream) {
+        Accumulate<T, Set<T>> action = new Accumulate<T, Set<T>>() {
+            private Set<T> acc = null ;
+
+            @Override
+            public void accumulate(T item) {
+                acc.add(item) ;
+            }
+
+            @Override
+            public Set<T> get() {
+                return acc ;
+            }
+
+            @Override
+            public void start() {
+                acc = new HashSet<>() ;
+            }
+
+            @Override
+            public void finish() {}
+        } ;
+        return reduce(stream, action) ;
+    }
+
+//    public static <T> List<T> toList(Iterable<? extends T> stream) {
+//        return toList(stream.iterator()) ;
+//    }
+
+    public static <T> List<T> toList(Iterator<? extends T> stream) {
+        Accumulate<T, List<T>> action = new Accumulate<T, List<T>>() {
+            private List<T> acc = null ;
+
+            @Override
+            public void accumulate(T item) {
+                acc.add(item) ;
+            }
+
+            @Override
+            public List<T> get() {
+                return acc ;
+            }
+
+            @Override
+            public void start() {
+                acc = new ArrayList<>() ;
+            }
+
+            @Override
+            public void finish() {}
+        } ;
+        return reduce(stream, action) ;
+    }
+
+    /**
+     * Create another iterator without risk of concurrent modification
+     * exceptions. This materializes the input iterator.
+     */
+    public static <T> Iterator<T> iterator(Iterator<? extends T> iterator) {
+        List<T> x = Iter.toList(iterator) ;
+        return x.iterator() ;
+    }
+
+    public interface Folder<X, Y> {
+        Y eval(Y acc, X arg) ;
+    }
+
+    public static <T, R> R foldLeft(Iterable<? extends T> stream, Folder<T, R> 
function, R value) {
+        return foldLeft(stream.iterator(), function, value) ;
+    }
+
+    public static <T, R> R foldLeft(Iterator<? extends T> stream, Folder<T, R> 
function, R value) {
+        // Tail recursion, unwound
+        for (; stream.hasNext();) {
+            T item = stream.next() ;
+            value = function.eval(value, item) ;
+        }
+        return value ;
+    }
+
+    public static <T, R> R foldRight(Iterable<? extends T> stream, Folder<T, 
R> function, R value) {
+        return foldRight(stream.iterator(), function, value) ;
+    }
+
+    public static <T, R> R foldRight(Iterator<? extends T> stream, Folder<T, 
R> function, R value) {
+        // Recursive.
+        if ( !stream.hasNext() )
+            return value ;
+        T item = stream.next() ;
+        return function.eval(foldRight(stream, function, value), item) ;
+    }
+
+    // Note fold-left and fold-right
+    // http://en.wikipedia.org/wiki/Fold_%28higher-order_function%29
+
+    // This reduce is fold-left (take first element, apply to rest of list)
+    // which copes with infinite lists.
+    // Fold-left starts by combining the first element, then moves on.
+
+    public static <T, R> R reduce(Iterable<? extends T> stream, Accumulate<T, 
R> aggregator) {
+        return reduce(stream.iterator(), aggregator) ;
+    }
+
+    public static <T, R> R reduce(Iterator<? extends T> stream, Accumulate<T, 
R> aggregator) {
+        aggregator.start() ;
+        for (; stream.hasNext();) {
+            T item = stream.next() ;
+            aggregator.accumulate(item) ;
+        }
+        aggregator.finish() ;
+        return aggregator.get() ;
+    }
+
+    // map without the results - do immediately.
+    // Also, apply with call in between?
+
+//    public static <T> void apply(Iterable<? extends T> stream, Action<T> 
action) {
+//        apply(stream.iterator(), action) ;
+//    }
+
+    public static <T> void apply(Iterator<? extends T> stream, Action<T> 
action) {
+        for (; stream.hasNext();) {
+            T item = stream.next() ;
+            action.apply(item) ;
+        }
+    }
+
+    // -- Map specific apply. No results - do immediately.
+
+    public static <K, V> void apply(Map<K, V> map, ActionKeyValue<K, V> 
action) {
+        for (Map.Entry<K, V> entry : map.entrySet())
+            action.apply(entry.getKey(), entry.getValue()) ;
+    }
+
+    // ---- Filter
+
+//    public static <T> Iterator<T> filter(Iterable<? extends T> stream, 
Filter<T> filter) {
+//        return filter(stream.iterator(), filter) ;
+//    }
+
+    public static <T> Iterator<T> filter(final Iterator<? extends T> stream, 
final Filter<T> filter) {
+        final Iterator<T> iter = new Iterator<T>() {
+
+            boolean finished     = false ;
+            boolean slotOccupied = false ;
+            T       slot ;
+
+            @Override
+            public boolean hasNext() {
+                if ( finished )
+                    return false ;
+                while (!slotOccupied) {
+                    if ( !stream.hasNext() ) {
+                        finished = true ;
+                        break ;
+                    }
+                    T nextItem = stream.next() ;
+                    if ( filter.accept(nextItem) ) {
+                        slot = nextItem ;
+                        slotOccupied = true ;
+                        break ;
+                    }
+                }
+                return slotOccupied ;
+            }
+
+            @Override
+            public T next() {
+                if ( hasNext() ) {
+                    slotOccupied = false ;
+                    return slot ;
+                }
+                throw new NoSuchElementException("filter.next") ;
+            }
+
+            @Override
+            public void remove() {
+                throw new UnsupportedOperationException("filter.remove") ;
+            }
+        } ;
+
+        return iter ;
+    }
+
+    private static class InvertedFilter<T> implements Filter<T> {
+        public static <T> Filter<T> invert(Filter<T> filter) {
+            return new InvertedFilter<>(filter) ;
+        }
+        private Filter<T> baseFilter ;
+
+        private InvertedFilter(Filter<T> baseFilter) {
+            this.baseFilter = baseFilter ;
+        }
+
+        @Override
+        public boolean accept(T item) {
+            return !baseFilter.accept(item) ;
+        }
+    }
+
+//    public static <T> Iterator<T> notFilter(Iterable<? extends T> stream, 
Filter<T> filter) {
+//        return notFilter(stream.iterator(), filter) ;
+//    }
+
+    public static <T> Iterator<T> notFilter(final Iterator<? extends T> 
stream, final Filter<T> filter) {
+        Filter<T> flippedFilter = InvertedFilter.invert(filter) ;
+        return filter(stream, flippedFilter) ;
+    }
+
+    // Filter-related
+
+//    /**
+//     * Return true if every element of stream passes the filter (reads the
+//     * stream)
+//     */
+//    public static <T> boolean every(Iterable<? extends T> stream, Filter<T> 
filter) {
+//        for (T item : stream)
+//            if ( !filter.accept(item) )
+//                return false ;
+//        return true ;
+//    }
+
+    /**
+     * Return true if every element of stream passes the filter (reads the
+     * stream until the first element not passing the filter)
+     */
+    public static <T> boolean every(Iterator<? extends T> stream, Filter<T> 
filter) {
+        for (; stream.hasNext();) {
+            T item = stream.next() ;
+            if ( !filter.accept(item) )
+                return false ;
+        }
+        return true ;
+    }
+
+//    /**
+//     * Return true if every element of stream passes the filter (reads the
+//     * stream until the first element passing the filter)
+//     */
+//    public static <T> boolean some(Iterable<? extends T> stream, Filter<T> 
filter) {
+//        for (T item : stream)
+//            if ( filter.accept(item) )
+//                return true ;
+//        return false ;
+//    }
+
+    /**
+     * Return true if one or more elements of stream passes the filter (reads
+     * the stream to first element passing the filter)
+     */
+    public static <T> boolean some(Iterator<? extends T> stream, Filter<T> 
filter) {
+        for (; stream.hasNext();) {
+            T item = stream.next() ;
+            if ( filter.accept(item) )
+                return true ;
+        }
+        return false ;
+    }
+
+    // ---- Map
+
+//    public static <T, R> Iterator<R> map(Iterable<? extends T> stream, 
Transform<T, R> converter) {
+//        return map(stream.iterator(), converter) ;
+//    }
+
+    public static <T, R> Iterator<R> map(final Iterator<? extends T> stream, 
final Transform<T, R> converter) {
+        final Iterator<R> iter = new Iterator<R>() {
+            @Override
+            public boolean hasNext() {
+                return stream.hasNext() ;
+            }
+
+            @Override
+            public R next() {
+                return converter.convert(stream.next()) ;
+            }
+
+            @Override
+            public void remove() {
+                throw new UnsupportedOperationException("map.remove") ;
+            }
+        } ;
+        return iter ;
+    }
+
+    public static <T, R> List<R> map(List<? extends T> list, Transform<T, R> 
converter) {
+        return toList(map(list.iterator(), converter)) ;
+    }
+
+    /**
+     * Projects each element of a sequence to an Iterator&lt;R&gt; and flattens
+     * the resulting sequences into one sequence.
+     */
+    public static <T, R> Iterator<R> mapMany(final Iterator<? extends T> 
stream,
+                                             final Transform<? super T, 
Iterator<R>> converter) {
+        final Iterator<R> iter = new Iterator<R>() {
+
+            private Iterator<? extends R> it = null ; // Iterator for the
+                                                      // current element of
+                                                      // stream.
+
+            @Override
+            public boolean hasNext() {
+                if ( it != null && it.hasNext() )
+                    // Element of the current iterator.
+                    return true ;
+                // Start or current iterator has ended.
+                it = null ;
+
+                // Need to move to next non-empty iterator of the stream.
+                while (stream.hasNext()) {
+                    it = converter.convert(stream.next()) ;
+                    if ( it.hasNext() )
+                        // There is something.
+                        return true ;
+                }
+                it = null ;
+                // Stream ran out.
+                return false ;
+            }
+
+            @Override
+            public R next() {
+                if ( !hasNext() )
+                    throw new NoSuchElementException() ;
+                // "it" is always left with something to yield if hashNext is
+                // true.
+                return it.next() ;
+            }
+
+            @Override
+            public void remove() {
+                throw new UnsupportedOperationException("mapMany.remove") ;
+            }
+        } ;
+
+        return iter ;
+    }
+
+//    public static <T, R> Iterator<R> mapMany(Iterable<? extends T> stream, 
Transform<T, Iterator<R>> converter) {
+//        return mapMany(stream.iterator(), converter) ;
+//    }
+
+    public static <T, R> List<R> mapMany(List<? extends T> list, Transform<T, 
Iterator<R>> converter) {
+        return toList(mapMany(list.iterator(), converter)) ;
+    }
+
+//    /**
+//     * Apply an action to everything in stream, yielding a stream of the same
+//     * items
+//     */
+//    public static <T> Iterator<T> operate(Iterable<? extends T> stream, 
Action<T> converter) {
+//        return operate(stream.iterator(), converter) ;
+//    }
+
+    /**
+     * Apply an action to everything in stream, yielding a stream of the same
+     * items
+     */
+    public static <T> Iterator<T> operate(final Iterator<? extends T> stream, 
final Action<T> action) {
+        final Iterator<T> iter = new Iterator<T>() {
+            @Override
+            public boolean hasNext() {
+                return stream.hasNext() ;
+            }
+
+            @Override
+            public T next() {
+                T t = stream.next() ;
+                action.apply(t) ;
+                return t ;
+            }
+
+            @Override
+            public void remove() {
+                throw new UnsupportedOperationException("operate.remove") ;
+            }
+        } ;
+        return iter ;
+    }
+
+    /** Print an iterator as it gets used - this adds a printing wrapper */
+    public static <T> Iterator<T> printWrapper(final Iterator<? extends T> 
stream) {
+        return Iter.printWrapper(System.out, stream) ;
+    }
+
+    /** Print an iterator as it gets used - this adds a printing wrapper */
+    public static <T> Iterator<T> printWrapper(final PrintStream out, final 
Iterator<? extends T> stream) {
+        Action<T> action = new Action<T>() {
+            @Override
+            public void apply(T item) {
+                out.println(item) ;
+            }
+        } ;
+        return Iter.operate(stream, action) ;
+    }
+
+//    /** Join two iteratables
+//     * If there, potentially, going to be many iterators, it is better to 
+//     * create an {@link IteratorConcat} explicitly and add each iterator.
+//     */
+//    public static <T> Iterator<T> append(Iterable<? extends T> iter1, 
Iterable<? extends T> iter2) {
+//        return IteratorCons.create(iterator(iter1), iterator(iter2)) ;
+//    }
+
+    /** Join two iterator
+     * If there, potentially, going to be many iterators, it is better to 
+     * create an {@link IteratorConcat} explicitly and add each iterator.
+     */
+    public static <T> Iterator<T> append(Iterator<? extends T> iter1, 
Iterator<? extends T> iter2) {
+        return IteratorCons.create(iter1, iter2) ;
+    }
+
+//    private static <T> Iterator<T> iterator(Iterable<T> iter) {
+//        return (iter == null) ? null : iter.iterator() ;
+//    }
+
+//    public static <T> Iterator<T> distinct(Iterable<T> iter) {
+//        return distinct(iter.iterator()) ;
+//    }
+
+    public static <T> Iterator<T> distinct(Iterator<T> iter) {
+        return filter(iter, new FilterUnique<T>()) ;
+    }
+
+//    /** Remove adjacent duplicates */
+//    public static <T> Iterator<T> distinctAdjacent(Iterable<T> iter) {
+//        return distinctAdjacent(iter.iterator()) ;
+//    }
+
+    /** Remove adjacent duplicates */
+    public static <T> Iterator<T> distinctAdjacent(Iterator<T> iter) {
+        return filter(iter, new FilterDistinctAdjacent<T>()) ;
+    }
+
+//    public static <T> Iterator<T> removeNulls(Iterable<T> iter) {
+//        return filter(iter, new FilterOutNulls<T>()) ;
+//    }
+
+    public static <T> Iterator<T> removeNulls(Iterator<T> iter) {
+        return filter(iter, new FilterOutNulls<T>()) ;
+    }
+
+    /** Take the first N elements of an iterator - stop early if too few */
+    public static <T> List<T> take(Iterator<T> iter, int N) {
+        iter = new IteratorN<>(iter, N) ;
+        List<T> x = new ArrayList<>(N) ;
+        for (; iter.hasNext();)
+            x.add(iter.next()) ;
+        return x ;
+    }
+
+    /** Iterator that only returns upto N items */
+    static class IteratorN<T> implements Iterator<T> {
+        private final Iterator<T> iter ;
+        private final int         N ;
+        private int               count ;
+
+        IteratorN(Iterator<T> iter, int N) {
+            this.iter = iter ;
+            this.N = N ;
+            this.count = 0 ;
+        }
+
+        @Override
+        public boolean hasNext() {
+            if ( count >= N )
+                return false ;
+            return iter.hasNext() ;
+        }
+
+        @Override
+        public T next() {
+            if ( count >= N )
+                throw new NoSuchElementException() ;
+            T x = iter.next() ;
+            count++ ;
+            return x ;
+        }
+
+        @Override
+        public void remove() {
+            // But leave the count as-is.
+            iter.remove() ;
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public static <T> Iterator<T> convert(Iterator<? > iterator) {
+        return (Iterator<T>)iterator ;
+    }
+
+//    /**
+//     * Count the iterable - many iterable objects have a .size() operation 
which
+//     * should be used in preference to this explicit counting operation
+//     */
+//    public static <T> long count(Iterable<T> iterable) {
+//        return count(iterable.iterator()) ;
+//    }
+
+    /** Count the iterator (this is destructive on the iterator) */
+    public static <T> long count(Iterator<T> iterator) {
+        long x = 0 ;
+        while (iterator.hasNext()) {
+            iterator.next() ;
+            x++ ;
+        }
+        return x ;
+        // ActionCount<T> action = new ActionCount<T>() ;
+        // Iter.apply(iterator, action) ;
+        // return action.getCount() ;
+    }
+
+//    // --- Consume the iterator.
+//    /** Consume the iterable */
+//    public static <T> void consume(Iterable<T> iterator) {
+//        count(iterator) ;
+//    }
+
+    /** Consume the iterator */
+    public static <T> void consume(Iterator<T> iterator) {
+        count(iterator) ;
+    }
+
+    // ---- String related helpers
+
+    public static <T> String asString(Iterable<T> stream) {
+        return asString(stream, new AccString<T>()) ;
+    }
+
+    public static <T> String asString(Iterator<T> stream) {
+        return asString(stream, new AccString<T>()) ;
+    }
+
+//    public static <T> String asString(Iter<T> stream) {
+//        return asString(stream, new AccString<T>()) ;
+//    }
+//
+
+    public static <T> String asString(Iterable<T> stream, String sep) {
+        return asString(stream, new AccString<T>(sep)) ;
+    }
+
+    public static <T> String asString(Iterator<T> stream, String sep) {
+        return asString(stream, new AccString<T>(sep)) ;
+    }
+
+//    public static <T> String asString(Iter<T> stream, String sep) {
+//        return asString(stream.iterator(), new AccString<T>(sep)) ;
+//    }
+//
+    public static <T> String asString(Iterable<T> stream, AccString<T> 
formatter) {
+        return asString(stream.iterator(), formatter) ;
+    }
+
+    public static <T> String asString(Iterator<T> stream, AccString<T> 
formatter) {
+        return reduce(stream, formatter) ;
+    }
+
+//    public static <T> String asString(Iter<T> stream, AccString<T> 
formatter) {
+//        return reduce(stream.iterator(), formatter) ;
+//    }
+
+    // ----
+
+    public static <T> void close(Iterator<T> iter) {
+        if ( iter instanceof Closeable )
+            ((Closeable)iter).close() ;
+    }
+
+
+    /**
+     * Print an iterator to stdout, return a copy of the iterator. Printing
+     * occurs now. See {@link #debug} for an operation to print as the
+     * iterator is used. 
+     */
+    public static <T> Iterator<T> log(Iterator<T> stream) {
+        return log(System.out, stream) ;
+    }
+
+    /**
+     * Print an iterator to stdout, return a copy of the iterator. Printing
+     * occurs when the returned iterator is used
+     */
+    public static <T> Iterator<T> log(final PrintStream out, Iterator<T> 
stream) {
+        Iterator<T> iter = debug(out, stream) ;
+        // And force it to run.
+        return Iter.toList(iter).iterator();
+    }
+    
+    /**
+     * Print an iterator to stdout, return a copy of the iterator. Printing
+     * occurs when the iterator is used.  See {@link #log} for
+     * an operation to print now. 
+     */
+    public static <T> Iterator<T> debug(Iterator<T> stream) {
+        return debug(System.out, stream) ;
+    }
+
+    /**
+     * Print an iterator to stdout, return a copy of the iterator. Printing
+     * occurs when the returned iterator is used
+     */
+    public static <T> Iterator<T> debug(final PrintStream out, Iterator<T> 
stream) {
+        try { 
+            Transform<T, T> x = new Transform<T, T>() {
+                @Override
+                public T convert(T item) {
+                    out.println(item) ;
+                    return item ;
+                }
+            } ;
+            return map(stream, x) ;
+        } finally { out.flush() ; }
+    }
+
+    /** Print an iterator (destructive) */
+    public static <T> void print(Iterator<T> stream) {
+        print(System.out, stream) ;
+    }
+
+    /** Print an iterator (destructive) */
+    public static <T> void print(final PrintStream out, Iterator<T> stream) {
+        Action<T> x = new Action<T>() {
+            @Override
+            public void apply(T item) {
+                out.println(item) ;
+            }
+
+        } ;
+        apply(stream, x) ;
+    }
+
+//    /** Print an iterable */
+//    public static <T> void print(PrintStream out, Iterable<T> iterable) {
+//        print(out, iterable.iterator()) ;
+//    }
+//
+//    /** Print an iterable */
+//    public static <T> void print(Iterable<T> iterable) {
+//        print(iterable.iterator()) ;
+//    }
+
+    /** Send the elements of the iterator to a sink - consumes the iterator */
+    public static <T> void sendToSink(Iterator<T> iter, Sink<T> sink) {
+        for (; iter.hasNext();) {
+            T thing = iter.next() ;
+            sink.send(thing) ;
+        }
+        sink.close() ;
+    }
+
+    /** Send the elements of the iterable to a sink */
+    public static <T> void sendToSink(Iterable<T> stream, Sink<T> sink) {
+        sendToSink(stream.iterator(), sink) ;
+    }
+
+    // ----
+    // Iter class part : factories
+
+    public static <T> Iter<T> iter(Iter<T> iter) {
+        return iter ;
+    }
+
+    // May not do what you expect. iter(int[]) is iter of one object (an int[])
+    // public static <T> Iter<T> iter(T...objects)
+    // { return Iter.iter(Arrays.asList(objects)) ; }
+
+    public static <T> Iter<T> iterSingleton(T x) {
+        return Iter.iter(SingletonIterator.create(x)) ;
+    }
+
+    public static <T> Iter<T> iter(Collection<T> collection) {
+        return Iter.iter(collection.iterator()) ;
+    }
+
+    public static <T> Iter<T> iter(Iterator<T> iterator) {
+        if ( iterator instanceof Iter<? > )
+            return (Iter<T>)iterator ;
+        return new Iter<>(iterator) ;
+    }
+
+//    public static <T> Iter<T> iter(Iterable<T> iterable) {
+//        if ( iterable instanceof Iter<? > )
+//            return (Iter<T>)iterable ;
+//        return new Iter<>(iterable.iterator()) ;
+//    }
+
+    public static <T> Iter<T> singletonIter(T item) {
+        return iter(new SingletonIterator<>(item)) ;
+    }
+
+    public static <T> Iter<T> nullIter() {
+        return iter(new NullIterator<T>()) ;
+    }
+
+    /**
+     * Materialize an iterator, that is, force it to run now - useful in
+     * debugging
+     */
+    public static <T> Iterator<T> materialize(Iterator<T> iter) {
+        return Iter.toList(iter).iterator() ;
+    }
+
+    public static <T> Iter<T> concat(Iter<T> iter1, Iter<T> iter2) {
+        if ( iter1 == null )
+            return iter2 ;
+        if ( iter2 == null )
+            return iter1 ;
+        return iter1.append(iter2) ;
+    }
+
+    public static <T> Iterator<T> concat(Iterator<T> iter1, Iterator<T> iter2) 
{
+        if ( iter1 == null )
+            return iter2 ;
+        if ( iter2 == null )
+            return iter1 ;
+        return Iter.iter(iter1).append(Iter.iter(iter2)) ;
+    }
+
+    public static <T> T first(Iterator<T> iter, Filter<T> filter) {
+        for (int idx = 0; iter.hasNext(); idx++) {
+            T t = iter.next() ;
+            if ( filter.accept(t) )
+                return t ;
+            // return idx ;
+        }
+        return null ;
+    }
+
+    public static <T> T first(Collection<T> collection, Filter<T> filter) {
+        return first(collection.iterator(), filter) ;
+    }
+
+    public static <T> int firstIndex(Iterator<T> iter, Filter<T> filter) {
+        for (int idx = 0; iter.hasNext(); idx++) {
+            T t = iter.next() ;
+            if ( filter.accept(t) )
+                return idx ;
+        }
+        return -1 ;
+    }
+
+    public static <T> int firstIndex(Collection<T> collection, Filter<T> 
filter) {
+        return firstIndex(collection.iterator(), filter) ;
+    }
+
+    public static <T> T last(Iterator<T> iter, Filter<T> filter) {
+        T thing = null ;
+        for (int idx = 0; iter.hasNext(); idx++) {
+            T t = iter.next() ;
+            if ( filter.accept(t) )
+                thing = t ;
+        }
+        return thing ;
+    }
+
+    public static <T> T last(Collection<T> collection, Filter<T> filter) {
+        return last(collection.iterator(), filter) ;
+    }
+
+    public static <T> int lastIndex(Iterator<T> iter, Filter<T> filter) {
+        int location = -1 ;
+        for (int idx = 0; iter.hasNext(); idx++) {
+            T t = iter.next() ;
+            if ( filter.accept(t) )
+                location = idx ;
+        }
+        return location ;
+    }
+
+    public static <T> int lastIndex(Collection<T> collection, Filter<T> 
filter) {
+        return lastIndex(collection.iterator(), filter) ;
+    }
+
+    // ------------------------------------------------------
+    // The class.
+
+    private Iterator<T> iterator ;
+
+    private Iter(Iterator<T> iterator) {
+        this.iterator = iterator ;
+    }
+
+    public Set<T> toSet() {
+        return toSet(iterator) ;
+    }
+
+    public List<T> toList() {
+        return toList(iterator) ;
+    }
+
+    public void sendToSink(Sink<T> sink) {
+        sendToSink(iterator, sink) ;
+    }
+
+    public T first(Filter<T> filter) {
+        return first(iterator, filter) ;
+    }
+
+    public int firstIndex(Filter<T> filter) {
+        return firstIndex(iterator, filter) ;
+    }
+
+    public T last(Filter<T> filter) {
+        return last(iterator, filter) ;
+    }
+
+    public int lastIndex(Filter<T> filter) {
+        return lastIndex(iterator, filter) ;
+    }
+
+    public Iter<T> filter(Filter<T> filter) {
+        return iter(filter(iterator, filter)) ;
+    }
+
+    public boolean every(Filter<T> filter) {
+        return every(iterator, filter) ;
+    }
+
+    public boolean some(Filter<T> filter) {
+        return some(iterator, filter) ;
+    }
+
+    public Iter<T> removeNulls() {
+        return filter(new FilterOutNulls<T>()) ;
+    }
+
+    public <R> Iter<R> map(Transform<T, R> converter) {
+        return iter(map(iterator, converter)) ;
+    }
+
+    /**
+     * Apply an action to everything in the stream, yielding a stream of the
+     * same items
+     */
+    public Iter<T> operate(Action<T> action) {
+        return iter(operate(iterator, action)) ;
+    }
+
+    public <R> R reduce(Accumulate<T, R> aggregator) {
+        return reduce(iterator, aggregator) ;
+    }
+
+    public void apply(Action<T> action) {
+        apply(iterator, action) ;
+    }
+
+    /** Join on an iterator.
+     * If there are going to be many iterators, uit is better to create an 
{@link IteratorConcat}
+     * and <tt>.add</tt> each iterator.  The overheads are much lower. 
+     */
+    public Iter<T> append(Iterator<T> iter) {
+        return iter(IteratorCons.create(iterator, iter)) ;
+    }
+
+    /** Return an Iter that yields at most the first N items */
+    public Iter<T> take(int N) {
+        return Iter.iter(take(iterator, N)) ;
+    }
+
+    /** Count the iterator (this is destructive on the iterator) */
+    public long count() {
+        ActionCount<T> action = new ActionCount<>() ;
+        apply(action) ;
+        return action.getCount() ;
+    }
+
+    public String asString() {
+        return asString(iterator) ;
+    }
+
+    public String asString(String sep) {
+        return asString(iterator, sep) ;
+    }
+
+    public Iter<T> distinct() {
+        return iter((distinct(iterator()))) ;
+    }
+
+    public Iter<T> distinctAdjacent() {
+        return iter(distinctAdjacent(iterator())) ;
+    }
+
+    // ---- Iterable
+//    @Override
+    public Iterator<T> iterator() {
+        return iterator ;
+    }
+
+    // ---- Iterator
+
+    @Override
+    public boolean hasNext() {
+        return iterator.hasNext() ;
+    }
+
+    @Override
+    public T next() {
+        return iterator.next() ;
+    }
+
+    @Override
+    public void remove() {
+        iterator.remove() ;
+    }
+}

Reply via email to