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

[1/9] jena git commit: Import of lib, io, logging and iterator packages

Repository: jena
Updated Branches:
  refs/heads/master 692118f9d -> 1320f8dbe


http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/lib/TestCache.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/lib/TestCache.java b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestCache.java
new file mode 100644
index 0000000..05513fe
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestCache.java
@@ -0,0 +1,149 @@
+/*
+ * 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.lib;
+
+import java.util.Arrays ;
+import java.util.Collection ;
+import java.util.List ;
+
+import org.apache.jena.atlas.iterator.Iter ;
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.apache.jena.atlas.lib.Cache ;
+import org.apache.jena.atlas.lib.CacheFactory ;
+import org.junit.Before ;
+import org.junit.Test ;
+import org.junit.runner.RunWith ;
+import org.junit.runners.Parameterized ;
+import org.junit.runners.Parameterized.Parameters ;
+
+@RunWith(Parameterized.class)
+public class TestCache extends BaseTest
+{
+    // Tests do not apply to cache1.
+    private static interface CacheMaker<K,V> { Cache<K,V> make(int size) ; String name() ; } 
+
+    private static CacheMaker<Integer, Integer> simple = 
+        new CacheMaker<Integer, Integer>()
+        { 
+        @Override
+        public Cache<Integer, Integer> make(int size) { return CacheFactory.createSimpleCache(size) ; }
+        @Override
+        public String name() { return "Simple" ; } 
+        } 
+    ;
+
+    private static CacheMaker<Integer, Integer> standard = 
+        new CacheMaker<Integer, Integer>()
+        {
+        @Override
+        public Cache<Integer, Integer> make(int size) { return CacheFactory.createCache(size) ; }
+        @Override
+        public String name() { return "Standard" ; } 
+        }
+    ;
+
+    @Parameters
+    public static Collection<Object[]> cacheMakers()
+    {
+        return Arrays.asList(new Object[][] {
+            { simple , 10 }
+            , { simple , 2 } 
+            , { simple , 1 }
+            , { standard , 10 }
+            , { standard , 2 }
+            , { standard , 1 }
+        } ) ; 
+    }
+
+    Cache<Integer, Integer> cache ;
+    CacheMaker<Integer,Integer> cacheMaker ;
+    int size ;
+    
+    
+    public TestCache(CacheMaker<Integer,Integer> cacheMaker, int size)
+    {
+        this.cacheMaker = cacheMaker ;
+        this.size = size ;
+        
+    }
+    
+    @Before public void before() { cache = cacheMaker.make(size) ; }
+    
+    @Test public void cache_00()
+    {
+        assertEquals(0, cache.size()) ;
+        assertTrue(cache.isEmpty()) ;
+    }
+    
+    @Test public void cache_01()
+    {
+        Integer x = cache.getIfPresent(7) ;
+        cache.put(7, 7) ;
+        assertEquals(1, cache.size()) ;
+        assertNull(x) ;
+        assertTrue(cache.containsKey(7)) ;
+        assertEquals(Integer.valueOf(7), cache.getIfPresent(7)) ;
+    }
+    
+    @Test public void cache_02()
+    {
+        cache.put(7, 7) ;
+        cache.put(8, 8) ;
+        // Not true for Cache1.
+        if ( size > 2 )
+            assertEquals(2, cache.size()) ;
+        if ( size > 2 )
+            assertTrue(cache.containsKey(7)) ;
+        
+        if ( size > 2 )
+            assertEquals(Integer.valueOf(7), cache.getIfPresent(7)) ;
+        
+        assertTrue(cache.containsKey(8)) ;
+        assertEquals(Integer.valueOf(8), cache.getIfPresent(8)) ;
+    }
+    
+    @Test public void cache_03()
+    {
+        cache.put(7, 7) ;
+        Integer x1 = cache.getIfPresent(7) ;
+        cache.put(7, 18) ;
+        assertEquals(1, cache.size()) ;
+        assertEquals(7, x1.intValue()) ;
+        assertTrue(cache.containsKey(7)) ;
+        assertEquals(Integer.valueOf(18), cache.getIfPresent(7)) ;
+    }
+    
+    @Test public void cache_04()
+    {
+        cache.clear() ;
+        cache.put(7, 77) ;
+        List<Integer> x = Iter.toList(cache.keys()) ;
+        assertEquals(1, x.size()) ;
+        assertEquals(Integer.valueOf(7), x.get(0)) ;
+    }
+    
+    @Test public void cache_05()
+    {
+        cache.clear() ;
+        cache.put(7, 77) ;
+        cache.clear() ;
+        assertEquals(0, cache.size()) ; 
+        assertTrue(cache.isEmpty()) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/lib/TestCache2.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/lib/TestCache2.java b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestCache2.java
new file mode 100644
index 0000000..2e640f3
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestCache2.java
@@ -0,0 +1,88 @@
+/*
+ * 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.lib;
+
+import java.util.concurrent.Callable ;
+
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.apache.jena.atlas.lib.Cache ;
+import org.apache.jena.atlas.lib.CacheFactory ;
+import org.apache.jena.atlas.lib.cache.Cache1 ;
+import org.junit.Test ;
+
+// Non-parameterized tests
+public class TestCache2 extends BaseTest
+{
+    // Cache1
+    @Test public void cache_10()
+    {
+        Cache<Integer, String> cache = new Cache1<>() ;
+        String str = cache.getIfPresent(1) ;
+        assertNull(str) ;
+
+        cache.put(1, "1") ;
+        str = cache.getIfPresent(1) ;
+        assertEquals("1", str) ;
+
+        cache.put(2, "2") ;
+        str = cache.getIfPresent(1) ;
+        assertNull(str) ;
+        
+        cache.put(1, "1") ;
+        str = cache.getIfPresent(2) ;
+        assertNull(str) ;
+        str = cache.getIfPresent(1) ;
+        assertEquals("1", str) ;
+    }
+    
+    
+    
+    static Callable<String> getter(final Integer key) {
+        return new Callable<String>() {
+            @Override
+            public String call() {
+                return key.toString() ; }
+        } ;
+    }
+
+    // Cache + getters
+    @Test public void cacheGetter_1()
+    {
+        Cache<Integer, String> cache = CacheFactory.createCache(2) ;
+        String str = cache.getOrFill(1,  getter(1)) ;
+        assertEquals("1", str) ;
+    }
+    
+    // Cache + getters
+    @Test public void cacheGetter_2()
+    {
+        Cache<Integer, String> cache = CacheFactory.createCache(2) ;
+        String str1 = cache.getOrFill(1, getter(1)) ;
+        String str2 = cache.getOrFill(2, getter(2)) ;
+        String str3 = cache.getOrFill(3, getter(3)) ;
+        assertEquals("1", str1) ;
+        assertEquals("2", str2) ;
+        assertEquals("3", str3) ;
+        cache.put(1, "10") ;
+        str1 = cache.getIfPresent(1) ;
+        assertEquals("10", str1) ;
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/lib/TestColumnMap.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/lib/TestColumnMap.java b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestColumnMap.java
new file mode 100644
index 0000000..fb44166
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestColumnMap.java
@@ -0,0 +1,107 @@
+/*
+ * 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.lib;
+
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.apache.jena.atlas.lib.ColumnMap ;
+import org.apache.jena.atlas.lib.Tuple ;
+import static org.apache.jena.atlas.lib.Tuple.* ;
+import org.junit.Test ;
+
+public class TestColumnMap extends BaseTest
+{
+    @Test public void remap1() 
+    {
+        ColumnMap x = new ColumnMap("SPO->POS", 2,0,1) ;   // S->2 etc
+        
+        Integer[] array = {0,1,2 } ;
+        assertEquals(Integer.valueOf(2), x.mapSlot(0, array) ) ;   
+        assertEquals(Integer.valueOf(0), x.mapSlot(1, array) ) ; 
+        assertEquals(Integer.valueOf(1), x.mapSlot(2, array) ) ; 
+    }
+    
+    @Test public void remap2() 
+    {
+        ColumnMap x = new ColumnMap("SPO->POS", 2,0,1) ;
+        Integer[] array = { 0,1,2 } ;
+        assertEquals(Integer.valueOf(1), x.fetchSlot(0, array)) ;   // The index 1 comes from position 0.
+        assertEquals(Integer.valueOf(2), x.fetchSlot(1, array)) ;
+        assertEquals(Integer.valueOf(0), x.fetchSlot(2, array)) ;
+    }
+
+    @Test public void remap3() 
+    {
+        ColumnMap x = new ColumnMap("POS", 2,0,1) ;
+        Tuple<String> tuple = createTuple("S", "P", "O") ;
+        Tuple<String> mapped = x.map(tuple) ;
+        Tuple<String> expected = createTuple("P", "O", "S") ;
+        assertEquals(expected, mapped) ;
+    }
+    
+    @Test public void remap4() 
+    {
+        ColumnMap x = new ColumnMap("POS", 2,0,1) ;
+        Tuple<String> tuple = createTuple("S", "P", "O") ;
+        Tuple<String> tuple2 = x.map(tuple) ;
+        tuple2 = x.unmap(tuple2) ;
+        assertEquals(tuple, tuple2) ;
+    }
+    
+    @Test public void compile1()
+    {
+        int[] x = ColumnMap.compileMapping("SPO", "POS") ;
+        // SPO -> POS so col 0 goes to col 2, col 1 goes to col 0 and col 2 goes to col 1
+        int[] expected = { 2,0,1 } ;
+        assertArrayEquals(expected, x) ;
+    }
+
+    @Test public void compile2()
+    {
+        int[] x = ColumnMap.compileMapping("SPOG", "GOPS") ;
+        int[] expected = { 3, 2, 1, 0 } ;
+        assertArrayEquals(expected, x) ;
+    }
+
+    @Test public void map1()
+    {
+        ColumnMap cmap = new ColumnMap("GSPO", "OSPG") ;
+        Tuple<String> tuple = createTuple("G", "S", "P", "O") ;
+        Tuple<String> mapped = cmap.map(tuple) ;
+        Tuple<String> expected = createTuple("O", "S", "P", "G") ;
+        assertEquals(expected, mapped) ;
+        Tuple<String> unmapped = cmap.unmap(mapped) ;
+        assertEquals(createTuple("G", "S", "P", "O"), unmapped) ;
+    }
+
+    @Test public void map2()
+    {
+        String[] x = { "G", "S", "P", "O" } ;
+        String[] y = { "O", "S", "P", "G" } ;
+        
+        ColumnMap cmap = new ColumnMap("Test", x, y) ;
+        Tuple<String> tuple = Tuple.create(x) ;
+        Tuple<String> mapped = cmap.map(tuple) ;
+        
+        Tuple<String> expected = Tuple.create(y) ;
+        assertEquals(expected, mapped) ;
+        Tuple<String> unmapped = cmap.unmap(mapped) ;
+        assertEquals(Tuple.create(x), unmapped) ;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/lib/TestDateTimeUtils.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/lib/TestDateTimeUtils.java b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestDateTimeUtils.java
new file mode 100644
index 0000000..8aae32b
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestDateTimeUtils.java
@@ -0,0 +1,82 @@
+/*
+ * 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.lib;
+
+import static org.junit.Assert.*;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+import org.apache.jena.atlas.lib.DateTimeUtils ;
+import org.junit.Test;
+
+public class TestDateTimeUtils {
+
+	@Test
+	public void testCalendarToXSDDateTimeString_1() throws Exception {
+		Calendar cal = createCalendar(1984, Calendar.MARCH, 22, 14, 32, 1, 0, "Z") ;
+		assertEquals("1984-03-22T14:32:01+00:00", DateTimeUtils.calendarToXSDDateTimeString(cal));
+		cal.setTimeZone(TimeZone.getTimeZone("MST"));
+		assertEquals("1984-03-22T07:32:01-07:00", DateTimeUtils.calendarToXSDDateTimeString(cal));
+	}
+
+    @Test
+    public void testCalendarToXSDDateTimeString_2() throws Exception {
+        Calendar cal = createCalendar(1984, Calendar.MARCH, 22, 14, 32, 1, 50, "Z") ;
+        assertEquals("1984-03-22T14:32:01.050+00:00", DateTimeUtils.calendarToXSDDateTimeString(cal));
+        cal.setTimeZone(TimeZone.getTimeZone("MST"));
+        assertEquals("1984-03-22T07:32:01.050-07:00", DateTimeUtils.calendarToXSDDateTimeString(cal));
+    }
+
+
+    @Test
+	public void testCalendarToXSDDateString() throws Exception {
+		Calendar cal = createCalendar(1984, Calendar.MARCH, 22, 23, 59, 1, 0, "Z");
+		cal.setTimeZone(TimeZone.getTimeZone("Z")) ;
+		assertEquals("1984-03-22+00:00", DateTimeUtils.calendarToXSDDateString(cal));
+		cal.setTimeZone(TimeZone.getTimeZone("MST"));
+		assertEquals("1984-03-22-07:00", DateTimeUtils.calendarToXSDDateString(cal));
+	}
+    
+    @Test
+	public void testCalendarToXSDTimeString_1() throws Exception {
+		Calendar cal = createCalendar(1984, Calendar.MARCH, 22, 14, 32, 1, 0, "GMT+01:00");
+		assertEquals("14:32:01+01:00", DateTimeUtils.calendarToXSDTimeString(cal));
+		// Different timezone - moves the cal point-in-time.
+		cal.setTimeZone(TimeZone.getTimeZone("MST"));
+		assertEquals("06:32:01-07:00", DateTimeUtils.calendarToXSDTimeString(cal));
+	}
+	
+    @Test
+    public void testCalendarToXSDTimeString_2() throws Exception {
+        Calendar cal = createCalendar(1984, Calendar.MARCH, 22, 14, 32, 1, 500, "GMT+01:00");
+        assertEquals("14:32:01.500+01:00", DateTimeUtils.calendarToXSDTimeString(cal));
+        // Different timezone - moves the cal point-in-time.
+        cal.setTimeZone(TimeZone.getTimeZone("MST"));
+        assertEquals("06:32:01.500-07:00", DateTimeUtils.calendarToXSDTimeString(cal));
+    }
+    
+	private static Calendar createCalendar(int year, int month, int dayOfMonth, int hourOfDay,
+	                                       int minute, int second, int milli, String tz) {
+	    GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone(tz)) ;
+	    cal.set(year, month, dayOfMonth, hourOfDay, minute, second) ;
+	    cal.set(Calendar.MILLISECOND, milli) ;
+	    return cal ;
+	}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/lib/TestFileOps.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/lib/TestFileOps.java b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestFileOps.java
new file mode 100644
index 0000000..92538f7
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestFileOps.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.atlas.lib;
+
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.apache.jena.atlas.lib.FileOps ;
+import org.apache.jena.atlas.lib.Tuple ;
+import org.junit.Test ;
+
+public class TestFileOps extends BaseTest
+{
+    /*
+     * t("") ;
+        t("/a/b/c") ;
+        t("/aa/bb/cc.ext") ;
+        t("cc.ext") ;
+        t("/cc.ext") ;
+        t("/") ;
+        t("xyz") ;
+        t("xyz/") ;
+     */
+
+    static void test(String fn, String path, String basename, String ext)
+    {
+        Tuple<String> t = FileOps.splitDirBaseExt(fn) ;
+        assertEquals(path, t.get(0)) ;
+        assertEquals(basename, t.get(1)) ;
+        assertEquals(ext, t.get(2)) ;
+        
+        if ( basename != null ) 
+            assertEquals(basename, FileOps.basename(fn)) ;
+        if ( ext != null ) 
+            assertEquals(ext, FileOps.extension(fn)) ;
+    }
+    
+    @Test public void split01() 
+    { test("/aa/bb/cc.ext", "/aa/bb", "cc", "ext") ; }
+
+    @Test public void split02() 
+    { test("/a/b/c", "/a/b", "c", null) ; }
+    
+    @Test public void split03() 
+    { test("cc.ext", null, "cc", "ext") ; }
+    
+    @Test public void split04() 
+    { test("/cc.ext", "", "cc", "ext") ; }
+    
+    @Test public void split05() 
+    { test("/", "", "", null) ; }
+    
+    @Test public void split06() 
+    { test("", null, "", null) ; }
+    
+    @Test public void split07() 
+    { test("xyz", null, "xyz", null) ; }
+    
+    @Test public void split08() 
+    { test("/xyz", "", "xyz", null) ; }
+    
+    @Test public void split09() 
+    { test("xyz/", "xyz", "", null) ; }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/lib/TestFilenameProcessing.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/lib/TestFilenameProcessing.java b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestFilenameProcessing.java
new file mode 100644
index 0000000..7782c55
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestFilenameProcessing.java
@@ -0,0 +1,136 @@
+/*
+ * 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.lib;
+
+import java.io.File ;
+
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.junit.Test ;
+
+public class TestFilenameProcessing extends BaseTest
+{
+    // See also TestFunction2 for tests for encode_for_uri
+    @Test public void encode_1() { encodeComponent("abc", "abc") ; }
+    @Test public void encode_2() { encodeComponent("", "") ; }
+    @Test public void encode_3() { encodeComponent(":/", "%3A%2F") ; }
+
+    // ---- Main tests.
+    // Portablility
+    
+    private static String cwd = new File(".").getAbsolutePath() ;
+    // Without trailing slash.
+    static { cwd = cwd.substring(0, cwd.length()-2) ; }
+    
+    @Test public void fileIRI_1()
+    {
+        String uri = testFileIRI("D.ttl") ; 
+        assertTrue(uri.endsWith("D.ttl")) ;
+    }
+    
+    @Test public void fileIRI_2()
+    {
+        String uri = testFileIRI("file:/D.ttl") ; 
+        assertTrue(uri.endsWith("D.ttl")) ;
+    }
+    
+    @Test public void fileIRI_3()
+    {
+        String uri = testFileIRI("file://D.ttl") ; 
+        assertTrue(uri.endsWith("D.ttl")) ;
+    }
+
+    @Test public void fileIRI_4()
+    {
+        String iri = testFileIRI("file:///D.ttl") ;
+        // Even on windows, this is used as-is so no drive letter. 
+        assertEquals("file:///D.ttl", iri) ;
+    }
+    
+    private static String testFileIRI(String fn)
+    {
+        String uri1 = IRILib.filenameToIRI(fn) ;
+        assertTrue(uri1.startsWith("file:///")) ;
+        String uri2 = IRILib.filenameToIRI(uri1) ;
+        assertEquals(uri1, uri2) ;
+        return uri1 ;
+    }
+
+    
+    @Test public void fileURL_1() { assertNotEquals(cwd, "") ; assertNotNull(cwd) ; filenameToIRI("abc", "file://"+cwd+"/abc" ) ; }
+    
+    @Test public void fileURL_2() { filenameToIRI("/abc", "file:///abc" ) ; }
+    
+    static boolean isWindows = File.pathSeparator.equals(";") ;
+
+    
+    @Test public void fileURL_3()
+    { 
+        if ( isWindows )
+            filenameToIRI("c:/Program File/App File", "file:///c:/Program%20File/App%20File") ; 
+        else
+            filenameToIRI("/Program File/App File", "file:///Program%20File/App%20File") ;
+    }
+
+    @Test public void fileURL_4()
+    { 
+        if ( isWindows )
+            filenameToIRI("c:/Program File/App Dir/", "file:///c:/Program%20File/App%20Dir/") ; 
+        else
+            filenameToIRI("/Program File/App Dir/", "file:///Program%20File/App%20Dir/") ;
+    }
+    
+    @Test public void fileURL_5()
+    {
+        if ( isWindows )
+            filenameToIRI("c:\\Windows\\Path", "file:///c:/Windows/Path") ;
+        else
+            filenameToIRI("c:\\Windows\\Path", "file://"+cwd+"/c:%5CWindows%5CPath") ;
+        
+    }
+    
+    @Test public void fileURL_6() { filenameToIRI("~user", "file://"+cwd+"/~user") ; }
+    @Test public void fileURL_7() { filenameToIRI(".", "file://"+cwd) ; }
+    
+    @Test public void fileURL_10() { filenameToIRI("file:abc", "file://"+cwd+"/abc" ) ; }
+    @Test public void fileURL_11() { filenameToIRI("file:/abc", "file:///abc" ) ; }
+    @Test public void fileURL_12() { filenameToIRI("file:", "file://"+cwd ) ; }
+    
+    @Test public void fileURL_13() { filenameToIRI("file:.", "file://"+cwd+"" ) ; }
+    @Test public void fileURL_14() {
+        String x = cwd ;
+        if ( isWindows )
+            x = x.replace('\\', '/') ; 
+        x = cwd.replaceAll("/[^/]*$", "") ;
+        filenameToIRI("file:..", "file://"+x ) ;
+    }
+
+    
+    private static void encodeComponent(String string, String result)
+    {
+        String r = IRILib.encodeUriComponent(string) ;
+        assertEquals(result, r) ;
+    }
+    
+    private static void filenameToIRI(String string, String result)
+    {
+        String r = IRILib.filenameToIRI(string) ;
+        assertEquals(result, r) ;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/lib/TestHex.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/lib/TestHex.java b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestHex.java
new file mode 100644
index 0000000..e092e60
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestHex.java
@@ -0,0 +1,81 @@
+/*
+ * 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.lib;
+
+
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.apache.jena.atlas.lib.Hex ;
+import org.junit.Test ;
+
+public class TestHex extends BaseTest
+{
+    @Test public void hex_01()
+    {
+        byte[] b = new byte[16] ;
+        test(0L, 0, b, 16) ;
+    }
+    
+    @Test public void hex_02()
+    {
+        byte[] b = new byte[16] ;
+        test(1L, 0, b, 16) ;
+    }
+
+    @Test public void hex_03()
+    {
+        byte[] b = new byte[16] ;
+        test(Long.MAX_VALUE, 0, b, 16) ;
+    }
+    
+    @Test public void hex_04()
+    {
+        byte[] b = new byte[16] ;
+        test(Long.MIN_VALUE, 0, b, 16) ;
+    }
+    
+    @Test public void hex_05()
+    {
+        byte[] b = new byte[16] ;
+        // -1L
+        test(0xFFFFFFFFFFFFFFFFL, 0, b, 16) ;
+    }
+
+    @Test public void hex_06()
+    {
+        byte[] b = new byte[16] ;
+        test(-1L, 0, b, 16) ;
+    }
+
+    private static void test(long value, int idx, byte[] b, int width)
+    {
+        int x = Hex.formatUnsignedLongHex(b, idx, value, width) ;
+        assertEquals(width, x) ;
+        for ( int i = 0 ; i < width ; i++ )
+        {
+            int v = b[i] ;
+            if ( v >= '0' && v <= '9' ) continue ;
+            if ( v >= 'a' && v <= 'f' ) continue ;
+            if ( v >= 'A' && v <= 'F' ) continue ;
+            fail(String.format("Not a hex digit: %02X",b[i])) ;
+        }
+        
+        long v = Hex.getLong(b, idx) ;
+        assertEquals(value, v) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/lib/TestListUtils.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/lib/TestListUtils.java b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestListUtils.java
new file mode 100644
index 0000000..ccaa56a
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestListUtils.java
@@ -0,0 +1,79 @@
+/*
+ * 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.lib;
+
+import static org.apache.jena.atlas.lib.ListUtils.unique ;
+
+import java.util.ArrayList ;
+import java.util.Arrays ;
+import java.util.List ;
+
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.junit.Test ;
+
+public class TestListUtils extends BaseTest
+{
+    @Test public void list01() 
+    {
+        List<Integer> x = Arrays.asList(1,2,3) ;
+        x = unique(x) ;
+        assertEquals(3, x.size()) ;
+        test(x, 1,2,3) ;
+    }
+
+    @Test public void list02() 
+    {
+        List<Integer> x = Arrays.asList(1,2,3,1,3,2) ;
+        x = unique(x) ;
+        assertEquals(3, x.size()) ;
+        test(x, 1,2,3) ;
+    }
+
+    @Test public void list03() 
+    {
+        List<Integer> x = new ArrayList<>() ;
+        x = unique(x) ;
+        assertEquals(0, x.size()) ;
+        test(x) ;
+    }
+
+    @Test public void list04() 
+    {
+        List<Integer> x = Arrays.asList(99) ;
+        x = unique(x) ;
+        assertEquals(1, x.size()) ;
+        test(x, 99) ;
+    }
+
+    @Test public void list05() 
+    {
+        List<Integer> x = Arrays.asList(1,1,2,3,1,1,3) ;
+        x = unique(x) ;
+        assertEquals(3, x.size()) ;
+        test(x, 1,2,3) ;
+    }
+
+    private void test(List<Integer> x, int... args)
+    {
+        assertEquals(args.length, x.size()) ;
+        
+        for ( int i = 0; i < args.length ; i++ )
+            assertEquals(args[i], x.get(i).intValue()) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/lib/TestMultiSet.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/lib/TestMultiSet.java b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestMultiSet.java
new file mode 100644
index 0000000..477386b
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestMultiSet.java
@@ -0,0 +1,143 @@
+/*
+ * 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.lib;
+
+import java.util.Arrays ;
+import java.util.Collections ;
+import java.util.List ;
+
+import org.apache.jena.atlas.iterator.Iter ;
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.apache.jena.atlas.lib.MultiSet ;
+import org.junit.Test ;
+
+
+public class TestMultiSet extends BaseTest
+{
+    @Test public void multiSet_01()
+    {
+        MultiSet<String> x = new MultiSet<>() ;
+        assertTrue(x.isEmpty()) ;
+        assertEquals(0, x.count("A")) ;
+    }
+
+    @Test public void multiSet_02()
+    {
+        MultiSet<String> x = new MultiSet<>() ;
+        x.add("A") ;
+        assertFalse(x.isEmpty()) ;
+        assertEquals(1, x.count("A") ) ;
+        x.add("A") ;
+        assertEquals(2, x.count("A") ) ;
+    }
+
+    @Test public void multiSet_03()
+    {
+        MultiSet<String> x = new MultiSet<>() ;
+        x.add("A") ;
+        x.add("A") ;
+        x.remove("A") ;
+        assertEquals(1, x.count("A") ) ;
+        assertTrue(x.contains("A")) ;
+        x.remove("A") ;
+        assertEquals(0, x.count("A") ) ;
+        assertFalse(x.contains("A")) ;
+    }
+
+    @Test public void multiSet_04()
+    {
+        String[] data = { } ;
+        iterTest(data) ;
+    }
+
+
+    @Test public void multiSet_05()
+    {
+        String[] data = { "A" } ;
+        iterTest(data) ;
+    }
+
+    @Test public void multiSet_06()
+    {
+        String[] data = { "A", "B", "C" } ;
+        iterTest(data) ;
+    }
+
+
+    @Test public void multiSet_07()
+    {
+        String[] data = { "A", "B", "C", "A" } ;
+        iterTest(data) ;
+    }
+
+    @Test public void multiSet_08()
+    {
+        String[] data = {  } ;
+        MultiSet<String> x = add(data) ;
+        assertEquals(0, x.size()) ;
+    }
+    
+    @Test public void multiSet_09()
+    {
+        String[] data = { "A", "A" } ;
+        MultiSet<String> x = add(data) ;
+        assertEquals(2, x.size()) ;
+    }
+
+    @Test public void multiSet_10()
+    {
+        String[] data = { "A", "A" } ;
+        MultiSet<String> x = add(data) ;
+        x.remove("A") ;
+        assertEquals(1, x.size()) ;
+        x.remove("A") ;
+        assertEquals(0, x.size()) ;
+        x.remove("A") ;
+        assertEquals(0, x.size()) ;
+    }
+    
+    @Test public void multiSet_11()
+    {
+        String[] data = { "A", "A" } ;
+        MultiSet<String> x = add(data) ;
+        long c = Iter.count(x.elements()) ;
+        assertEquals(1, c) ;
+    }
+    
+    private static MultiSet<String> add(String[] data)
+    {
+        MultiSet<String> x = new MultiSet<>() ;
+        for ( String str : data )
+            x.add(str) ;
+        return x ;
+    }
+    
+    private static void iterTest(String[] data)
+    {
+        List<String> expected = Arrays.asList(data) ;
+        MultiSet<String> x = new MultiSet<>() ;
+        for ( String str : data )
+            x.add(str) ;
+        List<String> actual = Iter.toList(x.iterator()) ;
+        Collections.sort(expected) ;
+        Collections.sort(actual) ;
+        assertEquals(expected, actual) ;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/lib/TestNumberUtils.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/lib/TestNumberUtils.java b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestNumberUtils.java
new file mode 100644
index 0000000..62c93da
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestNumberUtils.java
@@ -0,0 +1,106 @@
+/*
+ * 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.lib;
+
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.apache.jena.atlas.lib.NumberUtils ;
+import org.junit.Test ;
+
+public class TestNumberUtils extends BaseTest
+{
+    @Test public void int_format_01() { testInt(1, 1, "1") ; } 
+
+    @Test public void int_format_02() { testInt(1, 2, "01") ; } 
+    
+    @Test public void int_format_03() { testInt(0, 1, "0") ; }
+    
+    @Test public void int_format_04() { testInt(0, 2, "00") ; }
+
+    @Test public void int_format_05() { testInt(-1, 2, "-1") ; }
+   
+    @Test public void int_format_06() { testInt(-1, 3, "-01") ; }
+
+
+    @Test public void int_format_11() { testSigned(1, 2, "+1") ; } 
+    
+    @Test public void int_format_12() { testSigned(1, 3, "+01") ; } 
+
+    @Test public void int_format_13() { testSigned(0, 2, "+0") ; }
+    
+    @Test public void int_format_14() { testSigned(0, 3, "+00") ; }
+
+    @Test public void int_format_15() { testSigned(-1, 2, "-1") ; }
+   
+    @Test public void int_format_16() { testSigned(-1, 3, "-01") ; }
+    
+    @Test public void int_format_21() { testInt(1, "1") ; } 
+
+    @Test public void int_format_22() { testInt(0, "0") ; } 
+    
+    @Test public void int_format_23() { testInt(-1, "-1") ; }
+    
+    @Test public void int_format_24() { testInt(10, "10") ; }
+
+    @Test public void int_format_25() { testInt(100, "100") ; }
+
+    @Test public void int_format_26() { testInt(-10, "-10") ; }
+
+    @Test public void int_format_27() { testInt(-100, "-100") ; }
+
+
+    @Test public void int_format_31() { testUnsigned(1, 2, "01") ; } 
+    
+    @Test public void int_format_32() { testUnsigned(1, 1, "1") ; } 
+
+    @Test public void int_format_33() { testUnsigned(0, 1, "0") ; }
+
+    
+    private static void testInt(int value, String expected)
+    {
+        StringBuilder sb = new StringBuilder() ;
+        NumberUtils.formatInt(sb, value) ;
+        String result = sb.toString();
+        assertEquals(expected, result) ;
+    }
+
+    private static void testInt(int value, int width, String expected)
+    {
+        StringBuilder sb = new StringBuilder() ;
+        NumberUtils.formatInt(sb, value, width) ;
+        String result = sb.toString();
+        assertEquals(expected, result) ;
+    }
+
+    private static void testSigned(int value, int width, String expected)
+    {
+        StringBuilder sb = new StringBuilder() ;
+        NumberUtils.formatSignedInt(sb, value, width) ;
+        String result = sb.toString();
+        assertEquals(expected, result) ;
+    }
+    
+    private static void testUnsigned(int value, int width, String expected)
+    {
+        StringBuilder sb = new StringBuilder() ;
+        NumberUtils.formatUnsignedInt(sb, value, width) ;
+        String result = sb.toString();
+        assertEquals(expected, result) ;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/lib/TestRefLong.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/lib/TestRefLong.java b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestRefLong.java
new file mode 100644
index 0000000..88e683a
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestRefLong.java
@@ -0,0 +1,70 @@
+/*
+ * 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.lib;
+
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.apache.jena.atlas.lib.RefLong ;
+import org.junit.Test ;
+
+
+public class TestRefLong extends BaseTest
+{
+    @Test public void ref_01()
+    {
+        RefLong ref1 = new RefLong() ;
+        assertEquals(0, ref1.value()) ;
+        RefLong ref2 = new RefLong() ;
+        assertNotSame(ref1, ref2) ;
+    }
+    
+    @Test public void ref_02()
+    {
+        RefLong ref = new RefLong() ;
+        assertEquals(0, ref.value()) ;
+        ref.inc() ;
+        assertEquals(1, ref.value()) ;
+        ref.dec() ;
+        assertEquals(0, ref.value()) ;
+    }
+    
+    @Test public void ref_03()
+    {
+        RefLong ref = new RefLong(99) ;
+        assertEquals(99, ref.value()) ;
+        long x = ref.incAndGet() ;
+        assertEquals(100, x) ;
+        assertEquals(100, ref.value()) ;
+        x = ref.getAndInc() ;
+        assertEquals(100, x) ;
+        assertEquals(101, ref.value()) ;
+    }
+    
+    @Test public void ref_04()
+    {
+        RefLong ref = new RefLong(99) ;
+        assertEquals(99, ref.value()) ;
+        long x = ref.decAndGet() ;
+        assertEquals(98, x) ;
+        assertEquals(98, ref.value()) ;
+        x = ref.getAndDec() ;
+        assertEquals(98, x) ;
+        assertEquals(97, ref.value()) ;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/lib/TestReverseComparator.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/lib/TestReverseComparator.java b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestReverseComparator.java
new file mode 100644
index 0000000..80b9c0d
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestReverseComparator.java
@@ -0,0 +1,91 @@
+/*
+ * 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.lib;
+
+
+import java.util.ArrayList ;
+import java.util.Arrays ;
+import java.util.Collections ;
+import java.util.Comparator ;
+import java.util.List ;
+
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.apache.jena.atlas.lib.ReverseComparator ;
+import org.junit.Test ;
+
+public class TestReverseComparator extends BaseTest
+{
+    static Comparator<String> normal = new Comparator<String>()
+    {
+        @Override
+        public int compare(String o1, String o2)
+        {
+            return o1.compareTo(o2);
+        }
+    };
+    
+    static Comparator<String> reverse = new ReverseComparator<>(normal);
+    
+    
+    static Comparator<String> maxMin = new Comparator<String>()
+    {
+        @Override
+        public int compare(String o1, String o2)
+        {
+            int value = o1.compareTo(o2);
+            if (value > 0)
+                return Integer.MAX_VALUE;
+            else if (value < 0)
+                return Integer.MIN_VALUE;
+            else
+                return 0;
+        }
+    };
+    
+    static Comparator<String> reverseMaxMin = new ReverseComparator<>(maxMin);
+    
+    static List<String> items = Arrays.asList("a", "b", "c", "d");
+    static List<String> itemsReverse = Arrays.asList("d", "c", "b", "a");
+    
+    @Test public void reverse_01()
+    {
+        List<String> modified = new ArrayList<>(items);
+        Collections.sort(modified, reverse);
+        
+        test(itemsReverse, modified);
+    }
+    
+    @Test public void reverse_02()
+    {
+        List<String> modified = new ArrayList<>(items);
+        Collections.sort(modified, reverseMaxMin);
+        
+        test(itemsReverse, modified);
+    }
+    
+    private void test(List<?> expected, List<?> actual)
+    {
+        assertEquals(expected.size(), actual.size());
+        
+        for (int i=0; i<expected.size(); i++)
+        {
+            assertEquals(expected.get(i), actual.get(i)) ;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/lib/TestSetUtils.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/lib/TestSetUtils.java b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestSetUtils.java
new file mode 100644
index 0000000..d147262
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestSetUtils.java
@@ -0,0 +1,146 @@
+/*
+ * 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.lib;
+
+import static org.apache.jena.atlas.lib.ListUtils.asList ;
+
+import java.util.HashSet ;
+import java.util.List ;
+import java.util.Set ;
+
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.apache.jena.atlas.lib.SetUtils ;
+import org.junit.Test ;
+
+public class TestSetUtils extends BaseTest
+{
+    @Test public void set01() 
+    {
+        Set<Integer> x = set(1,2,3) ;
+        test(x,1,2,3) ;
+    }
+
+    @Test public void set02() 
+    {
+        Set<Integer> x1 = set(1,2,3) ;
+        Set<Integer> x2 = set(1,2,3) ;
+        Set<Integer> x3 = SetUtils.intersection(x1, x2) ;
+        test(x3, 1,2,3) ;
+        x3 = SetUtils.intersection(x2, x1) ;
+        test(x3, 1,2,3) ;
+    }
+
+    @Test public void set03() 
+    {
+        Set<Integer> x1 = set(1,2,3) ;
+        Set<Integer> x2 = set(2,9) ;
+        Set<Integer> x3 = SetUtils.intersection(x1, x2) ;
+        test(x3, 2) ;
+        x3 = SetUtils.intersection(x2, x1) ;
+        test(x3, 2) ;
+    }
+
+    @Test public void set04() 
+    {
+        Set<Integer> x1 = set(1,2,3) ;
+        Set<Integer> x2 = set(6,7,8) ;
+        Set<Integer> x3 = SetUtils.intersection(x1, x2) ;
+        test(x3) ;
+        x3 = SetUtils.intersection(x2, x1) ;
+        test(x3) ;
+    }
+
+    @Test public void set05() 
+    {
+        Set<Integer> x1 = set(1,2,3) ;
+        Set<Integer> x2 = set(1,2,3) ;
+        Set<Integer> x3 = SetUtils.union(x1, x2) ;
+        test(x3, 1,2,3) ;
+        x3 = SetUtils.union(x2, x1) ;
+        test(x3, 1,2,3) ;
+    }
+
+    @Test public void set06() 
+    {
+        Set<Integer> x1 = set(1,2,3) ;
+        Set<Integer> x2 = set(2,9) ;
+        Set<Integer> x3 = SetUtils.union(x1, x2) ;
+        test(x3, 1,2,3,9) ;
+        x3 = SetUtils.union(x2, x1) ;
+        test(x3, 1,2,3,9) ;
+    }
+
+    @Test public void set07() 
+    {
+        Set<Integer> x1 = set(1,2,3) ;
+        Set<Integer> x2 = set() ;
+        Set<Integer> x3 = SetUtils.union(x1, x2) ;
+        test(x3,1,2,3) ;
+        x3 = SetUtils.union(x2, x1) ;
+        test(x3,1,2,3) ;
+    }
+
+    @Test public void set08() 
+    {
+        Set<Integer> x1 = set(1,2,3) ;
+        Set<Integer> x2 = set() ;
+        Set<Integer> x3 = SetUtils.difference(x1, x2) ;
+        test(x3,1,2,3) ;
+        x3 = SetUtils.difference(x2, x1) ;
+        test(x3) ;
+    }
+
+    @Test public void set09() 
+    {
+        Set<Integer> x1 = set(1,2,3) ;
+        Set<Integer> x2 = set(3) ;
+        Set<Integer> x3 = SetUtils.difference(x1, x2) ;
+        test(x3,1,2) ;
+        x3 = SetUtils.difference(x2, x1) ;
+        test(x3) ;
+    }
+
+    @Test public void set10() 
+    {
+        Set<Integer> x1 = set(1,2,3) ;
+        Set<Integer> x2 = set(4,5,6) ;
+        Set<Integer> x3 = SetUtils.difference(x1, x2) ;
+        test(x3,1,2,3) ;
+        x3 = SetUtils.difference(x2, x1) ;
+        test(x3,4,5,6) ;
+    }
+    
+    // --------
+    
+    private static Set<Integer> set(int... values)
+    {
+        return new HashSet<>(asList(values)) ;
+    }
+
+    private void test(Set<Integer> x, int...values)
+    {
+        List<Integer> y = asList(values) ;
+        assertEquals(y.size(), x.size()) ;
+
+        for ( Integer aY : y )
+        {
+            assertTrue( x.contains( aY ) );
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/lib/TestStrUtils.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/lib/TestStrUtils.java b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestStrUtils.java
new file mode 100644
index 0000000..ff1e8c6
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestStrUtils.java
@@ -0,0 +1,59 @@
+/*
+ * 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.lib;
+
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.apache.jena.atlas.lib.StrUtils ;
+import org.junit.Test ;
+
+public class TestStrUtils extends BaseTest
+{
+    static char marker = '_' ;
+    static char esc[] = { ' ' , '_' } ; 
+    
+    static void test(String x)
+    {
+        test(x, null) ;
+    }
+    
+    static void test(String x, String z)
+    {
+        String y = StrUtils.encodeHex(x, marker, esc) ;
+        if ( z != null )
+            assertEquals(z, y) ;
+        String x2 = StrUtils.decodeHex(y, marker) ;
+        assertEquals(x, x2) ;
+    }
+    
+    @Test public void enc01() { test("abc") ; } 
+
+    @Test public void enc02() { test("") ; } 
+
+    @Test public void enc03() { test("_", "_5F" ) ; } 
+    
+    @Test public void enc04() { test(" ", "_20" ) ; } 
+    
+    @Test public void enc05() { test("_ _", "_5F_20_5F" ) ; } 
+    
+    @Test public void enc06() { test("_5F", "_5F5F" ) ; } 
+    
+    @Test public void enc07() { test("_2") ; } 
+    
+    @Test public void enc08() { test("AB_CD", "AB_5FCD") ; } 
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/lib/TestTrie.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/lib/TestTrie.java b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestTrie.java
new file mode 100644
index 0000000..1cb9e2e
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestTrie.java
@@ -0,0 +1,346 @@
+/*
+ * 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.lib;
+
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Tests for the {@link Trie} class
+ * 
+ */
+public class TestTrie {
+
+    /**
+     * Add a single key value
+     */
+    @Test
+    public void trie_add_01() {
+        Trie<Integer> trie = new Trie<>();
+        trie.add("test", 123);
+        Assert.assertTrue(trie.contains("test"));
+        Assert.assertEquals((Integer) 123, trie.get("test"));
+    }
+
+    /**
+     * Add two key values
+     */
+    @Test
+    public void trie_add_02() {
+        Trie<Integer> trie = new Trie<>();
+        trie.add("test", 123);
+        trie.add("other", 456);
+        Assert.assertTrue(trie.contains("test"));
+        Assert.assertTrue(trie.contains("other"));
+        Assert.assertEquals((Integer) 123, trie.get("test"));
+        Assert.assertEquals((Integer) 456, trie.get("other"));
+    }
+
+    /**
+     * Replace an existing key value
+     */
+    @Test
+    public void trie_add_03() {
+        Trie<Integer> trie = new Trie<>();
+        trie.add("test", 123);
+        trie.add("test", 456);
+        Assert.assertTrue(trie.contains("test"));
+        Assert.assertEquals((Integer) 456, trie.get("test"));
+    }
+
+    /**
+     * Adding a null value is ignored
+     */
+    @Test
+    public void trie_add_04() {
+        Trie<Integer> trie = new Trie<>();
+        trie.add("test", null);
+        Assert.assertFalse(trie.contains("test"));
+    }
+    
+    /**
+     * Adding a null key is permitted - provides access to root of Trie
+     */
+    @Test
+    public void trie_add_05() {
+        Trie<Integer> trie = new Trie<>();
+        trie.add(null, 123);
+        Assert.assertTrue(trie.contains(null));
+        Assert.assertEquals((Integer)123, trie.get(null));
+    }
+    
+    /**
+     * Adding an empty key is permitted - provides access to root of Trie 
+     */
+    @Test
+    public void trie_add_06() {
+        Trie<Integer> trie = new Trie<>();
+        trie.add("", 123);
+        Assert.assertTrue(trie.contains(""));
+        Assert.assertEquals((Integer)123, trie.get(""));
+    }
+    
+    /**
+     * Test for non-existent key
+     */
+    @Test
+    public void trie_contains_01() {
+        Trie<Integer> trie = new Trie<>();
+        Assert.assertFalse(trie.contains("test"));
+    }
+
+    /**
+     * Test for keys with and without values required
+     */
+    @Test
+    public void trie_contains_02() {
+        Trie<Integer> trie = new Trie<>();
+        trie.add("test", 123);
+        Assert.assertTrue(trie.contains("test"));
+        Assert.assertTrue(trie.contains("test", true));
+        Assert.assertTrue(trie.contains("test", 123));
+
+        // Any prefix of an added key exists if we don't require it to have a
+        // value
+        Assert.assertFalse(trie.contains("t"));
+        Assert.assertTrue(trie.contains("t", false));
+    }
+    
+    /**
+     * Test for non-existent null key
+     */
+    @Test
+    public void trie_contains_03() {
+        Trie<Integer> trie = new Trie<>();
+        Assert.assertFalse(trie.contains(null));
+        Assert.assertTrue(trie.contains(null, false));
+    }
+    
+    /**
+     * Test for empty key
+     */
+    @Test
+    public void trie_contains_04() {
+        Trie<Integer> trie = new Trie<>();
+        Assert.assertFalse(trie.contains(""));
+        Assert.assertTrue(trie.contains("", false));
+    }
+
+    /**
+     * Removing a key value
+     */
+    @Test
+    public void trie_remove_01() {
+        Trie<Integer> trie = new Trie<>();
+        trie.add("test", 123);
+        Assert.assertTrue(trie.contains("test"));
+        Assert.assertEquals((Integer) 123, trie.get("test"));
+
+        // Removing does not fully remove the key it merely nulls the value
+        trie.remove("test");
+        Assert.assertFalse(trie.contains("test"));
+        Assert.assertTrue(trie.contains("test", false));
+        Assert.assertNull(trie.get("test"));
+    }
+
+    /**
+     * Removing a key value, removing a key which is a prefix of another leaves
+     * the other intact
+     */
+    @Test
+    public void trie_remove_02() {
+        Trie<Integer> trie = new Trie<>();
+        trie.add("test", 123);
+        trie.add("testing", 456);
+        Assert.assertTrue(trie.contains("test"));
+        Assert.assertEquals((Integer) 123, trie.get("test"));
+        Assert.assertTrue(trie.contains("testing"));
+        Assert.assertEquals((Integer) 456, trie.get("testing"));
+
+        // Removing does not fully remove the key it merely nulls the value
+        trie.remove("test");
+        Assert.assertFalse(trie.contains("test"));
+        Assert.assertTrue(trie.contains("test", false));
+        Assert.assertNull(trie.get("test"));
+
+        // It also does not remove any keys who had the removed key as a prefix
+        Assert.assertTrue(trie.contains("testing"));
+        Assert.assertEquals((Integer) 456, trie.get("testing"));
+    }
+    
+    /**
+     * Test for removing null key - provides access to trie root
+     */
+    @Test
+    public void trie_remove_03() {
+        Trie<Integer> trie = new Trie<>();
+        trie.add(null, 123);
+        Assert.assertTrue(trie.contains(null));
+        Assert.assertEquals((Integer)123, trie.get(null));
+        
+        trie.remove(null);
+        Assert.assertFalse(trie.contains(null));
+    }
+    
+    /**
+     * Test for removing null key - provides access to trie root
+     */
+    @Test
+    public void trie_remove_04() {
+        Trie<Integer> trie = new Trie<>();
+        trie.add("", 123);
+        Assert.assertTrue(trie.contains(""));
+        Assert.assertEquals((Integer)123, trie.get(""));
+        
+        trie.remove("");
+        Assert.assertFalse(trie.contains(""));
+    }
+    
+    /**
+     * Test prefix search
+     */
+    @Test
+    public void trie_prefix_search_01() {
+        Trie<Integer> trie = new Trie<>();
+        trie.add("test", 123);
+        trie.add("testing", 456);
+        
+        //Prefix search on "test" should return two values
+        List<Integer> matches = trie.prefixSearch("test");
+        Assert.assertEquals(2, matches.size());
+        
+        //Prefix search on "testi" should return one value
+        matches = trie.prefixSearch("testi");
+        Assert.assertEquals(1, matches.size());
+        
+        //Prefix search on "testingly" should return no values
+        matches = trie.prefixSearch("testingly");
+        Assert.assertEquals(0, matches.size());
+        
+        //Prefix search on null key should give two values
+        matches = trie.prefixSearch(null);
+        Assert.assertEquals(2, matches.size());
+    }
+    
+    /**
+     * Test partial search
+     */
+    @Test
+    public void trie_partial_search_01() {
+        Trie<Integer> trie = new Trie<>();
+        trie.add("test", 123);
+        trie.add("testing", 456);
+        
+        //Partial search on "test" should return one values
+        List<Integer> matches = trie.partialSearch("test");
+        Assert.assertEquals(1, matches.size());
+        
+        //Prefix search on "testi" should return one values
+        matches = trie.partialSearch("testi");
+        Assert.assertEquals(1, matches.size());
+        
+        //Prefix search on "testingly" should return two values
+        matches = trie.partialSearch("testingly");
+        Assert.assertEquals(2, matches.size());
+        
+        //Prefix search on null key should give no values
+        matches = trie.partialSearch(null);
+        Assert.assertEquals(0, matches.size());
+    }
+    
+    /**
+     * Test longest match
+     */
+    @Test
+    public void trie_longest_match_01() {
+        Trie<Integer> trie = new Trie<>();
+        trie.add("test", 123);
+        trie.add("testing", 456);
+        Assert.assertTrue(trie.contains("test"));
+        Assert.assertTrue(trie.contains("testing"));
+        
+        Assert.assertEquals((Integer)456, trie.longestMatch("testing"));
+    }
+    
+    /**
+     * Test longest match
+     */
+    @Test
+    public void trie_longest_match_02() {
+        Trie<Integer> trie = new Trie<>();
+        trie.add("test", 123);
+        trie.add("testing", 456);
+        
+        Assert.assertEquals((Integer)456, trie.longestMatch("testingly"));
+    }
+    
+    /**
+     * Test longest match
+     */
+    @Test
+    public void trie_longest_match_03() {
+        Trie<Integer> trie = new Trie<>();
+        trie.add("test", 123);
+        trie.add("testing", 456);
+        trie.remove("testing");
+        
+        Assert.assertEquals((Integer)123, trie.longestMatch("testing"));
+    }
+    
+    /**
+     * Test shortest match
+     */
+    @Test
+    public void trie_shortest_match_01() {
+        Trie<Integer> trie = new Trie<>();
+        trie.add("test", 123);
+        trie.add("testing", 456);
+        Assert.assertTrue(trie.contains("test"));
+        Assert.assertTrue(trie.contains("testing"));
+        
+        Assert.assertEquals((Integer)123, trie.shortestMatch("testing"));
+    }
+    
+    /**
+     * Test shortest match
+     */
+    @Test
+    public void trie_shortest_match_02() {
+        Trie<Integer> trie = new Trie<>();
+        trie.add("test", 123);
+        trie.add("testing", 456);
+        
+        Assert.assertEquals((Integer)123, trie.shortestMatch("testingly"));
+    }
+    
+    /**
+     * Test shortest match
+     */
+    @Test
+    public void trie_shortest_match_03() {
+        Trie<Integer> trie = new Trie<>();
+        trie.add("test", 123);
+        trie.add("testing", 456);
+        trie.remove("test");
+        
+        Assert.assertEquals((Integer)456, trie.shortestMatch("testing"));
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/lib/TestXMLLib.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/lib/TestXMLLib.java b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestXMLLib.java
new file mode 100644
index 0000000..cae99ef
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestXMLLib.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.atlas.lib;
+
+import org.apache.jena.atlas.lib.XMLLib ;
+import org.junit.Assert ;
+import org.junit.Test ;
+
+/** XML related functionality */
+public class TestXMLLib
+{
+    @Test public void ws_collapse_01()  { test("abc", "abc") ; }
+    @Test public void ws_collapse_02()  { test(" abc", "abc") ; }
+    @Test public void ws_collapse_03()  { test(" abc ", "abc") ; }
+    @Test public void ws_collapse_04()  { test(" a b c ", "a b c") ; }
+    @Test public void ws_collapse_05()  { test("\babc", "\babc") ; }
+    @Test public void ws_collapse_06()  { test("", "") ; }
+    @Test public void ws_collapse_07()  { test(" ", "") ; }
+    @Test public void ws_collapse_08()  { test(" \t\t\t\t\t\t\t   ", "") ; }
+    
+    // String.trim : "Returns a copy of the string, with leading and trailing whitespace omitted."
+    // but later says it trims anything <= 0x20.  There are lots of control characters in x01-x1F. 
+    // We only want to trim \n \r \t and space. 
+    
+    private static void test(String str1, String str2)
+    {
+        String result = XMLLib.WScollapse(str1) ;
+        Assert.assertEquals(str2, result) ;
+    }
+
+}


[3/9] jena git commit: Import of lib, io, logging and iterator packages

Posted by an...@apache.org.
http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/logging/FmtLog.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/logging/FmtLog.java b/jena-base/src/main/java/org/apache/jena/atlas/logging/FmtLog.java
new file mode 100644
index 0000000..590c49e
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/logging/FmtLog.java
@@ -0,0 +1,162 @@
+/**
+ * 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.logging;
+
+import java.util.IllegalFormatException ;
+
+import org.slf4j.Logger ;
+import org.slf4j.LoggerFactory ;
+
+/** Logging with String.format (which can be a expensive)
+ *  The formatting operations are not designed specifically for performance, 
+ *  but they do delay forming strings for output
+ *  until it is know that a log message is actually
+ *  required by level setting. 
+ *  
+ *  An odd effect is order of the arguments - vararg arguments must be last
+ *  so the order is Logger/Thorwable?/Format/args.
+ *  
+ *  Also support: Class object instead of logger to help ad hoc usage. 
+ */
+public class FmtLog {
+    /* Log at 'trace' level. */
+    public static void trace(Logger log, String fmt, Object...args) {
+        if ( log.isTraceEnabled() )
+            log.trace(format(fmt, args)) ;
+    }
+
+    /* Log at 'trace' level. */
+    public static void trace(Logger log, Throwable th, String fmt, Object...args) {
+        if ( log.isTraceEnabled() )
+            log.trace(format(fmt, args), th) ;
+    }
+
+    /* Log at 'trace' level. */
+    public static void trace(Class<?> cls, String fmt, Object...args) {
+        trace(log(cls), fmt, args) ;
+    }
+
+    /* Log at 'trace' level. */
+    public static void trace(Class<?> cls, Throwable th, String fmt, Object...args) {
+        trace(log(cls), th, fmt, args) ;
+    }
+
+    /* Log at 'debug' level */
+    public static void debug(Logger log, String fmt, Object...args) {
+        if ( log.isDebugEnabled() )
+            log.debug(format(fmt, args)) ;
+    }
+
+    /* Log at 'debug' level */
+    public static void debug(Logger log, Throwable th, String fmt, Object...args) {
+        if ( log.isDebugEnabled() )
+            log.debug(format(fmt, args), th) ;
+    }
+
+    /* Log at 'debug' level */
+    public static void debug(Class<?> cls, String fmt, Object...args) {
+        debug(log(cls), fmt, args) ;
+    }
+
+    /* Log at 'debug' level */
+    public static void debug(Class<?> cls, Throwable th, String fmt, Object...args) {
+        debug(log(cls), th, fmt, args) ;
+    }
+
+    /* Log at 'info' level */
+    public static void info(Logger log, String fmt, Object...args) {
+        if ( log.isInfoEnabled() )
+            log.info(format(fmt, args)) ;
+    }
+
+    /* Log at 'info' level */
+    public static void info(Logger log, Throwable th, String fmt, Object...args) {
+        if ( log.isInfoEnabled() )
+            log.info(format(fmt, args), th) ;
+    }
+
+    /* Log at 'info' level */
+    public static void info(Class<?> cls, String fmt, Object...args) {
+        info(log(cls), fmt, args) ;
+    }
+
+    /* Log at 'info' level */
+    public static void info(Class<?> cls, Throwable th, String fmt, Object...args) {
+        info(log(cls), th, fmt, args) ;
+    }
+
+    /* Log at 'warn' level */
+    public static void warn(Logger log, String fmt, Object...args) {
+        if ( log.isWarnEnabled() )
+            log.warn(format(fmt, args)) ;
+    }
+
+    /* Log at 'warn' level */
+    public static void warn(Logger log, Throwable th, String fmt, Object...args) {
+        if ( log.isWarnEnabled() )
+            log.warn(format(fmt, args), th) ;
+    }
+
+    /* Log at 'warn' level */
+    public static void warn(Class<?> cls, String fmt, Object...args) {
+        warn(log(cls), fmt, args) ;
+    }
+
+    /* Log at 'warn' level */
+    public static void warn(Class<?> cls, Throwable th, String fmt, Object...args) {
+        warn(log(cls), th, fmt, args) ;
+    }
+
+   /* Log at 'error' level */
+    public static void error(Logger log, String fmt, Object...args) {
+        if ( log.isErrorEnabled() )
+            log.error(format(fmt, args)) ;
+    }
+
+    /* Log at 'error' level */
+    public static void error(Logger log, Throwable th, String fmt, Object...args) {
+        if ( log.isErrorEnabled() )
+            log.error(format(fmt, args), th) ;
+    }
+
+    /* Log at 'error' level */
+    public static void error(Class<?> cls, String fmt, Object...args) {
+        error(log(cls), fmt, args) ;
+    }
+
+    /* Log at 'error' level */
+    public static void error(Class<?> cls, Throwable th, String fmt, Object...args) {
+        error(log(cls), th, fmt, args) ;
+    }
+
+
+    private static String format(String fmt, Object[] args) {
+        try {
+            return String.format(fmt, args) ;
+        } catch (IllegalFormatException ex) {
+            // return something, however grotty.
+            return fmt+" "+args ;
+        }
+    }
+
+    private static Logger log(Class<?> cls) {
+        return LoggerFactory.getLogger(cls) ;
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/logging/Log.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/logging/Log.java b/jena-base/src/main/java/org/apache/jena/atlas/logging/Log.java
new file mode 100644
index 0000000..47783fc
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/logging/Log.java
@@ -0,0 +1,116 @@
+/*
+ * 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.logging ;
+
+import org.slf4j.Logger ;
+import org.slf4j.LoggerFactory ;
+
+/* Simple wrappers and operations for convenient, non-time critical logging.
+ */
+public class Log {
+    private Log() {}
+
+    static public void info(String caller, String msg) {
+        log(caller).info(msg) ;
+    }
+
+    static public void info(Object caller, String msg) {
+        log(caller.getClass()).info(msg) ;
+    }
+
+    static public void info(Class<? > cls, String msg) {
+        log(cls).info(msg) ;
+    }
+
+    static public void info(Object caller, String msg, Throwable th) {
+        log(caller.getClass()).info(msg, th) ;
+    }
+
+    static public void info(Class<? > cls, String msg, Throwable th) {
+        log(cls).info(msg, th) ;
+    }
+
+    static public void debug(String caller, String msg) {
+        log(caller).debug(msg) ;
+    }
+
+    static public void debug(Object caller, String msg) {
+        log(caller.getClass()).debug(msg) ;
+    }
+
+    static public void debug(Class<? > cls, String msg) {
+        log(cls).debug(msg) ;
+    }
+
+    static public void debug(Object caller, String msg, Throwable th) {
+        log(caller.getClass()).debug(msg, th) ;
+    }
+
+    static public void debug(Class<? > cls, String msg, Throwable th) {
+        log(cls).debug(msg, th) ;
+    }
+
+    static public void warn(String caller, String msg) {
+        log(caller).warn(msg) ;
+    }
+
+    static public void warn(Object caller, String msg) {
+        warn(caller.getClass(), msg) ;
+    }
+
+    static public void warn(Class<? > cls, String msg) {
+        log(cls).warn(msg) ;
+    }
+
+    static public void warn(Object caller, String msg, Throwable th) {
+        warn(caller.getClass(), msg, th) ;
+    }
+
+    static public void warn(Class<? > cls, String msg, Throwable th) {
+        log(cls).warn(msg, th) ;
+    }
+
+    static public void fatal(Object caller, String msg) {
+        fatal(caller.getClass(), msg) ;
+    }
+
+    static public void fatal(Class<? > cls, String msg) {
+        log(cls).error(msg) ;
+    }
+
+    static public void fatal(Object caller, String msg, Throwable th) {
+        fatal(caller.getClass(), msg, th) ;
+    }
+
+    static public void fatal(Class<? > cls, String msg, Throwable th) {
+        log(cls).error(msg, th) ;
+    }
+
+    static public void fatal(String caller, String msg) {
+        log(caller).error(msg) ;
+    }
+
+    static private Logger log(Class<? > cls) {
+        return LoggerFactory.getLogger(cls) ;
+    }
+
+    static private Logger log(String loggerName) {
+        return LoggerFactory.getLogger(loggerName) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/logging/LogCtl.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/logging/LogCtl.java b/jena-base/src/main/java/org/apache/jena/atlas/logging/LogCtl.java
new file mode 100644
index 0000000..ad1adce
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/logging/LogCtl.java
@@ -0,0 +1,282 @@
+/**
+ * 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.logging;
+
+import java.io.* ;
+import java.util.Properties ;
+
+import org.apache.jena.atlas.AtlasException ;
+import org.apache.jena.atlas.lib.StrUtils ;
+import org.apache.log4j.PropertyConfigurator ;
+import org.apache.log4j.xml.DOMConfigurator ;
+import org.slf4j.Logger ;
+
+/** Setup and control of logging - needs access to log4j binaries */ 
+public class LogCtl {
+
+    /**
+     * Turn on a logger (all levels). Works for Log4j and Java logging as the
+     * logging provider to Apache common logging or slf4j.
+     */
+    
+    static public void enable(Logger logger) {
+        enable(logger.getName()) ;
+    }
+
+    static public void enable(String logger) {
+        set(logger, "all") ;
+    }
+
+    static public void set(Logger logger, String level) {
+        set(logger.getName(), level) ;
+    }
+
+    /** Turn on a logger (specific level levels) */
+    static public void set(Class<? > logger, String level) {
+        set(logger.getName(), level) ;
+    }
+
+    static public void set(String logger, String level) {
+        org.apache.log4j.Level level1 = org.apache.log4j.Level.ALL ;
+        java.util.logging.Level level2 = java.util.logging.Level.ALL ;
+        if ( level.equalsIgnoreCase("info") ) {
+            level1 = org.apache.log4j.Level.INFO ;
+            level2 = java.util.logging.Level.INFO ;
+        }
+    
+        if ( level.equalsIgnoreCase("debug") ) {
+            level1 = org.apache.log4j.Level.DEBUG ;
+            level2 = java.util.logging.Level.FINE ;
+        }
+    
+        if ( level.equalsIgnoreCase("warn") ) {
+            level1 = org.apache.log4j.Level.WARN ;
+            level2 = java.util.logging.Level.WARNING ;
+        }
+        if ( level.equalsIgnoreCase("error") ) {
+            level1 = org.apache.log4j.Level.ERROR ;
+            level2 = java.util.logging.Level.SEVERE ;
+        }
+        logLevel(logger, level1, level2) ;
+    }
+
+    static public void logLevel(String logger, org.apache.log4j.Level level1, java.util.logging.Level level2) {
+        if ( level1 != null )
+            org.apache.log4j.LogManager.getLogger(logger).setLevel(level1) ;
+        if ( level2 != null )
+            java.util.logging.Logger.getLogger(logger).setLevel(level2) ;
+    }
+
+    /**
+     * Turn on a logger (all levels). Works for Log4j and Java logging as the
+     * logging provider to Apache common logging or slf4j.
+     */
+    static public void enable(Class<? > logger) {
+        org.apache.log4j.LogManager.getLogger(logger).setLevel(org.apache.log4j.Level.ALL) ;
+        java.util.logging.Logger.getLogger(logger.getName()).setLevel(java.util.logging.Level.ALL) ;
+    }
+
+    /**
+     * Turn on a logger (all levels). Works for Log4j and Java logging as the
+     * logging provider to Apache common logging or slf4j.
+     */
+    static public void disable(String logger) {
+        org.apache.log4j.LogManager.getLogger(logger).setLevel(org.apache.log4j.Level.OFF) ;
+        java.util.logging.Logger.getLogger(logger).setLevel(java.util.logging.Level.OFF) ;
+    }
+
+    /**
+     * Turn on a logger (all levels). Works for Log4j and Java logging as the
+     * logging provider to Apache common logging or slf4j.
+     */
+    static public void disable(Class<? > logger) {
+        org.apache.log4j.LogManager.getLogger(logger).setLevel(org.apache.log4j.Level.OFF) ;
+        java.util.logging.Logger.getLogger(logger.getName()).setLevel(java.util.logging.Level.OFF) ;
+    }
+
+    /**
+     * Set to info level. Works for Log4j and Java logging as the logging
+     * provider to Apache common logging or slf4j.
+     */
+    static public void setInfo(String logger) {
+        org.apache.log4j.LogManager.getLogger(logger).setLevel(org.apache.log4j.Level.INFO) ;
+        java.util.logging.Logger.getLogger(logger).setLevel(java.util.logging.Level.INFO) ;
+    }
+
+    /**
+     * Set to info level. Works for Log4j and Java logging as the logging
+     * provider to Apache common logging or slf4j.
+     */
+    static public void setInfo(Class<? > logger) {
+        org.apache.log4j.LogManager.getLogger(logger).setLevel(org.apache.log4j.Level.INFO) ;
+        java.util.logging.Logger.getLogger(logger.getName()).setLevel(java.util.logging.Level.INFO) ;
+    }
+
+    /**
+     * Set to warning level. Works for Log4j and Java logging as the logging
+     * provider to Apache common logging or slf4j.
+     */
+    static public void setWarn(String logger) {
+        org.apache.log4j.LogManager.getLogger(logger).setLevel(org.apache.log4j.Level.WARN) ;
+        java.util.logging.Logger.getLogger(logger).setLevel(java.util.logging.Level.WARNING) ;
+    }
+
+    /**
+     * Set to warning level. Works for Log4j and Java logging as the logging
+     * provider to Apache common logging or slf4j.
+     */
+    static public void setWarn(Class<? > logger) {
+        org.apache.log4j.LogManager.getLogger(logger).setLevel(org.apache.log4j.Level.WARN) ;
+        java.util.logging.Logger.getLogger(logger.getName()).setLevel(java.util.logging.Level.WARNING) ;
+    }
+
+    /**
+     * Set to error level. Works for Log4j and Java logging as the logging
+     * provider to Apache common logging or slf4j.
+     */
+    static public void setError(String logger) {
+        org.apache.log4j.LogManager.getLogger(logger).setLevel(org.apache.log4j.Level.ERROR) ;
+        java.util.logging.Logger.getLogger(logger).setLevel(java.util.logging.Level.SEVERE) ;
+    }
+
+    /**
+     * Set to error level. Works for Log4j and Java logging as the logging
+     * provider to Apache common logging or slf4j.
+     */
+    static public void setError(Class<? > logger) {
+        org.apache.log4j.LogManager.getLogger(logger).setLevel(org.apache.log4j.Level.ERROR) ;
+        java.util.logging.Logger.getLogger(logger.getName()).setLevel(java.util.logging.Level.SEVERE) ;
+    }
+
+    private static String log4Jsetup = StrUtils.strjoinNL
+    ("## Plain output to stdout",
+     "log4j.appender.jena.plain=org.apache.log4j.ConsoleAppender",
+     "log4j.appender.jena.plain.target=System.out",
+     "log4j.appender.jena.plain.layout=org.apache.log4j.PatternLayout",
+     "log4j.appender.jena.plain.layout.ConversionPattern=%m%n"
+     ,
+     "## Plain output with level, to stderr",
+     "log4j.appender.jena.plainlevel=org.apache.log4j.ConsoleAppender",
+     "log4j.appender.jena.plainlevel.target=System.err",
+     "log4j.appender.jena.plainlevel.layout=org.apache.log4j.PatternLayout",
+     "log4j.appender.jena.plainlevel.layout.ConversionPattern=%-5p %m%n"
+    
+     , "## Everything", "log4j.rootLogger=INFO, jena.plainlevel",
+     "log4j.logger.com.hp.hpl.jena=WARN",
+     "log4j.logger.org.openjena=WARN",
+     "log4j.logger.org.apache.jena=WARN",
+     "log4j.logger.org.apache.jena.tdb.loader=INFO"
+    
+     , "## Parser output"
+     , "log4j.additivity.org.apache.jena.riot=false"
+     , "log4j.logger.org.apache.jena.riot=INFO, jena.plainlevel ") ;
+    // ---- java.util.logging - because that's always present.
+    static String defaultProperties = StrUtils.strjoinNL
+        (
+         // Handlers - output
+         // All (comma
+         // separated)
+         // "handlers=java.util.logging.ConsoleHandler,org.apache.jena.atlas.logging.java.ConsoleHandlerStdout",
+    
+         // Atlas.
+         "handlers=org.openjena.atlas.logging.java.ConsoleHandlerStdout",
+         "org.apache.atlas.jena.logging.java.ConsoleHandlerStdout.level=INFO",
+         "java.util.logging.ConsoleHandler.formatter=atlas.logging.java.TextFormatter"
+    ) ;
+
+    /**
+     * Set logging
+     * <ol>
+     * <li>Check for -Dlog4j.configuration.</li>
+     * <li>Looks for log4j.properties file in current directory.</li>
+     * </ol>
+     * Return true if we think Log4J is not initialized.
+     */
+    
+    public static void setLog4j() {
+        if ( System.getProperty("log4j.configuration") == null ) {
+            String fn = "log4j.properties" ;
+            File f = new File(fn) ;
+            if ( f.exists() )
+                System.setProperty("log4j.configuration", "file:" + fn) ;
+        }
+    }
+
+    /** Set log4j properties (XML or properties file) */
+    public static void setLog4j(String filename) {
+        if ( filename.toLowerCase().endsWith(".xml") )
+            DOMConfigurator.configure(filename) ;
+        else
+            PropertyConfigurator.configure(filename) ;
+    }
+
+    /**
+     * Set logging, suitable for a command line application.
+     * If "log4j.configuration" not set, then use the built-in default, 
+     * else just leave to log4j startup.
+     */
+    public static void setCmdLogging() {
+        setCmdLogging(log4Jsetup) ;
+    }
+
+    /**
+     * Set logging, suitable for a command line application.
+     * If "log4j.configuration" not set, then use the provided default 
+     * (log4j properties syntax) else just leave to log4j startup.
+     */
+    public static void setCmdLogging(String defaultConfig) {
+        if (System.getProperty("log4j.configuration") == null )
+            resetLogging(defaultConfig) ;
+    }
+
+    public static void resetLogging(String config) {
+        Properties p = new Properties() ;
+        InputStream in = new ByteArrayInputStream(StrUtils.asUTF8bytes(config)) ;
+        try {
+            p.load(in) ;
+        } catch (IOException ex) {}
+        PropertyConfigurator.configure(p) ;
+        System.setProperty("log4j.configuration", "set") ;
+    }
+
+    public static void setJavaLogging() {
+        setJavaLogging("logging.properties") ;
+    }
+
+    public static void setJavaLogging(String file) {
+        try {
+            InputStream details = new FileInputStream(file) ;
+            java.util.logging.LogManager.getLogManager().readConfiguration(details) ;
+        } catch (Exception ex) {
+            throw new AtlasException(ex) ;
+        }
+    }
+
+    public static void setJavaLoggingDft() {
+        try {
+            InputStream details = new ByteArrayInputStream(defaultProperties.getBytes("UTF-8")) ;
+            java.util.logging.LogManager.getLogManager().readConfiguration(details) ;
+    
+        } catch (Exception ex) {
+            throw new AtlasException(ex) ;
+        }
+    }
+
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/logging/ProgressLogger.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/logging/ProgressLogger.java b/jena-base/src/main/java/org/apache/jena/atlas/logging/ProgressLogger.java
new file mode 100644
index 0000000..344885d
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/logging/ProgressLogger.java
@@ -0,0 +1,133 @@
+/*
+ * 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.logging;
+
+import static org.apache.jena.atlas.lib.DateTimeUtils.* ;
+import org.apache.jena.atlas.lib.Timer ;
+import org.slf4j.Logger ;
+
+/** Progress monitor */
+public class ProgressLogger
+{
+    private final Logger log ;
+    private final long tickPoint ;
+    private final int superTick ;
+    private final Timer timer ;
+    private final String label ;
+    
+    private long counterBatch = 0 ;
+    private long counterTotal = 0 ;
+    
+    private long lastTime = 0 ;
+    
+    public ProgressLogger(Logger log, String label, long tickPoint, int superTick)
+    {
+        this.log = log ;
+        this.label = label ;
+        this.tickPoint = tickPoint ;
+        this.superTick = superTick ;
+        this.timer = new Timer() ;
+    }
+    
+    public void startMessage() { 
+        print("Start:") ;
+    }
+    
+    public void finishMessage() { 
+        // Elapsed.
+        long timePoint = timer.getTimeInterval() ;
+    
+        // *1000L is milli to second conversion
+        if ( timePoint != 0 ) {
+            double time = timePoint/1000.0 ;
+            long runAvgRate   = (counterTotal * 1000L) / timePoint ;
+            
+            print("Finished: %,d %s %.2fs (Avg: %,d)", counterTotal, label, time, runAvgRate) ;
+        }
+        else
+            print("Finished: %,d %s (Avg: ----)", counterTotal, label) ;
+    }
+    
+    public void start()
+    {
+        timer.startTimer() ;
+        lastTime = 0 ;
+    }
+
+    public long finish()
+    {
+        long totalTime = timer.endTimer() ;
+        return totalTime ;
+    }
+    
+    public long getTicks()
+    {
+        return counterTotal ;
+    }
+    
+    public void tick()
+    {
+        counterBatch++ ;
+        counterTotal++ ;
+    
+        if ( tickPoint(counterTotal, tickPoint) )
+        {
+            long timePoint = timer.readTimer() ;
+            long thisTime = timePoint - lastTime ;
+        
+            // *1000L is milli to second conversion
+            if ( thisTime != 0 && timePoint != 0 ) {
+                long batchAvgRate = (counterBatch * 1000L) / thisTime;
+                long runAvgRate   = (counterTotal * 1000L) / timePoint ;
+                print("Add: %,d %s (Batch: %,d / Avg: %,d)", counterTotal, label, batchAvgRate, runAvgRate) ;
+            } else {
+                print("Add: %,d %s (Batch: ---- / Avg: ----)", counterTotal, label) ;
+            }
+            
+            lastTime = timePoint ;
+
+            if ( tickPoint(counterTotal, superTick*tickPoint) )
+                elapsed(timePoint) ;
+            counterBatch = 0 ;
+            lastTime = timePoint ;
+        }
+    }
+    
+    private void elapsed(long timerReading)
+    {
+        float elapsedSecs = timerReading/1000F ;
+        print("  Elapsed: %,.2f seconds [%s]", elapsedSecs, nowAsString()) ;
+    }
+    
+    /** Print a message in the form for this ProgressLogger */ 
+    public void print(String fmt, Object...args)
+    {
+        if ( log != null && log.isInfoEnabled() )
+        {
+            String str = String.format(fmt, args) ;
+            log.info(str) ;
+        }
+    }
+    
+    static boolean tickPoint(long counter, long quantum)
+    {
+        return counter%quantum == 0 ;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/logging/java/ConsoleHandlerStdout.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/logging/java/ConsoleHandlerStdout.java b/jena-base/src/main/java/org/apache/jena/atlas/logging/java/ConsoleHandlerStdout.java
new file mode 100644
index 0000000..7bfab7e
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/logging/java/ConsoleHandlerStdout.java
@@ -0,0 +1,75 @@
+/*
+ * 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.logging.java;
+
+import java.util.logging.Formatter ;
+import java.util.logging.Handler ;
+import java.util.logging.LogManager ;
+import java.util.logging.LogRecord ;
+
+
+public class ConsoleHandlerStdout extends Handler 
+{
+
+    private void configure()
+    {
+        LogManager manager = LogManager.getLogManager();
+        String cname = getClass().getName();
+        
+        String cls = manager.getProperty(cname+".formatter") ;
+        Formatter fmt = null ;
+        
+        try {
+            if (cls != null) {
+                Class<?> clz = ClassLoader.getSystemClassLoader().loadClass(cls);
+                fmt = (Formatter) clz.newInstance();
+            }
+        } catch (Exception ex) {
+            // We got one of a variety of exceptions in creating the
+            // class or creating an instance.
+            // Drop through.
+        }
+        if ( fmt == null )
+            fmt = new TextFormatter() ;
+        setFormatter(fmt) ;
+    }
+    
+    public ConsoleHandlerStdout()
+    {
+        configure() ;
+    }
+    
+    @Override
+    public void close() throws SecurityException
+    {}
+
+    @Override
+    public void flush()
+    { System.out.flush(); }
+
+    @Override
+    public void publish(LogRecord record)
+    {
+        if ( ! super.isLoggable(record) )
+            return ;
+        String s = getFormatter().format(record) ;
+        System.out.print(s) ;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/logging/java/TextFormatter.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/logging/java/TextFormatter.java b/jena-base/src/main/java/org/apache/jena/atlas/logging/java/TextFormatter.java
new file mode 100644
index 0000000..3e97942
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/logging/java/TextFormatter.java
@@ -0,0 +1,53 @@
+/*
+ * 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.logging.java;
+
+import java.text.MessageFormat ;
+import java.util.Date ;
+import java.util.logging.Formatter ;
+import java.util.logging.LogRecord ;
+
+/** A pattern-like log formatter */
+public class TextFormatter extends Formatter
+{
+    @Override
+    public String format(LogRecord record) {
+        String loggerName = record.getLoggerName();
+        if(loggerName == null) {
+            loggerName = "root";
+        }
+        
+        int i = loggerName.lastIndexOf('.') ; 
+        String loggerNameShort = loggerName.substring(i+1) ;
+            
+        String formatted$ = record.getMessage() ;
+        if ( record.getParameters() != null )
+            formatted$ = MessageFormat.format(formatted$, record.getParameters()) ;
+        
+        // %tT (%5$tT) is %5$tH:%5$tM:%5$tS
+        // %tF is 2008-11-22 "%tY-%tm-%td"
+        return String.format("%5$tT %3$-5s %2$-25s :: %6$s\n", 
+                             loggerName,                        // 1
+                             loggerNameShort,                   // 2
+                             record.getLevel(),                 // 3
+                             Thread.currentThread().getName(),  // 4
+                             new Date(record.getMillis()),      // 5
+                             formatted$) ;                      // 6
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/base/Closeable.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/base/Closeable.java b/jena-base/src/main/java/org/apache/jena/base/Closeable.java
new file mode 100644
index 0000000..59a06aa
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/base/Closeable.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.base;
+
+/** Close this object.
+ *  This form does not allow Exceptions (it does allow RuntimeExceptions)  
+ */
+public interface Closeable {
+    public void close() ; 
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/base/Sys.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/base/Sys.java b/jena-base/src/main/java/org/apache/jena/base/Sys.java
new file mode 100644
index 0000000..8dbe8d8
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/base/Sys.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.base;
+
+import java.io.File ;
+
+public class Sys {
+    /** Is this windows? */
+    public static final boolean isWindows = (File.pathSeparatorChar == ';') ;
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/TC_Atlas.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/TC_Atlas.java b/jena-base/src/test/java/org/apache/jena/atlas/TC_Atlas.java
new file mode 100644
index 0000000..f455207
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/TC_Atlas.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.atlas;
+
+import org.apache.jena.atlas.io.TS_IO ;
+import org.apache.jena.atlas.iterator.TS_Iterator ;
+import org.apache.jena.atlas.lib.TS_Lib ;
+import org.junit.runner.RunWith ;
+import org.junit.runners.Suite ;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses( {
+    // Library
+      TS_Lib.class
+    , TS_Iterator.class
+    , TS_IO.class
+//    , TS_Event.class
+//    , TS_JSON.class
+//    , TS_Data.class
+//    , TS_Web.class
+//    , TestCSVParser.class
+}) 
+
+public class TC_Atlas
+{}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/io/AbstractTestPeekInputStream.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/io/AbstractTestPeekInputStream.java b/jena-base/src/test/java/org/apache/jena/atlas/io/AbstractTestPeekInputStream.java
new file mode 100644
index 0000000..d127385
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/io/AbstractTestPeekInputStream.java
@@ -0,0 +1,243 @@
+/*
+ * 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 org.apache.jena.atlas.io.PeekInputStream ;
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.junit.Test ;
+
+public abstract class AbstractTestPeekInputStream extends BaseTest
+{
+    static int INIT_LINE = PeekInputStream.INIT_LINE ;
+    static int INIT_COL = PeekInputStream.INIT_COL ;
+    
+    abstract PeekInputStream make(String contents, int size) ;
+    
+    @Test public void read0()
+    {
+        assertEquals("Init line", 1, INIT_LINE) ;
+        assertEquals("Init col", 1, INIT_COL) ;
+    }
+    
+    @Test public void read1()
+    {
+        PeekInputStream in = make("") ;
+        checkLineCol(in, INIT_LINE, INIT_COL) ;
+        
+        int x = in.peekByte() ;
+        assertEquals(-1, x) ;
+        x = in.readByte() ;
+        assertEquals(-1, x) ;
+        x = in.readByte() ;
+        assertEquals(-1, x) ;
+    }
+    
+    @Test public void read2()
+    {
+        // Assumes we start at (1,1) 
+        PeekInputStream in = make("a") ;
+        checkLineCol(in, INIT_LINE, INIT_COL) ;
+        
+        int x = in.peekByte() ;
+        assertEquals('a', x) ;
+        checkLineCol(in, INIT_LINE, INIT_COL) ;
+        
+        x = in.readByte() ;
+        checkLineCol(in, INIT_LINE, INIT_COL+1) ;
+        assertEquals('a', x) ;
+        
+        x = in.peekByte() ;
+        assertEquals(-1, x) ;
+        
+        x = in.readByte() ;
+        assertEquals(-1, x) ;
+    }
+
+    @Test public void read3()
+    {
+        String c = "abcde" ;
+        PeekInputStream in = make(c) ;
+        
+        for ( int i = 0 ; i < c.length(); i++ )
+        {
+            checkLineCol(in, INIT_LINE, i+INIT_COL) ;
+            long z = in.getPosition() ;
+            assertEquals(i, in.getPosition()) ;
+            assertEquals(c.charAt(i), in.readByte()) ;
+        }
+        assertTrue(in.eof()) ;
+    }
+
+    @Test public void read4()
+    {
+        position("abcde") ;
+    }
+
+    @Test public void read5()
+    {
+        position("abc\nde") ;
+    }
+
+    @Test public void read6()
+    {
+        position("abc\nde\n") ;
+    }
+    
+    @Test public void read7()
+    {
+        position("") ;
+    }
+
+    @Test public void read8()
+    {
+        position("x") ;
+    }
+
+
+    @Test public void read9()
+    {
+        PeekInputStream in = make("a\nb\n") ;
+        checkLineCol(in, INIT_LINE, INIT_COL) ;
+        int x = in.peekByte() ;
+        assertEquals('a', x) ;
+        checkLineCol(in, INIT_LINE, INIT_COL) ;
+        
+        x = in.readByte() ;
+        assertEquals('a', x) ;
+        checkLineCol(in, INIT_LINE, INIT_COL+1) ;
+
+        x = in.readByte() ;
+        assertEquals('\n', x) ;
+        checkLineCol(in, INIT_LINE+1, INIT_COL) ;
+    }
+    
+    @Test public void unread1()
+    {
+        PeekInputStream in = make("abc") ;
+        assertEquals('a', in.peekByte()) ;
+        in.pushbackByte('Z') ;
+        assertEquals('Z', in.peekByte()) ;
+        contains(in, "Zabc") ;
+    }
+
+    @Test public void unread2()
+    {
+        PeekInputStream in = make("abc") ;
+        checkLineCol(in, INIT_LINE, INIT_COL) ;
+        int ch = in.readByte() ;
+        // Pushback does not move line/col backwards.
+        checkLineCol(in, INIT_LINE, INIT_COL+1) ;
+        assertEquals('b', in.peekByte()) ;
+        checkLineCol(in, INIT_LINE, INIT_COL+1) ;
+        in.pushbackByte('a') ;
+        checkLineCol(in, INIT_LINE, INIT_COL+1) ;
+        contains(in, "abc") ;
+    }
+    
+    @Test public void unread3()
+    {
+        PeekInputStream in = make("") ;
+        int ch = in.readByte() ;
+        assertEquals(-1, in.peekByte()) ;
+        in.pushbackByte('a') ;
+        contains(in, "a") ;
+    }
+
+    @Test public void unread4()
+    {
+        PeekInputStream in = make("") ;
+        int ch = in.readByte() ;
+        assertEquals(-1, in.peekByte()) ;
+        in.pushbackByte('0') ;
+        in.pushbackByte('1') ;
+        in.pushbackByte('2') ;
+        in.pushbackByte('3') ;
+        contains(in, "3210") ;   // Backwards!
+    }
+
+    @Test public void unread5()
+    {
+        PeekInputStream in = make("") ;
+        long lineNum = in.getLineNum() ;
+        long colNum = in.getColNum() ;
+        
+        checkLineCol(in, lineNum, colNum) ;
+        
+        in.pushbackByte('0') ;
+        checkLineCol(in, lineNum, colNum) ;  // Unmoved.
+        in.pushbackByte('1') ;
+        checkLineCol(in, lineNum, colNum) ;
+        assertEquals('1', in.readByte()) ;
+        
+        checkLineCol(in, lineNum, colNum) ;  // Unmoved.
+        in.pushbackByte('2') ;
+        in.pushbackByte('3') ;
+        checkLineCol(in, lineNum, colNum) ;  // Unmoved.
+        assertEquals('3', in.peekByte()) ;
+        contains(in, "320") ;
+    }
+
+    private void checkLineCol(PeekInputStream in, long lineNum, long colNum)
+    {
+        assertEquals("Line", lineNum, in.getLineNum()) ; 
+        assertEquals("Column", colNum, in.getColNum()) ;
+    }
+    
+    private void position(String contents)
+    {
+        PeekInputStream in = make(contents) ;
+        
+        int line = INIT_LINE ;
+        int col = INIT_COL ;
+        checkLineCol(in, line, col) ;
+        assertEquals(0, in.getPosition()) ;
+        
+        for ( int i = 0 ; i < contents.length(); i++ )
+        {
+            int x = in.readByte() ;
+            if ( x != -1 )
+            {
+                if ( x == '\n' )
+                {
+                    line++ ;
+                    col = INIT_COL ;
+                }
+                else
+                    col++ ;
+            }
+            assertEquals(contents.charAt(i), x) ;
+            assertEquals(i+1, in.getPosition()) ;
+            checkLineCol(in, line, col) ;
+        }
+        assertTrue(in.eof()) ;
+    }
+    
+    private void contains(PeekInputStream in, String contents)
+    {
+        for ( int i = 0 ; i < contents.length(); i++ )
+        {
+            int x = in.readByte() ;
+            assertEquals("\""+contents+"\" -- Index "+i+" Expected:'"+contents.charAt(i)+"' Got: '"+(char)x+"'", contents.charAt(i), x) ;
+        }
+        assertTrue(in.eof()) ;
+    }
+    
+    private PeekInputStream make(String contents)
+    { return make(contents, 2) ; }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/io/AbstractTestPeekReader.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/io/AbstractTestPeekReader.java b/jena-base/src/test/java/org/apache/jena/atlas/io/AbstractTestPeekReader.java
new file mode 100644
index 0000000..7ab67b5
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/io/AbstractTestPeekReader.java
@@ -0,0 +1,243 @@
+/*
+ * 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 org.apache.jena.atlas.io.PeekReader ;
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.junit.Test ;
+
+public abstract class AbstractTestPeekReader extends BaseTest
+{
+    static int INIT_LINE = PeekReader.INIT_LINE ;
+    static int INIT_COL = PeekReader.INIT_COL ;
+    
+    abstract PeekReader make(String contents, int size) ;
+    
+    @Test public void read0()
+    {
+        assertEquals("Init line", 1, INIT_LINE) ;
+        assertEquals("Init col", 1, INIT_COL) ;
+    }
+    
+    @Test public void read1()
+    {
+        PeekReader r = make("") ;
+        checkLineCol(r, INIT_LINE, INIT_COL) ;
+        
+        int x = r.peekChar() ;
+        assertEquals(-1, x) ;
+        x = r.readChar() ;
+        assertEquals(-1, x) ;
+        x = r.readChar() ;
+        assertEquals(-1, x) ;
+    }
+    
+    @Test public void read2()
+    {
+        // Assumes we start at (1,1) 
+        PeekReader r = make("a") ;
+        checkLineCol(r, INIT_LINE, INIT_COL) ;
+        
+        int x = r.peekChar() ;
+        assertEquals('a', x) ;
+        checkLineCol(r, INIT_LINE, INIT_COL) ;
+        
+        x = r.readChar() ;
+        checkLineCol(r, INIT_LINE, INIT_COL+1) ;
+        assertEquals('a', x) ;
+        
+        x = r.peekChar() ;
+        assertEquals(-1, x) ;
+        
+        x = r.readChar() ;
+        assertEquals(-1, x) ;
+    }
+
+    @Test public void read3()
+    {
+        String c = "abcde" ;
+        PeekReader r = make(c) ;
+        
+        for ( int i = 0 ; i < c.length(); i++ )
+        {
+            checkLineCol(r, INIT_LINE, i+INIT_COL) ;
+            long z = r.getPosition() ;
+            assertEquals(i, r.getPosition()) ;
+            assertEquals(c.charAt(i), r.readChar()) ;
+        }
+        assertTrue(r.eof()) ;
+    }
+
+    @Test public void read4()
+    {
+        position("abcde") ;
+    }
+
+    @Test public void read5()
+    {
+        position("abc\nde") ;
+    }
+
+    @Test public void read6()
+    {
+        position("abc\nde\n") ;
+    }
+    
+    @Test public void read7()
+    {
+        position("") ;
+    }
+
+    @Test public void read8()
+    {
+        position("x") ;
+    }
+
+
+    @Test public void read9()
+    {
+        PeekReader r = make("a\nb\n") ;
+        checkLineCol(r, INIT_LINE, INIT_COL) ;
+        int x = r.peekChar() ;
+        assertEquals('a', x) ;
+        checkLineCol(r, INIT_LINE, INIT_COL) ;
+        
+        x = r.readChar() ;
+        assertEquals('a', x) ;
+        checkLineCol(r, INIT_LINE, INIT_COL+1) ;
+
+        x = r.readChar() ;
+        assertEquals('\n', x) ;
+        checkLineCol(r, INIT_LINE+1, INIT_COL) ;
+    }
+    
+    @Test public void unread1()
+    {
+        PeekReader r = make("abc") ;
+        assertEquals('a', r.peekChar()) ;
+        r.pushbackChar('Z') ;
+        assertEquals('Z', r.peekChar()) ;
+        contains(r, "Zabc") ;
+    }
+
+    @Test public void unread2()
+    {
+        PeekReader r = make("abc") ;
+        checkLineCol(r, INIT_LINE, INIT_COL) ;
+        int ch = r.readChar() ;
+        // Pushback does not move line/col backwards.
+        checkLineCol(r, INIT_LINE, INIT_COL+1) ;
+        assertEquals('b', r.peekChar()) ;
+        checkLineCol(r, INIT_LINE, INIT_COL+1) ;
+        r.pushbackChar('a') ;
+        checkLineCol(r, INIT_LINE, INIT_COL+1) ;
+        contains(r, "abc") ;
+    }
+    
+    @Test public void unread3()
+    {
+        PeekReader r = make("") ;
+        int ch = r.readChar() ;
+        assertEquals(-1, r.peekChar()) ;
+        r.pushbackChar('a') ;
+        contains(r, "a") ;
+    }
+
+    @Test public void unread4()
+    {
+        PeekReader r = make("") ;
+        int ch = r.readChar() ;
+        assertEquals(-1, r.peekChar()) ;
+        r.pushbackChar('0') ;
+        r.pushbackChar('1') ;
+        r.pushbackChar('2') ;
+        r.pushbackChar('3') ;
+        contains(r, "3210") ;   // Backwards!
+    }
+
+    @Test public void unread5()
+    {
+        PeekReader r = make("") ;
+        long lineNum = r.getLineNum() ;
+        long colNum = r.getColNum() ;
+        
+        checkLineCol(r, lineNum, colNum) ;
+        
+        r.pushbackChar('0') ;
+        checkLineCol(r, lineNum, colNum) ;  // Unmoved.
+        r.pushbackChar('1') ;
+        checkLineCol(r, lineNum, colNum) ;
+        assertEquals('1', r.readChar()) ;
+        
+        checkLineCol(r, lineNum, colNum) ;  // Unmoved.
+        r.pushbackChar('2') ;
+        r.pushbackChar('3') ;
+        checkLineCol(r, lineNum, colNum) ;  // Unmoved.
+        assertEquals('3', r.peekChar()) ;
+        contains(r, "320") ;
+    }
+
+    private void checkLineCol(PeekReader r, long lineNum, long colNum)
+    {
+        assertEquals("Line", lineNum, r.getLineNum()) ; 
+        assertEquals("Column", colNum, r.getColNum()) ;
+    }
+    
+    private void position(String contents)
+    {
+        PeekReader r = make(contents) ;
+        
+        int line = INIT_LINE ;
+        int col = INIT_COL ;
+        checkLineCol(r, line, col) ;
+        assertEquals(0, r.getPosition()) ;
+        
+        for ( int i = 0 ; i < contents.length(); i++ )
+        {
+            int x = r.readChar() ;
+            if ( x != -1 )
+            {
+                if ( x == '\n' )
+                {
+                    line++ ;
+                    col = INIT_COL ;
+                }
+                else
+                    col++ ;
+            }
+            assertEquals(contents.charAt(i), x) ;
+            assertEquals(i+1, r.getPosition()) ;
+            checkLineCol(r, line, col) ;
+        }
+        assertTrue(r.eof()) ;
+    }
+    
+    private void contains(PeekReader r, String contents)
+    {
+        for ( int i = 0 ; i < contents.length(); i++ )
+        {
+            int x = r.readChar() ;
+            assertEquals("\""+contents+"\" -- Index "+i+" Expected:'"+contents.charAt(i)+"' Got: '"+(char)x+"'", contents.charAt(i), x) ;
+        }
+        assertTrue(r.eof()) ;
+    }
+    
+    private PeekReader make(String contents)
+    { return make(contents, 2) ; }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/io/TS_IO.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/io/TS_IO.java b/jena-base/src/test/java/org/apache/jena/atlas/io/TS_IO.java
new file mode 100644
index 0000000..4479243
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/io/TS_IO.java
@@ -0,0 +1,45 @@
+/*
+ * 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 org.junit.runner.RunWith ;
+import org.junit.runners.Suite ;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses( {
+    // Basic classes
+    TestIndentedWriter.class
+    , TestStreamUTF8.class
+    , TestBlockUTF8.class
+    , TestInputStreamBuffered.class
+
+    // Peek readers.
+    , TestPeekReaderSource.class
+    , TestPeekReaderCharSequence.class
+    , TestPeekInputStreamSource.class
+
+    // Writers
+    , TestBufferingWriter.class
+    // Other
+    , TestPrintUtils.class
+} )
+public class TS_IO
+{
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/io/TestBlockUTF8.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/io/TestBlockUTF8.java b/jena-base/src/test/java/org/apache/jena/atlas/io/TestBlockUTF8.java
new file mode 100644
index 0000000..e29d78a
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/io/TestBlockUTF8.java
@@ -0,0 +1,254 @@
+/*
+ * 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.ByteArrayOutputStream ;
+import java.io.IOException ;
+import java.io.OutputStreamWriter ;
+import java.io.Writer ;
+import java.nio.Buffer ;
+import java.nio.ByteBuffer ;
+import java.nio.CharBuffer ;
+import java.nio.charset.Charset ;
+import java.nio.charset.CharsetDecoder ;
+import java.nio.charset.CharsetEncoder ;
+
+import org.apache.jena.atlas.io.BlockUTF8 ;
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.apache.jena.atlas.lib.Chars ;
+import org.junit.Test ;
+
+public class TestBlockUTF8 extends BaseTest
+{
+    // Need array and non-array versions.
+    
+    static Charset utf8 = Chars.charsetUTF8 ;
+    static CharsetDecoder dec = utf8.newDecoder() ;
+    static CharsetEncoder enc = utf8.newEncoder() ;
+
+    // UTF-8 encoding.
+    // character '¢' = code point U+00A2 -> C2 A2
+    // character '€' = code point U+20AC -> E2 82 AC
+
+    static private final String asciiBase             = "abc" ;
+    static private final String latinBase             = "Àéíÿ" ;
+    static private final String latinExtraBase        = "ỹfifl" ;  // fi-ligature, fl-ligature
+    static private final String greekBase             = "αβγ" ;
+    static private final String hewbrewBase           = "אבג" ;
+    static private final String arabicBase            = "ءآأ";
+    static private final String symbolsBase           = "☺☻♪♫" ;
+    static private final String chineseBase           = "孫子兵法" ; // The Art of War 
+    static private final String japaneseBase          = "日本" ;    // Japanese
+    static private final String binaryStr1            = "abc\uD800xyz" ;    // A single surrogate, without it's pair. 
+    static private final String binaryStr2            = "\uD800" ;          // A single surrogate, without it's pair. 
+    static private final String binaryStr3            = "\u0000" ;          // A zero character  
+
+    static private final byte[] binaryBytes1 = {} ;         
+    static private final byte[] binaryBytes2 = { (byte)0x00 } ;             // Java encoding of 0 codepoint is 0         
+    static private final byte[] binaryBytes3 = { (byte)0xC0, (byte)0x80 } ;     // Modifed unicode zero codepoint.         
+
+    @Test public void convert_in_00() { testIn("") ; }
+    @Test public void convert_in_01() { testIn(asciiBase) ; }
+    @Test public void convert_in_02() { testIn(latinBase) ; }
+    @Test public void convert_in_03() { testIn(latinExtraBase) ; }
+    @Test public void convert_in_04() { testIn(greekBase) ; }
+    @Test public void convert_in_05() { testIn(hewbrewBase) ; }
+    @Test public void convert_in_06() { testIn(arabicBase) ; }
+    @Test public void convert_in_07() { testIn(symbolsBase) ; }
+    @Test public void convert_in_08() { testIn(chineseBase) ; }
+    @Test public void convert_in_09() { testIn(japaneseBase) ; }
+    @Test public void convert_in_10() { testInOutBinary(binaryStr1) ; }  
+    @Test public void convert_in_11() { testInOutBinary(binaryStr2) ; }  
+    @Test public void convert_in_12() { testInOutBinary(binaryStr3) ; }  
+
+    @Test public void convert_out_00() { testOut("") ; }
+    @Test public void convert_out_01() { testOut(asciiBase) ; }
+    @Test public void convert_out_02() { testOut(latinBase) ; }
+    @Test public void convert_out_03() { testOut(latinExtraBase) ; }
+    @Test public void convert_out_04() { testOut(greekBase) ; }
+    @Test public void convert_out_05() { testOut(hewbrewBase) ; }
+    @Test public void convert_out_06() { testOut(arabicBase) ; }
+    @Test public void convert_out_07() { testOut(symbolsBase) ; }
+    @Test public void convert_out_08() { testOut(chineseBase) ; }
+    @Test public void convert_out_09() { testOut(japaneseBase) ; }
+    @Test public void convert_out_10() { testOut(binaryStr1) ; }
+    @Test public void convert_out_11() { testOut(binaryStr2) ; }
+    @Test public void convert_out_12() { testOut(binaryStr3) ; }
+
+    // While it is key is chars->bytes-chars, we also test bytes->bytes 
+    @Test public void binary_01() { testBinary(binaryBytes1) ; }
+    @Test public void binary_02() { testBinary(binaryBytes2) ; }
+    @Test public void binary_03() { testBinary(binaryBytes3, binaryBytes2) ; }
+
+    @Test public void binary_10() { testBinary(binaryBytes2, CharBuffer.wrap(binaryStr3)) ; }
+    @Test public void binary_11() { testBinary(binaryBytes3, CharBuffer.wrap(binaryStr3)) ; }
+
+    
+    static void testIn(String x)
+    {
+        testIn(x, allocByteBufferArray, allocCharBufferArray) ;
+        testIn(x, allocByteBufferDirect, allocCharBufferDirect) ;
+
+    }
+    static void testIn(String x, Alloc<ByteBuffer> allocBB, Alloc<CharBuffer> allocCB)
+    {
+        // Test as binary.
+        testInOutBinary(x) ;
+
+        // Now test, comparing to std Java.
+        // Correct answer, in bytes
+        ByteBuffer bytes = ByteBuffer.wrap(stringAsBytes(x)) ;
+        // To bytes.stringAsBytes
+        int N = x.length() ;
+        CharBuffer cb = CharBuffer.wrap(x.toCharArray()) ;
+        ByteBuffer bb = allocBB.allocate(4*N) ;
+        BlockUTF8.fromChars(cb, bb) ;
+        bb.flip() ;
+
+        assertTrue("Bytes", sameBytes(bytes, bb)) ;
+        // From bytes.
+        CharBuffer cb2 = allocCB.allocate(N) ;
+        BlockUTF8.toChars(bb, cb2) ;
+        cb2.flip() ;
+        String str = cb2.toString() ;
+        assertEquals(x, str) ;
+    }
+
+    // Tesing, but not against what Java would do (it replaces bad chars, we want binary).
+    static void testInOutBinary(String x)
+    {
+        int N = x.length() ;
+        CharBuffer cb = CharBuffer.wrap(x.toCharArray()) ;
+        ByteBuffer bb = ByteBuffer.allocate(4*N) ;
+        BlockUTF8.fromChars(cb, bb) ;
+        bb.flip() ;
+        CharBuffer cb2 = CharBuffer.allocate(N) ;
+        BlockUTF8.toChars(bb, cb2) ;
+        // compare cb and cb2.
+        String str = new String(cb2.array(), 0, cb2.position()) ;
+        assertEquals(x, str) ;
+
+        // And re-code as bytes.
+        CharBuffer cb3 = CharBuffer.wrap(x.toCharArray()) ;
+        ByteBuffer bb3 = ByteBuffer.allocate(4*N) ;
+        BlockUTF8.fromChars(cb3, bb3) ;
+        bb3.flip() ;
+        assertArrayEquals(bb.array(), bb3.array()) ;
+    }
+
+    static void testOut(String x)
+    {
+        testOut(x, allocByteBufferArray, allocCharBufferArray) ;
+        testOut(x, allocByteBufferDirect, allocCharBufferDirect) ;
+    }
+    
+    static interface Alloc<T extends Buffer> { T allocate(int len) ; } 
+    static Alloc<ByteBuffer> allocByteBufferArray = new Alloc<ByteBuffer>() {
+        @Override public ByteBuffer allocate(int len) { return ByteBuffer.allocate(len) ; }
+     } ;
+     static Alloc<ByteBuffer> allocByteBufferDirect = new Alloc<ByteBuffer>() {
+         @Override public ByteBuffer allocate(int len) { return ByteBuffer.allocateDirect(len) ; }
+     } ;
+     static Alloc<CharBuffer> allocCharBufferArray = new Alloc<CharBuffer>() {
+         @Override public CharBuffer allocate(int len) { return CharBuffer.allocate(len) ; }
+      } ;
+      static Alloc<CharBuffer> allocCharBufferDirect = new Alloc<CharBuffer>() {
+          @Override public CharBuffer allocate(int len) { return ByteBuffer.allocateDirect(2*len).asCharBuffer() ; }
+    } ;
+    
+    static void testOut(String x, Alloc<ByteBuffer> allocBB, Alloc<CharBuffer> allocCB)
+    {
+        testBinary(stringAsBytes(x)) ;
+
+        int N = x.length() ;
+        // First - get bytes the Java way.
+        ByteBuffer bytes = ByteBuffer.wrap(stringAsBytes(x)) ;
+        CharBuffer cb = allocCB.allocate(N) ;
+
+        BlockUTF8.toChars(bytes, cb) ;
+        cb.flip() ;
+        bytes.flip() ;
+
+        String str = cb.toString() ;
+        ByteBuffer bytes2 = allocBB.allocate(bytes.capacity()) ;
+        BlockUTF8.fromChars(cb, bytes2) ;
+        bytes2.flip() ;
+        assertTrue("Chars", sameBytes(bytes, bytes2)) ;
+    }
+
+    static void testBinary(byte[] binary, CharBuffer chars)
+    {
+        int N = binary.length ;
+        ByteBuffer bytes = ByteBuffer.wrap(binary) ;
+        CharBuffer cb = CharBuffer.allocate(N) ;
+        BlockUTF8.toChars(bytes, cb) ;
+        cb.flip() ;
+        assertTrue("Binary", sameChars(chars, cb));
+    }
+
+    static void testBinary(byte[] binary)
+    {
+        testBinary(binary, binary) ;
+    }
+
+    static void testBinary(byte[] binary, byte[] expected)
+    {
+        int N = binary.length ;
+        ByteBuffer bytes = ByteBuffer.wrap(binary) ;
+        CharBuffer cb = CharBuffer.allocate(N) ;
+        BlockUTF8.toChars(bytes, cb) ;
+        cb.flip() ;
+        bytes.position(0) ;
+        ByteBuffer bytes2 = ByteBuffer.allocate(2*N) ;  // Null bytes get expanded.
+        BlockUTF8.fromChars(cb, bytes2) ;
+        bytes2.flip() ;
+        sameBytes(bytes, bytes2) ;
+        assertTrue("Binary", sameBytes(ByteBuffer.wrap(expected), bytes2)) ;
+    }
+
+    // Does not move position.
+    static boolean sameBytes(ByteBuffer bb1, ByteBuffer bb2)
+    {
+        if ( bb1.remaining() != bb2.remaining() ) return false ;
+    
+        for ( int i = 0 ; i < bb1.remaining() ; i++ )
+            if ( bb1.get(i+bb1.position()) != bb2.get(i+bb2.position()) ) return false ;
+        return true ;
+    }
+    // Does not move position.
+    static boolean sameChars(CharBuffer cb1, CharBuffer cb2)
+    {
+        if ( cb1.remaining() != cb2.remaining() ) return false ;
+    
+        for ( int i = 0 ; i < cb1.remaining() ; i++ )
+            if ( cb1.get(i+cb1.position()) != cb2.get(i+cb2.position()) ) return false ;
+        return true ;
+    }
+    static byte[] stringAsBytes(String x)
+    {
+        try {
+            ByteArrayOutputStream bout = new ByteArrayOutputStream() ;
+            try(Writer out = new OutputStreamWriter(bout, utf8)) {
+                out.write(x) ;
+            }
+            byte[] bytes = bout.toByteArray() ;
+            return bytes ;
+        } catch (IOException ex) { throw new RuntimeException(ex) ; } 
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/io/TestBufferingWriter.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..08e81c6
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/io/TestBufferingWriter.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.StringWriter ;
+
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.junit.Test ;
+
+public class TestBufferingWriter extends BaseTest {
+    StringWriter    sw = null ;
+    BufferingWriter w  = null ;
+
+    public void create(int size, int blobSize) {
+        sw = new StringWriter() ;
+        w = new BufferingWriter(sw, size, blobSize) ;
+    }
+
+    public String string() {
+        return sw.toString() ;
+    }
+
+    @Test
+    public void write_01() {
+        create(10, 5) ;
+        w.output("x") ;
+        w.flush() ;
+        String x = string() ;
+        assertEquals("x", x) ;
+    }
+
+    @Test
+    public void write_02() {
+        create(10, 5) ;
+        w.output("foofoo") ; // Large object
+        w.flush() ;
+        String x = string() ;
+        assertEquals("foofoo", x) ;
+    }
+
+    @Test
+    public void write_03() {
+        create(10, 8) ;
+        w.output("a") ;
+        w.output("b") ;
+        w.output("c") ;
+        w.flush() ;
+        String x = string() ;
+        assertEquals("abc", x) ;
+    }
+
+    @Test
+    public void write_04() {
+        create(10, 8) ;
+        w.output("abcdefghijklmnopqrstuvwxyz") ;
+        w.output("XYZ") ;
+        w.flush() ;
+        String x = string() ;
+        assertEquals("abcdefghijklmnopqrstuvwxyzXYZ", x) ;
+    }
+
+    @Test
+    public void write_05() {
+        create(10, 8) ;
+        w.output("") ;
+        w.flush() ;
+        String x = string() ;
+        assertEquals("", x) ;
+    }
+
+    @Test
+    public void write_06() {
+        // Test closing the stream without flushing (the flush should be done
+        // implicitly)
+        create(100, 50) ;
+        w.output("test") ;
+        w.close() ;
+        String x = string() ;
+        assertEquals("test", x) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/io/TestIndentedWriter.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/io/TestIndentedWriter.java b/jena-base/src/test/java/org/apache/jena/atlas/io/TestIndentedWriter.java
new file mode 100644
index 0000000..77608d4
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/io/TestIndentedWriter.java
@@ -0,0 +1,58 @@
+/*
+ * 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 org.apache.jena.atlas.io.IndentedLineBuffer ;
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.junit.Test ;
+
+public class TestIndentedWriter extends BaseTest
+{
+    @Test public void write01()
+    {
+        try(IndentedLineBuffer b = new IndentedLineBuffer()) {
+            b.print("hell") ;
+            b.print("o") ;
+            assertEquals("hello", b.asString()) ;
+        }
+    }
+    
+    @Test public void write02()
+    {
+        try(IndentedLineBuffer b = new IndentedLineBuffer()) {
+            b.incIndent() ;
+            b.print("hell") ;
+            b.print("o") ;
+            b.decIndent() ;
+            assertEquals("  hello", b.asString()) ;
+        }
+    }
+    
+    @Test public void write03()
+    {
+        try(IndentedLineBuffer b = new IndentedLineBuffer()) {
+            b.incIndent() ;
+            b.printf("0x%04X", 1) ;
+            b.println() ;
+            b.print("XX") ;
+            b.decIndent() ;
+            assertEquals("  0x0001\n  XX", b.asString()) ;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/io/TestInputStreamBuffered.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/io/TestInputStreamBuffered.java b/jena-base/src/test/java/org/apache/jena/atlas/io/TestInputStreamBuffered.java
new file mode 100644
index 0000000..292bb64
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/io/TestInputStreamBuffered.java
@@ -0,0 +1,109 @@
+/*
+ * 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.ByteArrayInputStream ;
+import java.io.IOException ;
+import java.io.InputStream ;
+
+import org.apache.jena.atlas.io.InputStreamBuffered ;
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.apache.jena.atlas.lib.Bytes ;
+import org.junit.Test ;
+
+public class TestInputStreamBuffered extends BaseTest
+{
+    @Test public void test_01() throws IOException
+    {
+        InputStream in = stream("") ;
+        InputStream in2 = new InputStreamBuffered(in) ;
+        int x = count(in2) ;
+        assertEquals(0, x) ;
+    }
+    
+    @Test public void test_02() throws IOException
+    {
+        InputStream in = stream(1,2,3,4) ;
+        InputStream in2 = new InputStreamBuffered(in) ;
+        check(in2, 1,2,3,4) ;
+    }
+    
+    @Test public void test_03() throws IOException
+    {
+        InputStream in = stream(1,2,3,4) ;
+        InputStream in2 = new InputStreamBuffered(in, 2) ;
+        check(in2, 1,2,3,4) ;
+    }
+    
+    @Test public void test_04() throws IOException
+    {
+        InputStream in = stream(1,2,3,4) ;
+        InputStream in2 = new InputStreamBuffered(in, 1) ;
+        check(in2, 1,2,3,4) ;
+        assertEquals(-1, in.read()) ;
+    }
+    
+    
+    private static InputStream stream(String data)
+    {
+        byte[] b = Bytes.string2bytes(data) ;
+        return new ByteArrayInputStream(b) ;
+    }
+    
+    private static InputStream stream(byte...bytes)
+    {
+        return new ByteArrayInputStream(bytes) ;
+    }
+
+    private static InputStream stream(int...bytes)
+    {
+        return stream(ints2bytes(bytes)) ;
+    }
+
+    // Convenience.
+    private static byte[] ints2bytes(int...values)
+    {
+        byte b[] = new byte[values.length] ;
+        for ( int i = 0 ; i < b.length ; i++ )
+            b[i] = (byte)values[i] ;
+        return b ;
+    }
+
+    private static int count(InputStream in) throws IOException
+    {
+        int count = 0 ;
+        while(in.read() != -1 )
+            count++ ;
+        return count ;
+    }
+
+    private static void check(InputStream in, int ...bytes) throws IOException
+    {
+        check(in, ints2bytes(bytes)) ;
+    }
+
+    private static void check(InputStream in, byte ...bytes) throws IOException
+    {
+        for ( byte b : bytes )
+        {
+            assertEquals(b, (byte)in.read()) ;
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/io/TestPeekInputStreamSource.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/io/TestPeekInputStreamSource.java b/jena-base/src/test/java/org/apache/jena/atlas/io/TestPeekInputStreamSource.java
new file mode 100644
index 0000000..379a78a
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/io/TestPeekInputStreamSource.java
@@ -0,0 +1,44 @@
+/*
+ * 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.ByteArrayInputStream ;
+import java.io.UnsupportedEncodingException ;
+
+import org.apache.jena.atlas.io.PeekInputStream ;
+
+public class TestPeekInputStreamSource extends AbstractTestPeekInputStream
+{
+    @Override
+    PeekInputStream make(String contents, int size)
+    {
+        // Very carefuly ensure this is not a byte array-based PeekReader
+        ByteArrayInputStream bin ;
+        try
+        {
+            bin = new ByteArrayInputStream(contents.getBytes("ASCII")) ;
+        } catch (UnsupportedEncodingException ex)
+        {
+            ex.printStackTrace();
+            return null ;
+        }
+        
+        return PeekInputStream.make(bin, size) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/io/TestPeekReaderCharSequence.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/io/TestPeekReaderCharSequence.java b/jena-base/src/test/java/org/apache/jena/atlas/io/TestPeekReaderCharSequence.java
new file mode 100644
index 0000000..f6c5c42
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/io/TestPeekReaderCharSequence.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.io;
+
+import org.apache.jena.atlas.io.PeekReader ;
+
+
+
+public class TestPeekReaderCharSequence extends AbstractTestPeekReader
+{
+    @Override
+    PeekReader make(String contents, int size)
+    {
+        return PeekReader.readString(contents) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/io/TestPeekReaderSource.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/io/TestPeekReaderSource.java b/jena-base/src/test/java/org/apache/jena/atlas/io/TestPeekReaderSource.java
new file mode 100644
index 0000000..2624351
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/io/TestPeekReaderSource.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.atlas.io;
+
+import java.io.StringReader ;
+
+import org.apache.jena.atlas.io.PeekReader ;
+
+public class TestPeekReaderSource extends AbstractTestPeekReader
+{
+    @Override
+    PeekReader make(String contents, int size)
+    {
+        // Very carefuly ensure this is not a string-based PeekReader
+        StringReader r = new StringReader(contents) ;
+        return PeekReader.make(r, size) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/io/TestPrintUtils.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/io/TestPrintUtils.java b/jena-base/src/test/java/org/apache/jena/atlas/io/TestPrintUtils.java
new file mode 100644
index 0000000..aa6f3a6
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/io/TestPrintUtils.java
@@ -0,0 +1,59 @@
+/*
+ * 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.ByteArrayOutputStream ;
+import java.io.IOException ;
+import java.io.OutputStreamWriter ;
+import java.io.Writer ;
+
+import org.apache.jena.atlas.io.OutputUtils ;
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.junit.Test ;
+
+public class TestPrintUtils extends BaseTest
+{
+    @Test public void hex1()
+    {
+        String s = test(0,4) ;
+        assertEquals("0000", s) ;
+    }
+    
+    @Test public void hex2()
+    {
+        String s = test(1,8) ;
+        assertEquals("00000001", s) ;
+    }
+
+    @Test public void hex3()
+    {
+        String s = test(0xFF,2) ;
+        assertEquals("FF", s) ;
+    }
+
+    private static String test(int value, int width)
+    {
+        ByteArrayOutputStream x = new ByteArrayOutputStream() ;
+        Writer out = new OutputStreamWriter(x) ;
+        OutputUtils.printHex(out, value, width) ;
+        try { out.flush() ; } catch (IOException ex) {}
+        String s = x.toString() ;
+        return s ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/io/TestStreamUTF8.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/io/TestStreamUTF8.java b/jena-base/src/test/java/org/apache/jena/atlas/io/TestStreamUTF8.java
new file mode 100644
index 0000000..4c92978
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/io/TestStreamUTF8.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.atlas.io;
+
+import java.io.ByteArrayInputStream ;
+import java.io.ByteArrayOutputStream ;
+import java.io.IOException ;
+import java.io.OutputStreamWriter ;
+import java.io.Writer ;
+import java.nio.charset.Charset ;
+import java.nio.charset.CharsetDecoder ;
+import java.nio.charset.CharsetEncoder ;
+
+import org.apache.jena.atlas.io.InStreamUTF8 ;
+import org.apache.jena.atlas.io.OutStreamUTF8 ;
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.apache.jena.atlas.lib.Chars ;
+import org.junit.Test ;
+
+public class TestStreamUTF8 extends BaseTest
+    {
+        static Charset utf8 = Chars.charsetUTF8 ;
+        static CharsetDecoder dec = utf8.newDecoder() ;
+        static CharsetEncoder enc = utf8.newEncoder() ;
+        
+        // UTF-8 encoding.
+        // character '¢' = code point U+00A2 -> C2 A2
+        // character '€' = code point U+20AC -> E2 82 AC
+        
+        static private final String asciiBase             = "abc" ;
+        static private final String latinBase             = "Àéíÿ" ;
+        static private final String latinExtraBase        = "ỹfifl" ;  // fi-ligature, fl-ligature
+        static private final String greekBase             = "αβγ" ;
+        static private final String hewbrewBase           = "אבג" ;
+        static private final String arabicBase            = "ءآأ";
+        static private final String symbolsBase           = "☺☻♪♫" ;
+        static private final String chineseBase           = "孫子兵法" ; // The Art of War 
+        static private final String japaneseBase          = "日本" ;    // Japanese
+        
+        @Test public void test_in_00() { testIn("") ; }
+        @Test public void test_in_01() { testIn(asciiBase) ; }
+        @Test public void test_in_02() { testIn(latinBase) ; }
+        @Test public void test_in_03() { testIn(latinExtraBase) ; }
+        @Test public void test_in_04() { testIn(greekBase) ; }
+        @Test public void test_in_05() { testIn(hewbrewBase) ; }
+        @Test public void test_in_06() { testIn(arabicBase) ; }
+        @Test public void test_in_07() { testIn(symbolsBase) ; }
+        @Test public void test_in_08() { testIn(chineseBase) ; }
+        @Test public void test_in_09() { testIn(japaneseBase) ; }
+        
+        @Test public void test_out_00() { testIn("") ; }
+        @Test public void test_out_01() { testOut(asciiBase) ; }
+        @Test public void test_out_02() { testOut(latinBase) ; }
+        @Test public void test_out_03() { testOut(latinExtraBase) ; }
+        @Test public void test_out_04() { testOut(greekBase) ; }
+        @Test public void test_out_05() { testOut(hewbrewBase) ; }
+        @Test public void test_out_06() { testOut(arabicBase) ; }
+        @Test public void test_out_07() { testOut(symbolsBase) ; }
+        @Test public void test_out_08() { testOut(chineseBase) ; }
+        @Test public void test_out_09() { testOut(japaneseBase) ; }
+        
+        static void testIn(String x)
+        {
+            try {
+                byte[] bytes = stringAsBytes(x) ;
+
+                ByteArrayInputStream bin = new ByteArrayInputStream(bytes) ;
+                // Create string from bytes
+                try(InStreamUTF8 r = new InStreamUTF8(bin)) {
+                    char[] cbuff = new char[x.length()*10] ;    // Way too big
+                    int len = r.read(cbuff) ;
+                    String str = new String(cbuff, 0 , len) ;
+                    assertEquals(x, str) ;
+                }
+            } catch (IOException ex) { throw new RuntimeException(ex) ; } 
+        }
+
+        static void testOut(String x)
+        {
+            try {
+                byte[] bytes = stringAsBytes(x) ;
+                ByteArrayOutputStream bout = new ByteArrayOutputStream() ;
+                try(Writer out = new OutStreamUTF8(bout)) {
+                    out.write(x) ;
+                }
+                byte[] bytes2 = bout.toByteArray() ;
+                assertArrayEquals(bytes, bytes2) ;
+            } catch (IOException ex) { throw new RuntimeException(ex) ; } 
+        }
+
+        static byte[] stringAsBytes(String x)
+        {
+            try {
+                ByteArrayOutputStream bout = new ByteArrayOutputStream() ;
+                try(Writer out = new OutputStreamWriter(bout, utf8)) {
+                    out.write(x) ;
+                }
+                byte[] bytes = bout.toByteArray() ;
+                return bytes ;
+            } catch (IOException ex) { throw new RuntimeException(ex) ; } 
+        }
+    }


[2/9] jena git commit: Import of lib, io, logging and iterator packages

Posted by an...@apache.org.
http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/iterator/TS_Iterator.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/iterator/TS_Iterator.java b/jena-base/src/test/java/org/apache/jena/atlas/iterator/TS_Iterator.java
new file mode 100644
index 0000000..37c8430
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/iterator/TS_Iterator.java
@@ -0,0 +1,38 @@
+/*
+ * 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.junit.runner.RunWith ;
+import org.junit.runners.Suite ;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses( {
+    TestIter.class
+    , TestIteratorPeek.class
+    , TestIteratorArray.class
+    , TestIteratorPushback.class
+    , TestIteratorWithHistory.class
+    , TestIteratorWithBuffer.class
+    , TestIteratorSlotted.class
+} )
+
+public class TS_Iterator
+{
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/iterator/TestIter.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/iterator/TestIter.java b/jena-base/src/test/java/org/apache/jena/atlas/iterator/TestIter.java
new file mode 100644
index 0000000..56cf8a9
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/iterator/TestIter.java
@@ -0,0 +1,539 @@
+/*
+ * 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 static org.junit.Assert.assertEquals ;
+import static org.junit.Assert.assertFalse ;
+import static org.junit.Assert.assertTrue ;
+
+import java.util.ArrayList ;
+import java.util.Arrays ;
+import java.util.Iterator ;
+import java.util.List ;
+
+import org.junit.Test ;
+
+public class TestIter
+{
+    List<String> data0 = new ArrayList<>() ;
+    List<String> data1 = Arrays.asList("a") ;
+    List<String> data2 = Arrays.asList("x","y","z") ;
+    List<String> data3 = Arrays.asList(null, "x", null, null, null, "y", "z", null);
+ 
+    @Test
+    public void append_1()
+    {
+        Iterator<String> iter = Iter.append(data1.iterator(), data0.iterator()) ;
+        test(iter, "a") ;
+    }
+        
+        
+    @Test
+    public void append_2()
+    {
+        Iterator<String> iter = Iter.append(data0.iterator(), data1.iterator()) ;
+        test(iter, "a") ;
+    }
+        
+    @Test
+    public void append_3()
+    {
+        Iterator<String> iter = Iter.append(data1.iterator(), data2.iterator()) ;
+        test(iter, "a", "x", "y", "z") ;
+    }
+
+    @Test
+    public void append_4()
+    {
+        List<String> L = new ArrayList<>(3);
+        L.add("a");
+        L.add("b");
+        L.add("c");
+        List<String> R = new ArrayList<>(3);
+        R.add("d");
+        R.add("e");
+        R.add("f");
+        
+        
+        Iterator<String> LR = Iter.append(L.iterator(), R.iterator()) ;
+        
+        while (LR.hasNext())
+        {
+            String s = LR.next();
+            
+            if ("c".equals(s))
+            {
+                LR.hasNext();  // test for JENA-60
+                LR.remove();
+            }
+        }
+        
+        assertEquals("ab", Iter.asString(L, ""));
+        assertEquals("def", Iter.asString(R, ""));
+    }
+    
+    @Test
+    public void append_5()
+    {
+        List<String> L = new ArrayList<>(3);
+        L.add("a");
+        L.add("b");
+        L.add("c");
+        List<String> R = new ArrayList<>(3);
+        R.add("d");
+        R.add("e");
+        R.add("f");
+        
+        
+        Iterator<String> LR = Iter.append(L.iterator(), R.iterator()) ;
+        
+        while (LR.hasNext())
+        {
+            String s = LR.next();
+            
+            if ("d".equals(s))
+            {
+                LR.hasNext();  // test for JENA-60
+                LR.remove();
+            }
+        }
+        
+        assertEquals("abc", Iter.asString(L, ""));
+        assertEquals("ef", Iter.asString(R, ""));
+    }
+    
+    @Test
+    public void append_6()
+    {
+        List<String> L = new ArrayList<>(3);
+        L.add("a");
+        L.add("b");
+        L.add("c");
+        List<String> R = new ArrayList<>(3);
+        R.add("d");
+        R.add("e");
+        R.add("f");
+        
+        
+        Iterator<String> LR = Iter.append(L.iterator(), R.iterator()) ;
+        
+        while (LR.hasNext())
+        {
+            LR.next() ;
+        }
+        LR.remove() ;
+        
+        assertEquals("abc", Iter.asString(L, "")) ;
+        assertEquals("de", Iter.asString(R, "")) ;
+    }
+    
+    @Test
+    public void asString_1() 
+    {
+        String x = Iter.asString(data0, "") ;
+        assertEquals("", x) ;
+    }
+
+    @Test
+    public void asString_2() 
+    {
+        String x = Iter.asString(data1, "") ;
+        assertEquals("a", x) ;
+    }
+
+    @Test
+    public void asString_3() 
+    {
+        String x = Iter.asString(data1, "/") ;
+        assertEquals("a", x) ;
+    }
+
+    @Test
+    public void asString_4() 
+    {
+        String x = Iter.asString(data2, "/") ;
+        assertEquals("x/y/z", x) ;
+    }
+    
+    private void test(Iterator<?> iter, Object... items)
+    {
+        for ( Object x : items )
+        {
+            assertTrue(iter.hasNext()) ;
+            assertEquals(x, iter.next()) ;
+        }
+        assertFalse(iter.hasNext()) ;
+    }
+    
+    static Iter.Folder<String, String> f1 = new Iter.Folder<String, String>() {
+                                                 @Override
+                                                public String eval(String acc, String arg)
+                                                 {
+                                                     return acc + arg ;
+                                                 }
+                                             } ;
+    
+    @Test
+    public void fold_01() 
+    {
+        String[] x = { "a", "b", "c" } ;
+        String z = Iter.foldLeft(Arrays.asList(x), f1, "X") ;
+        assertEquals("Xabc", z) ;
+    }
+    
+    @Test
+    public void fold_02() 
+    {
+        String[] x = { "a", "b", "c" } ;
+        String z = Iter.foldRight(Arrays.asList(x), f1, "X") ;
+        assertEquals("Xcba", z) ;
+    }
+    
+    @Test
+    public void fold_03() 
+    {
+        String[] x = {  } ;
+        String z = Iter.foldLeft(Arrays.asList(x), f1, "X") ;
+        assertEquals("X", z) ;
+    }
+    
+    @Test
+    public void fold_04() 
+    {
+        String[] x = { } ;
+        String z = Iter.foldRight(Arrays.asList(x), f1, "X") ;
+        assertEquals("X", z) ;
+    }
+
+    
+    @Test
+    public void map_01()
+    {
+        Iterator<String> it = Iter.map(data2.iterator(), new Transform<String,String>()
+        {
+            @Override
+            public String convert(String item)
+            {
+                return item + item;
+            }
+        });
+        test(it, "xx", "yy", "zz");
+    }
+    
+    @Test
+    public void mapMany_01()
+    {
+        Iterator<String> it = Iter.mapMany(data2.iterator(), new Transform<String,Iterator<String>>()
+        {
+            @Override
+            public Iterator<String> convert(String item)
+            {
+                List<String> l = new ArrayList<>(2);
+                l.add(item);
+                l.add(item + item);
+                return l.iterator();
+            }
+        });
+        
+        test(it, "x", "xx", "y", "yy", "z", "zz");
+    }
+    
+    @Test
+    public void mapMany_02()
+    {
+        Iterator<String> it = Iter.mapMany(data2.iterator(), new Transform<String,Iterator<String>>()
+        {
+            @Override
+            public Iterator<String> convert(String item)
+            {
+                return Iter.nullIterator() ;
+            }
+        });
+        
+        assertFalse(it.hasNext()) ;
+    }
+    
+    @Test
+    public void mapMany_03()
+    {
+        Iterator<String> it = Iter.mapMany(data2.iterator(), new Transform<String,Iterator<String>>()
+        {
+            int count = 0 ;
+            @Override
+            public Iterator<String> convert(String item)
+            {
+                count++ ;
+                if ( count%2 == 1 )
+                    return Iter.singleton(item) ;
+                else
+                    return Iter.nullIterator() ;
+            }
+        });
+        
+        test(it, "x", "z");
+    }
+
+    @Test
+    public void mapMany_04()
+    {
+        Iterator<String> it = Iter.mapMany(data2.iterator(), new Transform<String,Iterator<String>>()
+        {
+            int count = 0 ;
+            @Override
+            public Iterator<String> convert(String item)
+            {
+                count++ ;
+                if ( count%2 == 0 )
+                    return Iter.singleton(item) ;
+                else
+                    return Iter.nullIterator() ;
+            }
+        });
+        
+        test(it, "y");
+    }
+
+	
+    Filter<String> filter = new Filter<String>() {
+        @Override
+        public boolean accept(String item)
+        {
+            return item.length() == 1 ;
+        }} ;
+   
+    @Test
+    public void first_01()
+    {
+        Iter<String> iter = Iter.nullIter() ;
+        assertEquals(null, Iter.first(iter, filter)) ;
+    }
+
+    @Test
+    public void first_02()
+    {
+        List<String> data = Arrays.asList( "11", "A", "B", "C") ;
+        assertEquals("A", Iter.first(data, filter)) ;
+    }
+
+    @Test
+    public void first_03()
+    {
+        List<String> data = Arrays.asList( "11", "AA", "BB", "CC") ;
+        assertEquals(null, Iter.first(data, filter)) ;
+    }
+ 
+    @Test
+    public void first_04()
+    {
+        Iter<String> iter = Iter.nullIter() ;
+        assertEquals(-1, Iter.firstIndex(iter, filter)) ;
+    }
+
+    @Test
+    public void first_05()
+    {
+        List<String> data = Arrays.asList( "11", "A", "B", "C") ;
+        assertEquals(1, Iter.firstIndex(data, filter)) ;
+    }
+
+    @Test
+    public void first_06()
+    {
+        List<String> data = Arrays.asList( "11", "AA", "BB", "CC") ;
+        assertEquals(-1, Iter.firstIndex(data, filter)) ;
+    }
+
+    @Test
+    public void last_01()
+    {
+        Iter<String> iter = Iter.nullIter() ;
+        assertEquals(null, Iter.last(iter, filter)) ;
+    }
+
+    @Test
+    public void last_02()
+    {
+        List<String> data = Arrays.asList( "11", "A", "B", "C") ;
+        assertEquals("C", Iter.last(data, filter)) ;
+    }
+
+    @Test
+    public void last_03()
+    {
+        List<String> data = Arrays.asList( "11", "AA", "BB", "CC") ;
+        assertEquals(null, Iter.last(data, filter)) ;
+    }
+ 
+    @Test
+    public void last_04()
+    {
+        Iter<String> iter = Iter.nullIter() ;
+        assertEquals(-1, Iter.lastIndex(iter, filter)) ;
+    }
+
+    @Test
+    public void last_05()
+    {
+        List<String> data = Arrays.asList( "11", "A", "B", "C") ;
+        assertEquals(3, Iter.lastIndex(data, filter)) ;
+    }
+
+    @Test
+    public void last_06()
+    {
+        List<String> data = Arrays.asList( "11", "AA", "BB", "CC") ;
+        assertEquals(-1, Iter.lastIndex(data, filter)) ;
+    }
+    
+    @Test
+    public void take_01()
+    {
+        List<String> data = Arrays.asList( "1", "A", "B", "CC") ;
+        List<String> data2 = Iter.take(data.iterator(), 2) ;
+        assertEquals(2, data2.size()) ;
+        assertEquals("1", data2.get(0)) ;
+        assertEquals("A", data2.get(1)) ;
+    }
+    
+    
+    @Test
+    public void take_02()
+    {
+        List<String> data = Arrays.asList( "1", "A", "B", "CC") ;
+        List<String> data2 = Iter.take(data.iterator(), 0) ;
+        assertEquals(0, data2.size()) ;
+    }
+
+    @Test
+    public void take_03()
+    {
+        List<String> data = Arrays.asList( "1", "A", "B", "CC") ;
+        List<String> data2 = Iter.take(data.iterator(), 10) ;
+        assertEquals(4, data2.size()) ;
+        assertEquals("1", data2.get(0)) ;
+        assertEquals("A", data2.get(1)) ;
+        assertEquals("B", data2.get(2)) ;
+        assertEquals("CC", data2.get(3)) ;
+    }
+    
+    @Test
+    public void filter_01()
+    {
+        test(Iter.removeNulls(data3.iterator()), "x", "y", "z");
+    }
+    
+    @Test
+    public void filter_02()
+    {
+        Iterator<String> it = Iter.filter(data3.iterator(), new Filter<String>()
+        {
+            @Override
+            public boolean accept(String item)
+            {
+                return "x".equals(item) || "z".equals(item) ;
+            }
+        });
+        
+        test(it, "x", "z");
+    }
+    
+    @Test
+    public void filter_03()
+    {
+        Iterator<String> it = Iter.filter(data3.iterator(), new Filter<String>()
+        {
+            @Override
+            public boolean accept(String item)
+            {
+                return (null == item) || "x".equals(item) ;
+            }
+        });
+        
+        test(it, null, "x", null, null, null, null);
+    }
+    
+    @Test public void distinct_01() 
+    {
+        List<String> x = Arrays.asList("a", "b", "a") ;
+        Iterator<String> iter = Iter.distinct(x.iterator()) ;
+        test(iter, "a", "b") ;
+    }
+    
+    @Test public void distinct_02() 
+    {
+        List<String> x = Arrays.asList("a", "b", "a") ;
+        Iterator<String> iter = Iter.distinctAdjacent(x.iterator()) ;
+        test(iter, "a", "b", "a") ;
+    }
+    
+    @Test public void distinct_03() 
+    {
+        List<String> x = Arrays.asList("a", "a", "b", "b", "b", "a", "a") ;
+        Iterator<String> iter = Iter.distinct(x.iterator()) ;
+        test(iter, "a", "b") ;
+    }
+    
+    @Test public void distinct_04() 
+    {
+        List<String> x = Arrays.asList("a", "a", "b", "b", "b", "a", "a") ;
+        Iterator<String> iter = Iter.distinctAdjacent(x.iterator()) ;
+        test(iter, "a", "b", "a") ;
+    }
+    
+    private static class AlwaysAcceptFilter implements Filter<Object> {
+        @Override
+        public boolean accept(Object o) {
+            return true;
+        }
+    }
+
+    private static class NeverAcceptFilter implements Filter<Object> {
+        @Override
+        public boolean accept(Object o) {
+            return false;
+        }
+    }
+
+    
+    private static class AlwaysAcceptFilterStack extends FilterStack<Object> {
+        public AlwaysAcceptFilterStack(Filter<Object> f) {
+            super(f);
+        }
+
+        @Override
+        public boolean acceptAdditional(Object o) {
+            return true;
+        }
+    }
+
+    @Test
+    public void testFilterStack_01() {
+        Filter<Object> filter = new AlwaysAcceptFilter();
+        FilterStack<Object> filterStack = new AlwaysAcceptFilterStack(filter);
+        assertTrue(filterStack.accept(new Object()));
+    }
+    
+    @Test
+    public void testFilterStack_02() {
+        Filter<Object> filter = new NeverAcceptFilter();
+        FilterStack<Object> filterStack = new AlwaysAcceptFilterStack(filter);
+        assertFalse(filterStack.accept(new Object()));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/iterator/TestIteratorArray.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/iterator/TestIteratorArray.java b/jena-base/src/test/java/org/apache/jena/atlas/iterator/TestIteratorArray.java
new file mode 100644
index 0000000..5108865
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/iterator/TestIteratorArray.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.iterator;
+
+import java.util.Iterator ;
+import java.util.NoSuchElementException ;
+
+import org.apache.jena.atlas.iterator.IteratorArray ;
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.junit.Test ;
+
+public class TestIteratorArray extends BaseTest
+{
+    IteratorArray<String> create(String ... a)
+    {
+        return IteratorArray.create(a) ;
+    }
+    
+    IteratorArray<String> create(int start, int finish, String ... a)
+    {
+        return IteratorArray.create(a, start, finish) ;
+    }
+    
+    @Test public void arrayIterator_1()
+    {
+        Iterator<String> iter = create() ;
+        assertFalse(iter.hasNext()) ;
+        assertFalse(iter.hasNext()) ;
+    }
+
+    @Test public void arrayIterator_2()
+    {
+        Iterator<String> iter = create("a") ;
+        assertTrue(iter.hasNext()) ;
+        assertEquals("a", iter.next()) ;
+        assertFalse(iter.hasNext()) ;
+        assertFalse(iter.hasNext()) ;
+    }
+
+    
+    @Test public void arrayIterator_3()
+    {
+        Iterator<String> iter = create("a", "b", "c") ;
+        assertTrue(iter.hasNext()) ;
+        assertEquals("a", iter.next()) ;
+        assertTrue(iter.hasNext()) ;
+        assertEquals("b", iter.next()) ;
+        assertTrue(iter.hasNext()) ;
+        assertEquals("c", iter.next()) ;
+        assertFalse(iter.hasNext()) ;
+        assertFalse(iter.hasNext()) ;
+    }
+    
+    @Test public void arrayIterator_4()
+    {
+        Iterator<String> iter = create("a") ;
+        assertEquals("a", iter.next()) ;
+        try { iter.next() ; fail("Expected NoSuchElementException") ; }
+        catch (NoSuchElementException ex) {}
+    }
+    
+    @Test public void arrayIterator_5()
+    {
+        Iterator<String> iter = create(0,1, "a", "b", "c") ;
+        assertEquals("a", iter.next()) ;
+        assertFalse(iter.hasNext()) ;
+    }
+    
+    @Test public void arrayIterator_6()
+    {
+        Iterator<String> iter = create(1, 3, "a", "b", "c", "d") ;
+        assertEquals("b", iter.next()) ;
+        assertEquals("c", iter.next()) ;
+        assertFalse(iter.hasNext()) ;
+    }
+    
+    @Test public void arrayIterator_7()
+    {
+        IteratorArray<String> iter = create(1, 3, "a", "b", "c", "d") ;
+        assertEquals("b", iter.current()) ;
+        assertEquals("b", iter.current()) ;
+        assertEquals("b", iter.next()) ;
+        assertEquals("c", iter.current()) ;
+        assertEquals("c", iter.next()) ;
+        assertFalse(iter.hasNext()) ;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/iterator/TestIteratorPeek.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/iterator/TestIteratorPeek.java b/jena-base/src/test/java/org/apache/jena/atlas/iterator/TestIteratorPeek.java
new file mode 100644
index 0000000..33f0b70
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/iterator/TestIteratorPeek.java
@@ -0,0 +1,107 @@
+/*
+ * 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.ArrayList ;
+import java.util.List ;
+
+import org.apache.jena.atlas.iterator.Iter ;
+import org.apache.jena.atlas.iterator.IteratorArray ;
+import org.apache.jena.atlas.iterator.PeekIterator ;
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.junit.Test ;
+
+public class TestIteratorPeek extends BaseTest
+{
+    List<String> data0 = new ArrayList<>() ;
+    List<String> data1 = new ArrayList<>() ;
+    {
+        data1.add("a") ;
+    }
+    
+    List<String> data2 = new ArrayList<>() ;
+    {
+        data2.add("x") ;
+        data2.add("y") ;
+        data2.add("z") ;
+    }
+    
+    @Test public void iter_01() 
+    {
+        Iter<String> iter = Iter.iter(data2) ;
+        iter = iter.append(data2.iterator()) ;
+        test(iter, "x", "y", "z", "x", "y", "z") ;
+    }
+    
+    private void test(Iter<?> iter, Object... items)
+    {
+        for ( Object x : items )
+        {
+            assertTrue(iter.hasNext()) ;
+            assertEquals(x, iter.next()) ;
+        }
+        assertFalse(iter.hasNext()) ;
+    }
+    
+    private static PeekIterator<String> create(String...a)
+    { 
+        return new PeekIterator<>(IteratorArray.create(a)) ;
+    }
+    
+    @Test public void peek_1()
+    {
+        PeekIterator<String> peek = create("a", "b", "c") ;
+        assertEquals("a", peek.peek()) ;
+        test(Iter.iter(peek), "a", "b", "c") ;
+    }
+    
+    @Test public void peek_2()
+    {
+        PeekIterator<String> peek = create() ;
+        assertFalse(peek.hasNext()) ;
+    }
+
+    @Test public void peek_3()
+    {
+        PeekIterator<String> peek = create("a") ;
+        assertEquals("a", peek.peek()) ;
+    }
+
+    @Test public void peek_4()
+    {
+        PeekIterator<String> peek = create("a") ;
+        assertEquals("a", peek.peek()) ;
+        assertEquals("a", peek.peek()) ;
+        assertEquals("a", peek.next()) ;
+        assertFalse(peek.hasNext()) ;
+    }
+
+    @Test public void peek_5()
+    {
+        PeekIterator<String> peek = create("a", "b") ;
+        assertEquals("a", peek.peek()) ;
+        assertEquals("a", peek.peek()) ;
+        assertEquals("a", peek.next()) ;
+        assertTrue(peek.hasNext()) ;
+        assertEquals("b", peek.peek()) ;
+        assertEquals("b", peek.peek()) ;
+        assertEquals("b", peek.next()) ;
+        assertFalse(peek.hasNext()) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/iterator/TestIteratorPushback.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/iterator/TestIteratorPushback.java b/jena-base/src/test/java/org/apache/jena/atlas/iterator/TestIteratorPushback.java
new file mode 100644
index 0000000..51916a3
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/iterator/TestIteratorPushback.java
@@ -0,0 +1,93 @@
+/*
+ * 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.ArrayList ;
+import java.util.List ;
+
+import org.apache.jena.atlas.iterator.Iter ;
+import org.apache.jena.atlas.iterator.PushbackIterator ;
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.junit.Test ;
+
+public class TestIteratorPushback extends BaseTest
+{
+
+    static List<String> data = new ArrayList<>() ;
+    static {
+        data.add("a") ;
+        data.add("b") ;
+        data.add("c") ;
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void pushback01() { new PushbackIterator<String>(null) ; }
+    
+    @Test public void pushback02()
+    { 
+        PushbackIterator<String> iter = new PushbackIterator<>(data.iterator()) ;
+        assertEquals("a", iter.next()) ;
+        assertEquals("b", iter.next()) ;
+        assertEquals("c", iter.next()) ;
+        assertFalse(iter.hasNext()) ;
+    }
+    
+    @Test public void pushback03()
+    { 
+        PushbackIterator<String> iter = new PushbackIterator<>(data.iterator()) ;
+        iter.pushback("x") ;
+        assertEquals("x", iter.next()) ;
+        assertEquals("a", iter.next()) ;
+        assertEquals(2, Iter.count(iter)) ;
+    }
+    
+    @Test public void pushback04()
+    { 
+        PushbackIterator<String> iter = new PushbackIterator<>(data.iterator()) ;
+        assertEquals("a", iter.next()) ;
+        iter.pushback("x") ;
+        assertEquals("x", iter.next()) ;
+        assertEquals("b", iter.next()) ;
+        assertEquals(1, Iter.count(iter)) ;
+    }
+    
+    @Test public void pushback05()
+    { 
+        PushbackIterator<String> iter = new PushbackIterator<>(data.iterator()) ;
+        assertEquals("a", iter.next()) ;
+        iter.pushback("x") ;
+        iter.pushback("y") ;
+        assertEquals("y", iter.next()) ;
+        assertEquals("x", iter.next()) ;
+        assertEquals("b", iter.next()) ;
+        assertEquals(1, Iter.count(iter)) ;
+    }
+    
+    @Test public void pushback06()
+    { 
+        PushbackIterator<String> iter = new PushbackIterator<>(data.iterator()) ;
+        assertEquals(3, Iter.count(iter)) ;
+        iter.pushback("x") ;
+        iter.pushback("y") ;
+        assertEquals("y", iter.next()) ;
+        assertEquals("x", iter.next()) ;
+        assertFalse(iter.hasNext()) ;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/iterator/TestIteratorSlotted.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/iterator/TestIteratorSlotted.java b/jena-base/src/test/java/org/apache/jena/atlas/iterator/TestIteratorSlotted.java
new file mode 100644
index 0000000..8abf440
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/iterator/TestIteratorSlotted.java
@@ -0,0 +1,136 @@
+/*
+ * 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.Arrays ;
+import java.util.Collection ;
+import java.util.Iterator ;
+import java.util.List ;
+
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.junit.Test ;
+import org.junit.runner.RunWith ;
+import org.junit.runners.Parameterized ;
+import org.junit.runners.Parameterized.Parameters ;
+
+@RunWith(Parameterized.class)
+
+public class TestIteratorSlotted extends BaseTest
+{
+    @Parameters(name = "{index}: {0}")
+    public static Collection<Object[]> implementations() {
+        IterFactory factory1 = new IterFactory() {
+            @Override
+            public IteratorSlotted<String> create(String... array) {
+                return new IterStr1(array) ;
+            }} ;  
+        IterFactory factory2 = new IterFactory() {
+                @Override
+                public IteratorSlotted<String> create(String... array) {
+                    return new IterStr2(array) ;
+                }} ;  
+        return Arrays.asList(new Object[][] { {"hasMore accurate", factory1}, {"hasMore always true", factory2} }) ;
+    }
+    
+    /** Accurate hasMore */
+    static class IterStr1 extends IteratorSlotted<String>
+    {
+        private List<String>     array ;
+        private Iterator<String> iter ;
+
+        IterStr1(String... array) {
+            this.array = Arrays.asList(array) ;
+            iter = this.array.iterator() ;
+        }
+
+        @Override
+        protected String moveToNext() {
+            return iter.next() ;
+        }
+
+        @Override
+        protected boolean hasMore() {
+            return iter.hasNext() ;
+        }
+    }
+    
+    /** hasMore ios always true, returns null in moveToNext */
+    static class IterStr2 extends IteratorSlotted<String>
+    {
+        private List<String>     array ;
+        private Iterator<String> iter ;
+
+        IterStr2(String... array) {
+            this.array = Arrays.asList(array) ;
+            iter = this.array.iterator() ;
+        }
+
+        @Override
+        protected String moveToNext() {
+            if ( !iter.hasNext() )
+                return null ;
+            return iter.next() ;
+        }
+
+        @Override
+        protected boolean hasMore() {
+            return true ;
+        }
+    }
+    
+
+    interface IterFactory { IteratorSlotted<String> create(String...array) ; }
+    
+
+    private IterFactory factory ;
+    
+    public TestIteratorSlotted(String name, IterFactory factory) {
+        this.factory = factory ;
+    }
+    
+    @Test public void iter_01()
+    {
+        IteratorSlotted<String> iter = factory.create() ;
+        assertFalse(iter.hasNext()) ;
+    }
+    
+    @Test public void iter_02()
+    {
+        IteratorSlotted<String> iter = factory.create("A") ;
+        assertTrue(iter.hasNext()) ;
+        assertEquals("A", iter.peek()) ;
+        assertEquals("A", iter.peek()) ;
+        assertEquals("A", iter.next()) ;
+        assertFalse(iter.hasNext()) ;
+        assertNull(iter.peek()) ;
+    }
+    
+    @Test public void iter_03()
+    {
+        IteratorSlotted<String> iter = factory.create("A", "B") ;
+        assertTrue(iter.hasNext()) ;
+        assertEquals("A", iter.peek()) ;
+        assertEquals("A", iter.next()) ;
+        assertEquals("B", iter.peek()) ;
+        assertEquals("B", iter.next()) ;
+        assertFalse(iter.hasNext()) ;
+    }
+    
+    
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/iterator/TestIteratorWithBuffer.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/iterator/TestIteratorWithBuffer.java b/jena-base/src/test/java/org/apache/jena/atlas/iterator/TestIteratorWithBuffer.java
new file mode 100644
index 0000000..5bc70fd
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/iterator/TestIteratorWithBuffer.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.iterator;
+
+import java.util.Arrays ;
+import java.util.List ;
+
+import org.apache.jena.atlas.iterator.IteratorWithBuffer ;
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.junit.Test ;
+
+public class TestIteratorWithBuffer extends BaseTest
+{
+
+    @Test public void iterBuffer_01()
+    {
+        IteratorWithBuffer<String> iter = createBuffered(1, "a", "b", "c") ;
+        assertEquals(1, iter.currentSize()) ;
+        assertEquals("a", iter.peek(0)) ;
+        assertEquals(1, iter.currentSize()) ;
+        assertEquals("a", iter.next()) ;
+        assertEquals(1, iter.currentSize()) ;
+        assertEquals("b", iter.peek(0)) ;
+        assertEquals(1, iter.currentSize()) ;
+        assertEquals("b", iter.next()) ;
+        assertEquals(1, iter.currentSize()) ;
+        assertEquals("c", iter.peek(0)) ;
+        assertEquals(1, iter.currentSize()) ;
+        assertEquals("c", iter.next()) ;
+        assertEquals(0, iter.currentSize()) ;
+        assertEquals(null, iter.peek(0)) ;
+        assertEquals(0, iter.currentSize()) ;
+    }
+
+    @Test public void iterBuffer_02()
+    {
+        IteratorWithBuffer<String> iter = createBuffered(2, "a", "b", "c") ;
+        assertEquals(2, iter.currentSize()) ;
+        assertEquals("a", iter.peek(0)) ;
+        assertEquals("b", iter.peek(1)) ;
+        assertEquals("a", iter.next()) ;
+        
+        assertEquals("b", iter.peek(0)) ;
+        assertEquals("c", iter.peek(1)) ;
+        assertEquals("b", iter.next()) ;
+        
+        assertEquals("c", iter.peek(0)) ;
+        assertEquals(null, iter.peek(1)) ;
+        assertEquals("c", iter.next()) ;
+        assertEquals(null, iter.peek(0)) ;
+    }
+
+    @Test public void iterBuffer_03()
+    {
+        IteratorWithBuffer<String> iter = createBuffered(1) ;
+        assertEquals(null, iter.peek(0)) ;
+    }
+
+    @Test(expected=IndexOutOfBoundsException.class)
+    public void iterBuffer_04()
+    {
+        IteratorWithBuffer<String> iter = createBuffered(0, "a") ;
+        assertEquals(null, iter.peek(0)) ;
+    }
+
+    @Test public void iterBuffer_05()
+    {
+        IteratorWithBuffer<String> iter = createBuffered(2, "a") ;
+        assertEquals("a", iter.peek(0)) ;
+        assertEquals(null, iter.peek(1)) ;
+        assertEquals("a", iter.next()) ;
+    }
+
+    private IteratorWithBuffer<String> createBuffered(int N, String... strings)
+    {
+        List<String> data = Arrays.asList(strings) ;
+        IteratorWithBuffer<String> iter = new IteratorWithBuffer<>(data.iterator(), N) ;
+        return iter ;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/iterator/TestIteratorWithHistory.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/iterator/TestIteratorWithHistory.java b/jena-base/src/test/java/org/apache/jena/atlas/iterator/TestIteratorWithHistory.java
new file mode 100644
index 0000000..b2545b2
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/iterator/TestIteratorWithHistory.java
@@ -0,0 +1,87 @@
+/*
+ * 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.Arrays ;
+import java.util.List ;
+
+import org.apache.jena.atlas.iterator.IteratorWithHistory ;
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.junit.Test ;
+
+public class TestIteratorWithHistory extends BaseTest
+{
+    @Test public void iterHistory_01()
+    {
+        IteratorWithHistory<String> iter = createHistory(1, "a", "b", "c") ;
+        assertEquals(0, iter.currentSize()) ;
+        assertEquals(null, iter.getPrevious(0)) ;
+    }
+    
+    @Test public void iterHistory_02()
+    {
+        IteratorWithHistory<String> iter = createHistory(1, "a", "b", "c") ;
+        assertEquals("a", iter.next()) ;
+        assertEquals(1, iter.currentSize()) ;
+    }
+
+    @Test public void iterHistory_03()
+    {
+        IteratorWithHistory<String> iter = createHistory(2, "a", "b", "c") ;
+        assertEquals("a", iter.next()) ;
+        assertEquals("b", iter.next()) ;
+        assertEquals(2, iter.currentSize()) ;
+        assertEquals("b", iter.getPrevious(0)) ;
+        assertEquals("a", iter.getPrevious(1)) ;
+    }
+
+    @Test(expected=IndexOutOfBoundsException.class)
+    public void iterHistory_04()
+    {
+        IteratorWithHistory<String> iter = createHistory(2, "a", "b", "c") ;
+        iter.getPrevious(2) ;
+    }
+    
+    @Test
+    public void iterHistory_05()
+    {
+        IteratorWithHistory<String> iter = createHistory(2, "a", "b", "c") ;
+        assertEquals("a", iter.next()) ;
+        assertEquals("a", iter.getPrevious(0)) ;
+        assertEquals(1, iter.currentSize()) ;
+        
+        assertEquals("b", iter.next()) ;
+        assertEquals("b", iter.getPrevious(0)) ;
+        assertEquals("a", iter.getPrevious(1)) ;
+        assertEquals(2, iter.currentSize()) ;
+        
+        assertEquals("c", iter.next()) ;
+        assertEquals(2, iter.currentSize()) ;
+        assertEquals("c", iter.getPrevious(0)) ;
+        assertEquals("b", iter.getPrevious(1)) ;
+    }
+
+    private IteratorWithHistory<String> createHistory(int N, String... strings)
+    {
+        List<String> data = Arrays.asList(strings) ;
+        IteratorWithHistory<String> iter = new IteratorWithHistory<>(data.iterator(), N) ;
+        return iter ;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/junit/BaseTest.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/junit/BaseTest.java b/jena-base/src/test/java/org/apache/jena/atlas/junit/BaseTest.java
new file mode 100644
index 0000000..6d5f15a
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/junit/BaseTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.junit;
+
+import java.util.ArrayList ;
+import java.util.List ;
+import java.util.Locale ;
+
+import org.junit.Assert ;
+
+public class BaseTest extends Assert
+{
+    public static void assertEqualsIgnoreCase(String msg, String a, String b)
+    {
+        a = a.toLowerCase(Locale.ROOT) ;
+        b = b.toLowerCase(Locale.ROOT) ;
+        assertEquals(msg, a, b) ;
+    }
+    
+    public static <T> void assertEqualsUnordered(List<T> list1, List<T> list2) {
+        if ( list1.size() != list2.size() )
+            fail("Expected: "+list1+" : Actual: "+list2) ;
+        List<T> list2a = new ArrayList<>(list2) ;
+        for ( T elt : list1 )
+            list2a.remove(elt) ;
+        if ( list2a.size() != 0 )
+            fail("Expected: "+list1+" : Actual: "+list2) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/lib/TS_Lib.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/lib/TS_Lib.java b/jena-base/src/test/java/org/apache/jena/atlas/lib/TS_Lib.java
new file mode 100644
index 0000000..b91d4ad
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/lib/TS_Lib.java
@@ -0,0 +1,57 @@
+/*
+ * 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.lib;
+
+
+import org.junit.runner.RunWith ;
+import org.junit.runners.Suite ;
+
+/**
+ * Tests for the Atlas lib package
+ *
+ */
+@RunWith(Suite.class)
+@Suite.SuiteClasses( {
+    TestAlg.class
+    , TestBitsLong.class
+    , TestBitsInt.class
+    , TestBytes.class
+    , TestHex.class
+    , TestListUtils.class
+    , TestSetUtils.class
+    , TestCache.class
+    , TestCache2.class
+    , TestColumnMap.class
+    , TestFileOps.class
+    , TestStrUtils.class
+    , TestXMLLib.class
+    , TestAlarmClock.class
+    , TestRefLong.class
+    , TestReverseComparator.class
+    , TestTrie.class
+    , TestFilenameProcessing.class
+    , TestMultiSet.class
+    , TestNumberUtils.class
+    , TestDateTimeUtils.class
+} )
+
+public class TS_Lib
+{
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/lib/TestAlarmClock.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/lib/TestAlarmClock.java b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestAlarmClock.java
new file mode 100644
index 0000000..7d2fbfe
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestAlarmClock.java
@@ -0,0 +1,92 @@
+/*
+ * 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.lib ;
+
+import static org.apache.jena.atlas.lib.Lib.sleep ;
+
+import java.util.concurrent.atomic.AtomicInteger ;
+
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.junit.Test ;
+
+public class TestAlarmClock extends BaseTest {
+    AtomicInteger count    = new AtomicInteger(0) ;
+    Runnable      callback = new Runnable() {
+                               @Override
+                               public void run() {
+                                   count.getAndIncrement() ;
+                               }
+                           } ;
+
+    @Test
+    public void alarm_01() {
+        AlarmClock alarmClock = new AlarmClock() ;
+        // Very long - never happens.
+        alarmClock.add(callback, 10000000) ;
+        alarmClock.cancel(callback) ;
+        assertEquals(0, count.get()) ;
+        alarmClock.release() ;
+    }
+
+    @Test
+    public void alarm_02() {
+        AlarmClock alarmClock = new AlarmClock() ;
+        // Short - happens.
+        alarmClock.add(callback, 10) ;
+        sleep(150) ;
+        assertEquals(1, count.get()) ;
+        // try to cancel anyway.
+        alarmClock.cancel(callback) ;
+        alarmClock.release() ;
+    }
+
+    @Test
+    public void alarm_03() {
+        AlarmClock alarmClock = new AlarmClock() ;
+        alarmClock.add(callback, 10) ;
+        alarmClock.add(callback, 1000000) ;
+        sleep(150) ;
+        // ping1 went off.
+        assertEquals(1, count.get()) ;
+        alarmClock.cancel(callback) ;
+        alarmClock.release() ;
+    }
+
+    @Test
+    public void alarm_04() {
+        AlarmClock alarmClock = new AlarmClock() ;
+        alarmClock.add(callback, 10) ;
+        alarmClock.add(callback, 20) ;
+        sleep(200) ;
+        // ping1 went off. ping2 went off.
+        assertEquals(2, count.get()) ;
+        alarmClock.release() ;
+    }
+
+    @Test
+    public void alarm_05() {
+        AlarmClock alarmClock = new AlarmClock() ;
+        alarmClock.add(callback, 1000) ;
+        alarmClock.reset(callback, 2000) ;
+        sleep(50) ;
+        // The reset should have removed the callback before it happened.
+        assertEquals(0, count.get()) ;
+        alarmClock.release() ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/lib/TestAlg.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/lib/TestAlg.java b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestAlg.java
new file mode 100644
index 0000000..b7eb6e5
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestAlg.java
@@ -0,0 +1,226 @@
+/*
+ * 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.lib;
+
+import java.nio.ByteBuffer ;
+import java.nio.ByteOrder ;
+import java.nio.IntBuffer ;
+
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.apache.jena.atlas.lib.Alg ;
+import org.junit.Test ;
+
+public class TestAlg extends BaseTest
+{
+    // Linear search is really there to test binary search.
+    @Test public void linear1() 
+    {
+        int[] data = {1, 2, 3} ; 
+        IntBuffer b = make(data) ;
+        int idx = 0 ; 
+        
+        idx = Alg.linearSearch(b, 1) ;
+        assertEquals(0, idx) ;
+        
+        idx = Alg.linearSearch(b, 2) ;
+        assertEquals(1, idx) ;
+
+        idx = Alg.linearSearch(b, 3) ;
+        assertEquals(2, idx) ;
+        
+    }
+
+    @Test public void linear2() 
+    {
+        int[] data = {2, 4, 6} ; 
+        IntBuffer b = make(data) ;
+        int idx = 0 ; 
+        
+        idx = Alg.linearSearch(b, 1) ;
+        assertEquals(-1, idx) ;
+        
+        idx = Alg.linearSearch(b, 3) ;
+        assertEquals(-2, idx) ;
+
+        idx = Alg.linearSearch(b, 5) ;
+        assertEquals(-3, idx) ;
+        
+        idx = Alg.linearSearch(b, 7) ;
+        assertEquals(-4, idx) ;
+    }
+
+    @Test public void linear3() 
+    {
+        int[] data = {} ; 
+        IntBuffer b = make(data) ;
+        int idx = Alg.linearSearch(b, 1) ;
+        assertEquals(-1, idx) ;
+    }
+    
+    @Test public void linear4() 
+    {
+        int[] data = {9} ; 
+        IntBuffer b = make(data) ;
+        int idx ;
+        idx = Alg.linearSearch(b, 1) ;
+        assertEquals(-1, idx) ;
+        idx = Alg.linearSearch(b, 9) ;
+        assertEquals(0, idx) ;
+        idx = Alg.linearSearch(b, 100) ;
+        assertEquals(-2, idx) ;
+    }
+    
+    @Test public void linear5()
+    {
+        int[] data = {2, 4, 6, 8, 10} ; 
+        IntBuffer b = make(data) ;
+        int idx = 0 ;
+        idx = Alg.linearSearch(b, 1, 4, 6) ;
+        assertEquals(2, idx) ;
+        
+        idx = Alg.linearSearch(b, 1, 4, 5) ;
+        assertEquals(-3, idx) ;
+        
+        idx = Alg.linearSearch(b, 1, 4, 2) ;
+        assertEquals(-2, idx) ;
+        
+        idx = Alg.linearSearch(b, 1, 4, 10) ;
+        assertEquals(-5, idx) ;
+    }
+    
+    @Test public void linear6()
+    {
+        int[] data = {2, 4, 6, 8, 10} ; 
+        IntBuffer b = make(data) ;
+        int idx = 0 ;
+        idx = Alg.linearSearch(b, 3, 3, 6) ;
+        assertEquals(-4, idx) ;
+        idx = Alg.linearSearch(b, 3, 3, 5) ;
+        assertEquals(-4, idx) ;
+        idx = Alg.linearSearch(b, 3, 3, 50) ;
+        assertEquals(-4, idx) ;
+    }
+    
+    @Test public void linear7()
+    {
+        int[] data = {2, 4, 4, 8, 8} ; 
+        IntBuffer b = make(data) ;
+        int idx = 0 ;
+        idx = Alg.linearSearch(b, 4) ;
+        assertEquals(1, idx) ;
+        idx = Alg.linearSearch(b, 8) ;
+        assertEquals(3, idx) ;
+    }
+    
+    @Test public void binary1() 
+    {
+        int[] data = {1, 2, 3} ; 
+        IntBuffer b = make(data) ;
+        search(b, 1) ; 
+        search(b, 2) ;
+        search(b, 3) ;
+    }
+    
+    @Test public void binary2() 
+    {
+        int[] data = {2, 4, 6} ; 
+        IntBuffer b = make(data) ;
+        search(b, 1) ;
+        search(b, 3) ;
+        search(b, 5) ;
+        search(b, 7) ;
+    }
+    
+    @Test public void binary3() 
+    {
+        int[] data = {} ; 
+        IntBuffer b = make(data) ;
+        search(b, 1) ;
+    }
+    
+    @Test public void binary4() 
+    {
+        int[] data = {9} ; 
+        IntBuffer b = make(data) ;
+        search(b, 1) ;
+        search(b, 9) ;
+        search(b, 100) ;
+    }
+    
+    @Test public void binary5()
+    {
+        int[] data = {2, 4, 6, 8, 10} ; 
+        IntBuffer b = make(data) ;
+        search(b, 6, 1, 4) ;
+        search(b, 5, 1, 4) ;
+        search(b, 2, 1, 4) ;
+        search(b, 10, 1, 4) ;
+    }
+
+    @Test public void binary6()
+    {
+        int[] data = {2, 4, 6, 8, 10} ; 
+        IntBuffer b = make(data) ;
+        search(b, 6, 3, 3) ;
+        search(b, 5, 3, 3) ;
+        search(b, 50, 3, 3) ;
+    }
+
+    // Binary serach does not state which index is returned for sequenences of same value
+    @Test public void binary7()
+    {
+        int[] data = {2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 8} ; 
+        IntBuffer b = make(data) ;
+        int idx = 0 ;
+        idx = Alg.binarySearch(b, 4) ;
+        assertEquals(4, data[idx]) ;
+        idx = Alg.linearSearch(b, 8) ;
+        assertEquals(8, data[idx]) ;
+        
+        search(b, 3) ;
+        search(b, 5) ;
+        search(b, 9) ;
+    }
+    
+    private static IntBuffer make(int[] data) 
+    {
+        //IntBuffer x = IntBuffer.allocate(data.length) ;
+        ByteBuffer z = ByteBuffer.allocate(4*data.length) ;
+        z.order(ByteOrder.BIG_ENDIAN) ;
+        IntBuffer x = z.asIntBuffer() ;
+        
+        for ( int i = 0 ; i < data.length ; i++ )
+            x.put(i,data[i]) ;
+        return x ;
+    }
+
+    private static void search(IntBuffer b, int k)
+    {
+        int idx1 = Alg.linearSearch(b, k) ;
+        int idx2 = Alg.binarySearch(b, k) ;
+        assertEquals(idx1, idx2) ;
+    }
+    
+    private static void search(IntBuffer b, int k, int low, int high)
+    {
+        int idx1 = Alg.linearSearch(b, low, high, k) ;
+        int idx2 = Alg.binarySearch(b, low, high, k) ;
+        assertEquals(idx1, idx2) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/lib/TestBitsInt.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/lib/TestBitsInt.java b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestBitsInt.java
new file mode 100644
index 0000000..769aeb2
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestBitsInt.java
@@ -0,0 +1,468 @@
+/*
+ * 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.lib;
+
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.apache.jena.atlas.lib.BitsInt ;
+import org.junit.Test ;
+
+
+public class TestBitsInt extends BaseTest
+{
+    @Test public void testMask1()
+    {
+        int v = BitsInt.mask(0,1) ;
+        check(0x1, v) ;
+    }
+    
+    @Test public void testMask2()
+    {
+        int v = BitsInt.mask(0,2) ;
+        check(0x3, v) ;
+    }
+    
+    @Test public void testMask3()
+    {
+        int v = BitsInt.mask(1,2) ;
+        check(0x2, v) ;
+    }
+    
+    @Test public void testMask4()
+    {
+        int v = BitsInt.mask(0,32) ;
+        check(-1, v) ;
+    }
+    
+    @Test public void testMask5()
+    {
+        int v = BitsInt.mask(16,24) ;
+        check(0x00FF0000, v) ;
+    }
+
+    @Test public void testMask6()
+    {
+        int v = BitsInt.mask(16,32) ;
+        check(0xFFFF0000, v) ;
+    }
+
+    @Test public void testMask7()
+    {
+        int v = BitsInt.mask(0, 0) ;
+        check(0, v) ;
+    }
+    
+    @Test public void testMaskZero1()
+    {
+        int v = BitsInt.maskZero(0,1) ;
+        check(~0x1, v) ;
+    }
+    
+    @Test public void testMaskZero2()
+    {
+        int v = BitsInt.maskZero(0,2) ;
+        check(~0x3, v) ;
+    }
+    
+    @Test public void testMaskZero3()
+    {
+        int v = BitsInt.maskZero(1,2) ;
+        check(0xFFFFFFFD, v) ;
+    }
+    
+    @Test public void testMaskZero4()
+    {
+        int v = BitsInt.maskZero(0,32) ;
+        check(0, v) ;
+    }
+    
+    @Test public void testMaskZero5()
+    {
+        int v = BitsInt.maskZero(16,24) ;
+        check(0xFF00FFFF, v) ;
+    }
+
+    @Test public void testMaskZero6()
+    {
+        int v = BitsInt.maskZero(16,32) ;
+        check(0xFFFF, v) ;
+    }
+
+    @Test public void testMaskZero7()
+    {
+        int v = BitsInt.maskZero(0, 0) ;
+        check(-1, v) ;
+    }
+
+    @Test public void testClear1()
+    {
+        int v = 0xF0F0 ;
+        v = BitsInt.clear(v, 4, 8) ;
+        String s = Integer.toHexString(v) ;
+        check(0xF000, v ) ;
+    }
+
+    @Test public void testClear2()
+    {
+        int v = 0x80000000;
+        v = BitsInt.clear(v, 31, 32) ;
+        String s = Integer.toHexString(v) ;
+        check(0x0, v ) ;
+    }
+
+    @Test public void testClear3()
+    {
+        int v = 0xC0000000;
+        v = BitsInt.clear(v, 31, 32) ;
+        String s = Integer.toHexString(v) ;
+        check(0x40000000, v ) ;
+    }
+
+    @Test public void testClear4()
+    {
+        int v = -1 ;
+        v = BitsInt.clear(v, 31, 32) ;
+        String s = Integer.toHexString(v) ;
+        check(0x7FFFFFFF, v ) ;
+    }
+    
+    @Test public void testClear5()
+    {
+        int v = -1 ;
+        v = BitsInt.clear(v, 16, 32) ;
+        String s = Integer.toHexString(v) ;
+        check(0x0000FFFF, v ) ;
+    }
+
+    @Test public void testClear6()
+    {
+        int v = -1 ;
+        v = BitsInt.clear(v, 0, 16) ;
+        String s = Integer.toHexString(v) ;
+        check(0xFFFF0000, v ) ;
+    }
+
+    @Test public void testClear7()
+    {
+        int v = -1 ;
+        v = BitsInt.clear(v, 0, 0) ;
+        String s = Integer.toHexString(v) ;
+        check(-1, v ) ;
+    }
+
+    @Test public void testSet1()
+    {
+        int v = 0x0 ;
+        v = BitsInt.set(v, 0, 1) ;
+        check(1, v) ;
+    }
+    
+    @Test public void testSet2()
+    {
+        int v = 0x1 ;
+        v = BitsInt.set(v, 0, 1) ;
+        check(1, v) ;
+    }
+    
+    @Test public void testSet3()
+    {
+        int v = 0xF0 ;
+        v = BitsInt.set(v, 0, 1) ;
+        check(0xF1, v) ;
+    }
+    
+    @Test public void testSet4()
+    {
+        int v = 0xF0F0F0F0 ;
+        v = BitsInt.set(v, 0, 8) ;
+        check(0xF0F0F0FF, v) ;
+    }
+
+    @Test public void testSet5()
+    {
+        int v = 0 ;
+        v = BitsInt.set(v, 16, 24) ;
+        check(0x00FF0000, v) ;
+    }
+    
+    @Test public void testSet6()
+    {
+        int v = 0 ;
+        v = BitsInt.set(v, 31, 32) ;
+        check(0x80000000, v) ;
+    }
+    
+    @Test public void testSet7()
+    {
+        int v = 0 ;
+        v = BitsInt.set(v, 30, 32) ;
+        check(0xC0000000, v) ;
+    }
+    
+    @Test public void testSet8()
+    {
+        int v = 0 ;
+        v = BitsInt.set(v, 0, 32) ;
+        check(-1, v) ;
+    }
+    
+    @Test public void testSet9()
+    {
+        int v = 0 ;
+        v = BitsInt.set(v, 10, 10) ;
+        check(0, v) ;
+    }
+    
+    @Test public void testSetBit1()
+    {
+        int v = 0 ;
+        v = BitsInt.set(v, 0) ;
+        check(1, v) ;
+    }
+    
+    @Test public void testSetBit2()
+    {
+        int v = 0 ;
+        v = BitsInt.set(v, 1) ;
+        check(2, v) ;
+    }
+    
+    @Test public void testSetBit3()
+    {
+        int v = 1 ;
+        v = BitsInt.set(v, 0) ;
+        check(1, v) ;
+    }
+
+    @Test public void testSetBit4()
+    {
+        int v = -1 ;
+        v = BitsInt.set(v, 0) ;
+        check(-1, v) ;
+    }
+
+    @Test public void testSetBit5()
+    {
+        int v = 0 ;
+        v = BitsInt.set(v, 30) ;
+        check(0x40000000, v) ;
+    }
+
+    @Test public void testSetBit6()
+    {
+        int v = 0 ;
+        v = BitsInt.set(v, 31) ;
+        check(0x80000000, v) ;
+    }
+    
+    @Test public void testBitTest1()
+    {
+        int v = 0 ;
+        assertTrue(BitsInt.test(v, false, 0)) ;
+    }
+    
+    @Test public void testBitTest2()
+    {
+        int v = 1 ;
+        assertTrue(BitsInt.test(v, true, 0)) ;
+    }
+    
+    @Test public void testBitTest3()
+    {
+        int v = -1 ;
+        assertTrue(BitsInt.test(v, true, 31)) ;
+    }
+
+    @Test public void testBitTest4()
+    {
+        int v = 0x7FFFFFFF ;
+        assertTrue(BitsInt.test(v, false, 31)) ;
+    }
+
+    @Test public void testBitsTest1()
+    {
+        int v = 0x76543210 ;
+        assertTrue(BitsInt.test(v, 0x0, 0, 4)) ;
+    }
+    
+    @Test public void testBitsTest2()
+    {
+        int v = 0x76543210 ;
+        assertTrue(BitsInt.test(v, 0x10, 0, 8)) ;
+    }
+    
+    @Test public void testBitsTest3()
+    {
+        int v = 0x76543210 ;
+        assertTrue(BitsInt.test(v, v, 0, 32)) ;
+    }
+    
+    @Test public void testBitsTest4()
+    {
+        int v = 0x76543210 ;
+        assertFalse(BitsInt.test(v, 0, 0, 32)) ;
+    }
+    
+    @Test public void testBitsTest5()
+    {
+        int v = 0x76543210 ;
+        assertTrue(BitsInt.test(v, 0x00543200, 8, 24)) ;
+    }
+    
+    @Test public void testIsSet1()
+    {
+        int v = 0x00000010 ;
+        BitsInt.isSet(v, 4) ;
+        assertTrue(BitsInt.isSet(v, 4)) ;
+        assertFalse(BitsInt.isSet(v, 3)) ;
+        assertFalse(BitsInt.isSet(v, 5)) ;
+    }
+    
+    @Test public void testAccess1()
+    {
+        int v = -1 ;
+        v = BitsInt.access(v, 4, 8) ;
+        check(0xF0, v ) ;
+    }
+    
+    @Test public void testAccess2()
+    {
+        int v = 0x76543210 ;
+        v = BitsInt.access(v, 0, 8) ;
+        check(0x10, v ) ;
+    }
+
+    @Test public void testAccess3()
+    {
+        int v = 0x76543210 ;
+        v = BitsInt.access(v, 0, 32) ;
+        check(0x76543210, v ) ;
+    }
+
+    @Test public void testAccess4()
+    {
+        int v = 0xFEF43210 ;
+        v = BitsInt.access(v, 30, 32) ;
+        check(0xC0000000, v ) ;
+    }
+
+    @Test public void testAccess5()
+    {
+        int v = 0x76543210 ;
+        v = BitsInt.access(v, 0, 2) ;
+        check(0, v ) ;
+    }
+
+    @Test public void testPack1()
+    {
+        int v = 0 ;
+        v = BitsInt.pack(v, 0xF, 0, 4) ;
+        check(0xF, v ) ;
+    }
+    
+    @Test public void testPack2()
+    {
+        int v = 0xF0 ;
+        v = BitsInt.pack(v, 0x2, 0, 4) ;
+        check(0xF2, v ) ;
+    }
+    
+    @Test public void testPack3()
+    {
+        int v = -1 ;
+        v = BitsInt.pack(v, 0x2, 0, 8) ;
+        check(0xFFFFFF02, v ) ;
+    }
+
+    @Test public void testPack4()
+    {
+        int v = 0xFFFF0000 ;
+        v = BitsInt.pack(v, 0x2, 8, 16) ;
+        check(0xFFFF0200, v ) ;
+    }
+
+    @Test public void testPack5()
+    {
+        int v = 0xFFFF0000 ;
+        v = BitsInt.pack(v, 0xFF, 8, 16) ;
+        check(0xFFFFFF00, v ) ;
+    }
+
+    @Test public void testUnpack1()
+    {
+        int v = 0xABCDABCD ;
+        v = BitsInt.unpack(v, 0, 4) ;
+        check(0xD, v ) ;
+    }
+    
+    @Test public void testUnpack2()
+    {
+        int v = 0xABCDABCD ;
+        v = BitsInt.unpack(v, 31, 32) ;
+        check(1, v ) ;
+    }
+
+    @Test public void testUnpack3()
+    {
+        int v = 0xABCDABCD ;
+        v = BitsInt.unpack(v, 24, 32) ;
+        check(0xAB, v ) ;
+    }
+
+    @Test public void testUnpack4()
+    {
+        int v = 0xAB1234CD ;
+        v = BitsInt.unpack(v, 8, 24) ;
+        check(0x1234, v ) ;
+    }
+
+    @Test public void testUnpackStr1()
+    {
+        String s = "ABCD" ;
+        int v = BitsInt.unpack(s, 0, 4) ;
+        check(0xABCD, v ) ;
+    }
+    
+    @Test public void testUnpackStr2()
+    {
+        String s = "ABCD" ;
+        int v = BitsInt.unpack(s, 2, 4) ;
+        check(0xCD, v ) ;
+    }
+    
+    @Test public void testUnpackStr3()
+    {
+        String s = "ABCD" ;
+        int v = BitsInt.unpack(s, 0, 2) ;
+        check(0xAB, v ) ;
+    }
+    
+    private void check(int expected, int actual)
+    {
+        check(null, expected, actual) ;
+    }
+    
+    private void check(String msg, int expected, int actual)
+    {
+        if ( expected == actual ) return ;
+        String s = "Expected: "+Integer.toHexString(expected)+" : Got: "+Integer.toHexString(actual) ;
+        if ( msg != null )
+            s = msg+": "+s ;
+        assertFalse(s, true) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/lib/TestBitsLong.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/lib/TestBitsLong.java b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestBitsLong.java
new file mode 100644
index 0000000..1fea546
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestBitsLong.java
@@ -0,0 +1,468 @@
+/*
+ * 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.lib;
+
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.apache.jena.atlas.lib.BitsLong ;
+import org.junit.Test ;
+
+
+public class TestBitsLong extends BaseTest
+{
+    @Test public void testMask1()
+    {
+        long v = BitsLong.mask(0,1) ;
+        check(0x1L, v) ;
+    }
+    
+    @Test public void testMask2()
+    {
+        long v = BitsLong.mask(0,2) ;
+        check(0x3L, v) ;
+    }
+    
+    @Test public void testMask3()
+    {
+        long v = BitsLong.mask(1,2) ;
+        check(0x2L, v) ;
+    }
+    
+    @Test public void testMask4()
+    {
+        long v = BitsLong.mask(0,64) ;
+        check(-1L, v) ;
+    }
+    
+    @Test public void testMask5()
+    {
+        long v = BitsLong.mask(16,48) ;
+        check(0x0000FFFFFFFF0000L, v) ;
+    }
+
+    @Test public void testMask6()
+    {
+        long v = BitsLong.mask(16,64) ;
+        check(0xFFFFFFFFFFFF0000L, v) ;
+    }
+
+    @Test public void testMask7()
+    {
+        long v = BitsLong.mask(0, 0) ;
+        check(0L, v) ;
+    }
+    
+    @Test public void testMaskZero1()
+    {
+        long v = BitsLong.maskZero(0,1) ;
+        check(~0x1L, v) ;
+    }
+    
+    @Test public void testMaskZero2()
+    {
+        long v = BitsLong.maskZero(0,2) ;
+        check(~0x3L, v) ;
+    }
+    
+    @Test public void testMaskZero3()
+    {
+        long v = BitsLong.maskZero(1,2) ;
+        check(0xFFFFFFFFFFFFFFFDL, v) ;
+    }
+    
+    @Test public void testMaskZero4()
+    {
+        long v = BitsLong.maskZero(0,64) ;
+        check(0, v) ;
+    }
+    
+    @Test public void testMaskZero5()
+    {
+        long v = BitsLong.maskZero(16,48) ;
+        check(0xFFFF00000000FFFFL, v) ;
+    }
+
+    @Test public void testMaskZero6()
+    {
+        long v = BitsLong.maskZero(16,64) ;
+        check(0xFFFFL, v) ;
+    }
+
+    @Test public void testMaskZero7()
+    {
+        long v = BitsLong.maskZero(0, 0) ;
+        check(-1L, v) ;
+    }
+
+    @Test public void testClear1()
+    {
+        long v = 0xF0F0 ;
+        v = BitsLong.clear(v, 4, 8) ;
+        String s = Long.toHexString(v) ;
+        check(0xF000L, v ) ;
+    }
+
+    @Test public void testClear2()
+    {
+        long v = 0x8000000000000000L;
+        v = BitsLong.clear(v, 63, 64) ;
+        String s = Long.toHexString(v) ;
+        check(0x0L, v ) ;
+    }
+
+    @Test public void testClear3()
+    {
+        long v = 0xC000000000000000L;
+        v = BitsLong.clear(v, 63, 64) ;
+        String s = Long.toHexString(v) ;
+        check(0x4000000000000000L, v ) ;
+    }
+
+    @Test public void testClear4()
+    {
+        long v = -1 ;
+        v = BitsLong.clear(v, 63, 64) ;
+        String s = Long.toHexString(v) ;
+        check(0x7FFFFFFFFFFFFFFFL, v ) ;
+    }
+    
+    @Test public void testClear5()
+    {
+        long v = -1 ;
+        v = BitsLong.clear(v, 32, 64) ;
+        String s = Long.toHexString(v) ;
+        check(0x00000000FFFFFFFFL, v ) ;
+    }
+
+    @Test public void testClear6()
+    {
+        long v = -1 ;
+        v = BitsLong.clear(v, 0, 32) ;
+        String s = Long.toHexString(v) ;
+        check(0xFFFFFFFF00000000L, v ) ;
+    }
+
+    @Test public void testClear7()
+    {
+        long v = -1L ;
+        v = BitsLong.clear(v, 0, 0) ;
+        String s = Long.toHexString(v) ;
+        check(-1L, v ) ;
+    }
+
+    @Test public void testSet1()
+    {
+        long v = 0x0 ;
+        v = BitsLong.set(v, 0, 1) ;
+        check(1, v) ;
+    }
+    
+    @Test public void testSet2()
+    {
+        long v = 0x1 ;
+        v = BitsLong.set(v, 0, 1) ;
+        check(1, v) ;
+    }
+    
+    @Test public void testSet3()
+    {
+        long v = 0xF0 ;
+        v = BitsLong.set(v, 0, 1) ;
+        check(0xF1, v) ;
+    }
+    
+    @Test public void testSet4()
+    {
+        long v = 0xF0F0F0F0F0F0F0F0L ;
+        v = BitsLong.set(v, 0, 8) ;
+        check(0xF0F0F0F0F0F0F0FFL, v) ;
+    }
+
+    @Test public void testSet5()
+    {
+        long v = 0 ;
+        v = BitsLong.set(v, 16, 48) ;
+        check(0x0000FFFFFFFF0000L, v) ;
+    }
+    
+    @Test public void testSet6()
+    {
+        long v = 0 ;
+        v = BitsLong.set(v, 63, 64) ;
+        check(0x8000000000000000L, v) ;
+    }
+    
+    @Test public void testSet7()
+    {
+        long v = 0 ;
+        v = BitsLong.set(v, 62, 64) ;
+        check(0xC000000000000000L, v) ;
+    }
+    
+    @Test public void testSet8()
+    {
+        long v = 0 ;
+        v = BitsLong.set(v, 0, 64) ;
+        check(-1L, v) ;
+    }
+    
+    @Test public void testSet9()
+    {
+        long v = 0 ;
+        v = BitsLong.set(v, 10, 10) ;
+        check(0, v) ;
+    }
+    
+    @Test public void testSetBit1()
+    {
+        long v = 0 ;
+        v = BitsLong.set(v, 0) ;
+        check(1, v) ;
+    }
+    
+    @Test public void testSetBit2()
+    {
+        long v = 0 ;
+        v = BitsLong.set(v, 1) ;
+        check(2, v) ;
+    }
+    
+    @Test public void testSetBit3()
+    {
+        long v = 1 ;
+        v = BitsLong.set(v, 0) ;
+        check(1, v) ;
+    }
+
+    @Test public void testSetBit4()
+    {
+        long v = -1 ;
+        v = BitsLong.set(v, 0) ;
+        check(-1, v) ;
+    }
+
+    @Test public void testSetBit5()
+    {
+        long v = 0 ;
+        v = BitsLong.set(v, 62) ;
+        check(0x4000000000000000L, v) ;
+    }
+
+    @Test public void testSetBit6()
+    {
+        long v = 0 ;
+        v = BitsLong.set(v, 63) ;
+        check(0x8000000000000000L, v) ;
+    }
+    
+    @Test public void testBitTest1()
+    {
+        long v = 0 ;
+        assertTrue(BitsLong.test(v, false, 0)) ;
+    }
+    
+    @Test public void testBitTest2()
+    {
+        long v = 1 ;
+        assertTrue(BitsLong.test(v, true, 0)) ;
+    }
+    
+    @Test public void testBitTest3()
+    {
+        long v = -1 ;
+        assertTrue(BitsLong.test(v, true, 63)) ;
+    }
+
+    @Test public void testBitTest4()
+    {
+        long v = 0x7FFFFFFFFFFFFFFFL ;
+        assertTrue(BitsLong.test(v, false, 63)) ;
+    }
+
+    @Test public void testBitsTest1()
+    {
+        long v = 0xFEDCBA9876543210L ;
+        assertTrue(BitsLong.test(v, 0x0, 0, 4)) ;
+    }
+    
+    @Test public void testBitsTest2()
+    {
+        long v = 0xFEDCBA9876543210L ;
+        assertTrue(BitsLong.test(v, 0x10, 0, 8)) ;
+    }
+    
+    @Test public void testBitsTest3()
+    {
+        long v = 0xFEDCBA9876543210L ;
+        assertTrue(BitsLong.test(v, v, 0, 64)) ;
+    }
+    
+    @Test public void testBitsTest4()
+    {
+        long v = 0xFEDCBA9876543210L ;
+        assertFalse(BitsLong.test(v, 0, 0, 64)) ;
+    }
+    
+    @Test public void testBitsTest5()
+    {
+        long v = 0xFEDCBA9876543210L ;
+        assertTrue(BitsLong.test(v, 0x0000BA9876540000L, 16, 48)) ;
+    }
+    
+    @Test public void testIsSet1()
+    {
+        long v = 0x0000000000000010L ;
+        BitsLong.isSet(v, 4) ;
+        assertTrue(BitsLong.isSet(v, 4)) ;
+        assertFalse(BitsLong.isSet(v, 3)) ;
+        assertFalse(BitsLong.isSet(v, 5)) ;
+    }
+    
+    @Test public void testAccess1()
+    {
+        long v = -1 ;
+        v = BitsLong.access(v, 4, 8) ;
+        check(0xF0L, v ) ;
+    }
+    
+    @Test public void testAccess2()
+    {
+        long v = 0xFEDCBA9876543210L ;
+        v = BitsLong.access(v, 0, 8) ;
+        check(0x10L, v ) ;
+    }
+
+    @Test public void testAccess3()
+    {
+        long v = 0xFEDCBA9876543210L ;
+        v = BitsLong.access(v, 0, 64) ;
+        check(0xFEDCBA9876543210L, v ) ;
+    }
+
+    @Test public void testAccess4()
+    {
+        long v = 0xFEDCBA9876543210L ;
+        v = BitsLong.access(v, 62, 64) ;
+        check(0xC000000000000000L, v ) ;
+    }
+
+    @Test public void testAccess5()
+    {
+        long v = 0xFEDCBA9876543210L ;
+        v = BitsLong.access(v, 0, 2) ;
+        check(0L, v ) ;
+    }
+
+    @Test public void testPack1()
+    {
+        long v = 0 ;
+        v = BitsLong.pack(v, 0xFL, 0, 4) ;
+        check(0xFL, v ) ;
+    }
+    
+    @Test public void testPack2()
+    {
+        long v = 0xF0 ;
+        v = BitsLong.pack(v, 0x2, 0, 4) ;
+        check(0xF2L, v ) ;
+    }
+    
+    @Test public void testPack3()
+    {
+        long v = -1 ;
+        v = BitsLong.pack(v, 0x2, 0, 8) ;
+        check(0xFFFFFFFFFFFFFF02L, v ) ;
+    }
+
+    @Test public void testPack4()
+    {
+        long v = 0xFFFFFFFF00000000L ;
+        v = BitsLong.pack(v, 0x2, 16, 32) ;
+        check(0xFFFFFFFF00020000L, v ) ;
+    }
+
+    @Test public void testPack5()
+    {
+        long v = 0xFFFFFFFF00000000L ;
+        v = BitsLong.pack(v, 0xFFFF, 16, 32) ;
+        check(0xFFFFFFFFFFFF0000L, v ) ;
+    }
+
+    @Test public void testUnpack1()
+    {
+        long v = 0xABCDABCDABCDABCDL ;
+        v = BitsLong.unpack(v, 0, 4) ;
+        check(0xDL, v ) ;
+    }
+    
+    @Test public void testUnpack2()
+    {
+        long v = 0xABCDABCDABCDABCDL ;
+        v = BitsLong.unpack(v, 63, 64) ;
+        check(1L, v ) ;
+    }
+
+    @Test public void testUnpack3()
+    {
+        long v = 0xABCDABCDABCDABCDL ;
+        v = BitsLong.unpack(v, 56, 64) ;
+        check(0xABL, v ) ;
+    }
+
+    @Test public void testUnpack4()
+    {
+        long v = 0xABCD12345678ABCDL ;
+        v = BitsLong.unpack(v, 32, 48) ;
+        check(0x1234L, v ) ;
+    }
+
+    @Test public void testUnpackStr1()
+    {
+        String s = "ABCD" ;
+        long v = BitsLong.unpack(s, 0, 4) ;
+        check(0xABCDL, v ) ;
+    }
+    
+    @Test public void testUnpackStr2()
+    {
+        String s = "ABCD" ;
+        long v = BitsLong.unpack(s, 2, 4) ;
+        check(0xCDL, v ) ;
+    }
+    
+    @Test public void testUnpackStr3()
+    {
+        String s = "ABCD" ;
+        long v = BitsLong.unpack(s, 0, 2) ;
+        check(0xABL, v ) ;
+    }
+    
+    private void check(long expected, long actual)
+    {
+        check(null, expected, actual) ;
+    }
+    
+    private void check(String msg, long expected, long actual)
+    {
+        if ( expected == actual ) return ;
+        String s = "Expected: "+Long.toHexString(expected)+" : Got: "+Long.toHexString(actual) ;
+        if ( msg != null )
+            s = msg+": "+s ;
+        assertFalse(s, true) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/test/java/org/apache/jena/atlas/lib/TestBytes.java
----------------------------------------------------------------------
diff --git a/jena-base/src/test/java/org/apache/jena/atlas/lib/TestBytes.java b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestBytes.java
new file mode 100644
index 0000000..a857509
--- /dev/null
+++ b/jena-base/src/test/java/org/apache/jena/atlas/lib/TestBytes.java
@@ -0,0 +1,198 @@
+/*
+ * 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.lib;
+
+import java.nio.ByteBuffer ;
+
+import org.apache.jena.atlas.junit.BaseTest ;
+import org.apache.jena.atlas.lib.Bytes ;
+import org.junit.Test ;
+
+public class TestBytes extends BaseTest
+{
+    @Test public void packInt1()
+    {
+        byte[] b = new byte[4] ;
+        Bytes.setInt(0x01020304,b) ;
+        assertEquals(0x01, b[0]) ;
+        assertEquals(0x02, b[1]) ;
+        assertEquals(0x03, b[2]) ;
+        assertEquals(0x04, b[3]) ;
+    }
+    
+    @Test public void packInt2()
+    {
+        byte[] b = new byte[8] ;
+        Bytes.setInt(0x01020304,b,0) ;
+        Bytes.setInt(0x05060708,b,4) ;
+        assertEquals(0x01, b[0]) ;
+        assertEquals(0x02, b[1]) ;
+        assertEquals(0x03, b[2]) ;
+        assertEquals(0x04, b[3]) ;
+        assertEquals(0x05, b[4]) ;
+        assertEquals(0x06, b[5]) ;
+        assertEquals(0x07, b[6]) ;
+        assertEquals(0x08, b[7]) ;
+    }
+
+    @Test public void packInt3()
+    {
+        byte[] b = new byte[4] ;
+        Bytes.setInt(0xF1F2F3F4,b) ;
+        int i = Bytes.getInt(b) ;
+        assertEquals(0xF1F2F3F4, i) ;
+    }
+    
+    @Test public void packInt4()
+    {
+        byte[] b = new byte[8] ;
+        Bytes.setInt(0x01020304,b,0) ;
+        Bytes.setInt(0x05060708,b,4) ;
+
+        int i1 = Bytes.getInt(b,0) ;
+        int i2 = Bytes.getInt(b,4) ;
+        assertEquals(0x01020304, i1) ;
+        assertEquals(0x05060708, i2) ;
+    }
+
+    @Test public void packLong5()
+    {
+        byte[] b = new byte[8] ;
+        Bytes.setLong(0x0102030405060708L,b) ;
+        assertEquals(0x01, b[0]) ;
+        assertEquals(0x02, b[1]) ;
+        assertEquals(0x03, b[2]) ;
+        assertEquals(0x04, b[3]) ;
+        assertEquals(0x05, b[4]) ;
+        assertEquals(0x06, b[5]) ;
+        assertEquals(0x07, b[6]) ;
+        assertEquals(0x08, b[7]) ;
+   }
+    
+    @Test public void packLong6()
+    {
+        byte[] b = new byte[16] ;
+        Bytes.setLong(0x0102030405060708L,b,0) ;
+        Bytes.setLong(0x1112131415161718L,b,8) ;
+        assertEquals(0x01, b[0]) ;
+        assertEquals(0x02, b[1]) ;
+        assertEquals(0x03, b[2]) ;
+        assertEquals(0x04, b[3]) ;
+        assertEquals(0x05, b[4]) ;
+        assertEquals(0x06, b[5]) ;
+        assertEquals(0x07, b[6]) ;
+        assertEquals(0x08, b[7]) ;
+
+        assertEquals(0x11, b[0+8]) ;
+        assertEquals(0x12, b[1+8]) ;
+        assertEquals(0x13, b[2+8]) ;
+        assertEquals(0x14, b[3+8]) ;
+        assertEquals(0x15, b[4+8]) ;
+        assertEquals(0x16, b[5+8]) ;
+        assertEquals(0x17, b[6+8]) ;
+        assertEquals(0x18, b[7+8]) ;
+    }
+
+    @Test public void packLong7()
+    {
+        byte[] b = new byte[8] ;
+        Bytes.setLong(0xF1F2F3F4F5F6F7F8L,b) ;
+        long i = Bytes.getLong(b) ;
+        assertEquals (0xF1F2F3F4F5F6F7F8L,i) ;
+    }
+    
+    @Test public void packLong8()
+    {
+        byte[] b = new byte[16] ;
+        Bytes.setLong(0xF1F2F3F4F5F6F7F8L,b,0) ;
+        Bytes.setLong(0xA1A2A3A4A5A6A7A8L,b,8) ;
+
+        long i1 = Bytes.getLong(b,0) ;
+        long i2 = Bytes.getLong(b,8) ;
+        assertEquals(0xF1F2F3F4F5F6F7F8L,i1) ;
+        assertEquals(0xA1A2A3A4A5A6A7A8L,i2) ;
+    }
+
+    @Test public void compare1()    { compare(0, new byte[]{}, new byte[]{}) ; }
+    @Test public void compare2()    { compare(+1, new byte[]{1}, new byte[]{}) ; }
+    @Test public void compare3()    { compare(-1, new byte[]{1}, new byte[]{1,2}) ; }
+
+    @Test public void compare4()    { compare(+1, new byte[]{1,3}, new byte[]{1,2}) ; }
+    @Test public void compare5()    { compare(+1, new byte[]{1,3}, new byte[]{1,2,3}) ; }
+    @Test public void compare6()    { compare(-1, new byte[]{1,2}, new byte[]{1,2,3}) ; }
+    
+    byte[] bytes = { 1, 2, 3, 4, 5, 6 } ;
+    
+    @Test public void slice1()
+    {
+        byte[] x = Bytes.copyOf(bytes) ;
+        assertArrayEquals(bytes, x) ;
+    }
+    
+    @Test public void slice3()
+    {
+        byte[] x = Bytes.copyOf(bytes,3) ;
+        byte[] y = new byte[]{4,5,6} ;
+        assertArrayEquals(y, x) ;
+    }
+    
+    @Test public void slice2()
+    {
+        byte[] x = Bytes.copyOf(bytes,3,2) ;
+        byte[] y = new byte[]{4,5} ;
+        assertArrayEquals(y, x) ;
+    }
+    
+    private static void compare(int expected, byte[] b1, byte[] b2)
+    {
+        int x = Bytes.compare(b1, b2) ;
+        if ( x > 0 && expected > 0 ) return ;
+        if ( x == 0 && expected == 0 ) return ;
+        if ( x < 0 && expected < 0 ) return ;
+        fail("Does not compare: "+Bytes.asHex(b1)+" :: "+Bytes.asHex(b2)) ;
+    }
+    
+    private static void codec(String str)
+    {
+        ByteBuffer bb = ByteBuffer.allocate(16) ; 
+        Bytes.toByteBuffer(str, bb) ;
+        bb.flip() ;
+        String str2 = Bytes.fromByteBuffer(bb) ;
+        assertEquals(str, str2) ;
+    }
+    
+    static private final String asciiBase             = "abc" ;
+    static private final String latinBase             = "Àéíÿ" ;
+    static private final String latinExtraBase        = "ỹfifl" ;  // fi-ligature, fl-ligature
+    static private final String greekBase             = "αβγ" ;
+    static private final String hewbrewBase           = "אבג" ;
+    static private final String arabicBase            = "ءآأ";
+    static private final String symbolsBase           = "☺☻♪♫" ;
+    static private final String chineseBase           = "孫子兵法" ; // The Art of War 
+    static private final String japaneseBase          = "日本" ;    // Japanese
+    
+    @Test public void codec1()  { codec(asciiBase) ; }
+    @Test public void codec2()  { codec("") ; }
+    @Test public void codec3()  { codec(greekBase) ; }
+    @Test public void codec4()  { codec(hewbrewBase) ; }
+    @Test public void codec5()  { codec(arabicBase) ; }
+    @Test public void codec6()  { codec(symbolsBase) ; }
+    @Test public void codec7()  { codec(chineseBase) ; }
+    @Test public void codec8()  { codec(japaneseBase) ; }
+}


[5/9] jena git commit: Import of lib, io, logging and iterator packages

Posted by an...@apache.org.
http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/FileOps.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/FileOps.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/FileOps.java
new file mode 100644
index 0000000..6862f4a
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/FileOps.java
@@ -0,0 +1,239 @@
+/*
+ * 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.lib ;
+
+import java.io.File ;
+import java.io.FileInputStream ;
+import java.io.FileOutputStream ;
+import java.io.IOException ;
+import java.nio.channels.FileChannel ;
+
+import org.apache.jena.atlas.AtlasException ;
+import org.apache.jena.atlas.io.IO ;
+import org.apache.jena.atlas.logging.Log ;
+
+/** A library of utility operations on files and the filing system */
+public class FileOps {
+    // Update implementations as standard library functions appear that meet the contract.
+    private FileOps() {}
+
+    /**
+     * Delete a file
+     * 
+     * @param filename
+     */
+    public static void delete(String filename) {
+        delete(new File(filename), true) ;
+    }
+
+    /* Delete a file - don't check it worked */
+
+    public static void deleteSilent(String filename) {
+        delete(new File(filename), false) ;
+    }
+
+    public static void delete(File f, boolean reportExistsAfter) {
+        try {
+            /*
+             * Note: On windows, deleting a file which has been memory mapped
+             * does not delete the file.
+             */
+            f.delete() ;
+            if ( reportExistsAfter && f.exists() )
+                Log.warn(FileOps.class, "delete: *** File still exists: " + f) ;
+        }
+        catch (SecurityException ex) {
+            Log.warn(FileOps.class, "delete: " + f + ": Security exception; " + ex.getMessage()) ;
+        }
+
+    }
+
+    /** Delete all files in a directory */
+    public static void clearDirectory(String dir) {
+        File d = new File(dir) ;
+        for ( File f : d.listFiles() ) {
+            if ( f.isFile() )
+                delete(f, false) ;
+        }
+    }
+
+    /** Delete all files and directories (recursively) in a directory */
+    public static void clearAll(String d) {
+        clearAll(new File(d)) ;
+    }
+
+    /** Delete all files and directories (recursively) in a directory */
+    public static void clearAll(File d) {
+        if ( ! d.exists() )
+            return ;
+        
+        for ( File f : d.listFiles() ) {
+            if ( ".".equals(f.getName()) || "..".equals(f.getName()) )
+                continue ;
+            if ( f.isDirectory() )
+                clearAll(f) ;
+            f.delete() ;
+        }
+    }
+
+    /** See if there are any files in this directory */
+    public static boolean existsAnyFiles(String dir) {
+        File d = new File(dir) ;
+        File[] entries = d.listFiles() ;
+        if ( entries == null )
+            // Not a directory
+            return false ;
+        return entries.length > 0 ;
+    }
+
+    /** Test for existence */
+    public static boolean exists(String path) {
+        File f = new File(path) ;
+        return f.exists() ;
+    }
+
+    /** Test for an empty file */
+    public static boolean isEmpty(String filename) {
+        File f = new File(filename) ;
+        if ( f.exists() )
+            return true ;
+        if ( f.isFile() )
+            return f.length() == 0 ;
+        throw new AtlasException("Not a file") ;
+    }
+
+    /** Ensure a directory exists */ 
+    public static void ensureDir(String dirname) {
+        File dir = new File(dirname) ;
+        if ( !dir.exists() )
+            dir.mkdirs() ;
+    }
+
+    /**
+     * Split a file name into path, basename and extension. Nulls returned if
+     * don't make sense.
+     */
+    public static Tuple<String> splitDirBaseExt(String filename) {
+        String path = null ;
+        String basename = filename ;
+        String ext = null ;
+
+        int j = filename.lastIndexOf('/') ;
+        if ( j < 0 )
+            j = filename.lastIndexOf('\\') ;
+
+        if ( j >= 0 ) {
+            path = filename.substring(0, j) ;
+            basename = filename.substring(j + 1) ;
+        }
+
+        int i = basename.lastIndexOf('.') ;
+
+        if ( i > -1 ) {
+            ext = basename.substring(i + 1) ;
+            basename = basename.substring(0, i) ;
+        }
+
+        return Tuple.createTuple(path, basename, ext) ;
+    }
+
+    /**
+     * Split a file name into path and filename. Nulls returned if don't make
+     * sense.
+     */
+    public static Tuple<String> splitDirFile(String filename) {
+        String path = null ;
+        String fn = filename ;
+
+        int j = filename.lastIndexOf('/') ;
+        if ( j < 0 )
+            j = filename.lastIndexOf('\\') ;
+
+        if ( j >= 0 ) {
+            path = filename.substring(0, j) ;
+            fn = filename.substring(j + 1) ;
+        }
+        return Tuple.createTuple(path, fn) ;
+    }
+
+    /** Return the basename (no path, no extension) */
+    public static String basename(String filename) {
+        int j = filename.lastIndexOf('/') ;
+        if ( j < 0 )
+            j = filename.lastIndexOf('\\') ;
+
+        String fn = (j >= 0) ? filename.substring(j + 1) : filename ;
+        int i = fn.lastIndexOf('.') ;
+
+        if ( i > -1 )
+            return fn.substring(0, i) ;
+        return fn ;
+    }
+
+    /** Return the extension (or "") */
+    public static String extension(String filename) {
+        int iSlash = filename.lastIndexOf('/') ;
+        int iBack = filename.lastIndexOf('\\') ;
+        int iExt = filename.lastIndexOf('.') ;
+        if ( iBack > iSlash )
+            iSlash = iBack ;
+        return iExt > iSlash ? filename.substring(iExt + 1).toLowerCase() : "" ;
+    }
+
+    public static String fullPath(String filename) {
+        File f = new File(filename) ;
+        return f.getAbsolutePath() ;
+    }
+
+    public static String fullDirectoryPath(String filename) {
+        File f = new File(filename) ;
+        if ( f.isDirectory() ) {
+            return f.getAbsolutePath() ;
+        } else if ( f.getParentFile() != null ) {
+            return f.getParentFile().getAbsolutePath() ;
+        } else {
+            return f.getAbsolutePath() ;
+        }
+    }
+
+    /** Copy a file */
+    public static void copyFile(File source, File dest) {
+        try {
+            @SuppressWarnings("resource")
+            FileChannel sourceChannel = new FileInputStream(source).getChannel() ;
+            @SuppressWarnings("resource")
+            FileChannel destChannel = new FileOutputStream(dest).getChannel() ;
+            destChannel.transferFrom(sourceChannel, 0, sourceChannel.size()) ;
+            sourceChannel.close() ;
+            destChannel.close() ;
+        }
+        catch (IOException ex) {
+            IO.exception(ex) ;
+        }
+    }
+
+    // public static String getExt(String filename)
+    // {
+    // int i = filename.lastIndexOf('.') ;
+    // int j = filename.lastIndexOf('/') ;
+    // if ( i > j )
+    // return filename.substring(i+1) ;
+    // return null ;
+    // }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/Hex.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/Hex.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/Hex.java
new file mode 100644
index 0000000..27ebbb5
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/Hex.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.lib;
+
+import org.apache.jena.atlas.AtlasException ;
+
+/** Working in hex ... */
+public class Hex
+{
+    // No checking, fixed width.
+    public static int formatUnsignedLongHex(final byte[] b, final int start, final long value, final int width)
+    {
+        // Insert from low value end to high value end.
+        int idx = start+width-1 ;
+        int w = width ;
+        long x = value ;
+        
+        while ( w > 0 )
+        {
+            int d = (int)(x & 0xF) ;
+            x = x>>>4 ; // Unsigned shift.
+            byte ch = Bytes.hexDigitsUC[d] ; 
+            b[idx] = ch ;
+            w-- ;
+            idx-- ;
+
+            if ( x == 0 )
+                break ;
+        }
+
+        if ( x != 0 )
+            throw new AtlasException("formatUnsignedLongHex: overflow") ;
+
+        while ( w > 0 )
+        {
+            b[idx] = '0' ;
+            idx-- ;
+            w-- ;
+        }
+        return width ;
+    }
+    
+    // No checking, fixed width.
+    public static long getLong(byte[] arr, int idx)
+    {
+        long x = 0 ;
+        for ( int i = 0 ; i < 16 ; i++ )
+        {
+            byte c = arr[idx] ;
+            int v = hexByteToInt(c) ;
+            x = x << 4 | v ;  
+            idx++ ; 
+        }
+        return x ;
+    }
+    
+    public static int hexByteToInt(int c)
+    {
+        if ( '0' <= c && c <= '9' )   
+            return c-'0' ;
+        else if ( 'A' <= c && c <= 'F' )
+            return c-'A'+10 ;
+        else if ( 'a' <= c && c <= 'f' )
+            return c-'a'+10 ;
+        else
+            throw new IllegalArgumentException("Bad index char : "+c) ;
+    }
+    
+    /** Return the value of the hex digit, or the marker value if not a hex digit.*/
+    public static int hexByteToInt(int c, int marker)
+    {
+        if ( '0' <= c && c <= '9' )   
+            return c-'0' ;
+        else if ( 'A' <= c && c <= 'F' )
+            return c-'A'+10 ;
+        else if ( 'a' <= c && c <= 'f' )
+            return c-'a'+10 ;
+        else
+            return marker ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/IRILib.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/IRILib.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/IRILib.java
new file mode 100644
index 0000000..daafb74
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/IRILib.java
@@ -0,0 +1,243 @@
+/*
+ * 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.lib;
+
+import java.io.File ;
+import java.io.IOException ;
+
+import org.apache.jena.atlas.AtlasException ;
+import org.apache.jena.base.Sys ;
+
+/** Operations related to IRIs */
+public class IRILib
+{
+    // http://www.w3.org/TR/xpath-functions/#func-encode-for-uri
+    // Encodes delimiters.
+    
+    /* RFC 3986
+     * 
+     * unreserved  = ALPHA / DIGIT / "-" / "." / "_" / "~"
+     * gen-delims  = ":" / "/" / "?" / "#" / "[" / "]" / "@"
+     * sub-delims  = "!" / "$" / "&" / "'" / "(" / ")"
+                    / "*" / "+" / "," / ";" / "="
+     */
+    
+    private static char uri_reserved[] = 
+    { '!', '*', '"', '\'', '(', ')', ';', ':', '@', '&', 
+      '=', '+', '$', ',', '/', '?', '%', '#', '[', ']' } ;
+
+    // No allowed in URIs
+    private static char uri_non_chars[] = { '<', '>', '{', '}', '|', '\\', '`', '^', ' ',  '\n', '\r', '\t', '£' } ;
+    
+    // RFC 2396
+    //private static char uri_unwise[]    = { '{' , '}', '|', '\\', '^', '[', ']', '`' } ;
+
+
+    private static char[] charsComponent =
+    // reserved, + non-chars + nasties.
+    { '!', '*', '"', '\'', '(', ')', ';', ':', '@', '&', 
+      '=', '+', '$', ',', '/', '?', '%', '#', '[', ']',
+      '{', '}', '|', '\\', '`', '^',
+      ' ', '<', '>', '\n', '\r', '\t', '£' } ;
+    
+    private static char[] charsFilename =
+        // reserved, + non-chars + nasties.
+        // Leave : (Windows drive charcater) and / (separator) alone
+        // include SPC.
+        // Should this include "~"?
+        { '!', '*', '"', '\'', '(', ')', ';', /*':',*/ '@', '&', 
+          '=', '+', '$', ',', /*'/',*/ '?', '%', '#', '[', ']',
+          '{', '}', '|', '\\', '`', '^',
+          ' ', '<', '>', '\n', '\r', '\t'} ;
+
+    private static char[] charsPath =  
+    {   // Reserved except leave the separators alone. 
+        '!', '*', '"', '\'', '(', ')', ';', /*':',*/ '@', '&',
+        '=', '+', '$', ',', /*'/',*/ '?', '%', '#', '[', ']',
+        '{', '}', '|', '\\', '`', '^',
+        // Other junk 
+        ' ', '<', '>', '\n', '\r', '\t' } ;
+
+    // The initializers must have run.
+    static final String cwd ; 
+    static final String cwdURL ;
+    
+    // Current directory, with trailing "/"
+    // This matters for resolution.
+    static { 
+        String x = new File(".").getAbsolutePath() ;
+        x = x.substring(0, x.length()-1) ;
+        cwd = x ;
+        cwdURL = plainFilenameToURL(cwd) ;
+    }
+    
+    // See also IRIResolver
+    /** Return a string that is an IRI for the filename.*/
+    public static String fileToIRI(File f) {
+        return filenameToIRI(f.getAbsolutePath()) ;
+    }
+    
+    /** Create a string that is a IRI for the filename.
+     *  <li>The file name may already have {@code file:}.
+     *  <li>The file name may be relative. 
+     *  <li>Encode using the rules for a path (e.g. ':' and'/' do not get encoded)
+     *  <li>Non-IRI characters get %-encoded. 
+     */
+    public static String filenameToIRI(String fn) {
+        if ( fn == null ) return cwdURL ;
+        
+        if ( fn.length() == 0 ) return cwdURL ;
+        
+        if ( fn.startsWith("file:") )
+            return normalizeFilenameURI(fn) ;
+        return plainFilenameToURL(fn) ;
+    }
+    
+    /** Convert a file: IRI to a filename */
+    public static String IRIToFilename(String iri) {
+        if ( ! iri.startsWith("file:") )
+            throw new AtlasException("Not a file: URI: "+iri) ; 
+        
+        String fn ;
+        if ( iri.startsWith("file:///") )
+            fn = iri.substring("file://".length()) ;
+        else
+            fn = iri.substring("file:".length()) ;
+        return decode(fn) ;
+    }
+    
+    /** Convert a plain file name (no file:) to a file: URL */
+    private static String plainFilenameToURL(String fn) {
+        // No "file:"
+        // Make Absolute filename.
+        boolean trailingSlash = fn.endsWith("/") ;
+        File file = new File(fn) ;
+        
+        try { fn = file.getCanonicalPath() ; }
+        catch (IOException e) { fn = file.getAbsolutePath() ; }
+        
+        if ( trailingSlash && ! fn.endsWith("/") )
+            fn = fn + "/" ;
+        
+        if ( Sys.isWindows )
+        {
+            // C:\ => file:///C:/... 
+            if ( fn.length() >= 2 && fn.charAt(1) == ':' )
+                // Windows drive letter - already absolute path.
+                // Make "URI" absolute path
+                fn = "/"+fn ;
+            // Convert \ to /
+            // Maybe should do this on all platforms? i.e consistency.
+            fn = fn.replace('\\', '/' ) ;
+        }
+        
+        fn = encodeFileURL(fn) ;
+        return "file://"+fn ;
+    }
+    
+    
+    /** Sanitize a "file:" URL. Must start "file:" */
+    private static String normalizeFilenameURI(String fn) {
+        if ( ! fn.startsWith("file:/") )
+        {
+            // Relative path.
+            String fn2 = fn.substring("file:".length()) ;
+            return plainFilenameToURL(fn2) ;
+        }
+        
+        // Starts file:///
+        if ( fn.startsWith("file:///") )
+            // Assume it's good and return as-is.
+            return fn ;
+
+        if ( fn.startsWith("file://") )
+        {
+            String fn2 = fn.substring("file:/".length()) ;  // Leave one "/"
+            return plainFilenameToURL(fn2) ;
+        }
+
+        // Must be file:/
+        String fn2 = fn.substring("file:".length()) ;
+        return plainFilenameToURL(fn2) ;
+    }
+
+    /** Encode using the rules for a component (e.g. ':' and '/' get encoded) 
+     * Does not encode non-ASCII characters 
+     */
+    public static String encodeUriComponent(String string) {
+        String encStr = StrUtils.encodeHex(string,'%', charsComponent) ;
+        return encStr ;
+    }
+
+    /** Encode using the rules for a file: URL.  
+     *  Does not encode non-ASCII characters
+     */
+    public static String encodeFileURL(String string) {
+        String encStr = StrUtils.encodeHex(string,'%', charsFilename) ;
+        return encStr ;
+    }
+
+    /** Encode using the rules for a path (e.g. ':' and '/' do not get encoded) */
+    public static String encodeUriPath(String uri) {
+        // Not perfect.
+        // Encode path.
+        // %-encode chars.
+        uri = StrUtils.encodeHex(uri, '%', charsPath) ;
+        return uri ;
+    }
+
+    public static String decode(String string) {
+        return StrUtils.decodeHex(string, '%') ;
+    }
+
+    public static String encodeNonASCII(String string) {
+        if ( ! containsNonASCII(string) )
+            return string ;
+        
+        byte[] bytes = StrUtils.asUTF8bytes(string) ;
+        StringBuilder sw = new StringBuilder() ;
+        for ( byte b : bytes )
+        {
+            // Signed bytes ...
+            if ( b > 0 )
+            {
+                sw.append( (char) b );
+                continue;
+            }
+
+            int hi = ( b & 0xF0 ) >> 4;
+            int lo = b & 0xF;
+            sw.append( '%' );
+            sw.append( Chars.hexDigitsUC[hi] );
+            sw.append( Chars.hexDigitsUC[lo] );
+        }
+        return sw.toString() ;
+    }
+
+    public static boolean containsNonASCII(String string){
+        boolean clean = true ;
+        for ( int i = 0 ; i < string.length() ; i++ )
+        {
+            char ch = string.charAt(i) ;
+            if ( ch >= 127 )
+                return true;
+        }
+        return false ;
+    } 
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/InternalErrorException.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/InternalErrorException.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/InternalErrorException.java
new file mode 100644
index 0000000..b20b2d6
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/InternalErrorException.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.lib;
+
+public class InternalErrorException extends RuntimeException
+{
+    public InternalErrorException()                         { super() ; }
+    public InternalErrorException(String msg)               { super(msg) ; }
+    public InternalErrorException(Throwable th)             { super(th) ; }
+    public InternalErrorException(String msg, Throwable th) { super(msg, th) ; }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/Lib.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/Lib.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/Lib.java
new file mode 100644
index 0000000..6c9c442
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/Lib.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.atlas.lib;
+
+import java.util.List ;
+import java.util.zip.Adler32 ;
+import java.util.zip.CRC32 ;
+import java.util.zip.Checksum ;
+
+import org.apache.jena.atlas.logging.Log ;
+
+public class Lib
+{
+    private Lib() {}
+    
+    public static final void sync(Object object)
+    {
+        if ( object instanceof Sync )
+            ((Sync)object).sync() ;
+    }
+    
+    /** Return true if obj1 and obj are both null or are .equals, else return false */
+    public static final <T> boolean equal(T obj1, T obj2)
+    {
+        if ( obj1 == null )
+            return obj2 == null ;
+        // obj1 != null
+        if ( obj2 == null )
+            return false ;
+        return obj1.equals(obj2) ;
+    }
+    
+    /** Return true if obj1 and obj are both null or are .equals, else return false */
+    public static final boolean equalsIgnoreCase(String str1, String str2)
+    {
+        if ( str1 == null )
+            return str2 == null ;
+        return str1.equalsIgnoreCase(str2) ;
+    }
+    
+
+    /** Return true if obj1 and obj are ! equal */
+    public static final <T> boolean notEqual(T obj1, T obj2)
+    {
+        return ! equal(obj1, obj2) ;
+    }
+
+    /** Safely return the class short name for an object -- obj.getClass().getSimpleName() */
+    static public final String className(Object obj) {
+        if ( obj == null )
+            return "null" ;
+        return classShortName(obj.getClass()) ;
+    }
+
+    /** Safely return the class short name for a class */
+    static public final String classShortName(Class<? > cls) {
+        if ( cls == null )
+            return "null" ;
+        return cls.getSimpleName() ;
+    }
+
+    /** Do two lists have the same elements? */
+    public static <T> boolean equalsListAsSet(List<T> list1, List<T> list2) {
+        if ( list1 == null && list2 == null )
+            return true ;
+        if ( list1 == null ) return false ;
+        if ( list2 == null ) return false ;
+        return list1.containsAll(list2) && list2.containsAll(list1) ;
+    }
+
+    /** HashCode - allow nulls */
+    public static final int hashCodeObject(Object obj) { return hashCodeObject(obj, -4) ; }
+    
+    /** HashCode - allow nulls */
+    public static final int hashCodeObject(Object obj, int nullHashCode)
+    {
+        if ( obj == null )
+            return nullHashCode ; 
+        return obj.hashCode() ;
+    }
+    
+    public static final void sleep(int milliSeconds)
+    {
+        try  { Thread.sleep(milliSeconds) ; }
+        catch (InterruptedException ex) { Log.warn(Lib.class, "interrupted", ex) ; }
+    }
+    
+    /** 
+     * @see CRC32
+     */
+    public static long crc32(byte[] bytes)
+    {
+        return crc(new CRC32(), bytes) ;
+    }
+    
+    /** Faster than CRC32, nearly as good.
+     * @see Adler32
+     */
+    public static long adler32(byte[] bytes)
+    {
+        return crc(new Adler32(), bytes) ;
+    }
+
+    private static long crc(Checksum alg, byte[] bytes)
+    {
+        alg.reset() ;
+        alg.update(bytes, 0, bytes.length) ;
+        return alg.getValue() ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/ListUtils.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/ListUtils.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/ListUtils.java
new file mode 100644
index 0000000..78d591a
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/ListUtils.java
@@ -0,0 +1,150 @@
+/*
+ * 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.lib;
+
+import java.util.ArrayList ;
+import java.util.List ;
+
+
+import org.apache.jena.atlas.io.IndentedWriter ;
+import org.apache.jena.atlas.iterator.Action ;
+import org.apache.jena.atlas.iterator.FilterUnique ;
+import org.apache.jena.atlas.iterator.Iter ;
+import org.apache.jena.atlas.logging.Log ;
+
+/** Various things for lists */
+public class ListUtils
+{
+    private ListUtils() {}
+
+    public static <T>
+    List<T> unique(List<T> list)
+    {
+        Iter<T> iter = Iter.iter(list.iterator()) ;
+        return iter.filter(new FilterUnique<T>()).toList() ;
+    }
+    
+    public static
+    List<Integer> asList(int... values)
+    {
+        List<Integer> x = new ArrayList<>() ;
+        for ( int v : values )
+            x.add(v) ;
+        return x ;
+    }
+    
+    public static <T> String str(T[] array)
+    {
+        StringBuilder buff = new StringBuilder() ;
+        String sep = "[" ;
+
+        for ( T anArray : array )
+        {
+            buff.append( sep );
+            sep = ", ";
+            buff.append( anArray );
+        }
+        buff.append("]") ;
+        return buff.toString() ;
+    }
+    
+    public static String str(int[] array)
+    {
+        StringBuilder buff = new StringBuilder() ;
+        String sep = "[" ;
+
+        for ( int anArray : array )
+        {
+            buff.append( sep );
+            sep = ", ";
+            buff.append( anArray );
+        }
+        buff.append("]") ;
+        return buff.toString() ;
+    }
+    
+    public static String str(long[] array)
+    {
+        StringBuilder buff = new StringBuilder() ;
+        String sep = "[" ;
+
+        for ( long anArray : array )
+        {
+            buff.append( sep );
+            sep = ", ";
+            buff.append( anArray );
+        }
+        buff.append("]") ;
+        return buff.toString() ;
+    }
+
+    public static <T> void print(IndentedWriter out, List<T> list)
+    { 
+        print(out, list, " ") ;
+    }
+    
+    public static <T> void print(final IndentedWriter out, List<T> list, final String sep)
+    {
+        Action<T> output = new Action<T>() {
+            boolean first = true ;
+            @Override
+            public void apply(T item)
+            {
+                if ( ! first ) out.print(sep) ;
+                out.print(item.toString()) ;
+                first = false ;
+            }
+        } ;
+        Iter.apply(list.iterator(), output) ;
+    }
+    
+    /** Return a list of lists of all the elements of collection in every order
+     *  Easy to run out of heap memory.
+     */  
+    static public <T> List<List<T>> permute(List<T> c)
+    {
+        if ( c.size() > 5 )
+        {
+            Log.warn(ListUtils.class, "Attempt to permute more than 5 items - think again") ;
+            return null ;
+        }
+        
+        List<List<T>> x = new ArrayList<>() ;
+        if ( c.size() == 1 )
+        {
+            x.add(c) ;
+            return x ;
+        }
+
+        for ( T obj : c )
+        {
+            List<T> c2 = new ArrayList<>(c) ;
+            c2.remove(obj) ;
+            List<List<T>> x2 = permute(c2) ;
+            // For each list returned
+            for ( List<T> x3 : x2 )
+            {
+                // Gives a more expected ordering
+                x3.add(0,obj) ;
+                x.add(x3) ;
+            }
+        }
+        return x ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/Map2.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/Map2.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/Map2.java
new file mode 100644
index 0000000..75697e0
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/Map2.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.lib;
+
+
+import java.util.Iterator ;
+import java.util.Map ;
+
+import org.apache.jena.atlas.iterator.Iter ;
+
+
+/**
+ * A map with parent sharing. New entries go into a map but lookup is passed to
+ * the parent map if nothing is found at this level.
+ */
+public class Map2<K, V> implements Iterable<K>
+{
+    private final Map<K, V> map1 ; 
+    private final Map2<K, V> map2 ;
+
+    public Map2(Map<K,V> map1, Map2<K,V> map2)
+    {
+        this.map1 = map1 ;
+        this.map2 = map2 ;
+    }
+    
+    public boolean containsKey(K key)
+    {
+        if ( map1.containsKey(key) )
+             return true ;
+        if ( map2 != null )
+            return map2.containsKey(key) ;
+        return false;
+    }
+
+//    public boolean containsValue(V value)
+//    {
+//        if ( map1.containsValue(value) )
+//            return true ;
+//        if ( map2 != null )
+//            return map2.containsValue(value) ;
+//        return false;
+//    }
+
+    public V get(K key)
+    {
+        V v = map1.get(key) ;
+        if ( v != null ) return v ;
+        if ( map2 != null )
+            return map2.get(key) ;
+        return null ;
+    }
+
+    public void put(K key, V value)
+    {
+        if ( map2 != null && map2.containsKey(key) )
+            throw new IllegalArgumentException("Parent map already contains "+key) ;
+        map1.put(key, value) ;
+    }
+
+    // The keys.
+    @Override
+    public Iterator<K> iterator()
+    {
+        Iter<K> iter1 = Iter.iter(map1.keySet().iterator()) ;
+        if ( map2 == null )
+            return iter1 ; 
+        return iter1.append(map2.iterator()) ;
+    }
+    
+    public boolean isEmpty()
+    {
+        boolean x = map1.isEmpty() ;
+        if ( ! x ) return false ;
+        if ( map2 != null )
+            return map2.isEmpty() ;
+        return true ;
+    }
+    
+    public int size()
+    {
+        int x = map1.size() ;
+        if ( map2 != null )
+            x += map2.size();
+        return x ;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/MapUtils.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/MapUtils.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/MapUtils.java
new file mode 100644
index 0000000..7999a25
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/MapUtils.java
@@ -0,0 +1,45 @@
+/*
+ * 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.lib;
+
+import java.util.Map ;
+
+/** Map specific functions */
+public class MapUtils
+{
+    private MapUtils() {}
+    
+    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()) ;
+    }
+    
+    public static <T> void increment(Map<T, Integer> countMap, T key)
+    { increment(countMap, key, 1) ; }
+    
+    public static <T> void increment(Map<T, Integer> countMap, T key, int incr)
+    {
+        Integer integer = countMap.get(key) ;
+        if ( integer == null ) 
+            countMap.put(key, incr) ;
+        else
+            countMap.put(key, integer+incr) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/MultiMap.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/MultiMap.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/MultiMap.java
new file mode 100644
index 0000000..2782ea6
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/MultiMap.java
@@ -0,0 +1,146 @@
+/*
+ * 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.lib;
+
+import java.util.* ;
+
+import org.apache.jena.atlas.iterator.IteratorConcat ;
+
+/* Map from K to collection of V */
+
+public abstract class MultiMap<K, V>
+{
+    private Map<K, Collection<V>> map = new HashMap<>() ;
+
+    protected abstract Collection<V> createCollection() ;
+    
+    public static <K, V> MultiMapToList<K, V> createMapList() { return new MultiMapToList<>() ; }
+    public static <K, V> MultiMapToSet<K, V> createMapSet() { return new MultiMapToSet<>() ; }
+    
+    protected MultiMap() { }
+    
+    protected Collection<V> getByKey(K key) { 
+        return map.get(key) ; 
+    } 
+
+    public abstract Collection<V> get(K key) ; 
+    
+    public V getOne(K key) { 
+        Collection<V> c = map.get(key) ;
+        if ( c == null || c.size() == 0 ) 
+            return null ;
+        return c.iterator().next() ;
+    }
+    
+    public void putAll(K key, @SuppressWarnings("unchecked") V ... values)
+    {
+        for ( V v : values)
+            put(key, v) ;
+    }
+    
+    public void put(K key, V value)
+    { 
+        Collection<V> x = map.get(key) ;
+        if ( x == null )
+        {
+            x = createCollection() ;
+            map.put(key, x) ;
+        }
+        x.add(value) ;
+    }
+    
+    public void remove(K key, V value)  {
+        if ( map.containsKey(key))
+            map.get(key).remove(value) ;
+    }
+    public void removeKey(K key)        { map.remove(key) ; }
+    
+    protected Collection<V> valuesForKey(K key) { return map.get(key); }
+    public abstract Collection<V> values(K key) ;
+    public abstract Collection<V> values() ;
+
+    public boolean containsKey(K key) { return map.containsKey(key) ; }
+    
+    public Set<K> keys()        { return map.keySet() ; }
+    
+    public void clear()         { map.clear() ; }
+    
+    public boolean isEmpty()    { return map.isEmpty() ; }
+
+    /** Does not materialise the contents */
+    public Iterator<V> flatten()
+    {
+        IteratorConcat<V> all = new IteratorConcat<>() ;
+        for ( K k : map.keySet() )        
+        {
+            Collection<V> x =  map.get(k) ;
+            all.add(x.iterator()) ;
+        }
+        return all ;
+    }
+    
+    @Override
+    public boolean equals(Object obj) {
+        if ( this == obj )
+            return true ;
+        if ( obj == null )
+            return false ;
+        if ( getClass() != obj.getClass() )
+            return false ;
+        MultiMap<?,?> other = (MultiMap<?,?>)obj ;
+        if ( map == null ) {
+            if ( other.map != null )
+                return false ;
+        } else if ( !map.equals(other.map) )
+            return false ;
+        return true ;
+    }
+    
+    @Override
+    public int hashCode()       { return map.hashCode()^ 0x01010101 ; }
+    
+    @Override
+    public String toString()
+    {
+        StringBuilder sb = new StringBuilder() ;
+        sb.append("{ ") ;
+        boolean firstKey = true ;
+        for ( K key : keys() )
+        {
+            if ( ! firstKey )
+                sb.append(", ") ;
+            firstKey = false ;
+            sb.append(key) ;
+            sb.append(" => [") ;
+            boolean firstValue = true ; 
+            for ( V value : values(key) )
+            {
+                if ( firstValue )
+                    sb.append(" ") ;
+                else
+                    sb.append(", ") ;
+                sb.append(value) ;
+                firstValue = false ;
+            }
+            sb.append(" ] ") ;
+        }
+        sb.append("}") ;
+        return sb.toString() ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/MultiMapToList.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/MultiMapToList.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/MultiMapToList.java
new file mode 100644
index 0000000..ac1ba70
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/MultiMapToList.java
@@ -0,0 +1,43 @@
+/**
+ * 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.lib;
+
+import java.util.ArrayList ;
+import java.util.Collection ;
+import java.util.List ;
+
+import org.apache.jena.atlas.iterator.Iter ;
+
+public class MultiMapToList<K,V> extends MultiMap<K,V> {
+    public static <K, V> MultiMapToList<K, V> create() { return new MultiMapToList<>() ; }
+    
+    @Override
+    protected Collection<V> createCollection() {
+        return new ArrayList<>() ;
+    }
+    
+    @Override
+    public List<V> get(K key) { return (List<V>)getByKey(key); }
+    
+    @Override
+    public List<V> values(K key) { return (List<V>)valuesForKey(key); }
+    
+    @Override
+    public List<V> values() { return Iter.toList(flatten()) ; }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/MultiMapToSet.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/MultiMapToSet.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/MultiMapToSet.java
new file mode 100644
index 0000000..b89e05b
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/MultiMapToSet.java
@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.atlas.lib;
+
+import java.util.Collection ;
+import java.util.HashSet ;
+import java.util.Set ;
+
+import org.apache.jena.atlas.iterator.Iter ;
+
+public class MultiMapToSet<K,V> extends MultiMap<K,V> {
+    public static <K, V> MultiMapToSet<K, V> create() { return new MultiMapToSet<>() ; }
+    
+    @Override
+    protected Collection<V> createCollection()
+    {
+        return new HashSet<>() ;
+    }
+    
+    @Override
+    public Set<V> get(K key) { return (Set<V>)getByKey(key) ; }
+    
+    @Override
+    public Set<V> values(K key) { return (Set<V>)valuesForKey(key); }
+
+    @Override
+    public Set<V> values() { return Iter.toSet(flatten()) ; }
+
+
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/MultiSet.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/MultiSet.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/MultiSet.java
new file mode 100644
index 0000000..3701042
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/MultiSet.java
@@ -0,0 +1,218 @@
+/*
+ * 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.lib;
+
+import java.util.ArrayList ;
+import java.util.HashMap ;
+import java.util.Iterator ;
+import java.util.List ;
+import java.util.Map ;
+import java.util.NoSuchElementException ;
+
+/** A MultiSet - also known as a Bag
+ */
+
+public class MultiSet<T> implements Iterable<T>
+{
+    private Map<T,RefLong> map   = new HashMap<>() ;
+    private int multiSetSize = 0 ;
+    
+    private RefLong _get(T obj)
+    {
+        RefLong z = map.get(obj) ;
+        if ( z == null )
+        {
+            z = new RefLong(0) ;
+            map.put(obj, z) ;
+        }
+        return z ;
+    }
+ 
+    /** Does it contain any elements at all? */
+    public boolean isEmpty()        { return map.isEmpty() ; }
+
+    /** Does it contain the object? */
+    public boolean contains(T obj)  { return map.containsKey(obj) ; }
+    
+    /** Yield one object per element (i.e without counts) */
+    public Iterator<T> elements()   { return map.keySet().iterator() ; }
+
+    /** Add an object */
+    public void add(T obj)          { _get(obj).inc(); multiSetSize++ ; } 
+
+    /** Add an object, with cardinality n */
+    public void add(T obj, long n)
+    { 
+        if ( n <= 0 ) return ;
+        _get(obj).add(n) ;
+        multiSetSize += n ;
+    }
+    
+    /** Remove one occurrence of the object from the multiset */
+    public void remove(T obj)
+    {
+        RefLong x = map.get(obj) ;
+        if ( x == null ) return ;
+        x.dec() ;
+        multiSetSize-- ;
+        if ( x.value() == 0 )
+            map.remove(obj) ;
+    }
+    
+    /** Remove N occurrences of the object from the multiset */
+    public void remove(T obj, long n)
+    {
+        RefLong x = map.get(obj) ;
+        if ( x == null ) return ;
+        long z = x.value() ;
+        if ( z < n )
+            n = z ;
+        x.subtract(n) ;
+        multiSetSize -= n ;
+        if ( x.value() <= 0 )
+            map.remove(obj) ;
+    }    
+    
+
+    /** Remove all occurrences of the object in themultiset */
+    public void removeAll(T obj)
+    {
+        RefLong x = map.get(obj) ;
+        if ( x == null )
+            return ;
+        multiSetSize -= x.value() ;
+        map.remove(obj) ;
+    }
+
+    /* Remove everything */
+    public void clear() { map.clear() ; multiSetSize = 0 ; }
+    
+    
+    /** Get the count of the number of times the object appears in the multiset - i.e. it's cardinality.
+     * Returns zero when not present.
+     */
+    public long count(T obj)
+    {
+        if ( ! map.containsKey(obj) ) return 0 ;
+        return map.get(obj).value() ;
+    }
+    
+    public int size()
+    {
+//        int count = 0 ;
+//        for ( Map.Entry<T, RefLong> e : map.entrySet() )
+//            count += e.getValue().value() ;
+//        //return count ;
+//        if ( count != multiSetSize )
+//        {
+//            Log.warn(this, "Mismatch") ;
+//            return count ; 
+//        }
+
+        return multiSetSize ;
+    }
+    
+    private Iterator<T> iterator1()
+    {
+        // CRUDE
+        List<T> expanded = new ArrayList<>() ;
+        for ( Map.Entry<T, RefLong> e : map.entrySet() )
+        {
+            for ( int i = 0 ; i < e.getValue().value() ; i++ )
+                expanded.add(e.getKey()) ;
+        }
+        
+        return expanded.iterator() ;
+    }
+    
+    @Override
+    public Iterator<T> iterator()
+    {
+        return new Iterator<T>() {
+            
+            Iterator<T> keys = map.keySet().iterator() ;
+            T key = null ;
+            long keyCount = 0 ;
+            T slot = null ;
+            
+            @Override
+            public boolean hasNext()
+            {
+                if ( slot != null )
+                    return true ;
+                
+                if ( keys == null ) 
+                    return false ;
+                
+                if ( key != null )
+                {
+                    if ( keyCount < count(key) )
+                    {
+                        keyCount++ ;
+                        slot = key ;
+                        return true ;
+                    }
+                    // End of this key.
+                    key = null ;
+                }
+                    
+                if ( keys.hasNext() )
+                {
+                    key = keys.next() ;
+                    keyCount = 1 ;
+                    slot = key ;
+                    return true ;
+                }
+                keys = null ;
+                return false ;
+            }
+
+            @Override
+            public T next()
+            {
+                if ( ! hasNext() ) throw new NoSuchElementException() ;
+                T x = slot ;
+                slot = null ;
+                return x ;
+            }
+
+            @Override
+            public void remove()
+            { throw new UnsupportedOperationException() ; }
+        } ; 
+    }
+    
+    @Override 
+    public String toString()
+    {
+        StringBuilder sb = new StringBuilder() ;
+        sb.append("{") ;
+        String sep = "" ;
+        for ( Map.Entry<T, RefLong> e : map.entrySet() )
+        {
+            sb.append(sep) ;
+            sep = ", " ;
+            sb.append(e.getKey().toString()) ;
+            sb.append("=") ;
+            sb.append(Long.toString(e.getValue().value())) ;
+        }
+        sb.append("}") ;
+        return sb.toString() ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/NotImplemented.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/NotImplemented.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/NotImplemented.java
new file mode 100644
index 0000000..b7e91f1
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/NotImplemented.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.lib;
+
+public class NotImplemented extends InternalErrorException
+{
+    public NotImplemented()                         { super() ; }
+    public NotImplemented(String msg)               { super(msg) ; }
+    public NotImplemented(Throwable th)             { super(th) ; }
+    public NotImplemented(String msg, Throwable th) { super(msg, th) ; }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/NumberUtils.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/NumberUtils.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/NumberUtils.java
new file mode 100644
index 0000000..4f51beb
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/NumberUtils.java
@@ -0,0 +1,133 @@
+/*
+ * 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.lib;
+
+import org.apache.jena.atlas.AtlasException ;
+
+public class NumberUtils
+{
+    // Maximum length of a length 1,2,3,4...
+    private final static int [] maxTable = { 
+        9, 99, 999, 9999, 99999, 999999, 9999999,
+        99999999, 999999999, Integer.MAX_VALUE };
+
+    /** Fast, but basic, integer to StringBuilder */
+    public static void formatInt(StringBuilder sb, int value) 
+    { 
+        // SeeAlso Integer.toString.
+        int len = length(value) ;
+        formatInt(sb, value, len, false) ;
+    }
+    
+    /** Fast, but basic, integer to StringBuilder : always signed */
+    public static void formatSignedInt(StringBuilder sb, int value) 
+    { 
+        int len = length(value) ;
+        if ( value >= 0 )
+            len++ ;
+        formatInt(sb, value, len, true) ;
+    }
+
+    static int length(int x)
+    {
+        if ( x < 0 )
+            return length(-x)+1 ;
+        
+        for (int i=0; ; i++)
+            if (x <= maxTable[i])
+                return i+1;
+    }
+
+    /** Place a fixed width representation of a non-negative int into the string buffer */ 
+    public static void formatInt(StringBuilder sb, int value, int width)
+    { 
+        formatInt(sb, value, width, false) ;
+    }
+    
+    /** Place a fixed width representation into the string buffer : always signed. */ 
+    public static void formatSignedInt(StringBuilder sb, int value, int width) 
+    { 
+        formatInt(sb, value, width, true) ;
+    }
+
+    /** Format an integer, which may be signed */
+    public static void formatInt(StringBuilder sb, int value, int width, boolean signAlways)
+    {
+        boolean negative = (value < 0 ) ;
+        
+        if ( negative )
+        {
+            value = -value ;
+            width -- ;
+            sb.append('-') ;
+        }
+        else if ( signAlways )
+        {
+            width -- ;
+            sb.append('+') ;
+        }
+
+        formatUnsignedInt(sb, value, width) ;
+    }
+
+    /** Place a fixed width representation into the string buffer : never signed. */ 
+    public static void formatUnsignedInt(StringBuilder sb, int value, int width) 
+    { 
+        char chars[] = new char[width] ;
+        formatUnsignedInt$(chars, value, width) ;
+        
+        // Append - the buffer was filled backwards. 
+        for ( int i = 0 ; i < width ; i++ )
+            // Un-backwards.
+            sb.append(chars[width-1-i]) ;
+        
+    }
+
+    // No checking.  char[] filled backwards
+    private static int formatUnsignedInt$(char[] b, int x, int width)
+    {
+        // x >= 0 
+        // Inserts chars backwards
+        int idx = 0 ;
+        while ( width > 0 )
+        {
+            int i = x%10 ;
+            char ch = Chars.digits10[i] ;
+            b[idx] = ch ;
+            width-- ;
+            idx++ ;
+
+            x = x / 10 ;
+            if ( x == 0 )
+                break ;
+        }
+        
+        if ( x != 0 )
+            throw new AtlasException("formatInt: overflow[x="+x+", width="+width+"]") ;
+        
+        while ( width > 0 )
+        {
+            b[idx] = '0' ;
+            idx++ ;
+            width-- ;
+        }
+        return width ;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/Pair.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/Pair.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/Pair.java
new file mode 100644
index 0000000..4f3b055
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/Pair.java
@@ -0,0 +1,62 @@
+/*
+ * 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.lib;
+
+import static org.apache.jena.atlas.lib.Lib.hashCodeObject ;
+import static org.apache.jena.atlas.lib.StrUtils.str ;
+
+public class Pair<A, B>
+{
+    public static <L, R> Pair<L,R> create(L x, R y) { return new Pair<>(x,y) ; }
+    
+    final A a ;
+    final B b ;
+    public Pair(A a, B b) { this.a = a; this.b = b ; }
+    
+    public A getLeft()  { return a ; }
+    public B getRight() { return b ; }
+    
+    public A car() { return a ; }
+    public B cdr() { return b ; }
+    
+    @Override
+    public int hashCode()
+    {
+        return hashCodeObject(car()) ^ hashCodeObject(cdr())<<1 ; 
+    }
+
+    @Override
+    public boolean equals(Object other)
+    {
+        if ( this == other ) return true ;
+
+        // If it's a pair of a different <A,B> then .equals
+        // Pair<A,B>(null,null) is equal to Pair<C,D>(null ,null)
+        // Type erasure makes this hard to check otherwise.
+        // Use class X extends Pair<A,B> and implement .equals to do
+        // instanceof then call super.equals.
+        
+        if( ! ( other instanceof Pair<?,?> ) ) return false ;
+        Pair<?,?> p2 = (Pair<?,?>)other ;
+        return  Lib.equal(car(), p2.car()) && Lib.equal(cdr(), p2.cdr()) ;
+    }
+    
+    @Override 
+    public String toString() { return "("+str(a)+", "+str(b)+")" ; }  
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/Pool.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/Pool.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/Pool.java
new file mode 100644
index 0000000..f28939e
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/Pool.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.lib ;
+
+public interface Pool<T>
+{
+    public abstract void put(T item) ;
+
+    /** Get an item from the pool - return null if the pool is empty */
+    public abstract T get() ;
+
+    public abstract boolean isEmpty() ;
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/PoolBase.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/PoolBase.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/PoolBase.java
new file mode 100644
index 0000000..c6cb505
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/PoolBase.java
@@ -0,0 +1,57 @@
+/*
+ * 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.lib;
+
+import java.util.ArrayDeque ;
+
+/** A Pool of objects. Base implements a non-blocking pool (returns null on no entry)
+ * with infinite upper bound.  Set effective size by creating the right number of
+ * entries when created.
+ */ 
+public class PoolBase<T> implements Pool<T>
+{
+    // For convenience we operate a LIFO policy.
+    // This not part of the extenal contract of a "pool"
+    
+    ArrayDeque<T> pool = new ArrayDeque<>();
+    int maxSize = -1 ;  // Unbounded
+    
+    public PoolBase() {} 
+    //public Pool(int maxSize) { this.maxSize = maxSize ; }
+    
+    @Override
+    public void put(T item)
+    {
+        // Currently, unbounded
+        if ( maxSize >= 0 && pool.size() == 0 )
+        {}
+        pool.push(item) ;
+    }
+    
+    /** Get an item from the pool - return null if the pool is empty */
+    @Override
+    public T get()              
+    { 
+        if ( pool.size() == 0 ) return null ;
+        return pool.pop();
+    }
+    
+    @Override
+    public boolean isEmpty()    { return pool.size() == 0 ; } 
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/PoolSync.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/PoolSync.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/PoolSync.java
new file mode 100644
index 0000000..10e0a6a
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/PoolSync.java
@@ -0,0 +1,53 @@
+/*
+ * 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.lib;
+
+
+/** Synchronization wrapper for a pool */ 
+public class PoolSync<T> implements Pool<T>
+{
+    private Pool<T> pool ;
+
+    public static <T> Pool<T> create(Pool<T> pool)
+    { 
+        if ( pool instanceof PoolSync<?>)
+        {
+            PoolSync<T> p = (PoolSync<T>)pool ;
+            return p ;
+        }
+        return new PoolSync<>(pool) ;
+    }
+    
+    public PoolSync(Pool<T> pool) { this.pool = pool ; } 
+    
+    @Override
+    public final synchronized void put(T item)
+    {
+        pool.put(item) ;
+    }
+    
+    @Override
+    public final synchronized T get()              
+    { 
+        return pool.get();
+    }
+    
+    @Override
+    public final synchronized boolean isEmpty()    { return pool.isEmpty() ; } 
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/Problem.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/Problem.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/Problem.java
new file mode 100644
index 0000000..d571fe7
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/Problem.java
@@ -0,0 +1,25 @@
+/*
+ * 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.lib;
+
+public class Problem extends RuntimeException
+{
+    public Problem(String msg) { super(msg) ; }
+    public Problem(String msg, Throwable th) { super(msg, th) ; }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/PropertiesSorted.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/PropertiesSorted.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/PropertiesSorted.java
new file mode 100644
index 0000000..75060ee
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/PropertiesSorted.java
@@ -0,0 +1,87 @@
+/*
+ * 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.lib;
+
+import java.util.ArrayList ;
+import java.util.Collections ;
+import java.util.Comparator ;
+import java.util.Enumeration ;
+import java.util.Iterator ;
+import java.util.List ;
+import java.util.Properties ;
+
+/** Sorted output */
+public class PropertiesSorted extends Properties
+{
+    private Comparator<String> comparator = null ;
+    
+    //public SortedProperties() { super() ; }
+    
+    public PropertiesSorted(Comparator<String> comparator)
+    { 
+        super() ;
+        this.comparator = comparator ;
+    }
+    
+    
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    @Override
+    public synchronized Enumeration<Object> keys()
+    {
+        // Old world - enumeration, untyped. But we know they are strings (Propetries hides non-strings in get) 
+        Enumeration<Object> keys = super.keys() ;
+        List<String> keys2 = new ArrayList<>(super.size()) ;
+        
+        for( ; keys.hasMoreElements() ; )
+        {
+            Object obj = keys.nextElement() ;
+            if ( obj instanceof String )
+                keys2.add((String)obj);
+        }
+        // Keys are comparable because they are strings.
+        if ( comparator == null )
+            Collections.sort(keys2);
+        else
+            Collections.sort(keys2, comparator) ;
+        
+        return new IteratorToEnumeration(keys2.listIterator()) ;
+    }
+    
+    static class IteratorToEnumeration<T>  implements Enumeration<T>
+    {
+        private Iterator<T> iterator ;
+
+        public IteratorToEnumeration(Iterator<T> iterator)
+        {
+            this.iterator = iterator ;
+        }
+        
+        @Override
+        public boolean hasMoreElements()
+        {
+            return iterator.hasNext() ;
+        }
+
+        @Override
+        public T nextElement()
+        {
+            return iterator.next();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/PropertyUtils.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/PropertyUtils.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/PropertyUtils.java
new file mode 100644
index 0000000..d4ac5b7
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/PropertyUtils.java
@@ -0,0 +1,143 @@
+/*
+ * 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.lib ;
+
+import java.io.* ;
+import java.util.Objects ;
+import java.util.Properties ;
+
+import org.apache.jena.atlas.AtlasException ;
+import org.apache.jena.atlas.io.IO ;
+
+public class PropertyUtils {
+    /** Load properties from a file if the file exists */
+    static public Properties loadFromFile(String filename) throws IOException {
+        Properties properties = new Properties() ;
+        loadFromFile(properties, filename) ;
+        return properties ;
+    }
+
+    /** Load properties from a file if the file exists */
+    static public void loadFromFile(Properties properties, String filename) throws IOException {
+        Objects.requireNonNull(filename, "File name must not be null") ;
+        if ( "-".equals(filename) )
+            throw new IllegalArgumentException("Filename is \"-\" (stdin not supported)") ;
+
+        try (InputStream in = new FileInputStream(filename); Reader r = IO.asBufferedUTF8(in);) {
+            properties.load(r) ;
+        }
+    }
+
+    static public void storeToFile(Properties properties, String comment, String filename) throws IOException {
+        String str = comment ;
+        if ( str == null )
+            str = filename ;
+        try (FileOutputStream fos = new FileOutputStream(filename)) {
+            Writer w = IO.asBufferedUTF8(fos) ;
+            properties.store(w, "Metadata: " + str) ;
+            w.flush() ;
+        }
+    }
+
+    public static int getPropertyAsInteger(Properties properties, String key) {
+        String x = properties.getProperty(key) ;
+        if ( x == null )
+            throw new AtlasException("No such property key: " + key) ;
+        return Integer.parseInt(x) ;
+    }
+
+    public static int getPropertyAsInteger(Properties properties, String key, int defaultValue) {
+        String x = properties.getProperty(key) ;
+        if ( x == null )
+            return defaultValue ;
+        return Integer.parseInt(x) ;
+    }
+
+    public static boolean getPropertyAsBoolean(Properties properties, String key, boolean dftValue) {
+        String x = properties.getProperty(key) ;
+        if ( x == null )
+            return dftValue ;
+        if ( x.equalsIgnoreCase("true") )
+            return true ;
+        if ( x.equalsIgnoreCase("false") )
+            return true ;
+        throw new AtlasException("Value '" + x + "'not recognized for " + key) ;
+    }
+
+    public static Boolean getPropertyAsBoolean(Properties properties, String key) {
+        String x = properties.getProperty(key) ;
+        if ( x == null )
+            throw new AtlasException("No such property key: " + key) ;
+        if ( x.equalsIgnoreCase("true") )
+            return true ;
+        if ( x.equalsIgnoreCase("false") )
+            return true ;
+        throw new AtlasException("Value '" + x + "'not recognized for " + key) ;
+    }
+
+    /** Test whether a property has a value. Null tests equal to not present. */
+    public boolean propertyEquals(Properties properties, String key, String value) {
+        return Lib.equal(properties.getProperty(key), value) ;
+    }
+
+    /** Set property if not already set. */
+    public void ensurePropertySet(Properties properties, String key, String expected) {
+        getOrSetDefault(properties, key, expected) ;
+    }
+
+    /**
+     * Get property or the default value - also set the default value if not
+     * present
+     */
+    public String getOrSetDefault(Properties properties, String key, String expected) {
+        String x = properties.getProperty(key) ;
+        if ( x == null ) {
+            properties.setProperty(key, expected) ;
+            x = expected ;
+        }
+        return x ;
+    }
+
+    /** Check property is an expected value or set if missing */
+    public void checkOrSetProperty(Properties properties, String key, String expected) {
+        String x = properties.getProperty(key) ;
+        if ( x == null ) {
+            properties.setProperty(key, expected) ;
+            return ;
+        }
+        if ( x.equals(expected) )
+            return ;
+
+        inconsistent(properties, key, x, expected) ;
+    }
+
+    /** Check property has the vakue given - throw exception if not. */
+    public void checkMetadata(Properties properties, String key, String expected) {
+        String value = properties.getProperty(key) ;
+
+        if ( !Lib.equal(value, value) )
+            inconsistent(properties, key, value, expected) ;
+    }
+
+    private void inconsistent(Properties properties, String key, String actual, String expected) {
+        String msg = String.format("Inconsistent: key=%s value=%s expected=%s", key, (actual == null ? "<null>" : actual),
+                                   (expected == null ? "<null>" : expected)) ;
+        throw new AtlasException(msg) ;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/RandomLib.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/RandomLib.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/RandomLib.java
new file mode 100644
index 0000000..d2b3516
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/RandomLib.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.atlas.lib;
+
+import java.security.SecureRandom ;
+import java.util.Random ;
+
+public class RandomLib
+{
+    private RandomLib() {}
+    
+    /** Single instance, pure random generator */
+    public static final Random random = new SecureRandom() ;
+    
+    /** Random numbers, good seed, cheaper allocation */
+    public static final Random qrandom = randInit() ;
+
+    private static Random randInit()
+    {
+        // Cheap random numbers, well seeded.
+        int seed = random.nextInt() ;
+        return new Random(seed) ;   
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/Ref.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/Ref.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/Ref.java
new file mode 100644
index 0000000..e25b145
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/Ref.java
@@ -0,0 +1,54 @@
+/*
+ * 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.lib;
+
+
+/** A mutable container */
+public class Ref<T>
+{
+    private T value ;
+
+    public T getValue() { return value; }
+
+    public Ref(T initial)
+    { setValue(initial); }
+
+    public void setValue(T value)
+    { this.value = value; }
+
+    
+    // hashCode and equality are defined on object pointers. 
+//    @Override
+//    public int hashCode() { return value.hashCode()^0x1 ; }
+//    
+//    @Override
+//    public boolean equals(Object other)
+//    {
+//        if ( !(  other instanceof Ref ) )
+//            return false ;
+//        Ref<?> r = (Ref<?>)other ;
+//        return Lib.equals(value, r.value) ;
+//    }
+    
+    @Override
+    public String toString()
+    {
+        return "ref:"+value.toString() ; 
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/RefLong.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/RefLong.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/RefLong.java
new file mode 100644
index 0000000..30dc663
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/RefLong.java
@@ -0,0 +1,38 @@
+/*
+ * 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.lib;
+
+public final class RefLong
+{
+    private long value ;
+    public RefLong()                { this(0) ; }
+    public RefLong(long v)          { value = v ; }
+    public long value()             { return value ; }
+    public void inc()               { value++ ; } 
+    public void dec()               { --value ; }
+    public long getAndInc()         { return value++ ; }
+    public long incAndGet()         { return ++value ; }
+    public long getAndDec()         { return value-- ; }
+    public long decAndGet()         { return --value ; }
+    public void add(long v)         { value += v ; }
+    public void subtract(long v)    { value -= v ; }
+    public void set(long v)         { value = v ; }
+    @Override public String toString() { return "Ref:"+Long.toString(value) ; } 
+    // hashCode and equals are Object equality - this is a mutable object
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/Registry.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/Registry.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/Registry.java
new file mode 100644
index 0000000..b1e0d74
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/Registry.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.lib;
+
+import java.util.Collection ;
+import java.util.Map ;
+import java.util.concurrent.ConcurrentHashMap ;
+
+public class Registry<K,T>
+{
+    protected Map<K, T> registry = new ConcurrentHashMap<>() ;
+    
+    public Registry() {}
+    
+    public void put(K key, T value)     { registry.put(key, value) ; }
+    public T get(K key)                 { return registry.get(key) ; }
+    public boolean isRegistered(K key)  { return registry.containsKey(key) ; }
+    public void remove(K key)           { registry.remove(key) ; } 
+    public Collection<K> keys()         { return registry.keySet() ; }
+    //public Iterator<String> keys()      { return registry.keySet().iterator() ; }
+    public int size()                   { return registry.size() ; }
+    public boolean isEmpty()            { return registry.isEmpty() ; }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/ReverseComparator.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/ReverseComparator.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/ReverseComparator.java
new file mode 100644
index 0000000..7019b68
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/ReverseComparator.java
@@ -0,0 +1,38 @@
+/*
+ * 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.lib;
+
+import java.util.Comparator ;
+
+/** Comparator that compares the other way round to it's sub-comparator */  
+public class ReverseComparator<T> implements Comparator<T>
+{
+    private final Comparator<T> comparator ;
+    
+    public ReverseComparator(Comparator<T> comparator)
+    {
+        this.comparator = comparator ;
+    }
+    
+    @Override
+    public int compare(T o1, T o2)
+    {
+        return comparator.compare(o2, o1) ;
+    }
+}


[6/9] jena git commit: Import of lib, io, logging and iterator packages

Posted by an...@apache.org.
http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/BitsLong.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/BitsLong.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/BitsLong.java
new file mode 100644
index 0000000..e147033
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/BitsLong.java
@@ -0,0 +1,311 @@
+/*
+ * 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.lib;
+
+// NB shifting is "mod 64" -- <<64 is a no-op (not a clear).
+// http://mindprod.com/jgloss/masking.html
+
+/** Utilities for manipulating a bit pattern which are held in a 64 bit long
+ *  @see BitsInt
+ *  (java.util.BitSet does not allow getting the pattern as a long) 
+ */ 
+public final class BitsLong
+{
+    private BitsLong() {}
+
+    private static int LongLen = Long.SIZE ;
+    
+    /** Extract the value packed into bits start (inclusive) and finish (exclusive),
+     *  the value is returned the low part of the returned long.
+     *  The low bit is bit zero.
+     * @param bits
+     * @param start
+     * @param finish
+     * @return long
+     */ 
+    
+    public static final
+    long unpack(long bits, int start, int finish)
+    {
+        check(start, finish) ;
+        if ( finish == 0 ) return 0 ;
+        // Remove top bits by moving up.  Clear bottom bits by them moving down.
+        return (bits<<(LongLen-finish)) >>> ((LongLen-finish)+start) ;
+    }
+
+    /** Place the value into the bit pattern between start and finish
+     * and returns the new value.  Leaves other bits alone.
+     * @param bits
+     * @param value
+     * @param start
+     * @param finish
+     * @return long
+     */
+    public static final
+    long pack(long bits, long value, int start, int finish)
+    {
+        check(start, finish) ;
+        bits = clear$(bits, start, finish) ;
+        long mask = mask(start, finish) ;
+        bits = bits | ( (value<<start) & mask ) ;
+        return bits ;
+    }
+
+    /** Get bits from a hex string.
+     * 
+     * @param str
+     * @param startChar     Index of first character (counted from the left, string style). 
+     * @param finishChar    Index after the last character (counted from the left, string style).
+     * @return long
+     */
+    
+    public static final
+    long unpack(String str, int startChar, int finishChar)
+    {
+        String s = str.substring(startChar, finishChar) ;
+        return Long.parseLong(s, 16) ;
+    }
+
+    /** Set the bits specificied.
+     * 
+     * @param bits      Pattern
+     * @param bitIndex 
+     * @return          Modified pattern
+     */
+    public static final
+    long set(long bits, int bitIndex)
+    { 
+        check(bitIndex) ;
+        return set$(bits, bitIndex) ;
+    }
+
+    /** Set the bits from string (inc) to finish (exc) to one
+     * 
+     * @param bits      Pattern
+     * @param start     start  (inclusive)
+     * @param finish    finish (exclusive)
+     * @return          Modified pattern
+     */
+    public static final
+    long set(long bits, int start, int finish)
+    { 
+        check(start, finish) ;
+        return set$(bits, start, finish) ;
+    }
+
+    /** Test whether a bit is the same as isSet 
+     * @param bits      Pattern
+     * @param isSet     Test whether is set or not. 
+     * @param bitIndex  Bit index
+     * @return          Boolean
+     */
+    public static final
+    boolean test(long bits, boolean isSet, int bitIndex)
+    {
+        check(bitIndex) ;
+        return test$(bits, isSet, bitIndex) ;
+    }
+    
+    /** Test whether a bit is set 
+     * @param bits      Pattern
+     * @param bitIndex  Bit index
+     * @return          Boolean
+     */
+    public static final
+    boolean isSet(long bits, int bitIndex)
+    {
+        check(bitIndex) ;
+        return test$(bits, true, bitIndex) ;
+    }
+    
+    /** Test whether a range has a specific value or not   
+     * @param bits      Pattern
+     * @param value     Value to test for
+     * @param start     start  (inclusive)
+     * @param finish    finish (exclusive)
+     * @return          Boolean
+     */
+    public static final
+    boolean test(long bits, long value, int start, int finish)
+    {
+        check(start, finish) ;
+        return test$(bits, value, start, finish) ;
+    }
+    
+    /** Get the bits from start (inclusive) to finish (exclusive),
+     *  leaving them aligned in the long.  See also unpack, returns
+     *  the value found at that place.
+     *  @see #unpack(long, int, int)
+     *  @param bits
+     *  @param start
+     *  @param finish
+     *  @return lon     */
+    
+    public static final
+    long access(long bits, int start, int finish)
+    {
+        check(start, finish) ;
+        return access$(bits, start, finish) ; 
+    }
+    
+    /**
+     * Clear the bits specified.
+     *  @param bits
+     *  @param start
+     *  @param finish
+     *  @return long
+     */
+  public static final
+    long clear(long bits, int start, int finish)
+    {
+        check(start, finish) ;
+        return clear$(bits, start, finish) ;
+    }
+
+    /**
+     * Create a mask that has ones between bit positions start (inc) and finish (exc),
+     * and zeros elsewhere.
+     * @param start
+     * @param finish
+     * @return long
+     */
+    public static final
+    long mask(int start, int finish)
+    {
+        check(start, finish) ;
+        return mask$(start, finish) ;
+    }
+    
+    /**
+     * Create a mask that has zeros between bit positions start (inc) and finish (exc),
+     * and ones elsewhere
+     * @param start
+     * @param finish
+     * @return long
+     */
+    public static final
+    long maskZero(int start, int finish)
+    {
+        check(start, finish) ;
+        return maskZero$(start, finish) ;
+    }
+    
+    private static final
+    long clear$(long bits, int start, int finish)
+    {
+        long mask = maskZero$(start, finish) ;
+        bits = bits & mask ;
+        return bits ;
+    }
+
+    private static final
+    long set$(long bits, int bitIndex)
+    { 
+        long mask = mask$(bitIndex) ;
+        return bits | mask ;
+    }
+
+    private static final
+    long set$(long bits, int start, int finish)
+    { 
+        long mask = mask$(start, finish) ;
+        return bits | mask ;
+    }
+
+    private static
+    boolean test$(long bits, boolean isSet, int bitIndex)
+    {
+        return isSet == access$(bits, bitIndex) ;
+    }
+
+    private static
+    boolean test$(long bits, long value, int start, int finish)
+    {
+        long v = access$(bits, start, finish) ;
+        return v == value ;
+    }
+
+
+    
+    private static final
+    boolean access$(long bits, int bitIndex)
+    {
+        long mask = mask$(bitIndex) ;
+        return (bits & mask) != 0L ;
+    }
+    
+    private static final
+    long access$(long bits, int start, int finish)
+    {
+        // Two ways:
+//        long mask = mask$(start, finish) ;
+//        return bits & mask ;
+        
+        return ( (bits<<(LongLen-finish)) >>> (LongLen-finish+start) ) << start  ;
+    }
+    
+
+    private static final
+    long mask$(int bitIndex)
+    {
+        return 1L << bitIndex ;
+    }
+
+    private static final
+    long mask$(int start, int finish)
+    {
+    //        long mask = 0 ;
+    //        if ( finish == Long.SIZE )
+    //            // <<Long.SIZE is a no-op 
+    //            mask = -1 ;
+    //        else
+    //            mask = (1L<<finish)-1 ;
+        if ( finish == 0 )
+            // So start is zero and so the mask is zero.
+            return 0 ;
+
+        
+        long mask = -1 ;
+//        mask = mask << (LongLen-finish) >>> (LongLen-finish) ;      // Clear the top bits
+//        return mask >>> start << start ;                  // Clear the bottom bits
+        return mask << (LongLen-finish) >>> (LongLen-finish+start) << start ; 
+    }
+
+    private static final
+    long maskZero$(int start, int finish)
+    {
+
+        return ~mask$(start, finish) ;
+    }
+    
+    private static final
+    void check(long bitIndex)
+    {
+        if ( bitIndex < 0 || bitIndex >= LongLen ) throw new IllegalArgumentException("Illegal bit index: "+bitIndex) ;
+    }
+
+    private static final
+    void check(long start, long finish)
+    {
+        if ( start < 0 || start >= LongLen ) throw new IllegalArgumentException("Illegal start: "+start) ;
+        if ( finish < 0 || finish > LongLen ) throw new IllegalArgumentException("Illegal finish: "+finish) ;
+        if ( start > finish )  throw new IllegalArgumentException("Illegal range: ("+start+", "+finish+")") ;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/ByteBufferLib.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/ByteBufferLib.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/ByteBufferLib.java
new file mode 100644
index 0000000..c619fed
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/ByteBufferLib.java
@@ -0,0 +1,211 @@
+/*
+ * 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.lib ;
+
+import static java.lang.System.arraycopy ;
+
+import java.io.PrintStream ;
+import java.nio.ByteBuffer ;
+import java.util.Arrays ;
+
+public class ByteBufferLib {
+    public static boolean allowArray = true ;
+
+    private ByteBufferLib() {}
+
+    public static void fill(ByteBuffer bb, byte v) {
+        fill(bb, bb.position(), bb.limit(), v) ;
+    }
+
+    public static void fill(ByteBuffer bb, int start, int finish, byte v) {
+        for ( int i = start ; i < finish ; i++ )
+            bb.put(i, v) ;
+    }
+
+    public static String details(ByteBuffer byteBuffer) {
+        // Like ByteBuffer.toString but without the class.
+        return "[pos=" + byteBuffer.position() + " lim=" + byteBuffer.limit() + " cap=" + byteBuffer.capacity() + "]" ;
+    }
+
+    public static void print(ByteBuffer byteBuffer) {
+        print(System.out, byteBuffer) ;
+    }
+
+    public static void print(PrintStream out, ByteBuffer byteBuffer) {
+        byteBuffer = byteBuffer.duplicate() ;
+
+        out.printf("ByteBuffer[pos=%d lim=%d cap=%d]", byteBuffer.position(), byteBuffer.limit(), byteBuffer.capacity()) ;
+
+        // Print bytes.
+        int i = 0 ;
+        int maxBytes = 3 * 20 ;
+        for ( ; i < maxBytes && i < byteBuffer.limit() ; i++ ) {
+            if ( i % 20 == 0 )
+                out.println() ;
+            out.printf(" 0x%02X", byteBuffer.get(i)) ; // Does not move position
+        }
+        if ( i < byteBuffer.limit() ) {
+            if ( i % 24 == 0 )
+                out.println() ;
+            out.print(" ...") ;
+        }
+        // Print as 4-byte ints
+        // int maxSlots = 8 ;
+        // int i = 0 ;
+        // for ( ; i < maxSlots && 4*i < byteBuffer.limit() ; i++ )
+        // out.printf(" 0x%04X", byteBuffer.getInt(4*i)) ;
+        // if ( i < maxSlots )
+        // out.print(" ...") ;
+        out.println() ;
+    }
+
+    public static boolean sameValue(ByteBuffer bb1, ByteBuffer bb2) {
+        if ( bb1.capacity() != bb2.capacity() )
+            return false ;
+
+        for ( int i = 0 ; i < bb1.capacity() ; i++ )
+            if ( bb1.get(i) != bb2.get(i) )
+                return false ;
+        return true ;
+    }
+
+    /**
+     * Copy of a ByteBuffer - the contents are copied (unlike
+     * ByteBuffer.duplicate)
+     */
+    final public static ByteBuffer duplicate(ByteBuffer bb) {
+        ByteBuffer bb2 = ByteBuffer.allocate(bb.limit() - bb.position()) ;
+        int x = bb.position() ;
+        bb2.put(bb) ;
+        bb.position(x) ;
+
+        bb2.position(0) ;
+        bb2.limit(bb2.capacity()) ;
+        return bb2 ;
+    }
+
+    /** Copy from a byte buffer */
+    final public static byte[] bb2array(ByteBuffer bb, int start, int finish) {
+        byte[] b = new byte[finish - start] ;
+        bb2array(bb, start, finish, b) ;
+        return b ;
+    }
+
+    private static void bb2array(ByteBuffer bb, int start, int finish, byte[] b) {
+        for ( int j = 0, i = start ; i < finish ; i++ )
+            b[j] = bb.get(i) ;
+    }
+
+    // For non-array versions : beware of overlaps.
+    final public static void bbcopy(ByteBuffer bb, int src, int dst, int length, int slotLen) {
+        if ( src == dst )
+            return ;
+
+        if ( allowArray && bb.hasArray() ) {
+            acopyArray(bb, src, dst, length, slotLen) ;
+            return ;
+        }
+
+        if ( src < dst )
+            bbcopy1(bb, src, dst, length, slotLen) ;
+        else
+            bbcopy2(bb, src, dst, length, slotLen) ;
+    }
+
+    private final static void bbcopy1(ByteBuffer bb, int src, int dst, int length, int slotLen) {
+        int bDst = dst * slotLen ;
+        int bSrc = src * slotLen ;
+        int bLen = length * slotLen ;
+        // src < dst so top dst is not in the overlap : work backwards
+        for ( int i = bLen - 1 ; i >= 0 ; i-- )
+            bb.put(bDst + i, bb.get(bSrc + i)) ;
+    }
+
+    private final static void bbcopy2(ByteBuffer bb, int src, int dst, int length, int slotLen) {
+        int bDst = dst * slotLen ;
+        int bSrc = src * slotLen ;
+        int bLen = length * slotLen ;
+        // src > dst so dst[0] is not in the overlap
+        for ( int i = 0 ; i < bLen ; i++ )
+            bb.put(bDst + i, bb.get(bSrc + i)) ;
+    }
+
+    public final static void bbcopy(ByteBuffer bb1, int src, ByteBuffer bb2, int dst, int length, int slotLen) {
+        // Assume bb1 and bb2 are different and do not overlap.
+        if ( allowArray && bb1.hasArray() && bb2.hasArray() ) {
+            acopyArray(bb1, src, bb2, dst, length, slotLen) ;
+            return ;
+        }
+        // One or both does not have an array.
+
+        int bSrc = src * slotLen ;
+        int bDst = dst * slotLen ;
+        int bLen = length * slotLen ;
+
+        for ( int i = 0 ; i < bLen ; i++ )
+            bb2.put(bDst + i, bb1.get(bSrc + i)) ;
+    }
+
+    final public static void bbfill(ByteBuffer bb, int fromIdx, int toIdx, byte fillValue, int slotLen) {
+        if ( allowArray && bb.hasArray() ) {
+            afillArray(bb, fromIdx, toIdx, fillValue, slotLen) ;
+            return ;
+        }
+
+        int bStart = fromIdx * slotLen ;
+        int bFinish = toIdx * slotLen ;
+
+        for ( int i = bStart ; i < bFinish ; i++ )
+            bb.put(i, fillValue) ;
+    }
+
+    // To ArrayOps?
+
+    final private static void acopyArray(ByteBuffer bb, int src, int dst, int length, int slotLen) {
+        byte[] b = bb.array() ;
+
+        int offset = bb.arrayOffset() ;
+
+        int bSrc = src * slotLen ;
+        int bDst = dst * slotLen ;
+        int bLen = length * slotLen ;
+
+        arraycopy(b, offset + bSrc, b, offset + bDst, bLen) ;
+    }
+
+    final private static void acopyArray(ByteBuffer bb1, int src, ByteBuffer bb2, int dst, int length, int slotLen) {
+        byte[] b1 = bb1.array() ;
+        byte[] b2 = bb2.array() ;
+        int offset1 = bb1.arrayOffset() ;
+        int offset2 = bb2.arrayOffset() ;
+
+        int bSrc = src * slotLen ;
+        int bDst = dst * slotLen ;
+        int bLen = length * slotLen ;
+
+        arraycopy(b1, offset1 + bSrc, b2, offset2 + bDst, bLen) ;
+    }
+
+    final private static void afillArray(ByteBuffer bb, int fromIdx, int toIdx, byte fillValue, int slotLen) {
+        int offset = bb.arrayOffset() ;
+        int bStart = fromIdx * slotLen ;
+        int bFinish = toIdx * slotLen ;
+        Arrays.fill(bb.array(), bStart + offset, bFinish + offset, fillValue) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/Bytes.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/Bytes.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/Bytes.java
new file mode 100644
index 0000000..789aeeb
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/Bytes.java
@@ -0,0 +1,357 @@
+/*
+ * 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.lib;
+
+import java.io.UnsupportedEncodingException ;
+import java.nio.ByteBuffer ;
+import java.nio.CharBuffer ;
+import java.nio.charset.CharsetDecoder ;
+import java.nio.charset.CharsetEncoder ;
+import java.nio.charset.CoderResult ;
+
+/** Byte-oriented operations.  Packing and unpacking integers
+ *  is in network order (Big endian - which is the preferred order in Java)
+ *  {@link "http://en.wikipedia.org/wiki/Endianness"}
+ */  
+
+public class Bytes
+{
+    private Bytes() {}
+    
+    /** Compare two byte arrays which may be of different lengths */ 
+    public static int compare(byte[] x1, byte[] x2)
+    {
+        int n = Math.min(x1.length, x2.length) ;
+        
+        for ( int i = 0 ; i < n ; i++ )
+        {
+            byte b1 = x1[i] ;
+            byte b2 = x2[i] ;
+            if ( b1 == b2 )
+                continue ;
+            // Treat as unsigned values in the bytes. 
+            return (b1&0xFF) - (b2&0xFF) ;  
+        }
+
+        return x1.length - x2.length ;
+    }
+
+    public static int compareByte(byte b1, byte b2)
+    {
+        return (b1&0xFF) - (b2&0xFF) ;  
+    }
+    
+    public static byte[] copyOf(byte[] bytes)
+    {
+        return copyOf(bytes, 0, bytes.length) ;
+    }
+    
+    public static byte[] copyOf(byte[] bytes, int start)
+    {
+        return copyOf(bytes, start, bytes.length-start) ;
+    }
+    
+    public static byte[] copyOf(byte[] bytes, int start, int length)
+    {
+        byte[] newByteArray = new byte[length] ;
+        System.arraycopy(bytes, start, newByteArray, 0, length) ;
+        return newByteArray ;
+    }
+    
+    final public static byte[] hexDigitsUC = {
+        '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' ,
+        '9' , 'A' , 'B' , 'C' , 'D' , 'E' , 'F' };
+
+    final public static byte[] hexDigitsLC = {
+        '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' ,
+        '9' , 'a' , 'b' , 'c' , 'd' , 'e' , 'f' };
+
+    /** Get an int from a byte array (network order)
+     * @param b Byte Array
+     */
+    public static final int getInt(byte[]b)
+    { return getInt(b, 0) ; }
+    
+    /** Get an int from a byte array (network order)
+     * @param b Byte Array
+     * @param idx Starting point of bytes 
+     */
+    public static final int getInt(byte[]b, int idx)
+    {
+        return assembleInt(b[idx+0],
+                           b[idx+1],
+                           b[idx+2],
+                           b[idx+3]) ;
+    }
+
+    /** Get a long from a byte array (network order)
+     * @param b Byte Array
+     */
+    public static final long getLong(byte[]b)
+    { return getLong(b, 0) ; }
+    
+    /** Get a long from a byte array (network order)
+     * @param b Byte Array
+     * @param idx Starting point of bytes 
+     */
+    public static final long getLong(byte[]b, int idx)
+    {
+        return assembleLong(b[idx+0],
+                            b[idx+1],
+                            b[idx+2],
+                            b[idx+3],
+                            b[idx+4],
+                            b[idx+5],
+                            b[idx+6],
+                            b[idx+7]) ;
+
+    }
+
+    /** Put an int into a byte array
+     * @param value The integer
+     * @param b byte array 
+     */
+    public static final void setInt(int value, byte[]b)
+    { setInt(value, b, 0) ; }
+    
+    /** Put an int into a byte array from a given position
+     * @param x The integer
+     * @param b byte array 
+     * @param idx starting point
+     */
+    public static final void setInt(int x, byte[]b, int idx)
+    {
+//        b[idx+0] = byte3(value) ;
+//        b[idx+1] = byte2(value) ;
+//        b[idx+2] = byte1(value) ;
+//        b[idx+3] = byte0(value) ;
+      b[idx+0] = (byte)((x >> 24)&0xFF) ;
+      b[idx+1] = (byte)((x >> 16)&0xFF);
+      b[idx+2] = (byte)((x >>  8)&0xFF);
+      b[idx+3] = (byte)(x &0xFF);
+
+    }
+    
+    
+    /** Put a long into a byte array
+     * @param value The integer
+     * @param b byte array 
+     */
+    public static final void setLong(long value, byte[]b)
+    { setLong(value, b, 0) ; }
+    
+    /** Put a long into a byte array from a given position
+     * @param value The integer
+     * @param b byte array 
+     * @param idx starting point
+     */
+    public static final void setLong(long value, byte[] b, int idx) {
+        int lo = (int)(value & 0xFFFFFFFFL) ;
+        int hi = (int)(value >>> 32) ;
+        setInt(hi, b, idx) ;
+        setInt(lo, b, idx + 4) ;
+    }
+
+    /** int to byte array */
+    public static byte[] packInt(int val) {
+        byte[] valBytes = new byte[Integer.SIZE / Byte.SIZE] ;
+        setInt(val, valBytes, 0) ;
+        return valBytes ;
+    }
+
+    /** long to byte array */
+    public static byte[] packLong(long val) {
+        byte[] valBytes = new byte[Long.SIZE / Byte.SIZE] ;
+        setLong(val, valBytes, 0) ;
+        return valBytes ;
+    }
+
+    /** Make an int order of args -- high to low */
+    static private int assembleInt(byte b3, byte b2, byte b1, byte b0) {
+        return ( ((b3 & 0xFF) << 24) |
+                 ((b2 & 0xFF) << 16) |
+                 ((b1 & 0xFF) <<  8) |
+                 ((b0 & 0xFF) <<  0)
+            );
+    }
+
+    /** Make a long order of args -- high to low */
+    static private Long assembleLong(byte b7, byte b6, byte b5, byte b4, byte b3, byte b2, byte b1, byte b0)
+    {
+        
+        return  (((long)b7 & 0xFF) << 56) |
+                (((long)b6 & 0xFF) << 48) |
+                (((long)b5 & 0xFF) << 40) |
+                (((long)b4 & 0xFF) << 32) |
+                (((long)b3 & 0xFF) << 24) |
+                (((long)b2 & 0xFF) << 16) |
+                (((long)b1 & 0xFF) <<  8) |
+                (((long)b0 & 0xFF) <<  0) ;
+    }
+
+    private static byte byte3(int x) { return (byte)(x >> 24); }
+    private static byte byte2(int x) { return (byte)(x >> 16); }
+    private static byte byte1(int x) { return (byte)(x >>  8); }
+    private static byte byte0(int x) { return (byte)(x >>  0); }
+
+    /** Return the UTF-8 bytes for a string */
+    public static byte[] string2bytes(String x) {
+        try {
+            return x.getBytes("UTF-8") ;
+        }
+        catch (UnsupportedEncodingException ex) {
+            // Impossible.
+            ex.printStackTrace() ;
+            return null ;
+        }
+    }
+    
+    /** Return the string for some UTF-8 bytes */
+    public static String bytes2string(byte[] x) {
+        try {
+            return new String(x, "UTF-8") ;
+        }
+        catch (UnsupportedEncodingException ex) {
+            // Impossible-ish.
+            ex.printStackTrace() ;
+            return null ;
+        }
+    }
+
+    /** Encode a string into a ByteBuffer : on return position is the end of the encoding */
+    public static int toByteBuffer(CharSequence s, ByteBuffer bb) {
+        //BlockUTF8.fromChars(s, bb) ;
+        CharsetEncoder enc = Chars.allocEncoder();
+        int x = toByteBuffer(s, bb, enc) ;
+        Chars.deallocEncoder(enc) ;
+        return x ;
+    }
+    
+    /** Encode a string into a ByteBuffer : on return position is the end of the encoding */
+    public static int toByteBuffer(CharSequence s, ByteBuffer bb, CharsetEncoder enc) {
+        int start = bb.position() ;
+        CharBuffer cBuff = CharBuffer.wrap(s);
+        enc.reset();
+        CoderResult r = enc.encode(cBuff, bb, true) ;
+        if ( r.isOverflow() )
+            throw new InternalErrorException("Bytes.toByteBuffer: encode overflow (1)") ;
+        r = enc.flush(bb) ;
+        if ( r.isOverflow() )
+            throw new InternalErrorException("Bytes.toByteBuffer: encode overflow (2)") ;
+//        if ( r.isUnderflow() )
+//            throw new InternalErrorException("Bytes.toByteBuffer: encode underflow") ;
+        int finish = bb.position() ;
+        return finish-start ;
+    }
+    
+    /** Decode a string into a ByteBuffer */
+    public static String fromByteBuffer(ByteBuffer bb)
+    {
+        //return BlockUTF8.toString(bb) ;
+        // To be removed (Dec 2011)
+        CharsetDecoder dec = Chars.allocDecoder();
+        String x = fromByteBuffer(bb, dec) ;
+        Chars.deallocDecoder(dec) ;
+        return x ;
+    }
+    
+    /** Decode a string into a ByteBuffer */
+    public static String fromByteBuffer(ByteBuffer bb, CharsetDecoder dec) {
+        if ( bb.remaining() == 0 )
+            return "" ;
+        dec.reset() ;
+        CharBuffer cBuff = CharBuffer.allocate(bb.remaining()) ;
+        CoderResult r = dec.decode(bb, cBuff, true) ;
+        if ( r.isOverflow() )
+            throw new InternalErrorException("fromByteBuffer: decode overflow (1)") ;
+        r = dec.flush(cBuff) ;
+        if ( r.isOverflow() )
+            throw new InternalErrorException("fromByteBuffer: decode overflow (2)") ;
+        cBuff.flip() ;
+        return cBuff.toString() ;
+    }
+
+    /**
+     * Return a hex string representing the bytes, zero padded to length of byte
+     * array.
+     */
+    public static String asHex(byte[] bytes) {
+        return asHexUC(bytes) ;
+    }
+
+    public static String asHexUC(byte[] bytes) {
+        return asHex(bytes, 0, bytes.length, Chars.hexDigitsUC) ;
+    }
+
+    public static String asHexLC(byte[] bytes) {
+        return asHex(bytes, 0, bytes.length, Chars.hexDigitsLC) ;
+    }
+
+    public static String asHex(byte[] bytes, int start, int finish, char[] hexDigits) {
+        StringBuilder sw = new StringBuilder() ;
+        for ( int i = start ; i < finish ; i++ ) {
+            byte b = bytes[i] ;
+            int hi = (b & 0xF0) >> 4 ;
+            int lo = b & 0xF ;
+            // if ( i != start ) sw.append(' ') ;
+            sw.append(hexDigits[hi]) ;
+            sw.append(hexDigits[lo]) ;
+        }
+        return sw.toString() ;
+    }
+    
+    /** Return a hex string representing the bytes, zero padded to length of byte array. */
+    public static String asHex(byte b)
+    {
+        return asHexUC(b) ; 
+    }
+
+    public static String asHexUC(byte b)
+    {
+        return asHex(b, Chars.hexDigitsUC) ; 
+    }
+
+    public static String asHexLC(byte b)
+    {
+        return asHex(b, Chars.hexDigitsLC) ; 
+    }
+    
+    private static String asHex(byte b, char[] hexDigits)
+    {
+        int hi = (b & 0xF0) >> 4 ;
+        int lo = b & 0xF ;
+        char[] chars = new char[2] ;
+        chars[0] = hexDigits[hi] ;
+        chars[1] = hexDigits[lo] ;
+        return new String(chars) ;
+    }
+
+    
+    public static int hexCharToInt(char c)
+    {
+        if ( '0' <= c && c <= '9' )   
+            return c-'0' ;
+        else if ( 'A' <= c && c <= 'F' )
+            return c-'A'+10 ;
+        else if ( 'a' <= c && c <= 'f' )
+            return c-'a'+10 ;
+        else
+            throw new IllegalArgumentException("Bad index char : "+c) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/Cache.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/Cache.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/Cache.java
new file mode 100644
index 0000000..d7fa711
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/Cache.java
@@ -0,0 +1,56 @@
+/*
+ * 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.lib;
+
+import java.util.Iterator ;
+import java.util.concurrent.Callable ;
+
+/** A cache */
+public interface Cache<Key, Value>
+{
+    
+    /** Does the cache contain the key? */
+    public boolean containsKey(Key key) ;
+    
+    /** Get from cache - or return null. */  
+    public Value getIfPresent(Key key) ;
+    
+    /** Get from cache, of not present, call the {@code callable}
+     *  to try to fill the cache.
+     */
+    public Value getOrFill(Key key, Callable<Value> callable) ;
+
+    /** Insert into the cache */
+    public void put(Key key, Value thing) ;
+
+    /** Remove from cache - return true if key referenced an entry */
+    public void remove(Key key) ;
+    
+    /** Iterate over all keys. Iteratering over the keys requires the caller be thread-safe. */ 
+    public Iterator<Key> keys() ;
+    
+    public boolean isEmpty() ;
+    public void clear() ;
+    
+    /** Current size of cache */
+    public long size() ;
+    
+    /** Register a callback - called when an object is dropped from the cache (optional operation) */ 
+    public void setDropHandler(ActionKeyValue<Key,Value> dropHandler) ;
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/CacheFactory.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/CacheFactory.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/CacheFactory.java
new file mode 100644
index 0000000..e913a9d
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/CacheFactory.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.lib ;
+
+import org.apache.jena.atlas.lib.cache.* ;
+
+public class CacheFactory {
+    /**
+     * Create a cache which has space for up to a certain number of objects.
+     * This is an LRU cache, or similar.
+     * The cache returns null for a cache miss.
+     * The cache is thread-safe for single operations.
+     */
+    public static <Key, Value> Cache<Key, Value> createCache(int maxSize) {
+        return new CacheGuava<>(maxSize) ;
+    }
+
+    /** Create a null cache */
+    public static <Key, Value> Cache<Key, Value> createNullCache() {
+        return new Cache0<>() ;
+    }
+
+    /** Create a lightweight cache (e.g. slot replacement) */
+    public static <Key, Value> Cache<Key, Value> createSimpleCache(int size) {
+        return new CacheSimple<>(size) ;
+    }
+
+    /** One slot cache */
+    public static <Key, Value> Cache<Key, Value> createOneSlotCache() {
+        return new Cache1<>() ;
+    }
+
+    /**
+     * Create set-cache, rather than a map-cache.
+     * The cache is thread-safe for single operations.
+     * 
+     * @see Pool
+     */
+    public static <Obj> CacheSet<Obj> createCacheSet(int size) {
+        Cache<Obj, Object> c = createCache(size) ;
+        return new CacheSetImpl<Obj>(c) ;
+    }
+
+    /** Add a synchronization wrapper to an existing set-cache */
+    public static <Obj> CacheSet<Obj> createSync(CacheSet<Obj> cache) {
+        if ( cache instanceof CacheSetSync<? > )
+            return cache ;
+        return new CacheSetSync<>(cache) ;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/CacheSet.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/CacheSet.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/CacheSet.java
new file mode 100644
index 0000000..f5c3d22
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/CacheSet.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.atlas.lib;
+
+import org.apache.jena.atlas.iterator.Action ;
+
+public interface CacheSet<T>
+{
+    public void add(T e) ;
+    public void clear() ;
+    public boolean contains(T obj) ;
+    public boolean isEmpty() ;
+//    public Iterator<T> iterator() ;
+    public void remove(T obj) ;
+    public long size() ;
+    /** Register a callback - called when an object is dropped from the cache (optional operation) */ 
+    public void setDropHandler(Action<T> dropHandler) ;
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/CacheStats.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/CacheStats.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/CacheStats.java
new file mode 100644
index 0000000..d71c1a0
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/CacheStats.java
@@ -0,0 +1,28 @@
+/*
+ * 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.lib;
+
+/** A cache with some readable statistics */ 
+public interface CacheStats<K,V> extends Cache<K,V>
+{
+    public long getCacheEntries() ;
+    public long getCacheHits() ;
+    public long getCacheMisses() ;
+    public long getCacheEjects() ;
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/Callback.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/Callback.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/Callback.java
new file mode 100644
index 0000000..d2f04f9
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/Callback.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.lib;
+
+public interface Callback<T>
+{
+    public void proc(T arg) ;
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/Cell.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/Cell.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/Cell.java
new file mode 100644
index 0000000..6910c0c
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/Cell.java
@@ -0,0 +1,99 @@
+/*
+ * 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.lib ;
+
+
+/** Key-value slot, with chaining for lookup. */  
+public class Cell<K,V>
+{
+    private final Cell<K,V> previous ;
+    private final K key ;
+    private final V value ;
+
+    /** Create a slot with no key, value or parent - can be used a slot chain root */  
+    public Cell()               { this(null, null, null); }
+
+    public Cell(K key, V value) { this(key, value, null); }
+
+    private Cell(K key, V value, Cell<K, V> previous)
+    {
+        this.key = key ;
+        this.value = value ;
+        this.previous = previous ;
+    }
+
+    public Cell<K,V> extend(K key, V value)
+    {
+        return new Cell<>(key, value, this) ;
+    }
+    
+    public final V find(K k)
+    {
+        // Java, tail recursion, lack thereof.
+        Cell<K,V> slot = this ;
+
+        while (slot != null)
+        {
+            // Defend against null keys (e.g. the root of a slot chain). 
+            if ( k.equals(slot.key) )
+                return slot.value ;
+//            if ( previous == null )
+//              return null ;
+            slot = slot.previous ;
+        }
+        return null ;
+    }
+    
+      /* As it should be ... */
+//    public final V find(K k)
+//    {
+//        if ( k.equals(key) )
+//            return value ;
+//        if ( previous == null )
+//            return null ;
+//        return previous.find(k) ;
+//    }
+
+    private static final String sep = ", " ;
+    private void str(int level, StringBuilder acc)
+    {
+        if ( key == null && value == null )
+            return ;
+
+        if ( level != 0 )
+            acc.append(sep) ;
+        acc.append("(") ;
+        acc.append(key.toString()) ;
+        acc.append("->") ;
+        acc.append(value.toString()) ;
+        acc.append(")") ;
+        if ( previous != null )
+            previous.str(level+1, acc) ;
+    }
+
+    @Override
+    public String toString()
+    { 
+        StringBuilder sb = new StringBuilder() ;
+        sb.append("{ ") ;
+        str(0, sb) ;
+        sb.append(" }") ;
+        return sb.toString() ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/Chars.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/Chars.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/Chars.java
new file mode 100644
index 0000000..e9d4f55
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/Chars.java
@@ -0,0 +1,271 @@
+/*
+ * 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.lib;
+
+import java.nio.charset.Charset ;
+import java.nio.charset.CharsetDecoder ;
+import java.nio.charset.CharsetEncoder ;
+import java.nio.charset.CodingErrorAction ;
+
+public class Chars
+{
+    private Chars() {}
+    
+ // So also Bytes.hexDigits to get bytes.
+    final public static char[] digits10 = {
+        '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9'
+    } ;
+    
+    /** Hex digits : upper case **/ 
+    final public static char[] hexDigitsUC = {
+        '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' ,
+        '9' , 'A' , 'B' , 'C' , 'D' , 'E' , 'F' } ;
+
+    /** Hex digits : lower case **/ 
+    final public static char[] hexDigitsLC = {
+        '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' ,
+        '9' , 'a' , 'b' , 'c' , 'd' , 'e' , 'f' } ;
+
+    
+//         , 'g' , 'h' ,
+//        'i' , 'j' , 'k' , 'l' , 'm' , 'n' ,
+//        'o' , 'p' , 'q' , 'r' , 's' , 't' ,
+//        'u' , 'v' , 'w' , 'x' , 'y' , 'z'
+    
+    /** Java name for UTF-8 encoding */
+    private static final String encodingUTF8     = "utf-8" ;
+    /** Java name for ASCII encoding */
+    private static final String encodingASCII    = "ascii" ;
+    
+    public static final Charset charsetUTF8 = Charset.forName(encodingUTF8) ;
+    public static final Charset charsetASCII = Charset.forName(encodingASCII) ;
+    
+    // Pools for encoders/decoder.
+    // Better? use a ThreadLocal.
+    // Initial pool size. Any additional encoder/decoder are later
+    // placed in the pool - it's an infinite, reusing, growing pool.
+    
+    // Better?  If so, use these!
+    
+    private static final ThreadLocal<CharsetEncoder> threadCharsetEncoder =
+        new ThreadLocal<CharsetEncoder>() {
+        @Override protected CharsetEncoder initialValue() {
+            return createEncoder() ;
+        }
+    };
+
+    private static final ThreadLocal<CharsetDecoder> threadCharsetDecoder =
+        new ThreadLocal<CharsetDecoder>() {
+        @Override protected CharsetDecoder initialValue() {
+            return createDecoder() ;
+        }
+    };
+
+    /** Return a per-thread CharsetEncoder */ 
+    public static CharsetEncoder getThreadEncoder() { return threadCharsetEncoder.get() ; }
+
+    /** Return a per-thread CharsetDecoder */ 
+    public static CharsetDecoder getThreadDecoder() { return threadCharsetDecoder.get() ; }
+
+    private static final int PoolSize = 2 ;
+    private static Pool<CharsetEncoder> encoders = PoolSync.create(new PoolBase<CharsetEncoder>()) ;
+    private static Pool<CharsetDecoder> decoders = PoolSync.create(new PoolBase<CharsetDecoder>()) ;
+
+    static {
+        ThreadLocal<CharsetEncoder> t ;
+        
+        // Fill the pool.
+        for ( int i = 0 ; i < PoolSize ; i++ )
+        {
+            putEncoder(createEncoder()) ;
+            putDecoder(createDecoder()) ;
+        }
+    }
+    
+    /** Create a UTF-8 encoder */
+    public static CharsetEncoder createEncoder()    { return charsetUTF8.newEncoder() ; }
+    /** Create a UTF-8 decoder */
+    public static CharsetDecoder createDecoder()    { return charsetUTF8.newDecoder() ; }
+
+    /** Get a UTF-8 encoder from the pool (null if pool empty) */ 
+    public static CharsetEncoder getEncoder()       { return encoders.get() ; }
+    /** Add/return a UTF-8 encoder to the pool */
+    public static void putEncoder(CharsetEncoder encoder) { encoders.put(encoder) ; }
+
+    /** Get a UTF-8 decoder from the pool (null if pool empty) */ 
+    public static CharsetDecoder getDecoder()       { return decoders.get() ; }
+    /** Add/return a UTF-8 decoder to the pool */
+    public static void putDecoder(CharsetDecoder decoder) { decoders.put(decoder) ; }
+    
+    /** Allocate a CharsetEncoder, creating as necessary */
+    public static CharsetEncoder allocEncoder()
+    {
+        CharsetEncoder enc = Chars.getEncoder();
+        // Blocking finite Pool - does not happen.
+        // Plain Pool (sync wrapped) - might - allocate an extra one. 
+        if ( enc == null ) 
+            enc = Chars.createEncoder() ;
+        enc
+          .onMalformedInput(CodingErrorAction.REPLACE)
+          .onUnmappableCharacter(CodingErrorAction.REPLACE)
+          .reset() ;
+        
+        return enc ;
+    }
+    /** Deallocate a CharsetEncoder, may increase pool size */
+    public static void deallocEncoder(CharsetEncoder enc) { putEncoder(enc) ; }
+        
+    /** Allocate a CharsetDecoder, creating as necessary */
+    public static CharsetDecoder allocDecoder()
+    {
+        CharsetDecoder dec = Chars.getDecoder();
+        // Blocking finite Pool - does not happen.
+        // Plain Pool (sync wrapped) - might - allocate an extra one. 
+        if ( dec == null ) 
+            dec = Chars.createDecoder() ;
+        dec
+          .onMalformedInput(CodingErrorAction.REPLACE)
+          .onUnmappableCharacter(CodingErrorAction.REPLACE)
+          .reset() ;
+        
+        return dec ;
+    }
+    /** Deallocate a CharsetDecoder, may increase pool size */
+    public static void deallocDecoder(CharsetDecoder dec) { putDecoder(dec) ; }
+    
+    /** Is char in the array? */
+    public static boolean charInArray(int ch, char[] chars) {
+        for ( int xch : chars ) {
+            if ( ch == xch )
+                return true ;
+        }
+        return false ;
+    }
+
+    public static void encodeAsHex(StringBuilder buff, char marker, char ch)
+    {
+        if ( ch < 256 )
+        {
+            buff.append(marker) ;
+            int lo = ch & 0xF ;
+            int hi = (ch >> 4) & 0xF ;
+            buff.append(Chars.hexDigitsUC[hi]) ;                
+            buff.append(Chars.hexDigitsUC[lo]) ;
+            return ;
+        }
+        int n4 = ch & 0xF ;
+        int n3 = (ch >> 4) & 0xF ;
+        int n2 = (ch >> 8) & 0xF ;
+        int n1 = (ch >> 12) & 0xF ;
+        buff.append(marker) ;
+        buff.append(Chars.hexDigitsUC[n1]) ;                
+        buff.append(Chars.hexDigitsUC[n2]) ;
+        buff.append(marker) ;
+        buff.append(Chars.hexDigitsUC[n3]) ;
+        buff.append(Chars.hexDigitsUC[n4]) ;
+        
+    }
+
+    /** End of file - not a Unicode codepoint */
+    public static final int EOF             = -1 ;
+    // BOM  : U+FEFF encoded in bytes as xEF,0xBB,0xBF
+    public static final char BOM            = 0xFEFF ;  
+    
+    /** undefined character (exact meaning depends on use) - not a Unicode codepoint */
+    public static final int  UNSET           =  -2 ;
+    public static final char NL              = '\n' ;
+    public static final char CR              = '\r' ;
+    public static final char TAB             = '\t' ;
+    public static final char SPC             = ' ' ;
+    public static final char BSPACE          = '\b' ;
+    
+    public static final char CH_ZERO         =  (char)0 ;
+
+    public static final char CH_LBRACKET     = '[' ;
+    public static final char CH_RBRACKET     = ']' ;
+    
+    public static final char CH_LBRACE       = '{' ;
+    public static final char CH_RBRACE       = '}' ;
+
+    public static final char CH_LPAREN       = '(' ;
+    public static final char CH_RPAREN       = ')' ;
+
+    public static final char CH_LT           = '<' ;
+    public static final char CH_GT           = '>' ;
+    public static final char CH_UNDERSCORE   = '_' ;
+
+    public static final char CH_QUOTE1       = '\'' ;
+    public static final char CH_QUOTE2       = '"' ;
+
+    public static final char CH_EQUALS       = '=' ;
+    public static final char CH_STAR         = '*' ;
+    public static final char CH_DOT          = '.' ;
+    public static final char CH_COMMA        = ',' ;
+    public static final char CH_SEMICOLON    = ';' ;
+    public static final char CH_COLON        = ':' ;
+    public static final char CH_AMPHERSAND   = '&' ;
+    public static final char CH_AT           = '@' ;
+    public static final char CH_QMARK        = '?' ;
+    public static final char CH_HASH         = '#' ;
+    public static final char CH_PLUS         = '+' ;
+    public static final char CH_MINUS        = '-' ;
+    public static final char CH_DASH         = '-' ; // Alt name
+    public static final char CH_SLASH        = '/' ;
+    public static final char CH_RSLASH       = '\\' ;
+    public static final char CH_PERCENT      = '%' ;
+    
+    // Byte versions of the above
+    public static final byte B_NL            = NL ;
+    public static final byte B_CR            = CR ;
+    
+    public static final byte B_LBRACKET      = '[' ;
+    public static final byte B_RBRACKET      = ']' ;
+    
+    public static final byte B_LBRACE        = '{' ;
+    public static final byte B_RBRACE        = '}' ;
+
+    public static final byte B_LPAREN        = '(' ;
+    public static final byte B_RPAREN        = ')' ;
+
+    public static final byte B_LT            = '<' ;
+    public static final byte B_GT            = '>' ;
+    public static final byte B_UNDERSCORE    = '_' ;
+
+    public static final byte B_QUOTE1        = '\'' ;
+    public static final byte B_QUOTE2        = '"' ;
+
+    public static final byte B_EQUALS        = '=' ;
+    public static final byte B_STAR          = '*' ;
+    public static final byte B_DOT           = '.' ;
+    public static final byte B_COMMA         = ',' ;
+    public static final byte B_SEMICOLON     = ';' ;
+    public static final byte B_COLON         = ':' ;
+    public static final byte B_AT            = '@' ;
+    public static final byte B_AMPHERSAND    = '&' ;
+    public static final byte B_QMARK         = '?' ;
+    public static final byte B_HASH          = '#' ;
+    public static final byte B_PLUS          = '+' ;
+    public static final byte B_MINUS         = '-' ;
+    public static final byte B_DASH          = '-' ; // Alt name
+    public static final byte B_SLASH         = '/' ;
+    public static final byte B_RSLASH        = '\\' ;
+    public static final byte B_PERCENT       = '%' ;
+    
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/Closeable.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/Closeable.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/Closeable.java
new file mode 100644
index 0000000..2b6002a
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/Closeable.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.lib;
+
+/** Close this object.
+ *  This form does not allow Exceptions (it does allow RuntimeExceptions).  
+ */
+public interface Closeable
+{
+    public void close() ;
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/CollectionUtils.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/CollectionUtils.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/CollectionUtils.java
new file mode 100644
index 0000000..8bae6a4
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/CollectionUtils.java
@@ -0,0 +1,52 @@
+/*
+ * 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.lib;
+
+import java.util.Collection ;
+import java.util.Iterator ;
+
+public class CollectionUtils
+{
+    static public <T> void removeNulls(Collection<T> list)
+    {
+        for ( Iterator<T> iter = list.iterator() ; iter.hasNext() ; )
+        {
+            T e = iter.next() ;
+            if ( e == null )
+                iter.remove() ;
+        }
+    }
+    
+    static public <T> boolean disjoint(Collection<T> c1, Collection<T> c2)
+    {
+        if ( c2.size() < c1.size() )
+        {
+            Collection<T> t = c1 ;
+            c1 = c2 ;
+            c2 = t ;
+        }
+        
+        for ( T t : c1 )
+        {
+            if ( c2.contains(t) )
+                return false ;
+        }
+        return true ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/ColumnMap.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/ColumnMap.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/ColumnMap.java
new file mode 100644
index 0000000..18db563
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/ColumnMap.java
@@ -0,0 +1,254 @@
+/*
+ * 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.lib;
+
+import static java.lang.String.format ;
+
+import java.util.Arrays ;
+import java.util.List ;
+
+import org.apache.jena.atlas.AtlasException ;
+
+
+
+/** General descriptor of a reordering (mapping) of columns in tuples to columns in indexes, 
+ * for example, from triples to triple index order. 
+ *
+ * Naming: map is convert to the reordered form, fetch is get back.
+ */
+public class ColumnMap
+{
+    // Map from tuple order to index order
+    // So SPO->POS is (0->2, 1->0, 2->1)
+    // i.e. the location of the element after mapping.  
+    private int[] insertOrder ;            
+    
+    // The mapping from index to tuple order
+    // For POS->SPO, is (0->1, 1->2, 2->0)
+    // i.e. the location to fetch the mapped element from. 
+    private int[] fetchOrder ;
+
+    private String label ;
+
+    /** Construct a column mapping that maps the input (one col, one char) to the output */  
+    public ColumnMap(String input, String output)
+    {
+        this(input+"->"+output, compileMapping(input, output)) ;
+    }
+    
+    public <T> ColumnMap(String label, List<T> input, List<T> output)
+    {
+        this(label, compileMapping(input, output)) ;
+    }
+    
+    public <T> ColumnMap(String label, T[] input, T[] output)
+    {
+        this(label, compileMapping(input, output)) ;
+    }
+    
+    /** Construct a column map - the elements are the 
+     * mappings of a tuple originally in the order 0,1,2,...
+     * so SPO->POS is 2,0,1 (SPO->POS so S->2, P->0, O->1)   
+     * and not 1,2,0 (which is the extraction mapping).
+     * The label is just a lable and is not interpretted.
+     */
+    public ColumnMap(String label, int...elements)
+    {
+        this.label = label ;
+
+        this.insertOrder = new int[elements.length] ;
+        System.arraycopy(elements, 0, elements, 0, elements.length) ;
+        Arrays.fill(insertOrder, -1) ;
+        
+        this.fetchOrder = new int[elements.length] ;
+        Arrays.fill(fetchOrder, -1) ;
+    
+        for ( int i = 0 ; i < elements.length ; i++ )
+        {
+            int x = elements[i] ;
+            if ( x < 0 || x >= elements.length)
+                throw new IllegalArgumentException("Out of range: "+x) ;
+            // Checking
+            if ( insertOrder[i] != -1 || fetchOrder[x] != -1 )
+                throw new IllegalArgumentException("Inconsistent: "+ListUtils.str(elements)) ;
+            
+            insertOrder[i] = x ;
+            fetchOrder[x] = i ;
+        }
+    }
+    
+    /** Length of mapping */
+    
+    public int length() { return fetchOrder.length ; }
+    
+    /** Apply to an <em>unmapped</em> tuple to get the i'th slot after mapping : SPO->POS : 0'th slot is P from SPO */
+    public <T> T fetchSlot(int idx, Tuple<T> tuple)
+    { 
+        idx = fetchOrder[idx] ;     // Apply the reverse mapping as we are doing zero is P, so it's an unmap.
+        return tuple.get(idx) ;
+    }
+
+    /** Apply to an <em>unmapped</em> tuple to get the i'th slot after mapping : SPO->POS : 0'th slot is P from SPO */
+    public <T> T fetchSlot(int idx, T[] tuple)
+    { 
+        idx = fetchOrder[idx] ;     // Apply the reverse mapping as we are doing zero is P, so it's an unmap.
+        return tuple[idx] ;
+    }
+    
+    /** Apply to a <em>mapped</em> tuple to get the i'th slot as it appears after mapping : SPO->POS : 0'th slot is S from POS */
+    public <T> T mapSlot(int idx, Tuple<T> tuple)
+    { 
+        idx = insertOrder[idx] ;
+        return tuple.get(idx) ;
+    }
+    
+    /** Apply to a <em>mapped</em> tuple to get the i'th slot as it appears after mapping : SPO->POS : 0'th slot is S from POS */
+    public <T> T mapSlot(int idx, T[] tuple)
+    { 
+        idx = insertOrder[idx] ;        // Yes - it's the insert location we want to access 
+        return tuple[idx] ;
+    }
+    
+    /** Get the index of the i'th slot as it appears after mapping : SPO->POS : 0'th slot is S from POS so 2->0 */
+    public int mapSlotIdx(int idx)
+    { 
+        return insertOrder[idx] ;        // Yes - it's the insert location we want to access 
+    }
+
+    /** Get the index of the i'th slot as it appears from a mapping : for SPO->POS : 0'th slot is P so 1->0 */
+    public int fetchSlotIdx(int idx)
+    { 
+        return fetchOrder[idx] ;        // Yes - it's the insert location we want to access 
+    }
+
+    /** Apply to an <em>unmapped</em> tuple to get a tuple with the column mapping applied */
+    public <T> Tuple<T> map(Tuple<T> src)
+    {
+        return map(src, insertOrder) ;
+    }
+
+    /** Apply to a <em>mapped</em> tuple to get a tuple with the column mapping reverse-applied */
+    public <T> Tuple<T> unmap(Tuple<T> src)
+    {
+        return map(src, fetchOrder) ;
+    }
+
+    private <T> Tuple<T> map(Tuple<T> src, int[] map)
+    {
+        @SuppressWarnings("unchecked")
+        T[] elts = (T[])new Object[src.size()] ;
+        
+        for ( int i = 0 ; i < src.size() ; i++ )
+        {
+            int j = map[i] ;
+            elts[j] = src.get(i) ;
+        }
+        return Tuple.create(elts) ;
+    }
+    
+    /** Compile a mapping encoded as single charcaters e.g. "SPO", "POS" */
+    static int[] compileMapping(String domain, String range)
+    {
+        List<Character> input = StrUtils.toCharList(domain) ;
+        List<Character> output = StrUtils.toCharList(range) ;
+        return compileMapping(input, output) ;
+    }
+
+    /** Compile a mapping, encoded two list, the domain and range of the mapping function  */
+    static <T> int[] compileMapping(T[] domain, T[] range)
+    {
+        return compileMapping(Arrays.asList(domain), Arrays.asList(range)) ;
+    }
+    
+    /** Compile a mapping */
+    static <T> int[] compileMapping(List<T> domain, List<T>range)
+    {
+        if ( domain.size() != range.size() )
+            throw new AtlasException("Bad mapping: lengths not the same: "+domain+" -> "+range) ; 
+        
+        int[] cols = new int[domain.size()] ;
+        boolean[] mapped = new boolean[domain.size()] ;
+        //Arrays.fill(mapped, false) ;
+        
+        for ( int i = 0 ; i < domain.size() ; i++ )
+        {
+            T input = domain.get(i) ;
+            int j = range.indexOf(input) ;
+            if ( j < 0 )
+                throw new AtlasException("Bad mapping: missing mapping: "+domain+" -> "+range) ;
+            if ( mapped[j] )
+                throw new AtlasException("Bad mapping: duplicate: "+domain+" -> "+range) ;
+            cols[i] = j ;
+            mapped[j] = true ;
+        }
+        return cols ;
+    }
+    
+    @Override
+    public String toString()
+    {
+        //return label ; 
+        return format("%s:%s%s", label, mapStr(insertOrder), mapStr(fetchOrder)) ;
+    }
+
+    private Object mapStr(int[] map)
+    {
+        StringBuilder buff = new StringBuilder() ;
+        String sep = "{" ;
+        
+        for ( int i = 0 ; i < map.length ; i++ )
+        {
+            buff.append(sep) ;
+            sep = ", " ; 
+            buff.append(format("%d->%d", i, map[i])) ;
+        }
+        buff.append("}") ;
+        
+        return buff.toString() ;
+    }
+
+    public String getLabel()
+    {
+        return label ;
+    }
+    
+    /** Reorder the letters of a string by the same rules as this column map (forward, map direction)*/ 
+    public String mapName(String word)
+    {
+        return mapString(word, insertOrder) ;
+    }
+    
+    /** Reorder the letters of a string by the same rules as this column map (backward, fetch direction) */ 
+    public String unmapName(String word)
+    {
+        return mapString(word, fetchOrder) ;
+    }
+    
+    // Map is get from i and put to j
+    private String mapString(String src, int[] map)
+    {
+        char[] chars = new char[src.length()] ;
+        for ( int i = 0 ; i < src.length() ; i++ )
+        {
+            int j = map[i] ;
+            chars[j] = src.charAt(i) ;
+        }
+        return new String(chars) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/DS.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/DS.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/DS.java
new file mode 100644
index 0000000..f9c7095
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/DS.java
@@ -0,0 +1,45 @@
+/*
+ * 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.lib;
+
+
+import java.util.* ;
+
+public class DS
+{
+    private DS() {}
+ 
+    public static <X> Set<X> setOfNone()            { return Collections.emptySet()  ; }
+    public static <X> Set<X> setOfOne(X element)    { return Collections.singleton(element) ; }
+    public static <X> Set<X> set()                  { return new HashSet<>(); }
+    public static <X> Set<X> set(int initialSize)   { return new HashSet<>(initialSize); }
+    public static <X> Set<X> set(Set<X> other)      { return new HashSet<>(other); }
+
+    public static <K, V> Map<K,V> mapOfNone()       { return Collections.emptyMap() ; }
+    public static <K, V> Map<K,V> mapOfOne(K key, V value) { return Collections.singletonMap(key, value) ; }
+    public static <K, V> Map<K,V> map()             { return new HashMap<>(); }
+    public static <K, V> Map<K,V> map(int initialSize) { return new HashMap<>(initialSize); }
+    public static <K, V> Map<K,V> map(Map<K,V> other) { return new HashMap<>(other); }
+
+    public static <T> List<T> listOfNone()          { return Collections.emptyList() ; }
+    public static <T> List<T> listOfOne(T element)  { return Collections.singletonList(element) ; }
+    public static <T> List<T> list()                { return new ArrayList<>(); }
+    public static <T> List<T> list(int initialSize) { return new ArrayList<>(initialSize); }
+    public static <T> List<T> list(List<T> other)   { return new ArrayList<>(other); }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/DateTimeUtils.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/DateTimeUtils.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/DateTimeUtils.java
new file mode 100644
index 0000000..b33a514
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/DateTimeUtils.java
@@ -0,0 +1,127 @@
+/**
+ * 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.lib;
+
+import java.util.Calendar ;
+import java.util.Date ;
+import java.util.GregorianCalendar ;
+
+import org.apache.commons.lang3.time.FastDateFormat ;
+
+public class DateTimeUtils {
+
+    // Include timezone (even xsd:dates have timezones; Calendars have timezones)
+    // NB in SimpleDateFormat != FastDateFormat
+    //  SimpleDateFormat does not format Calendars.
+    //  SimpleDateFormat has "X" for  ISO format tmezones (+00:00)  
+    //    FastDateFormat uses "ZZ" for this.
+    private static final FastDateFormat dateTimeFmt_display = FastDateFormat.getInstance("yyyy/MM/dd HH:mm:ss z") ;
+    private static final FastDateFormat dateFmt_yyyymmdd    = FastDateFormat.getInstance("yyyy-MM-ddZZ") ;
+    // For milliseconds == 0
+    private static final FastDateFormat dateTimeFmt_XSD_ms0     = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ssZZ") ;
+    // For milliseconds != 0
+    private static final FastDateFormat dateTimeFmt_XSD_ms      = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSZZ") ;
+    // For milliseconds == 0
+    private static final FastDateFormat timeFmt_XSD_ms0         = FastDateFormat.getInstance("HH:mm:ssZZ") ;
+    // For milliseconds != 0
+    private static final FastDateFormat timeFmt_XSD_ms          = FastDateFormat.getInstance("HH:mm:ss.SSSZZ") ;
+
+    public static String nowAsXSDDateTimeString() {
+        return calendarToXSDDateTimeString(new GregorianCalendar()) ;
+    }
+
+    public static String todayAsXSDDateString() {
+        return calendarToXSDDateString(new GregorianCalendar()) ;
+    }
+
+    /** Return "now" as readable string (date in yyyy/MM/dd format) */
+    public static String nowAsString() {
+        return nowAsString(dateTimeFmt_display) ;
+    }
+
+    public static String nowAsString(String formatString) {
+        FastDateFormat df = FastDateFormat.getInstance(formatString) ;
+        return df.format(new Date()) ;
+    }
+
+    public static String nowAsString(FastDateFormat dateFormat) {
+        return dateFormat.format(new Date()) ;
+    }
+
+    private static boolean hasZeroMilliSeconds(Calendar cal) {
+        return ! cal.isSet(Calendar.MILLISECOND) || cal.get(Calendar.MILLISECOND) == 0 ;
+    }
+
+    // Canonical fom : if ms == 0, don't include in the string.
+    public static String calendarToXSDDateTimeString(Calendar cal) {
+        FastDateFormat fmt = hasZeroMilliSeconds(cal) 
+            ? dateTimeFmt_XSD_ms0 
+            : dateTimeFmt_XSD_ms ;
+        return calendarToXSDString(cal, fmt) ;
+    }
+
+    public static String calendarToXSDDateString(Calendar cal) {
+        return calendarToXSDString(cal, dateFmt_yyyymmdd) ;
+    }
+
+    // Canonical fom : if ms == 0, don't include in the string.
+    public static String calendarToXSDTimeString(Calendar cal) {
+        FastDateFormat fmt = hasZeroMilliSeconds(cal) 
+            ? timeFmt_XSD_ms0 
+            : timeFmt_XSD_ms ;
+        return calendarToXSDString(cal, fmt) ;
+    }
+
+    private static String calendarToXSDString(Calendar cal, FastDateFormat fmt) {
+        String lex = fmt.format(cal) ;
+        // lex = lex + calcTimezone(cal) ;
+        return lex ;
+    }
+
+    // Not needed because of FastDateFormat
+//    private static String calcTimezone(Calendar cal) {
+//        Date date = cal.getTime() ;
+//        TimeZone z = cal.getTimeZone() ;
+//        int tz = z.getRawOffset() ;
+//
+//        if ( z.inDaylightTime(date) ) {
+//            int tzDst = z.getDSTSavings() ;
+//            tz = tz + tzDst ;
+//        }
+//
+//        String sign = "+" ;
+//        if ( tz < 0 ) {
+//            sign = "-" ;
+//            tz = -tz ;
+//        }
+//
+//        int tzH = tz / (60 * 60 * 1000) ; // Integer divide towards zero.
+//        int tzM = (tz - tzH * 60 * 60 * 1000) / (60 * 1000) ;
+//
+//        String tzH_str = Integer.toString(tzH) ;
+//        String tzM_str = Integer.toString(tzM) ;
+//
+//        if ( tzH < 10 )
+//            tzH_str = "0" + tzH_str ;
+//        if ( tzM < 10 )
+//            tzM_str = "0" + tzM_str ;
+//        return sign + tzH_str + ":" + tzM_str ;
+//    }
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/EscapeStr.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/EscapeStr.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/EscapeStr.java
new file mode 100644
index 0000000..cd387f6
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/EscapeStr.java
@@ -0,0 +1,235 @@
+/*
+ * 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.lib;
+
+import org.apache.jena.atlas.AtlasException ;
+import org.apache.jena.atlas.io.AWriter ;
+import org.apache.jena.atlas.io.IndentedLineBuffer ;
+import org.apache.jena.atlas.io.OutputUtils ;
+
+public class EscapeStr
+{
+    // Tests: TestOutput
+    // See also OutputLangUtils.outputEsc.
+//    private final boolean ascii ;
+//
+//    public EscapeStr(CharSpace charSpace) { this.ascii = ( charSpace == CharSpace.ASCII ) ; } 
+//
+//    public void writeURI(AWriter w, String s)
+//    {
+//        if ( ascii )
+//            stringEsc(w, s, true, ascii) ;
+//        else
+//            // It's a URI - assume legal.
+//            w.print(s) ;
+//    }
+//
+//    public void writeStr(AWriter w, String s) 
+//    {
+//        stringEsc(w, s, true, ascii) ;
+//    }
+//
+//    public void writeStrMultiLine(AWriter w, String s) 
+//    {
+//        // N-Triples does not have """
+//        stringEsc(w, s, false, ascii) ;
+//    }
+//
+    // Utility
+    /*
+     * Escape characters in a string according to Turtle rules. 
+     */
+    public static String stringEsc(String s)
+    { return stringEsc(s, true, false) ; }
+
+    private static String stringEsc(String s, boolean singleLineString, boolean asciiOnly)
+    {
+        IndentedLineBuffer sb = new IndentedLineBuffer() ;
+        stringEsc(sb, s, singleLineString, asciiOnly) ;
+        return sb.toString() ;
+    }
+
+    public static void stringEsc(AWriter out, String s, boolean singleLineString, boolean asciiOnly)
+    {
+        int len = s.length() ;
+        for (int i = 0; i < len; i++) {
+            char c = s.charAt(i);
+
+            // \\ Escape always possible.
+            if (c == '\\') 
+            {
+                out.print('\\') ;
+                out.print(c) ;
+                continue ;
+            }
+            if ( singleLineString )
+            {
+                if ( c == '"' )         { out.print("\\\""); continue ; }
+                else if (c == '\n')     { out.print("\\n");  continue ; }
+                else if (c == '\t')     { out.print("\\t");  continue ; }
+                else if (c == '\r')     { out.print("\\r");  continue ; }
+                else if (c == '\f')     { out.print("\\f");  continue ; }
+            }
+            // Not \-style esacpe. 
+            if ( c >= 32 && c < 127 )
+                out.print(c);
+            else if ( !asciiOnly )
+                out.print(c);
+            else
+            {
+                // Outside the charset range.
+                // Does not cover beyond 16 bits codepoints directly
+                // (i.e. \U escapes) but Java keeps these as surrogate
+                // pairs and will print as characters
+                out.print( "\\u") ;
+                OutputUtils.printHex(out, c, 4) ;
+            }
+        }
+    }
+
+    // Utilities to remove escapes
+
+    /** Replace \ escapes (\\u, \t, \n etc) in a string */
+    public static String unescapeStr(String s)
+    { return unescapeStr(s, '\\') ; }
+    
+    /** Replace \ escapes (\\u, \t, \n etc) in a string */
+    public static String unescapeStr(String s, char escapeChar)
+    { return unescape(s, escapeChar, false) ; }
+
+    // Main worker function for unescaping strings.
+    public static String unescape(String s, char escape, boolean pointCodeOnly) {
+        int i = s.indexOf(escape) ;
+        
+        if ( i == -1 )
+            return s ;
+        
+        // Dump the initial part straight into the string buffer
+        StringBuilder sb = new StringBuilder(s.substring(0,i)) ;
+        
+        for ( ; i < s.length() ; i++ )
+        {
+            char ch = s.charAt(i) ;
+
+            if ( ch != escape )
+            {
+                sb.append(ch) ;
+                continue ;
+            }
+                
+            // Escape
+            if ( i >= s.length()-1 )
+                throw new AtlasException("Illegal escape at end of string") ;
+            char ch2 = s.charAt(i+1) ;
+            i = i + 1 ;
+            
+            // \\u and \\U
+            if ( ch2 == 'u' )
+            {
+                // i points to the \ so i+6 is next character
+                if ( i+4 >= s.length() )
+                    throw new AtlasException("\\u escape too short") ;
+                int x = hex(s, i+1, 4) ;
+                sb.append((char)x) ;
+                // Jump 1 2 3 4 -- already skipped \ and u
+                i = i+4 ;
+                continue ;
+            }
+            if ( ch2 == 'U' )
+            {
+                // i points to the \ so i+6 is next character
+                if ( i+8 >= s.length() )
+                    throw new AtlasException("\\U escape too short") ;
+                int x = hex(s, i+1, 8) ;
+                // Convert to UTF-16 codepoint pair.
+                sb.append((char)x) ;
+                // Jump 1 2 3 4 5 6 7 8 -- already skipped \ and u
+                i = i+8 ;
+                continue ;
+            }
+            
+            // Are we doing just point code escapes?
+            // If so, \X-anything else is legal as a literal "\" and "X" 
+            
+            if ( pointCodeOnly )
+            {
+                sb.append('\\') ;
+                sb.append(ch2) ;
+                i = i + 1 ;
+                continue ;
+            }
+            
+            // Not just codepoints.  Must be a legal escape.
+            char ch3 = 0 ;
+            switch (ch2)
+            {
+                case 'n': ch3 = '\n' ;  break ; 
+                case 't': ch3 = '\t' ;  break ;
+                case 'r': ch3 = '\r' ;  break ;
+                case 'b': ch3 = '\b' ;  break ;
+                case 'f': ch3 = '\f' ;  break ;
+                case '\'': ch3 = '\'' ; break ;
+                case '\"': ch3 = '\"' ; break ;
+                case '\\': ch3 = '\\' ; break ;
+                default:
+                    throw new AtlasException("Unknown escape: \\"+ch2) ;
+            }
+            sb.append(ch3) ;
+        }
+        return sb.toString() ;
+    }
+    
+    public static int hex(String s, int i, int len)
+    {
+//        if ( i+len >= s.length() )
+//        {
+//            
+//        }
+        int x = 0 ;
+        for ( int j = i ; j < i+len ; j++ )
+        {
+           char ch = s.charAt(j) ;
+           int k = 0  ;
+           switch (ch)
+           {
+               case '0': k = 0 ; break ; 
+               case '1': k = 1 ; break ;
+               case '2': k = 2 ; break ;
+               case '3': k = 3 ; break ;
+               case '4': k = 4 ; break ;
+               case '5': k = 5 ; break ;
+               case '6': k = 6 ; break ;
+               case '7': k = 7 ; break ;
+               case '8': k = 8 ; break ;
+               case '9': k = 9 ; break ;
+               case 'A': case 'a': k = 10 ; break ;
+               case 'B': case 'b': k = 11 ; break ;
+               case 'C': case 'c': k = 12 ; break ;
+               case 'D': case 'd': k = 13 ; break ;
+               case 'E': case 'e': k = 14 ; break ;
+               case 'F': case 'f': k = 15 ; break ;
+               default:
+                   throw new AtlasException("Illegal hex escape: "+ch) ;
+           }
+           x = (x<<4)+k ;
+        }
+        return x ;
+    }
+
+}


[8/9] jena git commit: Import of lib, io, logging and iterator packages

Posted by an...@apache.org.
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() ;
+    }
+}


[4/9] jena git commit: Import of lib, io, logging and iterator packages

Posted by an...@apache.org.
http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/SetUtils.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/SetUtils.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/SetUtils.java
new file mode 100644
index 0000000..f4e561d
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/SetUtils.java
@@ -0,0 +1,84 @@
+/*
+ * 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.lib;
+
+import java.util.HashSet ;
+import java.util.Set ;
+
+public class SetUtils
+{
+    private SetUtils() {}
+    
+    public static <X> Set<X> setOfOne(X element) { return DS.setOfOne(element) ; }
+    
+     // Set specific operations
+    
+    public static <T> Set<T> intersection(Set<? extends T> setLeft, Set<? extends T> setRight)
+    {
+        Set<T> results = new HashSet<>(setLeft) ;
+        results.retainAll(setRight) ;
+        return results ;
+    }
+
+    public static <T> boolean intersectionP(Set<? extends T> s1, Set<? extends T> s2)
+    {
+        for( T elt : s1 )
+        {
+            if ( s2.contains(elt) ) 
+                return true ;
+        }
+        return false ;
+    }
+
+    public static <T> Set<T> union(Set<? extends T> s1, Set<? extends T> s2)
+    {
+        Set<T> s3 = new HashSet<>(s1) ;
+        s3.addAll(s2) ;
+        return s3 ;
+    }
+
+    /** Return is s1 \ s2 */
+
+    public static <T> Set<T> difference(Set<? extends T> s1, Set<? extends T> s2)
+    {
+        Set<T> s3 = new HashSet<>(s1) ;
+        s3.removeAll(s2) ;
+        return s3 ;
+    }
+    
+    /** Return true if s1 and s2 are disjoint */
+    public static <T> boolean isDisjoint(Set<? extends T> s1, Set<? extends T> s2)
+    {
+        Set<? extends T> x = s1 ;
+        Set<? extends T> y = s2 ;
+        if ( s1.size() < s2.size() )
+        {
+            x = s2 ;
+            y = s1 ;
+        }        
+        
+        for ( T item : x )
+        {
+            if ( y.contains(item)) 
+                return false ;
+        }
+        return true ;
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/Sink.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/Sink.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/Sink.java
new file mode 100644
index 0000000..01bb809
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/Sink.java
@@ -0,0 +1,29 @@
+/*
+ * 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.lib;
+
+
+/** Interface for the destination of things */
+public interface Sink<T> extends Closeable
+{
+    // Can't help but think it should be "Pipe"
+    // If Sync looses Sync(boolean), then make this "extends Sync"
+    void send(T item) ;
+    void flush() ;
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/SinkCounting.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/SinkCounting.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/SinkCounting.java
new file mode 100644
index 0000000..62c6e43
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/SinkCounting.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.atlas.lib;
+
+
+
+public final class SinkCounting<T> extends SinkWrapper<T>
+{
+    private long count = 0 ;
+    
+    public SinkCounting(Sink<T> output)
+    { super(output) ; }
+    
+    public SinkCounting()
+    { super(new SinkNull<T>()) ; }
+    
+    @Override
+    public void send(T thing)
+    {
+        count++ ;
+        super.send(thing) ;
+    }
+    
+    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/lib/SinkLogging.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/SinkLogging.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/SinkLogging.java
new file mode 100644
index 0000000..e6928de
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/SinkLogging.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.atlas.lib;
+
+import org.slf4j.Logger ;
+
+public class SinkLogging<T> implements Sink<T>
+{
+    private Logger log ;
+
+    public SinkLogging(Logger log) { this.log = log ; }
+    
+    @Override
+    public void send(T item)
+    {
+        log.info("Sink: "+item) ;
+    }
+
+    @Override 
+    public void flush() { }
+    
+    @Override
+    public void close() {}
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/SinkNull.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/SinkNull.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/SinkNull.java
new file mode 100644
index 0000000..9242c97
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/SinkNull.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.atlas.lib;
+
+
+
+public class SinkNull<T> implements Sink<T>
+{
+    public static <X> SinkNull<X> create() { return new SinkNull<>() ; }
+    
+    /*@Override*/ @Override
+    public void send(T thing)  {}
+    /*@Override*/ @Override
+    public void close() {}
+    /*@Override*/ @Override
+    public void flush() { }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/SinkPrint.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/SinkPrint.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/SinkPrint.java
new file mode 100644
index 0000000..4fc694d
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/SinkPrint.java
@@ -0,0 +1,45 @@
+/*
+ * 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.lib;
+
+import java.io.PrintStream ;
+
+public class SinkPrint<T> implements Sink<T>
+{
+    
+    private PrintStream out ;
+
+    public SinkPrint() 
+    { this(System.out); }
+    
+    public SinkPrint(PrintStream out) 
+    { this.out = out ; }
+    
+    @Override
+    public void send(T item)
+    {
+        out.println("Sink: "+item) ;
+    }
+
+    @Override 
+    public void flush() { }
+    
+    @Override
+    public void close() {}
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/SinkSplit.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/SinkSplit.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/SinkSplit.java
new file mode 100644
index 0000000..8d50ee4
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/SinkSplit.java
@@ -0,0 +1,55 @@
+/*
+ * 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.lib;
+
+/** Split a sink stream and duplicate the operations onto two sinks 
+ *  See also: {@link SinkWrapper}
+ */
+public class SinkSplit<T> implements Sink<T>
+{
+    private final Sink<T> sink1 ;
+    private final Sink<T> sink2 ;
+
+    public SinkSplit(Sink<T> sink1, Sink<T> sink2)
+    {
+        this.sink1 = sink1 ;
+        this.sink2 = sink2 ;
+    }
+    
+    @Override
+    public void flush()
+    { 
+        sink1.flush();
+        sink2.flush();
+    }
+        
+    @Override
+    public void send(T item)
+    { 
+        sink1.send(item) ;
+        sink2.send(item) ;
+    }
+
+    @Override
+    public void close()
+    {
+        sink1.close(); 
+        sink2.close();
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/SinkToCollection.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/SinkToCollection.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/SinkToCollection.java
new file mode 100644
index 0000000..d7e04ba
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/SinkToCollection.java
@@ -0,0 +1,38 @@
+/**
+ * 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.lib;
+
+import java.util.Collection;
+
+/** Send items to a collection */
+public class SinkToCollection<T> implements Sink<T>
+{
+    private final Collection<T> c ;
+
+    public SinkToCollection(Collection<T> c) { this.c = c ; }
+
+    @Override
+    public void send(T item) { c.add(item); }
+
+    @Override
+    public void flush() {}
+
+    @Override
+    public void close() {}
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/SinkToQueue.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/SinkToQueue.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/SinkToQueue.java
new file mode 100644
index 0000000..f7e5dff
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/SinkToQueue.java
@@ -0,0 +1,52 @@
+/**
+ * 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.lib;
+
+import java.util.concurrent.BlockingQueue ;
+import java.util.concurrent.CancellationException ;
+
+/** Send items to a blocking queue */
+public class SinkToQueue<T> implements Sink<T>
+{
+    private final BlockingQueue<T> queue ;
+
+    public SinkToQueue(BlockingQueue<T> queue) { this.queue = queue ; }
+
+    @Override
+    public void send(T item)
+    {
+        try
+        {
+            if (Thread.interrupted()) throw new InterruptedException();
+            // Hopefully we'll never get passed null... but just in case
+            if (null == item) return;
+            queue.put(item);
+        }
+        catch (InterruptedException e)
+        {
+            throw new CancellationException();
+        }
+    }
+
+    @Override
+    public void flush() {}
+
+    @Override
+    public void close() {}
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/SinkWrapper.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/SinkWrapper.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/SinkWrapper.java
new file mode 100644
index 0000000..c6632ba
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/SinkWrapper.java
@@ -0,0 +1,44 @@
+/*
+ * 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.lib;
+
+/** Wrap one sink in another - to pass on behaviour, the derived Sink must call super.operation
+ *  See also: {@link SinkSplit}
+ */
+public class SinkWrapper<T> implements Sink<T>
+{
+    protected final Sink<T> sink ;
+
+    public SinkWrapper(Sink<T> sink)
+    {
+        this.sink = sink ;
+    }
+    
+    @Override
+    public void flush()
+    { sink.flush(); }
+
+    @Override
+    public void send(T item)
+    { sink.send(item) ; }
+
+    @Override
+    public void close()
+    { sink.close(); }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/StrUtils.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/StrUtils.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/StrUtils.java
new file mode 100644
index 0000000..9a9be76
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/StrUtils.java
@@ -0,0 +1,292 @@
+/*
+ * 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.lib;
+
+import java.io.UnsupportedEncodingException ;
+import java.util.ArrayList ;
+import java.util.List ;
+import java.util.Map ;
+
+
+public class StrUtils //extends StringUtils
+{
+    private StrUtils() {}
+    
+    /** strjoin with a newline as the separator */
+    public static String strjoinNL(String... args)
+    {
+        return join("\n", args) ;
+    }
+    
+    /** strjoin with a newline as the separator */
+    public static String strjoinNL(List<String> args)
+    {
+        return join("\n", args) ;
+    }
+    
+    /** Concatentate strings, using a separator */
+    public static String strjoin(String sep, String... args)
+    {
+        return join(sep, args) ;
+    }
+    
+    /** Concatentate string, using a separator */
+    public static String strjoin(String sep, List<String> args)
+    {
+        return join(sep, args) ;
+    }
+    
+    private static String join(String sep, List<String> a)
+    {
+        return join(sep, a.toArray(new String[0])) ;
+    }
+    
+    private static String join(String sep, String...a)
+    {
+        if ( a.length == 0 )
+            return "" ;
+
+        if ( a.length == 1)
+            return a[0] ;
+
+        StringBuilder sbuff = new StringBuilder() ;
+        sbuff.append(a[0]) ;
+
+        for ( int i = 1 ; i < a.length ; i++ )
+        {
+            if ( sep != null )
+                sbuff.append(sep) ;
+            sbuff.append(a[i]) ;
+        }
+        return sbuff.toString() ;
+    }
+    
+    public static final int CMP_GREATER  = +1 ;
+    public static final int CMP_EQUAL    =  0 ;
+    public static final int CMP_LESS     = -1 ;
+    
+    public static final int CMP_UNEQUAL  = -9 ;
+    public static final int CMP_INDETERMINATE  = 2 ;
+    
+    public static int strCompare(String s1, String s2)
+    {
+        // Value is the difference of the first differing chars
+        int x = s1.compareTo(s2) ;
+        if ( x < 0 ) return CMP_LESS ;
+        if ( x > 0 ) return CMP_GREATER ;
+        if ( x == 0 ) return CMP_EQUAL ;
+        throw new InternalErrorException("String comparison failure") ;
+    }
+    
+    public static int strCompareIgnoreCase(String s1, String s2)
+    {
+        // Value is the difference of the first differing chars
+        int x = s1.compareToIgnoreCase(s2) ;
+        if ( x < 0 ) return CMP_LESS ;
+        if ( x > 0 ) return CMP_GREATER ;
+        if ( x == 0 ) return CMP_EQUAL ;
+        throw new InternalErrorException("String comparison failure") ;
+    }
+
+    public static byte[] asUTF8bytes(String s)
+    {
+        try { return s.getBytes("UTF-8") ; }
+        catch (UnsupportedEncodingException ex)
+        { throw new InternalErrorException("UTF-8 not supported!") ; } 
+    }
+
+    public static String fromUTF8bytes(byte[] bytes)
+    {
+        try { return new String(bytes, "UTF-8") ; }
+        catch (UnsupportedEncodingException ex)
+        { throw new InternalErrorException("UTF-8 not supported!") ; } 
+    }
+    
+    public static String str(Object x)
+    {
+        if ( x == null ) return "<null>" ;
+        return x.toString() ;
+    }
+    
+    /** Split but also trim whiespace. */
+    public static String[] split(String s, String splitStr)
+    {
+        String[] x = s.split(splitStr) ;
+        for ( int i = 0 ; i < x.length ; i++ )
+        {
+            x[i] = x[i].trim() ;
+        }
+        return x ;
+    }
+    
+    /** Does one string contain another string?
+     * @param str1
+     * @param str2
+     * @return true if str1 contains str2
+     */
+    public final static boolean contains(String str1, String str2)
+    {
+        return str1.contains(str2) ;
+    }
+    
+    public final static String replace(String string, String target, String replacement)
+    {
+        return string.replace(target, replacement) ;
+    }
+    
+    public static String substitute(String str, Map<String, String>subs)
+    {
+        for ( Map.Entry<String, String> e : subs.entrySet() )
+        {
+            String param = e.getKey() ;
+            if ( str.contains(param) ) 
+                str = str.replace(param, e.getValue()) ;
+        }
+        return str ;
+    }
+    
+    public static String strform(Map<String, String>subs, String... args)
+    {
+        return substitute(strjoinNL(args),subs) ;
+    }
+
+    public static String chop(String x)
+    {
+        if ( x.length() == 0 )
+            return x ;
+        return x.substring(0, x.length()-1) ;
+    }
+
+    public static String noNewlineEnding(String x)
+    {
+        while ( x.endsWith("\n") || x.endsWith("\r") )
+            x = StrUtils.chop(x) ;
+        return x ;
+    }
+    
+    public static List<Character> toCharList(String str)
+    {
+        List<Character> characters = new ArrayList<>(str.length()) ;
+        for ( Character ch : str.toCharArray() )
+            characters.add(ch) ;
+        return characters ;
+    }
+    
+    // ==== Encoding and decoding strings based on a marker character (e.g. %)
+    // and then the hexadecimal representation of the character.  
+    // Only characters 0-255 can be encoded.
+    
+    /** Encode a string using hex values e.g. %20
+     * 
+     * @param str       String to encode
+     * @param marker    Marker character
+     * @param escapees  Characters to encode (must include the marker)
+     * @return          Encoded string (returns input object if no change)
+     */
+    public static String encodeHex(String str, char marker, char[] escapees)
+    {
+        // We make a first pass to see if there is anything to do.
+        // This is assuming
+        // (1) the string is shortish (e.g. fits in L1)
+        // (2) necessary escaping is not common
+        
+        int N = str.length();
+        int idx = 0 ;
+        // Scan stage.
+        for ( ; idx < N ; idx++ )
+        {
+            char ch = str.charAt(idx) ;
+            if ( Chars.charInArray(ch, escapees) )
+                break ;
+        }
+        if ( idx == N )
+            return str ;
+
+        // At least one char to convert
+        StringBuilder buff = new StringBuilder() ;
+        buff.append(str, 0, idx) ;  // Insert first part.
+        for ( ; idx < N ; idx++ )
+        {
+            char ch = str.charAt(idx) ;
+            if ( Chars.charInArray(ch, escapees) )
+            {
+                Chars.encodeAsHex(buff, marker, ch) ;
+                continue ;
+            }
+            buff.append(ch) ;
+        }
+        return buff.toString();
+    }
+
+    /** Decode a string using marked hex values e.g. %20
+     * 
+     * @param str       String to decode
+     * @param marker    The marker charcater
+     * @return          Decoded string (returns input object on no change)
+     */
+    public static String decodeHex(String str, char marker)
+    {
+        int idx = str.indexOf(marker) ;
+        if ( idx == -1 )
+            return str ;
+        StringBuilder buff = new StringBuilder() ;
+        
+        buff.append(str, 0, idx) ;
+        int N = str.length() ;
+        
+        for ( ; idx < N ; idx++ ) 
+        {
+            char ch = str.charAt(idx) ;
+            // First time through this is true, always.
+            if ( ch != marker )
+                buff.append(ch) ;
+            else
+            {
+                char hi = str.charAt(idx+1) ; 
+                char lo = str.charAt(idx+2) ;   // exceptions.
+                char ch2 = (char)(hexDecode(hi)<<4 | hexDecode(lo)) ;
+                buff.append(ch2) ;
+                idx += 2 ;
+            }
+        }
+        return buff.toString() ; 
+    }
+    
+    // Encoding is table-driven but for decode, we use code.
+    static private int hexDecode(char ch) {
+        if (ch >= '0' && ch <= '9' )
+            return ch - '0' ;
+        if ( ch >= 'A' && ch <= 'F' )
+            return ch - 'A' + 10 ;
+        if ( ch >= 'a' && ch <= 'f' )
+            return ch - 'a' + 10 ;
+        return -1 ;
+    }
+
+    public static String escapeString(String x)
+    {
+        return EscapeStr.stringEsc(x) ;
+    }
+    
+    public static String unescapeString(String x)
+    {
+        return EscapeStr.unescapeStr(x) ;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/Sync.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/Sync.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/Sync.java
new file mode 100644
index 0000000..c8e549b
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/Sync.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.lib;
+
+public interface Sync
+{
+    public void sync() ;
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/SystemUtils.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/SystemUtils.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/SystemUtils.java
new file mode 100644
index 0000000..fa208f8
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/SystemUtils.java
@@ -0,0 +1,57 @@
+/*
+ * 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.lib;
+
+import org.apache.jena.atlas.AtlasException ;
+import org.slf4j.Logger ;
+import org.slf4j.LoggerFactory ;
+
+public class SystemUtils
+{
+    private static Logger log = LoggerFactory.getLogger(SystemUtils.class.getName());
+    // Unfortunately, this tends to cause confusing logging.
+    private static boolean logging = false ;
+    
+    static public ClassLoader chooseClassLoader()
+    {
+        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+    
+        if ( logging && classLoader != null )
+            log.trace("Using thread classloader") ;
+        
+//        if (classLoader == null)
+//        {
+//            classLoader = this.getClass().getClassLoader();
+//            if ( classLoader != null )
+//                logger.trace("Using 'this' classloader") ;
+//        }
+        
+        if ( classLoader == null )
+        {
+            classLoader = ClassLoader.getSystemClassLoader() ;
+            if ( logging && classLoader != null )
+                log.trace("Using system classloader") ;
+        }
+        
+        if ( classLoader == null )
+            throw new AtlasException("Failed to find a classloader") ;
+        return classLoader ;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/Timer.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/Timer.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/Timer.java
new file mode 100644
index 0000000..304e086
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/Timer.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     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.lib ;
+
+import org.apache.jena.atlas.AtlasException ;
+
+/** A Timer of operations */ 
+public class Timer {
+
+    protected long    timeFinish = -1 ;
+    protected boolean inTimer    = false ;
+    protected long    timeStart  = 0 ;
+
+    public Timer() {}
+
+    public void startTimer() {
+        if ( inTimer )
+            throw new AtlasException("Already in timer") ;
+
+        timeStart = System.currentTimeMillis() ;
+        timeFinish = -1 ;
+        inTimer = true ;
+    }
+
+    /** Return time in millisecods */
+    public long endTimer() {
+        if ( !inTimer )
+            throw new AtlasException("Not in timer") ;
+        timeFinish = System.currentTimeMillis() ;
+        inTimer = false ;
+        return getTimeInterval() ;
+    }
+
+    public long readTimer() {
+        if ( !inTimer )
+            throw new AtlasException("Not in timer") ;
+        return System.currentTimeMillis() - timeStart ;
+    }
+
+    public long getTimeInterval() {
+        if ( inTimer )
+            throw new AtlasException("Still timing") ;
+        if ( timeFinish == -1 )
+            throw new AtlasException("No valid interval") ;
+
+        return timeFinish - timeStart ;
+    }
+
+    static public String timeStr(long timeInterval) {
+        return String.format("%.3f", timeInterval / 1000.0) ;
+    }
+
+    protected String timeStr(long timePoint, long startTimePoint) {
+        return timeStr(timePoint - startTimePoint) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/Trie.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/Trie.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/Trie.java
new file mode 100644
index 0000000..0d823bc
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/Trie.java
@@ -0,0 +1,407 @@
+/*
+ * 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.lib;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An implementation of a classic Trie, this is a mapping from strings to some
+ * value type optimized for fast prefix search and match. If you do not need
+ * prefix search then you should typically use a standard {@link Map} instead.
+ * <p>
+ * The empty or null string may be used as a special key to refer to the root
+ * node of the trie.
+ * </p>
+ * <p>
+ * A Trie cannot store null values since null is used as an internal marker to
+ * indicate that there is no value associated with a key. This is necessary
+ * because the nature of the data structure means that adding a key potentially
+ * adds multiple keys many of which will not be associated with a value.
+ * </p>
+ * 
+ * @param <T>
+ *            Type of the value stored.
+ */
+public final class Trie<T> {
+
+    private TrieNode<T> root = new TrieNode<>(null);
+
+    /**
+     * Adds a value to the trie overwriting any existing value
+     * <p>
+     * Note that a null value is treated as if the key does not actually exist
+     * in the tree so trying to add a null is a no-op. If you want to remove a
+     * key use the {@link #remove(String)} method instead.
+     * </p>
+     * 
+     * @param key
+     *            Key
+     * @param value
+     *            Value
+     */
+    public void add(String key, T value) {
+        if (value == null)
+            return;
+        TrieNode<T> n = this.moveToNode(key);
+        n.setValue(value);
+    }
+
+    /**
+     * Move to a node creating new nodes if necessary
+     * 
+     * @param key
+     *            Key
+     * @return Node
+     */
+    private TrieNode<T> moveToNode(String key) {
+        TrieNode<T> current = this.root;
+        if (key == null)
+            return current;
+        for (int i = 0; i < key.length(); i++) {
+            current = current.moveToChild(key.charAt(i));
+        }
+        return current;
+    }
+
+    /**
+     * Try to find a node in the trie
+     * 
+     * @param key
+     *            Key
+     * @return Node or null if not found
+     */
+    private TrieNode<T> find(String key) {
+        TrieNode<T> current = this.root;
+        if (key == null)
+            return current;
+        for (int i = 0; i < key.length(); i++) {
+            current = current.getChild(key.charAt(i));
+            if (current == null)
+                break;
+        }
+        return current;
+    }
+
+    /**
+     * Removes a value from the trie
+     * <p>
+     * This doesn't actually remove the key per-se rather sets the value
+     * associated with the key to null.
+     * </p>
+     * 
+     * @param key
+     *            Key
+     */
+    public void remove(String key) {
+        TrieNode<T> n = this.find(key);
+        if (n != null) {
+            n.setValue(null);
+        }
+    }
+
+    /**
+     * Gets whether a key exists in the trie and has a non-null value mapped to
+     * it
+     * 
+     * @param key
+     *            Key
+     * @return True if the key exists and has a non-null value mapped to it
+     */
+    public boolean contains(String key) {
+        return this.contains(key, true);
+    }
+
+    /**
+     * Gets whether a key exists in the trie and meets the given value criteria
+     * 
+     * @param key
+     *            Key
+     * @param requireValue
+     *            If true a key must have a non-null value associated with it to
+     *            be considered to be contained in the tree, if false then the
+     *            key must merely map to a node in the trie
+     * @return True if the key exists and the value criteria is met, false
+     *         otherwise
+     */
+    public boolean contains(String key, boolean requireValue) {
+        TrieNode<T> n = this.find(key);
+        if (n == null)
+            return false;
+        if (requireValue) {
+            return n.hasValue();
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * Gets whether a key value pair are present in the trie
+     * 
+     * @param key
+     *            Key
+     * @param value
+     *            Value
+     * @return True if the key value pair exists in the trie, false otherwise
+     */
+    public boolean contains(String key, T value) {
+        TrieNode<T> n = this.find(key);
+        if (n == null)
+            return false;
+        if (value == null && !n.hasValue())
+            return true;
+        return value.equals(n.getValue());
+    }
+
+    /**
+     * Gets the value associated with a key
+     * 
+     * @param key
+     *            Key
+     * @return Value
+     */
+    public T get(String key) {
+        TrieNode<T> n = this.find(key);
+        if (n == null)
+            return null;
+        return n.getValue();
+    }
+
+    /**
+     * Performs a prefix search and returns all values mapped under the given
+     * prefix. The entirety of the prefix must be matches, if you only want part
+     * of the prefix to be matched use the {@link #partialSearch(String)} method
+     * instead.
+     * 
+     * @param prefix
+     *            Prefix
+     * @return List of values associated with the given key
+     */
+    public List<T> prefixSearch(String prefix) {
+        TrieNode<T> n = this.find(prefix);
+        if (n == null)
+            return new ArrayList<>();
+        return Collections.unmodifiableList(n.getValues());
+    }
+
+    /**
+     * Performs a search and returns any value associated with any partial or
+     * whole prefix of the key
+     * 
+     * @param key
+     *            Key
+     * @return List of values associated with any partial prefix of the key
+     */
+    public List<T> partialSearch(String key) {
+        List<T> values = new ArrayList<>();
+        TrieNode<T> current = this.root;
+        if (key == null) {
+            if (current.hasValue())
+                values.add(current.getValue());
+        } else {
+            for (int i = 0; i < key.length(); i++) {
+                if (current.hasValue())
+                    values.add(current.getValue());
+                current = current.getChild(key.charAt(i));
+                if (current == null)
+                    return Collections.unmodifiableList(values);
+            }
+            
+            // If we reach here current is the complete key match
+            // so make sure to include it in the values list
+            if (current.hasValue()) {
+                values.add(current.getValue());
+            }
+            
+        }
+        return Collections.unmodifiableList(values);
+    }
+
+    /**
+     * Finds the shortest match for a given key i.e. returns the value
+     * associated with the shortest prefix of the key that has a value
+     * 
+     * @param key
+     *            Key
+     * @return Shortest Match or null if no possible matches
+     */
+    public T shortestMatch(String key) {
+        TrieNode<T> current = this.root;
+        if (key == null)
+            return current.getValue();
+        for (int i = 0; i < key.length(); i++) {
+            if (current.hasValue())
+                break;
+            current = current.getChild(key.charAt(i));
+            if (current == null)
+                return null;
+        }
+        return current.getValue();
+    }
+
+    /**
+     * Finds the longest match for a given key i.e. returns the value associated
+     * with the longest prefix of the key that has a value
+     * 
+     * @param key
+     *            Key
+     * @return Longest Match or null if no possible matches
+     */
+    public T longestMatch(String key) {
+        T value = null;
+        TrieNode<T> current = this.root;
+        if (key == null) {
+            return current.getValue();
+        } else {
+            for (int i = 0; i < key.length(); i++) {
+                if (current.hasValue())
+                    value = current.getValue();
+                current = current.getChild(key.charAt(i));
+                if (current == null)
+                    return value;
+            }
+            // If we reach here current is the complete key match
+            // so return its value if it has one
+            if (current.hasValue()) {
+                return current.getValue();
+            }
+        }
+        return value;
+    }
+
+    /**
+     * Represents a node in the Trie
+     * <p>
+     * The implementation is designed to be sparse such that we delay creation
+     * of things at both leafs and interior nodes until they are actually needed
+     * </p>
+     * 
+     */
+    private static class TrieNode<T> {
+        private Map<Character, TrieNode<T>> children = null;
+        private Character singletonChildChar = null;
+        private TrieNode<T> singletonChild = null;
+        private T value;
+
+        /**
+         * Creates a Trie Node
+         * 
+         * @param value
+         *            Value
+         */
+        public TrieNode(T value) {
+            this.value = value;
+        }
+
+        /**
+         * Gets the value
+         * 
+         * @return Value
+         */
+        public T getValue() {
+            return this.value;
+        }
+
+        /**
+         * Sets the value
+         * 
+         * @param value
+         *            Value
+         */
+        public void setValue(T value) {
+            this.value = value;
+        }
+
+        /**
+         * Returns whether a non-null value is associated with this node
+         * 
+         * @return True if there is a non-null value, false otherwise
+         */
+        public boolean hasValue() {
+            return this.value != null;
+        }
+
+        /**
+         * Gets the child (if it exists)
+         * 
+         * @param c
+         *            Character to move to
+         * @return Child
+         */
+        public TrieNode<T> getChild(Character c) {
+            if (this.children != null) {
+                return this.children.get(c);
+            } else if (c.equals(this.singletonChildChar)) {
+                return this.singletonChild;
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Moves to a child (creating a new node if necessary)
+         * 
+         * @param c
+         *            Character to move to
+         * @return Child
+         */
+        public TrieNode<T> moveToChild(Character c) {
+            TrieNode<T> n = this.getChild(c);
+            if (n == null) {
+                n = new TrieNode<>(null);
+                if (this.children != null) {
+                    // Add to existing map
+                    this.children.put(c, n);
+                } else if (this.singletonChildChar != null) {
+                    // Need to lazily create map
+                    this.children = new HashMap<>();
+                    this.children.put(this.singletonChildChar, this.singletonChild);
+                    this.children.put(c, n);
+                } else {
+                    // Singleton child
+                    this.singletonChildChar = c;
+                    this.singletonChild = n;
+                }
+            }
+            return n;
+        }
+
+        /**
+         * Gets all values from a given node and its descendants
+         * 
+         * @return Values
+         */
+        public List<T> getValues() {
+            List<T> values = new ArrayList<>();
+            if (this.hasValue()) {
+                values.add(this.value);
+            }
+            if (this.children != null) {
+                for (TrieNode<T> child : this.children.values()) {
+                    values.addAll(child.getValues());
+                }
+            } else if (this.singletonChild != null) {
+                values.addAll(this.singletonChild.getValues());
+            }
+            return values;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/Tuple.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/Tuple.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/Tuple.java
new file mode 100644
index 0000000..ae944a7
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/Tuple.java
@@ -0,0 +1,153 @@
+/*
+ * 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.lib ;
+
+import java.util.Arrays ;
+import java.util.Iterator ;
+import java.util.List ;
+
+import org.apache.jena.atlas.iterator.Iter ;
+import org.apache.jena.atlas.iterator.IteratorArray ;
+import org.apache.jena.atlas.iterator.Transform ;
+
+/** Tuple class - tuples are immutable and must be created initialized */
+public class Tuple<T> implements Iterable<T> {
+    // Interface this?
+    // Classes: TupleImpl, TupleSlice
+    public static <X> Tuple<X> createTuple(@SuppressWarnings("unchecked") X... elements) {
+        X[] els = elements ; // ArrayUtils.copy(elements) ;
+        return create(els) ;
+    }
+
+    /**
+     * Create a tuple from an array of elements. The array is not copied and
+     * should not be modified after this call.
+     * <p>
+     * There is also a {@link TupleBuilder} which does create an idendent
+     * copy, in case that style is preferrable for creating tuples.
+     */
+    public static <X> Tuple<X> create(X[] elements) {
+        return new Tuple<>(elements) ;
+    }
+
+    // TupleLib??
+    public static <T> Iterator<T> project(final int slot, Iterator<Tuple<T>> iter) {
+        Transform<Tuple<T>, T> projection = new Transform<Tuple<T>, T>() {
+            @Override
+            public T convert(Tuple<T> tuple) {
+                return tuple.get(slot) ;
+            }
+        } ;
+        return Iter.map(iter, projection) ;
+    }
+
+    public static <T> Iterator<Tuple<T>> prefix(final int prefixLength, Iterator<Tuple<T>> iter) {
+        Transform<Tuple<T>, Tuple<T>> sub = new Transform<Tuple<T>, Tuple<T>>() {
+            @Override
+            public Tuple<T> convert(Tuple<T> tuple) {
+                T[] x = ArrayUtils.copy(tuple.tuple, 0, prefixLength) ;
+                return Tuple.create(x) ;
+            }
+        } ;
+        return Iter.map(iter, sub) ;
+    }
+
+    protected final T[] tuple ;
+
+    protected Tuple(@SuppressWarnings("unchecked") T... tuple) {
+        this.tuple = tuple ;
+    }
+
+    public T get(int idx) {
+        return tuple[idx] ;
+    }
+
+    public int countNotNull() {
+        int x = 0 ;
+        for ( T item : tuple )
+            if ( item != null )
+                x++ ;
+        return x ;
+    }
+
+    public List<T> asList() {
+        return Arrays.asList(tuple) ;
+    }
+
+    public T[] tuple() {
+        return tuple ;
+    }
+
+    public T[] tupleCopy() {
+        return ArrayUtils.copy(tuple) ;
+    }
+
+    @Override
+    public Iterator<T> iterator() {
+        return IteratorArray.create(tuple) ;
+    }
+
+    /** Return a tuple with the column mapping applied */
+    public Tuple<T> map(ColumnMap colMap) {
+        return colMap.map(this) ;
+    }
+
+    /** Return a tuple with the column mapping reversed */
+    public Tuple<T> unmap(ColumnMap colMap) {
+        return colMap.unmap(this) ;
+    }
+
+    public int size() {
+        return tuple.length ;
+    }
+
+    @Override
+    public int hashCode() {
+        int x = 99 ;
+        for ( T n : tuple ) {
+            if ( n != null )
+                x = x << 1 ^ n.hashCode() ;
+        }
+        return x ;
+    }
+
+    /** Equality of tuples is based on equality of the elements in the tuple */
+    @Override
+    public boolean equals(Object other) {
+        if ( this == other )
+            return true ;
+        if ( !(other instanceof Tuple<? >) )
+            return false ;
+        Tuple<? > x = (Tuple<? >)other ;
+        if ( x.size() != this.size() )
+            return false ;
+        for ( int i = 0 ; i < tuple.length ; i++ ) {
+            Object obj1 = tuple[i] ;
+            Object obj2 = x.tuple[i] ;
+            if ( !Lib.equal(obj1, obj2) )
+                return false ;
+        }
+        return true ;
+    }
+
+    @Override
+    public String toString() {
+        return "[" + Iter.asString(iterator(), ", ") + "]" ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/TupleBuilder.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/TupleBuilder.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/TupleBuilder.java
new file mode 100644
index 0000000..278334d
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/TupleBuilder.java
@@ -0,0 +1,50 @@
+/*
+ * 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.lib;
+
+
+import java.util.ArrayList ;
+import java.util.List ;
+
+/** Tuple builder class - tuples are immutable, this is how to create them in the builder style */
+public class TupleBuilder<T> 
+{
+    private List<T> x = new ArrayList<>() ;
+    
+    public TupleBuilder() { } 
+    
+    public TupleBuilder<T> add(T element) {
+        x.add(element) ;
+        return this ;
+    }
+    
+    public TupleBuilder<T> reset() {
+        x.clear() ;
+        return this ;
+    }
+    
+    public Tuple<T> build() { 
+        @SuppressWarnings("unchecked")
+        T[] elts = (T[])new Object[x.size()] ; 
+        // Copy contents, should not create a new array because elts
+        // is created with the right size so elts == elts2 
+        T[] elts2 = x.toArray(elts) ;
+        return new Tuple<>(elts2) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/XMLLib.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/XMLLib.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/XMLLib.java
new file mode 100644
index 0000000..e8640df
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/XMLLib.java
@@ -0,0 +1,62 @@
+/*
+ * 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.lib;
+
+/** Operations in someway related to XML */
+public class XMLLib
+{
+    /** Trim the XML whitespace characters strictly needed for whitespace facet collapse.
+     * This <b>not</b> full whitespace facet collapse, which also requires processing of
+     * internal spaces.  Because none of the datatypes that have whitespace facet
+     * collapse and have values we extract can legally contain internal whitespace,
+     * we just need to trim the string.
+     * 
+     *  Java String.trim removes any characters less than 0x20. 
+     */
+    static String WScollapse(String string)
+    {
+        int len = string.length();
+        if ( len == 0 )
+            return string ;
+        
+        if ( (string.charAt(0) > 0x20) && (string.charAt(len-1) > 0x20) )
+            return string ;
+
+        int idx1 = 0 ;
+        for ( ; idx1 < len ; idx1++ )
+        {
+            char ch = string.charAt(idx1) ;
+            if ( ! testForWS(ch) )
+                break ;
+        }
+        int idx2 = len-1 ;
+        for ( ; idx2 > idx1 ; idx2-- )
+        {
+            char ch = string.charAt(idx2) ;
+            if ( ! testForWS(ch) )
+                break ;
+        }
+        return string.substring(idx1, idx2+1) ;
+    }
+
+    private static boolean testForWS(char ch)
+    {
+        return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t' ; 
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/Cache0.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/Cache0.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/Cache0.java
new file mode 100644
index 0000000..00befa0
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/Cache0.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     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.lib.cache ;
+
+import java.util.Iterator ;
+import java.util.concurrent.Callable ;
+
+import org.apache.jena.atlas.iterator.Iter ;
+import org.apache.jena.atlas.lib.ActionKeyValue ;
+import org.apache.jena.atlas.lib.Cache ;
+
+/** A cache that keeps nothing */
+public final class Cache0<K, V> implements Cache<K, V> {
+    @Override
+    public boolean containsKey(K key) {
+        return false ;
+    }
+
+    @Override
+    public V getIfPresent(K key) {
+        return null ;
+    }
+
+    @Override
+    public V getOrFill(K key, Callable<V> callable) {
+        return null ;
+    }
+
+    @Override
+    public void put(K key, V thing) {}
+
+    @Override
+    public void remove(K key) {}
+
+    @Override
+    public Iterator<K> keys() {
+        return Iter.nullIterator() ;
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return true ;
+    }
+
+    @Override
+    public void clear() {}
+
+    @Override
+    public long size() {
+        return 0 ;
+    }
+
+    @Override
+    public void setDropHandler(ActionKeyValue<K, V> dropHandler) {}
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/Cache1.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/Cache1.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/Cache1.java
new file mode 100644
index 0000000..38ab5c6
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/Cache1.java
@@ -0,0 +1,127 @@
+/*
+ * 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.lib.cache;
+
+import java.util.Iterator ;
+import java.util.concurrent.Callable ;
+
+import org.apache.jena.atlas.iterator.SingletonIterator ;
+import org.apache.jena.atlas.lib.ActionKeyValue ;
+import org.apache.jena.atlas.lib.Cache ;
+import org.apache.jena.atlas.lib.Lib ;
+
+/** A one-slot cache.*/
+public class Cache1<K, V> implements Cache<K,V>
+{
+    private ActionKeyValue<K, V> dropHandler = null ;
+    private K cacheKey ;
+    private V cacheValue ;
+    
+    public Cache1() { clear() ; }
+    
+    @Override
+    public boolean containsKey(K key)
+    {
+        if ( cacheKey == null )
+            return false ;
+        return cacheKey.equals(key) ;
+    }
+
+    @Override
+    public V getIfPresent(K key)
+    {
+        if ( cacheKey == null ) return null ;
+        if ( cacheKey.equals(key) ) return cacheValue ;
+        return null ;
+    }
+
+    @Override
+    public V getOrFill(K key, Callable<V> callable) {
+        return CacheOps.getOrFillSync(this, key, callable) ;
+    }
+
+    @Override
+    public void clear()
+    { 
+        if ( cacheKey == null )
+            return ;
+
+        K k = cacheKey ;
+        V v = cacheValue ;
+        cacheKey = null ;
+        cacheValue = null ;
+        
+        notifyDrop(k, v) ;
+    }
+
+    @Override
+    public boolean isEmpty()
+    {
+        return cacheKey == null ;
+    }
+
+    @Override
+    public Iterator<K> keys()
+    {
+        return new SingletonIterator<>(cacheKey) ;
+    }
+
+    @Override
+    public void put(K key, V thing)
+    {
+        if ( Lib.equal(cacheKey, key) && Lib.equal(cacheValue, thing) )
+            // No change.
+            return ;
+
+        // Change
+        K k = cacheKey ;
+        V v = cacheValue ;
+        // Displaces any existing cached key/value pair
+        cacheKey = key ;
+        cacheValue = thing ;
+        notifyDrop(k, v) ;
+    }
+
+    @Override
+    public void remove(K key)
+    {
+        if ( cacheKey == null ) return ;
+        if ( cacheKey.equals(key) )
+            clear() ;   // Will notify
+    }
+
+    @Override
+    public void setDropHandler(ActionKeyValue<K, V> dropHandler)
+    {
+        this.dropHandler = dropHandler ;
+    }
+
+    private void notifyDrop(K key, V thing)
+    {
+        if ( dropHandler != null && key != null )
+            dropHandler.apply(key, thing) ;
+    }
+    
+    @Override
+    public long size()
+    {
+        return (cacheKey == null) ? 0 : 1 ;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheGuava.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheGuava.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheGuava.java
new file mode 100644
index 0000000..fd5959c
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheGuava.java
@@ -0,0 +1,119 @@
+/**
+ * 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.lib.cache;
+
+import java.util.Iterator ;
+import java.util.concurrent.Callable ;
+import java.util.concurrent.ExecutionException ;
+
+import org.apache.jena.atlas.lib.ActionKeyValue ;
+import org.apache.jena.atlas.lib.Cache ;
+import org.apache.jena.atlas.logging.Log ;
+
+import org.apache.jena.ext.com.google.common.cache.CacheBuilder ;
+import org.apache.jena.ext.com.google.common.cache.RemovalListener ;
+import org.apache.jena.ext.com.google.common.cache.RemovalNotification ;
+
+/** Wrapper around com.google.common.cache */
+final
+public class CacheGuava<K,V> implements Cache<K, V>
+{
+    private ActionKeyValue<K, V> dropHandler = null ;
+    /*private*/ org.apache.jena.ext.com.google.common.cache.Cache<K,V> cache ;
+
+    public CacheGuava(int size)
+    {
+        RemovalListener<K,V> drop = new RemovalListener<K, V>() {
+            @Override
+            public void onRemoval(RemovalNotification<K, V> notification) {
+                if ( dropHandler != null )
+                    dropHandler.apply(notification.getKey(),
+                                      notification.getValue()) ;
+            }
+        } ;
+        cache = CacheBuilder.newBuilder()
+            .maximumSize(size)
+            .removalListener(drop)
+            .recordStats()
+            .concurrencyLevel(8)
+            .build() ;
+    }
+
+    // Change the interface to be ...
+    @Override
+    public V getOrFill(K key, Callable<V> filler) {
+        try {
+            return cache.get(key, filler) ;
+        }
+        catch (ExecutionException e) {
+            Log.warn(CacheGuava.class, "Execution exception filling cache", e) ;
+            return null ;
+        }
+    }
+
+    @Override
+    public V getIfPresent(K key) {
+        return cache.getIfPresent(key) ;
+    }
+
+    @Override
+    public void put(K key, V thing) {
+        if ( thing == null ) {
+            cache.invalidate(key); 
+            return ;
+        }
+        cache.put(key, thing) ;
+    }
+
+    @Override
+    public boolean containsKey(K key) {
+        return cache.getIfPresent(key) != null ;
+    }
+
+    @Override
+    public void remove(K key) {
+        cache.invalidate(key) ;
+    }
+
+    @Override
+    public Iterator<K> keys() {
+        return cache.asMap().keySet().iterator() ;
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return cache.size() == 0 ;
+    }
+
+    @Override
+    public void clear() {
+        cache.invalidateAll() ;
+    }
+
+    @Override
+    public long size() {
+        return cache.size() ;
+    }
+
+    @Override
+    public void setDropHandler(ActionKeyValue<K, V> dropHandler) {
+        this.dropHandler = dropHandler ;
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheOps.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheOps.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheOps.java
new file mode 100644
index 0000000..c8560bb
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheOps.java
@@ -0,0 +1,50 @@
+/**
+ * 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.lib.cache;
+
+import java.util.concurrent.Callable ;
+
+import org.apache.jena.atlas.AtlasException ;
+import org.apache.jena.atlas.lib.Cache ;
+
+/** Support operations for Cache functions */ 
+class CacheOps {
+    
+    /** Implementation of getOrFill based on Cache.get and Cache.put */ 
+    public static <K,V> V getOrFill(Cache<K,V> cache, K key, Callable<V> callable) {
+        V value = cache.getIfPresent(key) ;
+        if ( value == null ) {
+            try { value = callable.call() ; }
+            catch (Exception e) {
+                throw new AtlasException("Exception on cache fill", e) ;
+            }
+            if ( value != null )
+                cache.put(key, value) ;
+        }
+        return value ;
+    }
+
+    /** Thread safe implementation of getOrFill based on Cache.get and Cache.put */ 
+    public static <K,V> V getOrFillSync(Cache<K,V> cache, K key, Callable<V> callable) {
+        synchronized(cache) {
+            return getOrFill(cache, key, callable) ;
+        }
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheSetImpl.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheSetImpl.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheSetImpl.java
new file mode 100644
index 0000000..b3bf048
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheSetImpl.java
@@ -0,0 +1,93 @@
+/*
+ * 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.lib.cache ;
+
+import java.util.Iterator ;
+
+import org.apache.jena.atlas.iterator.Action ;
+import org.apache.jena.atlas.lib.ActionKeyValue ;
+import org.apache.jena.atlas.lib.Cache ;
+import org.apache.jena.atlas.lib.CacheSet ;
+
+/** Cache set */
+public class CacheSetImpl<T> implements CacheSet<T> {
+    // LinkHashSet does not have LRU support.
+    static Object    theOnlyValue = new Object() ;
+    Cache<T, Object> cacheMap     = null ;
+
+    public CacheSetImpl(Cache<T, Object> cache) {
+        cacheMap = cache ;
+    }
+
+    /** Callback for entries when dropped from the cache */
+    @Override
+    public void setDropHandler(Action<T> dropHandler) {
+        cacheMap.setDropHandler(new Wrapper<T>(dropHandler)) ;
+    }
+    
+    // From map action to set action.
+    static class Wrapper<T> implements ActionKeyValue<T, Object> {
+        Action<T> dropHandler ;
+
+        public Wrapper(Action<T> dropHandler) {
+            this.dropHandler = dropHandler ;
+        }
+
+        @Override
+        public void apply(T key, Object value) {
+            dropHandler.apply(key) ;
+        }
+
+    }
+
+    @Override
+    public void add(T e) {
+        cacheMap.put(e, theOnlyValue) ;
+    }
+
+    @Override
+    public void clear() {
+        cacheMap.clear() ;
+    }
+
+    @Override
+    public boolean contains(T obj) {
+        return cacheMap.containsKey(obj) ;
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return cacheMap.isEmpty() ;
+    }
+
+    public Iterator<T> iterator() {
+        return cacheMap.keys() ;
+    }
+
+    @Override
+    public void remove(T obj) {
+        cacheMap.remove(obj) ;
+    }
+
+    @Override
+    public long size() {
+        return cacheMap.size() ;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheSetSync.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheSetSync.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheSetSync.java
new file mode 100644
index 0000000..fc3a3bd
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheSetSync.java
@@ -0,0 +1,65 @@
+/*
+ * 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.lib.cache ;
+
+import org.apache.jena.atlas.iterator.Action ;
+import org.apache.jena.atlas.lib.CacheSet ;
+
+public class CacheSetSync<T> implements CacheSet<T> {
+    private CacheSet<T> cache ;
+
+    public CacheSetSync(CacheSet<T> cache) {
+        this.cache = cache ;
+    }
+
+    @Override
+    synchronized public void add(T e) {
+        cache.add(e) ;
+    }
+
+    @Override
+    synchronized public void clear() {
+        cache.clear() ;
+    }
+
+    @Override
+    synchronized public boolean contains(T obj) {
+        return cache.contains(obj) ;
+    }
+
+    @Override
+    synchronized public boolean isEmpty() {
+        return cache.isEmpty() ;
+    }
+
+    @Override
+    synchronized public void remove(T obj) {
+        cache.remove(obj) ;
+    }
+
+    @Override
+    synchronized public long size() {
+        return cache.size() ;
+    }
+
+    @Override
+    synchronized public void setDropHandler(Action<T> dropHandler) {
+        cache.setDropHandler(dropHandler) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheSetWrapper.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheSetWrapper.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheSetWrapper.java
new file mode 100644
index 0000000..c11ffb0
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheSetWrapper.java
@@ -0,0 +1,65 @@
+/*
+ * 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.lib.cache ;
+
+import org.apache.jena.atlas.iterator.Action ;
+import org.apache.jena.atlas.lib.CacheSet ;
+
+public class CacheSetWrapper<T> implements CacheSet<T> {
+    private CacheSet<T> cache ;
+
+    public CacheSetWrapper(CacheSet<T> cache) {
+        this.cache = cache ;
+    }
+
+    @Override
+    public void add(T e) {
+        cache.add(e) ;
+    }
+
+    @Override
+    public void clear() {
+        cache.clear() ;
+    }
+
+    @Override
+    public boolean contains(T obj) {
+        return cache.contains(obj) ;
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return cache.isEmpty() ;
+    }
+
+    @Override
+    public void remove(T obj) {
+        cache.remove(obj) ;
+    }
+
+    @Override
+    public long size() {
+        return cache.size() ;
+    }
+
+    @Override
+    public void setDropHandler(Action<T> dropHandler) {
+        cache.setDropHandler(dropHandler) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheSimple.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheSimple.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheSimple.java
new file mode 100644
index 0000000..a33d8e2
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheSimple.java
@@ -0,0 +1,161 @@
+/*
+ * 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.lib.cache;
+
+import java.util.Arrays ;
+import java.util.Iterator ;
+import java.util.concurrent.Callable ;
+
+import org.apache.jena.atlas.iterator.Iter ;
+import org.apache.jena.atlas.iterator.IteratorArray ;
+import org.apache.jena.atlas.lib.ActionKeyValue ;
+import org.apache.jena.atlas.lib.Cache ;
+
+
+/**
+ * A simple fixed size cache that uses the hash code to address a slot.
+ * Clash policy is to overwrite.
+ * No object creation during lookup or insert.
+ */
+
+public class CacheSimple<K,V> implements Cache<K,V>
+{
+    private final V[] values ; 
+    private final K[] keys ;
+    private final int size ;
+    private int currentSize = 0 ;
+    private ActionKeyValue<K,V> dropHandler = null ;
+    
+    public CacheSimple(int size)
+    { 
+        @SuppressWarnings("unchecked")
+        V[] x =  (V[])new Object[size] ;
+        values = x ;
+        
+        @SuppressWarnings("unchecked")
+        K[]  z =  (K[])new Object[size] ;
+        keys = z ;
+        
+        this.size = size ;
+    }
+    
+
+    @Override
+    public void clear()
+    { 
+        Arrays.fill(values, null) ;
+        Arrays.fill(keys, null) ;
+        // drop handler
+        currentSize = 0 ;
+    }
+
+    @Override
+    public boolean containsKey(K key)
+    {
+        return getIfPresent(key) != null ;
+    }
+
+    // Return key index : -(index+1) if the key does not match
+    private final int index(K key)
+    { 
+        int x = (key.hashCode()&0x7fffffff) % size ;
+        if ( keys[x] != null && keys[x].equals(key) )
+            return x ; 
+        return -x-1 ;
+    }
+    
+    private final int decode(int x)
+    { 
+        if ( x >= 0 ) return x ;
+        return -x-1 ;
+    }
+    
+    @Override
+    public V getIfPresent(K key)
+    {
+        int x = index(key) ;
+        if ( x < 0 )
+            return null ; 
+        return values[x] ;
+    }
+
+    @Override
+    public V getOrFill(K key, Callable<V> callable) {
+        return CacheOps.getOrFill(this, key, callable) ;
+    }
+
+    @Override
+    public void put(K key, V thing)
+    {
+        int x = index(key) ;
+        V old = null ;
+        if ( x < 0 )
+            // New.
+            x = decode(x) ;
+        else
+        {
+            // Drop the old K->V
+            old = values[x] ;
+            if ( dropHandler != null )
+                dropHandler.apply(keys[x], old) ;
+            currentSize-- ;
+        }
+        
+        values[x] = thing ;
+        if ( thing == null )
+            //put(,null) is a remove.
+            keys[x] = null ;
+        else {
+            keys[x] = key ;
+            currentSize++ ;
+        }
+    }
+
+    @Override
+    public void remove(K key)
+    {
+        put(key, null) ;
+    }
+
+    @Override
+    public long size()
+    {
+        return currentSize ;
+    }
+
+    @Override
+    public Iterator<K> keys()
+    {
+        Iterator<K> iter = IteratorArray.create(keys) ;
+        return Iter.removeNulls(iter) ;
+    }
+
+    @Override
+    public boolean isEmpty()
+    {
+        return currentSize == 0 ;
+    }
+
+    /** Callback for entries when dropped from the cache */
+    @Override
+    public void setDropHandler(ActionKeyValue<K,V> dropHandler)
+    {
+        this.dropHandler = dropHandler ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheWrapper.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheWrapper.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheWrapper.java
new file mode 100644
index 0000000..e655749
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/CacheWrapper.java
@@ -0,0 +1,65 @@
+/*
+ * 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.lib.cache;
+import java.util.Iterator ;
+import java.util.concurrent.Callable ;
+
+import org.apache.jena.atlas.lib.ActionKeyValue ;
+import org.apache.jena.atlas.lib.Cache ;
+
+
+
+public class CacheWrapper<Key,T> implements Cache<Key,T>
+{
+    protected Cache<Key,T> cache ;
+    
+    public CacheWrapper(Cache<Key,T> cache)         { this.cache = cache ; }
+
+    @Override
+    public void clear()                             { cache.clear(); }
+
+    @Override
+    public boolean containsKey(Key key)             { return cache.containsKey(key) ; }
+    
+    @Override
+    public T getIfPresent(Key key)                  { return cache.getIfPresent(key) ; }
+
+    @Override
+    public T getOrFill(Key key, Callable<T> callable)  { return cache.getOrFill(key, callable) ; }
+
+    @Override
+    public boolean isEmpty()                        { return cache.isEmpty() ; }
+
+    @Override
+    public Iterator<Key> keys()                     { return cache.keys(); }
+
+    @Override
+    public void put(Key key, T thing)               { cache.put(key, thing) ; }
+
+    @Override
+    public void remove(Key key)                     { cache.remove(key) ; }
+
+    @Override
+    public void setDropHandler(ActionKeyValue<Key, T> dropHandler)
+    { cache.setDropHandler(dropHandler) ; }
+
+    @Override
+    public long size()                              { return cache.size() ; }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/Getter.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/Getter.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/Getter.java
new file mode 100644
index 0000000..c9f879c
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/cache/Getter.java
@@ -0,0 +1,25 @@
+/*
+ * 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.lib.cache;
+
+/** A get interface */
+public interface Getter<K, V>
+{
+    public V get(K key) ; 
+}


[9/9] jena git commit: Import of lib, io, logging and iterator packages

Posted by an...@apache.org.
Import of lib, io, logging and iterator packages


Project: http://git-wip-us.apache.org/repos/asf/jena/repo
Commit: http://git-wip-us.apache.org/repos/asf/jena/commit/1320f8db
Tree: http://git-wip-us.apache.org/repos/asf/jena/tree/1320f8db
Diff: http://git-wip-us.apache.org/repos/asf/jena/diff/1320f8db

Branch: refs/heads/master
Commit: 1320f8dbe2ed08f10fda6e48aac9177557b1ed03
Parents: 692118f
Author: Andy Seaborne <an...@apache.org>
Authored: Tue Apr 28 22:03:14 2015 +0100
Committer: Andy Seaborne <an...@apache.org>
Committed: Tue Apr 28 22:03:14 2015 +0100

----------------------------------------------------------------------
 jena-base/pom.xml                               |   2 +-
 .../org/apache/jena/atlas/AtlasException.java   |  27 +
 .../apache/jena/atlas/RuntimeIOException.java   |  34 +
 .../java/org/apache/jena/atlas/io/AWriter.java  |  43 +
 .../org/apache/jena/atlas/io/AWriterBase.java   |  33 +
 .../org/apache/jena/atlas/io/BlockUTF8.java     | 281 ++++++
 .../apache/jena/atlas/io/BufferingWriter.java   | 180 ++++
 .../org/apache/jena/atlas/io/CharStream.java    |  31 +
 .../apache/jena/atlas/io/CharStreamBasic.java   |  58 ++
 .../jena/atlas/io/CharStreamBuffered.java       | 166 ++++
 .../apache/jena/atlas/io/CharStreamReader.java  |  56 ++
 .../jena/atlas/io/CharStreamSequence.java       |  47 +
 .../main/java/org/apache/jena/atlas/io/IO.java  | 325 +++++++
 .../org/apache/jena/atlas/io/InStreamASCII.java |  89 ++
 .../org/apache/jena/atlas/io/InStreamUTF8.java  | 254 +++++
 .../jena/atlas/io/IndentedLineBuffer.java       |  50 +
 .../apache/jena/atlas/io/IndentedWriter.java    | 366 +++++++
 .../jena/atlas/io/InputStreamBuffered.java      | 104 ++
 .../apache/jena/atlas/io/NullOutputStream.java  |  40 +
 .../org/apache/jena/atlas/io/OutStreamUTF8.java | 179 ++++
 .../org/apache/jena/atlas/io/OutputUtils.java   |  77 ++
 .../apache/jena/atlas/io/PeekInputStream.java   | 233 +++++
 .../org/apache/jena/atlas/io/PeekReader.java    | 270 ++++++
 .../org/apache/jena/atlas/io/PrintUtils.java    |  31 +
 .../org/apache/jena/atlas/io/Printable.java     |  24 +
 .../org/apache/jena/atlas/io/PrintableBase.java |  26 +
 .../org/apache/jena/atlas/io/StringWriterI.java |  30 +
 .../java/org/apache/jena/atlas/io/Writer2.java  |  97 ++
 .../apache/jena/atlas/iterator/AccString.java   |  66 ++
 .../apache/jena/atlas/iterator/Accumulate.java  |  27 +
 .../org/apache/jena/atlas/iterator/Action.java  |  24 +
 .../apache/jena/atlas/iterator/ActionCount.java |  31 +
 .../jena/atlas/iterator/ActionNothing.java      |  26 +
 .../org/apache/jena/atlas/iterator/Filter.java  |  22 +
 .../atlas/iterator/FilterDistinctAdjacent.java  |  40 +
 .../jena/atlas/iterator/FilterOutNulls.java     |  32 +
 .../apache/jena/atlas/iterator/FilterStack.java |  67 ++
 .../jena/atlas/iterator/FilterUnique.java       |  39 +
 .../org/apache/jena/atlas/iterator/Iter.java    | 971 +++++++++++++++++++
 .../jena/atlas/iterator/IteratorArray.java      |  92 ++
 .../atlas/iterator/IteratorBlockingQueue.java   |  73 ++
 .../jena/atlas/iterator/IteratorConcat.java     | 104 ++
 .../jena/atlas/iterator/IteratorCons.java       | 122 +++
 .../iterator/IteratorDelayedInitialization.java |  78 ++
 .../jena/atlas/iterator/IteratorInteger.java    |  61 ++
 .../atlas/iterator/IteratorResourceClosing.java | 107 ++
 .../jena/atlas/iterator/IteratorSlotted.java    | 117 +++
 .../jena/atlas/iterator/IteratorTruncate.java   |  79 ++
 .../jena/atlas/iterator/IteratorWithBuffer.java | 132 +++
 .../atlas/iterator/IteratorWithHistory.java     | 109 +++
 .../jena/atlas/iterator/IteratorWrapper.java    |  41 +
 .../apache/jena/atlas/iterator/MapUtils.java    |  36 +
 .../jena/atlas/iterator/NullIterator.java       |  49 +
 .../jena/atlas/iterator/PeekIterator.java       | 100 ++
 .../jena/atlas/iterator/PushbackIterator.java   |  62 ++
 .../atlas/iterator/RepeatApplyIterator.java     |  86 ++
 .../jena/atlas/iterator/SingletonIterator.java  |  51 +
 .../apache/jena/atlas/iterator/Transform.java   |  21 +
 .../jena/atlas/iterator/WrapperIterator.java    |  45 +
 .../apache/jena/atlas/lib/ActionKeyValue.java   |  24 +
 .../org/apache/jena/atlas/lib/AlarmClock.java   |  69 ++
 .../java/org/apache/jena/atlas/lib/Alg.java     | 239 +++++
 .../org/apache/jena/atlas/lib/Allocator.java    |  28 +
 .../org/apache/jena/atlas/lib/ArrayUtils.java   |  64 ++
 .../java/org/apache/jena/atlas/lib/BitsInt.java | 309 ++++++
 .../org/apache/jena/atlas/lib/BitsLong.java     | 311 ++++++
 .../apache/jena/atlas/lib/ByteBufferLib.java    | 211 ++++
 .../java/org/apache/jena/atlas/lib/Bytes.java   | 357 +++++++
 .../java/org/apache/jena/atlas/lib/Cache.java   |  56 ++
 .../org/apache/jena/atlas/lib/CacheFactory.java |  67 ++
 .../org/apache/jena/atlas/lib/CacheSet.java     |  34 +
 .../org/apache/jena/atlas/lib/CacheStats.java   |  28 +
 .../org/apache/jena/atlas/lib/Callback.java     |  24 +
 .../java/org/apache/jena/atlas/lib/Cell.java    |  99 ++
 .../java/org/apache/jena/atlas/lib/Chars.java   | 271 ++++++
 .../org/apache/jena/atlas/lib/Closeable.java    |  27 +
 .../apache/jena/atlas/lib/CollectionUtils.java  |  52 +
 .../org/apache/jena/atlas/lib/ColumnMap.java    | 254 +++++
 .../main/java/org/apache/jena/atlas/lib/DS.java |  45 +
 .../apache/jena/atlas/lib/DateTimeUtils.java    | 127 +++
 .../org/apache/jena/atlas/lib/EscapeStr.java    | 235 +++++
 .../java/org/apache/jena/atlas/lib/FileOps.java | 239 +++++
 .../java/org/apache/jena/atlas/lib/Hex.java     |  97 ++
 .../java/org/apache/jena/atlas/lib/IRILib.java  | 243 +++++
 .../jena/atlas/lib/InternalErrorException.java  |  27 +
 .../java/org/apache/jena/atlas/lib/Lib.java     | 126 +++
 .../org/apache/jena/atlas/lib/ListUtils.java    | 150 +++
 .../java/org/apache/jena/atlas/lib/Map2.java    | 104 ++
 .../org/apache/jena/atlas/lib/MapUtils.java     |  45 +
 .../org/apache/jena/atlas/lib/MultiMap.java     | 146 +++
 .../apache/jena/atlas/lib/MultiMapToList.java   |  43 +
 .../apache/jena/atlas/lib/MultiMapToSet.java    |  47 +
 .../org/apache/jena/atlas/lib/MultiSet.java     | 218 +++++
 .../apache/jena/atlas/lib/NotImplemented.java   |  27 +
 .../org/apache/jena/atlas/lib/NumberUtils.java  | 133 +++
 .../java/org/apache/jena/atlas/lib/Pair.java    |  62 ++
 .../java/org/apache/jena/atlas/lib/Pool.java    |  30 +
 .../org/apache/jena/atlas/lib/PoolBase.java     |  57 ++
 .../org/apache/jena/atlas/lib/PoolSync.java     |  53 +
 .../java/org/apache/jena/atlas/lib/Problem.java |  25 +
 .../apache/jena/atlas/lib/PropertiesSorted.java |  87 ++
 .../apache/jena/atlas/lib/PropertyUtils.java    | 143 +++
 .../org/apache/jena/atlas/lib/RandomLib.java    |  41 +
 .../java/org/apache/jena/atlas/lib/Ref.java     |  54 ++
 .../java/org/apache/jena/atlas/lib/RefLong.java |  38 +
 .../org/apache/jena/atlas/lib/Registry.java     |  39 +
 .../jena/atlas/lib/ReverseComparator.java       |  38 +
 .../org/apache/jena/atlas/lib/SetUtils.java     |  84 ++
 .../java/org/apache/jena/atlas/lib/Sink.java    |  29 +
 .../org/apache/jena/atlas/lib/SinkCounting.java |  41 +
 .../org/apache/jena/atlas/lib/SinkLogging.java  |  41 +
 .../org/apache/jena/atlas/lib/SinkNull.java     |  33 +
 .../org/apache/jena/atlas/lib/SinkPrint.java    |  45 +
 .../org/apache/jena/atlas/lib/SinkSplit.java    |  55 ++
 .../apache/jena/atlas/lib/SinkToCollection.java |  38 +
 .../org/apache/jena/atlas/lib/SinkToQueue.java  |  52 +
 .../org/apache/jena/atlas/lib/SinkWrapper.java  |  44 +
 .../org/apache/jena/atlas/lib/StrUtils.java     | 292 ++++++
 .../java/org/apache/jena/atlas/lib/Sync.java    |  24 +
 .../org/apache/jena/atlas/lib/SystemUtils.java  |  57 ++
 .../java/org/apache/jena/atlas/lib/Timer.java   |  72 ++
 .../java/org/apache/jena/atlas/lib/Trie.java    | 407 ++++++++
 .../java/org/apache/jena/atlas/lib/Tuple.java   | 153 +++
 .../org/apache/jena/atlas/lib/TupleBuilder.java |  50 +
 .../java/org/apache/jena/atlas/lib/XMLLib.java  |  62 ++
 .../org/apache/jena/atlas/lib/cache/Cache0.java |  72 ++
 .../org/apache/jena/atlas/lib/cache/Cache1.java | 127 +++
 .../apache/jena/atlas/lib/cache/CacheGuava.java | 119 +++
 .../apache/jena/atlas/lib/cache/CacheOps.java   |  50 +
 .../jena/atlas/lib/cache/CacheSetImpl.java      |  93 ++
 .../jena/atlas/lib/cache/CacheSetSync.java      |  65 ++
 .../jena/atlas/lib/cache/CacheSetWrapper.java   |  65 ++
 .../jena/atlas/lib/cache/CacheSimple.java       | 161 +++
 .../jena/atlas/lib/cache/CacheWrapper.java      |  65 ++
 .../org/apache/jena/atlas/lib/cache/Getter.java |  25 +
 .../org/apache/jena/atlas/logging/FmtLog.java   | 162 ++++
 .../java/org/apache/jena/atlas/logging/Log.java | 116 +++
 .../org/apache/jena/atlas/logging/LogCtl.java   | 282 ++++++
 .../jena/atlas/logging/ProgressLogger.java      | 133 +++
 .../logging/java/ConsoleHandlerStdout.java      |  75 ++
 .../jena/atlas/logging/java/TextFormatter.java  |  53 +
 .../java/org/apache/jena/base/Closeable.java    |  27 +
 .../src/main/java/org/apache/jena/base/Sys.java |  27 +
 .../java/org/apache/jena/atlas/TC_Atlas.java    |  41 +
 .../atlas/io/AbstractTestPeekInputStream.java   | 243 +++++
 .../jena/atlas/io/AbstractTestPeekReader.java   | 243 +++++
 .../java/org/apache/jena/atlas/io/TS_IO.java    |  45 +
 .../org/apache/jena/atlas/io/TestBlockUTF8.java | 254 +++++
 .../jena/atlas/io/TestBufferingWriter.java      |  97 ++
 .../jena/atlas/io/TestIndentedWriter.java       |  58 ++
 .../jena/atlas/io/TestInputStreamBuffered.java  | 109 +++
 .../atlas/io/TestPeekInputStreamSource.java     |  44 +
 .../atlas/io/TestPeekReaderCharSequence.java    |  32 +
 .../jena/atlas/io/TestPeekReaderSource.java     |  34 +
 .../apache/jena/atlas/io/TestPrintUtils.java    |  59 ++
 .../apache/jena/atlas/io/TestStreamUTF8.java    | 118 +++
 .../apache/jena/atlas/iterator/TS_Iterator.java |  38 +
 .../apache/jena/atlas/iterator/TestIter.java    | 539 ++++++++++
 .../jena/atlas/iterator/TestIteratorArray.java  | 104 ++
 .../jena/atlas/iterator/TestIteratorPeek.java   | 107 ++
 .../atlas/iterator/TestIteratorPushback.java    |  93 ++
 .../atlas/iterator/TestIteratorSlotted.java     | 136 +++
 .../atlas/iterator/TestIteratorWithBuffer.java  |  97 ++
 .../atlas/iterator/TestIteratorWithHistory.java |  87 ++
 .../org/apache/jena/atlas/junit/BaseTest.java   |  45 +
 .../java/org/apache/jena/atlas/lib/TS_Lib.java  |  57 ++
 .../apache/jena/atlas/lib/TestAlarmClock.java   |  92 ++
 .../java/org/apache/jena/atlas/lib/TestAlg.java | 226 +++++
 .../org/apache/jena/atlas/lib/TestBitsInt.java  | 468 +++++++++
 .../org/apache/jena/atlas/lib/TestBitsLong.java | 468 +++++++++
 .../org/apache/jena/atlas/lib/TestBytes.java    | 198 ++++
 .../org/apache/jena/atlas/lib/TestCache.java    | 149 +++
 .../org/apache/jena/atlas/lib/TestCache2.java   |  88 ++
 .../apache/jena/atlas/lib/TestColumnMap.java    | 107 ++
 .../jena/atlas/lib/TestDateTimeUtils.java       |  82 ++
 .../org/apache/jena/atlas/lib/TestFileOps.java  |  78 ++
 .../jena/atlas/lib/TestFilenameProcessing.java  | 136 +++
 .../java/org/apache/jena/atlas/lib/TestHex.java |  81 ++
 .../apache/jena/atlas/lib/TestListUtils.java    |  79 ++
 .../org/apache/jena/atlas/lib/TestMultiSet.java | 143 +++
 .../apache/jena/atlas/lib/TestNumberUtils.java  | 106 ++
 .../org/apache/jena/atlas/lib/TestRefLong.java  |  70 ++
 .../jena/atlas/lib/TestReverseComparator.java   |  91 ++
 .../org/apache/jena/atlas/lib/TestSetUtils.java | 146 +++
 .../org/apache/jena/atlas/lib/TestStrUtils.java |  59 ++
 .../org/apache/jena/atlas/lib/TestTrie.java     | 346 +++++++
 .../org/apache/jena/atlas/lib/TestXMLLib.java   |  47 +
 187 files changed, 20541 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/pom.xml
----------------------------------------------------------------------
diff --git a/jena-base/pom.xml b/jena-base/pom.xml
index a8c05e5..0acf545 100644
--- a/jena-base/pom.xml
+++ b/jena-base/pom.xml
@@ -74,7 +74,7 @@
         <artifactId>maven-surefire-plugin</artifactId>
         <configuration>
           <includes>
-            <include>org/apache/jena/iri/test/TestPackage.java</include>
+            <include>**/TS_*.java</include>
           </includes>
         </configuration>
       </plugin>

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/AtlasException.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/AtlasException.java b/jena-base/src/main/java/org/apache/jena/atlas/AtlasException.java
new file mode 100644
index 0000000..2754060
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/AtlasException.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;
+
+public class AtlasException extends RuntimeException
+{
+    public AtlasException()                          { super() ; }
+    public AtlasException(String msg)                { super(msg) ; }
+    public AtlasException(Throwable th)              { super(th) ; }
+    public AtlasException(String msg, Throwable th)  { super(msg, th) ; }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/RuntimeIOException.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/RuntimeIOException.java b/jena-base/src/main/java/org/apache/jena/atlas/RuntimeIOException.java
new file mode 100644
index 0000000..1d9938b
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/RuntimeIOException.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.atlas;
+
+/** Runtime eception used to wrap IOExceptions */
+public class RuntimeIOException extends AtlasException
+{
+    public RuntimeIOException()                          { super() ; }
+    public RuntimeIOException(String msg)                { super(msg) ; }
+    /**
+     * Wrap a Throwable - this is usually an IOException.
+     */
+    public RuntimeIOException(Throwable th)              { super(th) ; }
+    /**
+     * Wrap a Throwable - this is usually an IOException.
+     */
+    public RuntimeIOException(String msg, Throwable th)  { super(msg, th) ; }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/io/AWriter.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/io/AWriter.java b/jena-base/src/main/java/org/apache/jena/atlas/io/AWriter.java
new file mode 100644
index 0000000..c046544
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/io/AWriter.java
@@ -0,0 +1,43 @@
+/**
+ * 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 org.apache.jena.atlas.lib.Closeable ;
+
+
+/** Simple abstraction of a string/character output stream */
+
+public interface AWriter extends Closeable, AutoCloseable 
+{
+    public void write(char ch) ;
+    public void write(char[] cbuf) ; 
+    public void write(String string) ;
+
+    public void print(char ch) ;
+    public void print(char[] cbuf) ; 
+    public void print(String string) ;
+    public void printf(String fmt, Object ...arg) ;
+    public void println(String object) ;
+    public void println() ;
+    
+    public void flush() ;
+    @Override
+    public void close() ;
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/io/AWriterBase.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/io/AWriterBase.java b/jena-base/src/main/java/org/apache/jena/atlas/io/AWriterBase.java
new file mode 100644
index 0000000..bc80fc2
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/io/AWriterBase.java
@@ -0,0 +1,33 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.atlas.io;
+
+
+/** Simple abstraction of a string/character output stream */
+
+public abstract class AWriterBase implements AWriter
+{
+    @Override
+    public final void write(char ch)          { print(ch) ; }
+    @Override
+    public final void write(char[] cbuf)      { print(cbuf) ; }
+    @Override
+    public final void write(String string)    { print(string) ; }
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/io/BlockUTF8.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/io/BlockUTF8.java b/jena-base/src/main/java/org/apache/jena/atlas/io/BlockUTF8.java
new file mode 100644
index 0000000..2d5560b
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/io/BlockUTF8.java
@@ -0,0 +1,281 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.atlas.io ;
+
+import java.io.IOException ;
+import java.nio.ByteBuffer ;
+import java.nio.CharBuffer ;
+
+import org.apache.jena.atlas.lib.NotImplemented ;
+
+/**
+ * Convert between bytes and chars, UTF-8 only.
+ * 
+ * This code is just the UTF-8 encoding rules - it does not check for legality
+ * of the Unicode data.  The standard codec do, so do not round-trip with binary
+ * compatibility. (Example: a single element of a surrogate pair will
+ * be encoded/decoded without lost.
+ *  
+ * 
+ * The usual Charset encoders/decoders can be expensive to start up - they are also
+ * not thread safe. Sometimes we want to convert 10's of chars and UTF-8 can be
+ * done in code with no lookup tables (which, if used, are cache-unfriendly).
+ * 
+ * This code is thread safe.  It uses code in the hope that JITting will
+ * make it fast if used heavily.
+ */
+
+public class BlockUTF8
+{
+    // Looking in java.lang.StringCoding (Sun RT) is illuminating.
+    // The actual encode/decode code is in sun.nio.cs.UTF_8.(Decoder|Encoder)
+    // which has special cases for ByteBuffer, ByteBuffer with array (needs offsets)
+    // and byte[] <-> char[]
+
+    // It seems that chars -> bytes (on <100char strings) is faster with BlockUTF8
+    // but the conversion from bytes to string is faster with Java decoders (not by much though). 
+
+    /*
+     * 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
+     */
+    
+    
+    /** Convert the bytes in the ByteBuffer to characters in the CharBuffer.
+     * The CharBuffer must be large enough. 
+     */
+    public static void toChars(ByteBuffer bb, CharBuffer cb)
+    {
+//        if ( bb.hasArray() && cb.hasArray() )
+//        {
+//            toCharsArray(bb.array(), cb.array()) ;
+//            return ;
+//        }
+        toCharsBuffer(bb, cb) ;
+    }
+
+    /** Convert characters to UTF-8 bytes in the ByteBuffer.
+     * The ByteBuffer must be large enough. 
+     */
+    public static void fromChars(CharBuffer cb, ByteBuffer bb)
+    {
+//        if ( bb.hasArray() && cb.hasArray() )
+//        {
+//            fromCharsArray(cb.array(), bb.array()) ;
+//            return ;
+//        }
+        fromCharsBuffer(cb, bb) ;
+    }
+
+    /** Make a string from UTF-8 bytes in a ByteBuffer */ 
+    public static String toString(ByteBuffer bb)
+    {
+        // I think that the copy from some mutable collector to immutable string is inevitable in java.  
+        int len = bb.remaining() ;
+        CharBuffer cb = CharBuffer.allocate(len) ;
+        toChars(bb, cb) ;
+        return new String(cb.array(), 0, cb.position()) ;
+    }
+
+    // Using buffer access.
+    private static void toCharsBuffer(ByteBuffer bb, CharBuffer cb)
+    {
+        int idx = bb.position();
+        int limit = bb.limit() ;
+
+        for ( ;  idx < limit ; )
+        {
+            int x = bb.get() ;
+            if ( x > 0 && x <= 127 )
+            {
+                cb.put((char)x) ;
+                idx += 1 ;
+            } else if ( x == 0 )
+            {
+                // Pass through a null byte as the null character (illegal Unicode, Java compatible).
+                cb.put((char)x) ;
+                idx += 1 ;
+            }
+            else if ( (x & 0xE0) == 0xC0 )
+            {
+                // 10 => extension byte
+                // 110..... => 2 bytes
+                // Unroll common path
+                //int ch = readMultiBytes(bb, x & 0x1F, 2) ;
+                int x2 = bb.get() ;
+                if ( (x2 & 0xC0) != 0x80 )
+                    exception("Illegal UTF-8 processing character: 0x%04X",x2) ;
+                // 6 bits of x2
+                int ch = ( (x&0x1F) << 6) | (x2 & 0x3F); 
+                cb.put((char)ch) ;
+                idx += 2  ;
+            } 
+            else if ( (x & 0xF0) == 0xE0 ) 
+            {
+                //  1110.... => 3 bytes : 16 bits : not outside 16bit chars 
+                int ch = readMultiBytes(bb, x & 0x0F, 3) ;
+                cb.put((char)ch) ;
+                idx += 3 ;
+            }
+            else if ( (x & 0xF8) == 0xF0 ) 
+            {
+                // Looking like 4 byte charcater.
+                // 11110zzz => 4 bytes.
+                int ch = readMultiBytes(bb, x & 0x08, 4) ;
+                char chars[] = Character.toChars(ch) ;
+                cb.put(chars) ;
+                idx += 4 ;
+            }
+            else 
+            {
+                exception("Illegal UTF-8: 0x%04X",x) ;
+                return ;
+            }
+        }
+    }
+
+    private static void toCharsArray(byte[] bytes, char[] chars)
+    {
+        throw new NotImplemented() ;
+    }
+    
+    private static void fromCharsBuffer(CharBuffer cb, ByteBuffer bb)
+    {
+        // CharBuffers are CharSequences but charAt(i) adds a layer of work.
+        //int bytesStart = bb.position() ;
+        int idx = cb.position() ;
+        int limit = cb.limit() ;
+        for ( ; idx < limit ; idx ++ )
+        {
+            char ch = cb.get() ;
+            if ( ch != 0 && ch <= 127 )
+            {
+                // 7 bits
+                bb.put((byte)ch) ;
+            } 
+            else if ( ch == 0 )
+            {
+                // Java.
+                bb.put((byte)0x00) ;
+                // Modified UTF-8.
+                //bb.put((byte)0xC0) ;
+                //bb.put((byte)0x80) ;
+            } 
+            else 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 ) ;
+                bb.put((byte)x1) ;
+                bb.put((byte)x2) ;
+            } 
+            else 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 ) ;
+                bb.put((byte)x1) ;
+                bb.put((byte)x2) ;
+                bb.put((byte)x3) ;
+            }
+            //            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.
+            else if ( ch <= 0x1FFFFF )
+            {
+                // 21 bits : 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+                int x1 = ( ((ch>>(21-3))&0x7) | 0xF0 ) ;
+                outputBytes(bb, x1, 4, ch) ;
+            } 
+            else if ( ch <= 0x3FFFFFF )
+            {
+                // 26 bits : 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+                int x1 = ( ((ch>>(26-2))&0x3) | 0xF8 ) ;
+                outputBytes(bb, x1, 5, ch) ;
+            }
+            else if ( ch <= 0x7FFFFFFF )
+            {
+                // 32 bits : 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+                int x1 = ( ((ch>>(32-1))&0x1) | 0xFC ) ;
+                outputBytes(bb, x1, 6, ch) ;
+            }
+        }
+        //int bytesFinish = bb.position() ;
+    }
+
+    private static void fromCharsArray(char[] array, byte[] array2)
+    {
+        throw new NotImplemented() ;
+    }
+    
+    public static void fromChars(CharSequence cs, ByteBuffer bb)
+    {
+        fromChars(CharBuffer.wrap(cs), bb) ;
+    }
+
+    private static int readMultiBytes(ByteBuffer input, int start, int len)
+    {
+        // We have already read one byte.
+        if ( input.remaining() < (len-1) )
+            exception("Premature end to UTF-8 sequence at end of input") ;
+        int x = start ;
+        for ( int i = 0 ; i < len-1 ; i++ )
+        {
+            int x2 = input.get() ;
+            if ( (x2 & 0xC0) != 0x80 )
+                exception("Illegal UTF-8 processing character: 0x%04X",x2) ;
+            // 6 bits of x2
+            x = (x << 6) | (x2 & 0x3F); 
+        }
+        return x ;
+    }
+
+    /** Put bytes to the output ByteBuffer for charcater ch.
+     * The first byte is in x1 and already has the needed bits set. 
+     */
+    private static void outputBytes(ByteBuffer bb, int x1, int byteLength, int ch)
+    {
+        bb.put((byte)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
+            bb.put((byte)x) ;
+        }
+    }
+    
+    // Does not return 
+    private static void exception(String fmt, Object ...args)
+    {
+        String str = String.format(fmt,args) ;
+        IO.exception(new IOException(str)) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/io/BufferingWriter.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..b17e930
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/io/BufferingWriter.java
@@ -0,0 +1,180 @@
+/*
+ * 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.io.IO ;
+
+/**
+ * A buffering writer. Like BufferedWriter but with no synchronization. A
+ * "synchronized" per character can be expensive.
+ * <p>
+ * The standard java.io classes have hidden synchronization so in some very
+ * critical situations, this can be expensive.
+ * </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
+    // Sizes for this instance
+    private final int        blockSize ;
+    private final int        blobSize ;
+
+    private char[]           buffer    = new char[SIZE] ;
+    private int              idx       = 0 ;
+    private Writer           out ;
+
+    /** Create a buffering writer */
+    public BufferingWriter(Writer dest) {
+        this(dest, SIZE, BLOB_SIZE) ;
+    }
+
+    /** Create a buffering writer */
+    public BufferingWriter(Writer dest, int size) {
+        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 ;
+    }
+
+    /**
+     * Output a string
+     * @param string Characters
+     */
+    public void output(String string) {
+        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) ;
+
+        // There is no space or too big
+        if ( largeBlob || (blockSize - idx) < length )
+            flushBuffer() ;
+        // If too big, do directly.
+        if ( largeBlob /* too big */) {
+            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 ;
+    }
+
+    /** Output an array of characters */
+    public void output(char chars[]) {
+        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) ;
+
+        // There is no space or too big
+        if ( largeBlob || (blockSize - idx) < length )
+            flushBuffer() ;
+        // If too big, do directly.
+        if ( largeBlob /* too big */) {
+            try { out.write(chars) ; }
+            catch (IOException ex) { IO.exception(ex) ; }
+            return ;
+        }
+        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 ;
+    }
+
+    private void flushBuffer() {
+        if ( 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) ;
+    }
+
+    @Override
+    public void flush() {
+        flushBuffer() ;
+        IO.flush(out) ;
+    }
+
+    @Override
+    public void write(char[] cbuf, int off, int len) throws IOException {
+        output(cbuf, off, len) ;
+    }
+
+    @Override
+    public void write(char[] cbuf) throws IOException {
+        write(cbuf, 0, cbuf.length) ;
+    }
+
+    @Override
+    public void write(String string, int off, int len) throws IOException {
+        output(string, off, len) ;
+    }
+
+    @Override
+    public void write(String string) throws IOException {
+        output(string, 0, string.length()) ;
+    }
+
+    @Override
+    public void write(int ch) throws IOException {
+        output((char)ch) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/io/CharStream.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/io/CharStream.java b/jena-base/src/main/java/org/apache/jena/atlas/io/CharStream.java
new file mode 100644
index 0000000..f066843
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/io/CharStream.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;
+
+
+/** A simplifed reader interface without IOExceptions. 
+ * And it's an interface, not an abstract class 
+ */
+public interface CharStream
+{
+    public int advance() ;
+    
+    /** Close the stream - different name from java.io.Reader.close */
+    public void closeStream() ;     
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/io/CharStreamBasic.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/io/CharStreamBasic.java b/jena-base/src/main/java/org/apache/jena/atlas/io/CharStreamBasic.java
new file mode 100644
index 0000000..27416d9
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/io/CharStreamBasic.java
@@ -0,0 +1,58 @@
+/*
+ * 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.Reader ;
+
+/** A PeekReaderSource that does no buffering - just wraps a reader. */
+public final class CharStreamBasic extends CharStreamReader
+{
+    private Reader reader ;
+
+    CharStreamBasic(Reader reader)
+    {
+        this.reader = reader ;
+    }
+    
+    @Override
+    public int advance()
+    {
+        try
+        {
+            return reader.read() ;
+        } catch (IOException ex)
+        {
+            ex.printStackTrace();
+            return -1 ;
+        }
+    }
+
+    @Override
+    public void closeStream()
+    {
+        try
+        {
+            reader.close() ;
+        } catch (IOException ex)
+        {
+            ex.printStackTrace();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/io/CharStreamBuffered.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/io/CharStreamBuffered.java b/jena-base/src/main/java/org/apache/jena/atlas/io/CharStreamBuffered.java
new file mode 100644
index 0000000..519dc36
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/io/CharStreamBuffered.java
@@ -0,0 +1,166 @@
+/*
+ * 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.Reader ;
+
+/** Buffering reader without the (hidden) sync overhead in BufferedReader
+ * 
+ * @see java.io.BufferedReader
+ */ 
+
+
+public final class CharStreamBuffered extends CharStreamReader
+{
+    /*package*/ static final int CB_SIZE       = 128 * 1024 ;
+    
+    private final char[] chars ;            // CharBuffer?
+    private int buffLen = 0 ;
+    private int idx = 0 ;
+
+    private final Source source;
+    
+    public CharStreamBuffered(Reader r)
+    { this(r, CB_SIZE) ; }
+
+    /**
+     * @param r
+     * @param buffSize
+     */
+    public CharStreamBuffered(Reader r, int buffSize)
+    {
+        super() ;
+        source = new SourceReader(r) ;
+        chars = new char[buffSize] ;
+    }
+
+    // Local adapter/encapsulation
+    private interface Source
+    { 
+        int fill(char[] array) ;
+        void close() ; 
+    }
+    
+    static final class SourceReader implements Source
+    {
+        final Reader reader ;
+        SourceReader(Reader r) { reader = r ; }
+        
+        @Override
+        public void close()
+        { 
+            try { reader.close() ; } catch (IOException ex) { IO.exception(ex) ; } 
+        }
+        
+        @Override
+        public int fill(char[] array)
+        {
+            try { return reader.read(array) ; } catch (IOException ex) { IO.exception(ex) ; return -1 ; }
+        }
+    }
+    
+//    /** Faster?? for ASCII */
+//    static final class SourceASCII implements Source
+//    {
+//        final InputStream input ;
+//        SourceASCII(InputStream r) { input = r ; }
+//        public void close()
+//        { try { input.close() ; } catch (IOException ex) { exception(ex) ; } } 
+//        
+//        public final int fill(char[] array)
+//        {
+//            try {
+//                // Recycle.
+//                byte[] buff = new byte[array.length] ;
+//                int len = input.read(buff) ;
+//                for ( int i = 0 ; i < len ; i++ )
+//                {
+//                    byte b = buff[i] ;
+//                    if ( b < 0 )
+//                        throw new AtlasException("Illegal ASCII charcater: "+b) ;
+//                   array[i] = (char)b ;
+//                }
+//                return len ;
+//            } catch (IOException ex) { exception(ex) ; return -1 ; }
+//        }
+//    }
+//    
+//    static final class SourceChannel implements Source
+//    {
+//        final ReadableByteChannel channel ;
+//        CharsetDecoder decoder = Chars.createDecoder() ;
+//        SourceChannel(ReadableByteChannel r) { channel = r ; }
+//        
+//        @Override
+//        public void close()
+//        { 
+//            try { channel.close() ; } catch (IOException ex) { exception(ex) ; } 
+//        }
+//        
+//        @Override
+//        public int fill(char[] array)
+//        {
+//            // Encoding foo.
+////             Bytes
+////             
+////            ByteBuffer b = ByteBuffer.wrap(null) ;
+////            
+////            try { return channel.read(null).read(array) ; } catch (IOException ex) { exception(ex) ; return -1 ; }
+//            return -1 ;
+//        }
+//    }
+
+    @Override
+    public final int advance()
+    {
+        if ( idx >= buffLen )
+            // Points outside the array.  Refill it 
+            fillArray() ;
+        
+        // Advance one character.
+        if ( buffLen >= 0 )
+        {
+            char ch = chars[idx] ;
+            // Advance the lookahead character
+            idx++ ;
+            return ch ;
+        }  
+        else
+            // Buffer empty, end of stream.
+            return EOF ;
+    }
+
+    private int fillArray()
+    {
+        int x = source.fill(chars) ;
+        idx = 0 ;
+        buffLen = x ;   // Maybe -1
+        return x ;
+    }
+
+    
+    @Override
+    public void closeStream()
+    {
+        source.close() ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/io/CharStreamReader.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/io/CharStreamReader.java b/jena-base/src/main/java/org/apache/jena/atlas/io/CharStreamReader.java
new file mode 100644
index 0000000..d7dce11
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/io/CharStreamReader.java
@@ -0,0 +1,56 @@
+/*
+ * 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.Reader ;
+
+/** Machinary to add Reader functionality to a CharStream */
+public abstract class CharStreamReader extends Reader implements CharStream
+{
+    @Override
+    public int read(char[] cbuf, int off, int len) throws IOException
+    {
+        for ( int i = 0 ; i < len ; i++ )
+        {
+            int x = advance() ;
+            if ( x == -1 )
+                return (i==0)? -1 : i ;
+            cbuf[i] = (char)x ;
+        }
+        return len ;
+    }
+
+    @Override
+    public int read() throws IOException
+    {
+        return advance() ;
+    }
+
+    
+    @Override
+    public void close() throws IOException
+    { closeStream() ; }
+
+    @Override
+    public abstract int advance() ;
+
+    @Override
+    public abstract void closeStream() ;
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/io/CharStreamSequence.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/io/CharStreamSequence.java b/jena-base/src/main/java/org/apache/jena/atlas/io/CharStreamSequence.java
new file mode 100644
index 0000000..c0e9f40
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/io/CharStreamSequence.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.atlas.io;
+import static org.apache.jena.atlas.io.IO.EOF ;
+
+public final class CharStreamSequence implements CharStream
+{
+    private CharSequence string ;
+    // Next character.
+    private int idx ;
+
+    public CharStreamSequence(CharSequence string)
+    {
+        this.string = string ;
+        this.idx = 0 ;
+    }
+
+    @Override
+    public int advance()
+    {
+        if ( idx >= string.length() )
+            return EOF ;
+        return string.charAt(idx++);
+    }
+
+    @Override
+    public void closeStream()
+    {
+        string = null ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/io/IO.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/io/IO.java b/jena-base/src/main/java/org/apache/jena/atlas/io/IO.java
new file mode 100644
index 0000000..ac312e8
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/io/IO.java
@@ -0,0 +1,325 @@
+/*
+ * 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.* ;
+import java.nio.charset.Charset ;
+import java.nio.charset.StandardCharsets ;
+import java.util.zip.GZIPInputStream ;
+import java.util.zip.GZIPOutputStream ;
+
+import org.apache.jena.atlas.RuntimeIOException ;
+import org.apache.jena.atlas.lib.IRILib ;
+
+public class IO
+{
+    public static final int EOF = -1 ;
+    public static final int UNSET = -2 ;
+       
+    private static Charset utf8  = StandardCharsets.UTF_8 ;
+    private static Charset ascii = StandardCharsets.US_ASCII ;
+    
+    /** Open an input stream to a file. 
+     * If the filename is null or "-", return System.in
+     * If the filename ends in .gz, wrap in  GZIPInputStream  
+     */
+    static public InputStream openFile(String filename) {
+        try {
+           return openFileEx(filename) ;
+        }
+        catch (Exception ex) { throw new RuntimeIOException(ex) ; }
+    }
+    
+    /** Open an input stream to a file; do not mask IOExceptions. 
+     * If the filename is null or "-", return System.in
+     * If the filename ends in .gz, wrap in  GZIPInputStream  
+     * @param filename
+     * @throws FileNotFoundException 
+     * @throws IOException
+     */
+    static public InputStream openFileEx(String filename) throws IOException, FileNotFoundException {
+        if ( filename == null || filename.equals("-") )
+            return System.in ;
+        if ( filename.startsWith("file:") )
+        {
+            filename = filename.substring("file:".length()) ;
+            filename = IRILib.decode(filename) ;
+        }
+        InputStream in = new FileInputStream(filename) ;
+        if ( filename.endsWith(".gz") )
+            in = new GZIPInputStream(in) ;
+        return in ;
+    }
+    
+    /** Open a UTF8 Reader for a file. 
+     * If the filename is null or "-", use System.in
+     * If the filename ends in .gz, use GZIPInputStream  
+     */
+    static public Reader openFileUTF8(String filename)  { return openFileReader(filename, utf8) ; }
+
+    /** Open an ASCII Reader for a file. 
+     * If the filename is null or "-", use System.in
+     * If the filename ends in .gz, use GZIPInputStream  
+     */
+    static public Reader openFileASCII(String filename)  { return openFileReader(filename, ascii) ; }
+
+    private static Reader openFileReader(String filename, Charset charset)
+    {
+        InputStream in = openFile(filename) ;
+        return new InputStreamReader(in, charset) ;
+    }
+
+    /** Create an unbuffered reader that uses UTF-8 encoding */ 
+    static public Reader asUTF8(InputStream in)
+    {
+        return new InputStreamReader(in, utf8.newDecoder());
+    }
+    
+    /** Create a unbuffered reader that uses ASCII encoding */ 
+    static public Reader asASCII(InputStream in)
+    {
+        return new InputStreamReader(in, ascii.newDecoder());
+    }
+    
+    /** Create an buffered reader that uses UTF-8 encoding */ 
+    static public BufferedReader asBufferedUTF8(InputStream in) {
+        return new BufferedReader(asUTF8(in)) ;
+    }
+
+    /** Create a writer that uses UTF-8 encoding */ 
+    static public Writer asUTF8(OutputStream out) {
+        return new OutputStreamWriter(out, utf8.newEncoder());
+    }
+
+    /** Create a writer that uses ASCII encoding */ 
+    static public Writer asASCII(OutputStream out) {
+        return new OutputStreamWriter(out, ascii.newEncoder());
+    }
+
+    /** Create a writer that uses UTF-8 encoding and is buffered. */ 
+    static public Writer asBufferedUTF8(OutputStream out) {
+        Writer w =  new OutputStreamWriter(out, utf8.newEncoder());
+        return new BufferingWriter(w) ;
+    }
+
+    /** Open a file for output - may include adding gzip processing. */
+    static public OutputStream openOutputFile(String filename)
+    {
+        try {
+           return openOutputFileEx(filename) ;
+        }
+        catch (IOException ex) { IO.exception(ex) ; return null ; }
+    }
+    
+    /** Open an input stream to a file; do not mask IOExceptions. 
+     * If the filename ends in .gz, wrap in GZIPOutputStream  
+     * @param filename
+     * @throws FileNotFoundException If the output can't be opened.
+     * @throws IOException for bad gzip encoded data
+     */
+    static public OutputStream openOutputFileEx(String filename) throws FileNotFoundException,IOException
+    {
+        if ( filename == null || filename.equals("-") )
+            return System.out ;
+        if ( filename.startsWith("file:") )
+        {
+            filename = filename.substring("file:".length()) ;
+            filename = IRILib.decode(filename) ;
+        }
+        OutputStream out = new FileOutputStream(filename) ;
+        if ( filename.endsWith(".gz") )
+            out = new GZIPOutputStream(out) ;
+        return out ;
+    }
+    
+    /** Wrap in a general writer interface */ 
+    static public AWriter wrap(Writer w) { 
+        return Writer2.wrap(w) ;
+    }
+    
+    /** Wrap in a general writer interface */ 
+    static public AWriter wrapUTF8(OutputStream out)        { return wrap(asUTF8(out)) ; } 
+    
+    /** Wrap in a general writer interface */ 
+    static public AWriter wrapASCII(OutputStream out)       { return wrap(asASCII(out)) ; } 
+
+    /** Create a print writer that uses UTF-8 encoding */ 
+    static public PrintWriter asPrintWriterUTF8(OutputStream out) {
+        return new PrintWriter(asUTF8(out)); 
+    }
+
+    public static void close(org.apache.jena.atlas.lib.Closeable resource) {
+        resource.close() ;
+    }
+
+    public static void closeSilent(org.apache.jena.atlas.lib.Closeable resource) {
+        try { resource.close(); } catch (Exception ex) { }
+    }
+    
+    public static void close(java.io.Closeable resource) {
+        if ( resource == null )
+            return ;
+        try { resource.close(); } catch (IOException ex) { exception(ex) ; }
+    }
+    
+    public static void closeSilent(java.io.Closeable resource) {
+        if ( resource == null )
+            return ;
+        try { resource.close(); } catch (IOException ex) { }
+    }
+    
+    public static void close(AWriter resource) {
+        if ( resource == null )
+            return ;
+        resource.close();
+    }
+    
+    public static void closeSilent(AWriter resource) {
+        if ( resource == null )
+            return ;
+        try { resource.close();  } catch (Exception ex) { }
+    }
+
+    public static void close(IndentedWriter resource) {
+        if ( resource == null )
+            return ;
+        resource.close();
+    }
+    
+    public static void closeSilent(IndentedWriter resource) {
+        if ( resource == null )
+            return ;
+        try { resource.close();  } catch (Exception ex) { }
+    }
+
+    public static void exception(IOException ex) {
+        throw new RuntimeIOException(ex) ;
+    }
+
+    public static void exception(String msg, IOException ex) {
+        throw new RuntimeIOException(msg, ex) ;
+    }
+    
+    public static void flush(OutputStream out) { 
+        if ( out == null )
+            return ;
+        try { out.flush(); } catch (IOException ex) { exception(ex) ; }
+    }
+    
+    public static void flush(Writer out) {
+        if ( out == null )
+            return ;
+        try { out.flush(); } catch (IOException ex) { exception(ex) ; } 
+    }
+
+    public static void flush(AWriter out) {
+        if ( out == null )
+            return ;
+        out.flush(); 
+    }
+
+    private static final int BUFFER_SIZE = 32*1024 ; 
+    
+    public static byte[] readWholeFile(InputStream in) {
+        try(ByteArrayOutputStream out = new ByteArrayOutputStream(BUFFER_SIZE)) {
+            byte buff[] = new byte[BUFFER_SIZE] ;
+            while (true) {
+                int l = in.read(buff) ;
+                if ( l <= 0 )
+                    break ;
+                out.write(buff, 0, l) ;
+            }
+            return out.toByteArray() ;
+        }
+        catch (IOException ex) {
+            exception(ex) ;
+            return null ;
+        }
+    }
+    
+    /** Read a whole file as UTF-8
+     * @param filename
+     * @return String
+     * @throws IOException
+     */
+    
+    public static String readWholeFileAsUTF8(String filename) throws IOException {
+        try ( InputStream in = new FileInputStream(filename) ) {
+            return readWholeFileAsUTF8(in) ;
+        }
+    }
+
+    /** Read a whole stream as UTF-8
+     * 
+     * @param in    InputStream to be read
+     * @return      String
+     * @throws IOException
+     */
+    public static String readWholeFileAsUTF8(InputStream in) throws IOException {
+        // Don't buffer - we're going to read in large chunks anyway
+        try ( Reader r = asUTF8(in) ) {
+            return readWholeFileAsUTF8(r) ;
+        }
+    }
+    
+    /** Read a whole file as UTF-8
+     * 
+     * @param r
+     * @return String The whole file
+     * @throws IOException
+     */
+    
+    // Private worker as we are trying to force UTF-8. 
+    private static String readWholeFileAsUTF8(Reader r) throws IOException {
+        try(StringWriter sw = new StringWriter(BUFFER_SIZE)) {
+            char buff[] = new char[BUFFER_SIZE];
+            for (;;)
+            {
+                int l = r.read(buff);
+                if (l < 0)
+                    break;
+                sw.write(buff, 0, l);
+            }
+            return sw.toString();
+        }
+    }
+
+    public static String uniqueFilename(String directory, String base, String ext) {
+        File d = new File(directory) ;
+        if ( !d.exists() )
+            throw new IllegalArgumentException("Not found: " + directory) ;
+        try {
+            String fn0 = d.getCanonicalPath() + File.separator + base ;
+            String fn = fn0 ;
+            int x = 1 ;
+            while (true) {
+                if ( ext != null )
+                    fn = fn + "."+ext ;
+                File f = new File(fn) ;
+                if ( ! f.exists() )
+                    return fn ;
+                fn = fn0 + "-" + (x++) ;
+            }
+        } catch (IOException e) {
+            IO.exception(e) ;
+            return null ;
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/io/InStreamASCII.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/io/InStreamASCII.java b/jena-base/src/main/java/org/apache/jena/atlas/io/InStreamASCII.java
new file mode 100644
index 0000000..049449c
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/io/InStreamASCII.java
@@ -0,0 +1,89 @@
+/*
+ * 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.InputStream ;
+import java.io.Reader ;
+
+import org.apache.jena.atlas.AtlasException ;
+
+/** Fast and streaming.
+ */
+public final class InStreamASCII extends Reader implements CharStream
+{
+    private InputStreamBuffered input ;
+    private long count = 0 ;
+
+    public InStreamASCII(InputStream in)
+    {
+        if ( in instanceof InputStreamBuffered )
+        {
+            input = (InputStreamBuffered)in ;
+            return ;
+        }
+        input = new InputStreamBuffered(in) ;
+    }
+    
+    public InStreamASCII(InputStreamBuffered in) { input = in ; }
+
+    @Override
+    public boolean ready() throws IOException
+    {
+        return input.available() > 0 ;
+    }
+    
+    @Override
+    public void close() throws IOException
+    { input.close() ; }
+
+    @Override
+    public void closeStream()
+    { IO.close(input) ; }
+
+    @Override
+    public int read(char[] cbuf, int off, int len) throws IOException
+    {
+        for ( int i = off ; i < off+len ; i++ )
+        {
+            int x = read() ;
+            if ( x == -1 )
+            {
+                if ( i == off )
+                    return -1 ;
+                return (i-off) ;
+            }
+            if ( x > 128 )
+                throw new AtlasException("Illegal ASCII character : "+x) ;
+            cbuf[i] = (char)x ;
+        }
+        return len ; 
+    }
+
+    @Override
+    public int read() throws IOException
+    { return advance() ; }
+    
+    @Override
+    public int advance()
+    {
+        count++ ;
+        return input.advance() ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/io/InStreamUTF8.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/io/InStreamUTF8.java b/jena-base/src/main/java/org/apache/jena/atlas/io/InStreamUTF8.java
new file mode 100644
index 0000000..5903882
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/io/InStreamUTF8.java
@@ -0,0 +1,254 @@
+/*
+ * 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.ByteArrayInputStream ;
+import java.io.IOException ;
+import java.io.InputStream ;
+import java.io.Reader ;
+
+import org.apache.jena.atlas.AtlasException ;
+
+/** Fast and streaming UTF-8 */
+public final class InStreamUTF8 extends Reader implements CharStream
+{
+    // TODO Add line and col counts.
+    // See arq.utf8. 
+    // TODO Better ready()/available() in InputStreamBuffered
+    
+    // The standard Java way of doing this is via charset decoders.
+    // One small disadvantage is that bad UTF-8 does not get flagged as to
+    // the byte position of the error.
+    
+    // This class collects knowledge of how UTF-8 encoding works;
+    // the Java classes are usually slightly faster compared to using
+    // this class with an InputStreamBuffered but the difference is small.
+    // This class generated meaningful error messages (when line/col added).
+    
+    // The Java classes copy-convert a byte buffer into a char buffer.
+    // Sometimes, for example in a parser, this isn't a convenient model
+    // because the app is looking one character at a time and accumulating
+    // the chars until it sees the end of a token of arbitrary length
+    // or processes escape sequences.  
+    //
+    // The app might use a StringBuilder so the bytes get copied into
+    // a char buffer and out again.  Instead, this code assumes the
+    // app is in charge of that.
+    
+    // UTF-8 (UTF-16) is different from other character sets because 
+    // the relationship with Java's internal character representation is
+    // arithmetic, not a character mapping. 
+    
+    // Todo: chars > 16 bits -> surrogate pairs. 
+    
+    /*
+     * http://en.wikipedia.org/wiki/UTF-8
+     * http://tools.ietf.org/html/rfc3629
+     * http://www.ietf.org/rfc/rfc3629.txt
+     * 
+     * Unicode                                  Byte1       Byte2       Byte3       Byte4
+     * U+0000–U+007F    0 to 127                0xxxxxxx
+     * U+0080–U+07FF    128 to 2,047            110yyyxx    10xxxxxx 
+     * U+0800–U+FFFF    2,048 to 65,535         1110yyyy    10yyyyxx    10xxxxxx
+     * U+10000–U+10FFFF 65,536 to 1,114,111     11110zzz    10zzyyyy    10yyyyxx    10xxxxxx
+     * 
+     * Restricted cases (RFC 3629)
+     * 11110101-11110111    F5-F7   245-247     start of 4-byte sequence for codepoint above 10FFFF
+     * 11111000-11111011    F8-FB   248-251     start of 5-byte sequence
+     * 11111100-11111101    FC-FD   252-253     start of 6-byte sequence
+     * 
+     * Illegal:
+     * 11000000-11000001    C0-C1   192-193     Overlong encoding: start of a 2-byte sequence, but code point <= 127
+     * 11111110-11111111    FE-FF   254-255     Invalid: not defined by original UTF-8 specification
+     */
+    
+    // There is some sort of stream decoder backing the Sun implementation 
+    // of CharsetDecoder (sun.io.StreamDecoder) but it's not on all platforms
+    // I want a known decoder specifically for UTF8
+    
+    private InputStreamBuffered input ;
+    //private long count = 0 ;
+
+    public InStreamUTF8(InputStream in)
+    {
+        if ( in instanceof InputStreamBuffered )
+        {
+            input = (InputStreamBuffered)in ;
+            return ;
+        }
+        input = new InputStreamBuffered(in) ;
+    }
+    
+    public InStreamUTF8(InputStreamBuffered in) { input = in ; }
+    
+
+    @Override
+    public boolean ready() throws IOException
+    {
+        return input.available() > 0 ;
+    }
+    
+    @Override
+    public void close() throws IOException
+    { input.close() ; }
+    
+    @Override
+    public void closeStream()
+    { IO.close(input) ; }
+
+    @Override
+    public int read(char[] cbuf, int off, int len) throws IOException
+    {
+        // Doing this on a block of bytes may be faster.
+        for ( int i = off ; i < off+len ; i++ )
+        {
+            int x = read() ;
+            if ( x == -1 )
+            {
+                if ( i == off )
+                    return -1 ;
+                return (i-off) ;
+            }
+            cbuf[i] = (char)x ;
+        }
+        return len ; 
+    }
+
+    @Override
+    public final int read() throws IOException
+    { 
+        int ch = advance(input) ;
+        //if ( ! Character.isDefined(ch) ) throw new AtlasException(String.format("Undefined codepoint: 0x%04X", ch)) ;
+        return ch ;
+    }
+    
+    
+    /** Next codepoint, given the first byte of any UTF-8 byte sequence is already known.
+     *  Not necessarily a valid char (this function can be used a straight UTF8 decoder
+     */
+    @Override
+    public final int advance()
+    { return advance(input) ; }
+    
+    /** Next codepoint */
+    public static final int advance(InputStreamBuffered input)
+    {
+        int x = input.advance() ;
+        if ( x == -1 ) return -1 ;
+        return advance(input, x) ;
+    }
+    
+    /** Next codepoint, given the first byte of any UTF-8 byte sequence is already known.
+     * Not necessarily a valid char (this function can be used a straight UTF8 decoder
+     */
+    
+    public static final int advance(InputStreamBuffered input, int x)
+    {
+        //count++ ;
+        // Fastpath
+        if ( x == -1 || x <= 127 ) 
+        {
+            //count++ ;
+            return x ;
+        }
+
+        // 10 => extension byte
+        // 110..... => 2 bytes
+        if ( (x & 0xE0) == 0xC0 )
+        {
+            int ch = readMultiBytes(input, x & 0x1F, 2) ;
+            // count += 2 ;
+            return ch ;
+            
+        }
+        //  1110.... => 3 bytes : 16 bits : not outside 16bit chars 
+        if ( (x & 0xF0) == 0xE0 ) 
+        {
+            int ch = readMultiBytes(input, x & 0x0F, 3) ;
+            // count += 3 ;
+            //if ( ! Character.isDefined(ch) ) throw new AtlasException(String.format("Undefined codepoint: 0x%04X", ch)) ;
+            return ch ;
+        }
+
+        // Looking like 4 byte charcater.
+        int ch = -2 ;
+        // 11110zzz => 4 bytes.
+        if ( (x & 0xF8) == 0xF0 )
+        {
+             ch = readMultiBytes(input, x & 0x08, 4) ;
+             // Opsp - need two returns. Character.toChars(ch, chars, 0) ;
+             // count += 4 ;
+        }
+             
+        else 
+            IO.exception(new IOException("Illegal UTF-8: "+x)) ;
+
+        // This test will go off.  We're processing a 4 byte sequence but Java only supports 16 bit chars. 
+        if ( ch > Character.MAX_VALUE )
+            throw new AtlasException("Out of range character (must use a surrogate pair)") ;
+        if ( ! Character.isDefined(ch) ) throw new AtlasException(String.format("Undefined codepoint: 0x%04X", ch)) ;
+        return ch ;
+    }
+    
+    private static int readMultiBytes(InputStreamBuffered input, int start, int len) //throws IOException
+    {
+        //System.out.print(" -("+len+")") ; p(start) ;
+        
+        int x = start ;
+        for ( int i = 0 ; i < len-1 ; i++ )
+        {
+            int x2 = input.advance() ;
+            if ( x2 == -1 )
+                throw new AtlasException("Premature end to UTF-8 sequence at end of input") ;
+            
+            if ( (x2 & 0xC0) != 0x80 )
+                //throw new AtlasException("Illegal UTF-8 processing character "+count+": "+x2) ;
+                throw new AtlasException(String.format("Illegal UTF-8 processing character: 0x%04X",x2)) ;
+            // 6 bits of x2
+            x = (x << 6) | (x2 & 0x3F); 
+        }
+        return x ;
+    }
+
+    private static void p(int ch)
+    {
+        System.out.printf(" %02X", ch) ;
+        if ( ch == -1 )
+            System.out.println();
+    }
+    
+    public static String decode(byte[] bytes)
+    {
+        try
+        {
+            char[] chars = new char[bytes.length] ;
+            InputStream in = new ByteArrayInputStream(bytes) ;
+            StringBuilder buff = new StringBuilder() ;
+            Reader r = new InStreamUTF8(in) ;
+            int len ;
+            len = r.read(chars) ;
+            IO.close(r) ;
+            return new String(chars, 0, len) ;
+        } catch (IOException ex)
+        {
+            IO.exception(ex) ;
+            return null ;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/io/IndentedLineBuffer.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/io/IndentedLineBuffer.java b/jena-base/src/main/java/org/apache/jena/atlas/io/IndentedLineBuffer.java
new file mode 100644
index 0000000..7c7b5f5
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/io/IndentedLineBuffer.java
@@ -0,0 +1,50 @@
+/*
+ * 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 ;
+
+/** IndentLineBuffer is a buffer that records an indent level 
+ *  and uses that to insert a prefix at each line.
+ *  It can also insert line numbers at the beginning of lines.
+ */
+
+public class IndentedLineBuffer extends IndentedWriter
+{
+    StringWriter sw ;
+    public IndentedLineBuffer() { this(false) ; }
+    
+    public IndentedLineBuffer(boolean withLineNumbers)
+    {
+        super(new StringWriter(), withLineNumbers) ;
+        sw = (StringWriter)super.out ;
+    }
+    
+    public StringBuffer getBuffer() { return sw.getBuffer(); }
+    
+    public String asString() { return sw.toString() ; }
+    @Override
+    public String toString() { return asString() ; }
+
+    // Names more usually used for a buffer.
+    public void append(String fmt, Object... args) { printf(fmt, args) ; }
+    public void append(char ch)  { print(ch) ;}
+    
+    public void clear() { sw.getBuffer().setLength(0) ; }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/io/IndentedWriter.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/io/IndentedWriter.java b/jena-base/src/main/java/org/apache/jena/atlas/io/IndentedWriter.java
new file mode 100644
index 0000000..51414f9
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/io/IndentedWriter.java
@@ -0,0 +1,366 @@
+/*
+ * 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 java.lang.String.format ;
+
+import java.io.IOException ;
+import java.io.OutputStream ;
+import java.io.Writer ;
+
+import org.apache.jena.atlas.RuntimeIOException ;
+import org.apache.jena.atlas.lib.Closeable ;
+
+/** A writer that records what the current indentation level is, and
+ *  uses that to insert a prefix at each line. 
+ *  It can also insert line numbers at the beginning of lines. */
+
+public class IndentedWriter extends AWriterBase implements AWriter, Closeable
+{
+    /** Stdout wrapped in an IndentedWriter - no line numbers */
+    public static final IndentedWriter stdout = new IndentedWriter(System.out) ;
+    /** Stderr wrapped in an IndentedWriter - no line numbers */
+    public static final IndentedWriter stderr = new IndentedWriter(System.err) ;
+    
+    static {
+        stdout.setFlushOnNewline(true) ;
+        stderr.setFlushOnNewline(true) ;
+    }
+    // Note cases:if (!flatMode) 
+    // 1/ incIndent - decIndent with no output should not cause any padding
+    // 2/ newline() then no text, then finish should not cause a line number.
+    
+    protected Writer out = null ;
+    
+    protected static final int INDENT = 2 ;
+    protected int unitIndent = INDENT ;
+    protected int currentIndent = 0 ;
+    protected int column = 0 ;
+    protected int row = 1 ;
+    protected boolean lineNumbers = false ;
+    protected boolean startingNewLine = true ;
+    private char padChar = ' ' ;
+    private String endOfLineMarker = null ;     // Null mean none.
+    private String padString = null ;
+    
+    protected boolean flatMode = false ;
+    private boolean flushOnNewline = false ;
+    
+    private IndentedWriter() { this(System.out, false) ; }
+    
+    /** Construct a UTF8 IndentedWriter around an OutputStream */
+    public IndentedWriter(OutputStream outStream) { this(outStream, false) ; }
+    
+    /** Construct a UTF8 IndentedWriter around an OutputStream */
+    public IndentedWriter(OutputStream outStream, boolean withLineNumbers)
+    {
+        this(makeWriter(outStream), withLineNumbers) ;
+    }
+    
+    private static Writer makeWriter(OutputStream out)
+    {
+        // return BufferingWriter.create(out) ;
+        return IO.asBufferedUTF8(out) ;
+    }
+    
+    /** Using Writers directly is discouraged */
+    protected IndentedWriter(Writer writer) { this(writer, false) ; }
+    
+    /** Using Writers directly is discouraged */
+    protected IndentedWriter(Writer writer, boolean withLineNumbers)
+    {
+        out = writer ;
+        lineNumbers = withLineNumbers ;
+        startingNewLine = true ;
+    }
+
+    @Override
+    public void print(String str) 
+    {
+        if ( str == null )
+            str = "null" ;
+        if ( false )
+        {
+            // Don't check for embedded newlines.
+            write$(str) ;
+            return ;
+        }
+        for ( int i = 0 ; i < str.length() ; i++ )
+            printOneChar(str.charAt(i)) ;
+    }
+    
+    @Override
+    public void printf(String formatStr, Object... args)
+    {
+        print(format(formatStr, args)) ;
+    }
+    
+    @Override
+    public void print(char ch) { printOneChar(ch) ; }
+    
+    @Override
+    public void println(String str) { print(str) ; newline() ; }
+    public void println(char ch)  { print(ch) ; newline() ; }
+
+    @Override
+    public void println() { newline() ; }
+    
+    @Override
+    public void print(char[] cbuf)
+    {
+        for ( char aCbuf : cbuf )
+        {
+            printOneChar( aCbuf );
+        }
+    }
+
+    /** Print a string N times */
+    public void print(String s, int n)
+    {
+        for ( int i = 0 ; i < n ; i++ ) print(s) ;
+    }
+
+    /** Print a char N times */
+    public void print(char ch, int n)
+    {
+        lineStart() ;
+        for ( int i = 0 ; i < n ; i++ ) printOneChar(ch) ;
+    }
+
+    private char lastChar = '\0' ;
+    // Worker
+    private void printOneChar(char ch) 
+    {
+        // Turn \r\n into a single newline call.
+        // Assumes we don't get \r\r\n etc 
+        if ( ch == '\n' && lastChar == '\r' )
+        {
+            lastChar = ch ;
+            return ;
+        }
+        
+        lineStart() ; 
+        lastChar = ch ;
+        
+        // newline
+        if ( ch == '\n' || ch == '\r' )
+        { 
+            newline() ;
+            return ;
+        }
+        write$(ch) ;
+        column += 1 ;
+    }
+
+    private void write$(char ch) 
+    { try { out.write(ch) ; } catch (IOException ex) { throw new RuntimeIOException(ex) ; } }
+    
+    private void write$(String s) 
+    { try { out.write(s) ; } catch (IOException ex) { throw new RuntimeIOException(ex) ; } }
+    
+    public void newline()
+    {
+        lineStart() ; 
+
+        if ( endOfLineMarker != null )
+            print(endOfLineMarker) ;
+        if ( ! flatMode )
+            write$('\n') ;
+        startingNewLine = true ;
+        row++ ;
+        column = 0 ;
+        // Note that PrintWriters do not autoflush by default
+        // so if layered over a PrintWirter, need to flush that as well.  
+        if (flushOnNewline) flush() ;
+    }
+    
+    private boolean atStartOfLine() { return column <= currentIndent ; }
+
+    public void ensureStartOfLine()
+    {
+        if ( !atStartOfLine() )
+            newline() ;
+    }
+    
+    @Override
+    public void close() { try { out.close(); } catch (IOException ex) {} }
+    
+    @Override
+    public void flush() { try { out.flush(); } catch (IOException ex) {} }
+    
+    /** Pad to the indent (if we are before it) */
+    public void pad()
+    {
+        if ( startingNewLine && currentIndent > 0 )
+            lineStart() ;
+        padInt() ;
+    }
+    
+    /** Pad to a given number of columns EXCLUDING the indent.
+     * 
+     * @param col Column number (first column is 1).
+     */
+    public void pad(int col) { pad(col, false) ; }
+    
+    /** Pad to a given number of columns maybe including the indent.
+     * 
+     * @param col Column number (first column is 1).
+     * @param absoluteColumn Whether to include the indent
+     */
+    public void pad(int col, boolean absoluteColumn )
+    {
+        // Make absolute
+        if ( !absoluteColumn )
+            col = col+currentIndent ;
+        int spaces = col - column  ;
+        for ( int i = 0 ; i < spaces ; i++ )
+        {
+            write$(' ') ;        // Always a space.
+            column++ ;
+        }
+    }
+    
+    
+    private void padInt() 
+    {
+        if ( padString == null )
+        {
+            for ( int i = column ; i < currentIndent ; i++ )
+            {
+                write$(padChar) ;
+                column++ ;
+            }
+        }
+        else
+        {
+            for ( int i = column ; i < currentIndent ; i += padString.length() )
+            {
+                write$(padString) ;
+                column += padString.length() ;
+            }
+        }
+    }
+    
+    /** Get row/line (counts from 1) */
+    public int getRow() { return row ; }
+    /** Get the absolute column.
+     *  This is the location where the next charcter on the line will be printed.
+     *  The IndentedWriter may not yet have padded to this place.   
+     */
+    public int getCol() {
+        if ( currentIndent > column )
+            return currentIndent ;
+        return column ;
+    }
+    
+    /** Get indent from the left hand edge */ 
+    public int getAbsoluteIndent()       { return currentIndent ; }
+    /** Set indent from the left hand edge */ 
+    public void setAbsoluteIndent(int x) { currentIndent = x ; }
+
+    /** Position past current indent */ 
+    public int getCurrentOffset()
+    { 
+        int x = getCol() - getAbsoluteIndent() ;
+        if ( x >= 0 )
+            return x ;
+        // At start of line somehow.
+        return 0 ;
+    }
+    
+    public boolean hasLineNumbers()
+    {
+        return lineNumbers ;
+    }
+
+    public void setLineNumbers(boolean lineNumbers)
+    {
+        this.lineNumbers = lineNumbers ;
+    }
+    
+    public String getEndOfLineMarker()              { return endOfLineMarker ; }
+    
+    /** Set the marker included at end of line - set to null for "none".  Usually used for debugging. */ 
+    public void setEndOfLineMarker(String marker)   { endOfLineMarker = marker ; }
+    
+    /** Flat mode - print without NL, for a more compact representation - depends on caller */  
+    public boolean inFlatMode()                 { return flatMode ; }
+    public void setFlatMode(boolean flatMode)   { this.flatMode = flatMode ; }
+    
+    /** Flush on newline **/
+    public boolean getFlushOnNewline()      { return flushOnNewline; }
+    public void setFlushOnNewline(boolean flushOnNewline) { this.flushOnNewline = flushOnNewline; } 
+    
+    public char getPadChar()                { return padChar ; }
+    public void setPadChar(char ch)         { this.padChar  = ch ; }
+    public String getPadString()            { return padString ; }
+    public void setPadString(String str)    { this.padString = str ; unitIndent = str.length(); }
+
+    public void incIndent()      { incIndent(unitIndent) ; }
+    public void incIndent(int x)
+    {
+        currentIndent += x ;
+    }
+
+    public void decIndent() { decIndent(unitIndent) ; }
+    public void decIndent(int x) 
+    {
+        currentIndent -= x ;
+    }
+    
+    public void setUnitIndent(int x)    { unitIndent = x ; }
+    public int  getUnitIndent()         { return unitIndent ; }
+    public boolean atLineStart()        { return startingNewLine ; }
+    
+    private void lineStart()
+    {
+        if ( flatMode )
+        {
+            if ( startingNewLine && row > 1 )
+                // Space between each line.
+                write$(' ') ;
+            startingNewLine = false ;
+            return ;
+        }
+        
+        // Need to do its just before we append anything, not after a NL,
+        // so that a final blank does not cause a line number  
+        if ( startingNewLine )
+            insertLineNumber() ;
+        padInt() ;
+        startingNewLine = false ;
+    }
+    
+    private static int WidthLineNumber = 3 ;
+    
+    private void insertLineNumber()
+    {
+        if ( ! lineNumbers )
+            return ;
+        String s = Integer.toString(row) ;
+        for ( int i = 0 ; i < WidthLineNumber-s.length() ; i++ )
+            write$(' ') ;
+        write$(s) ;
+        write$(' ') ;
+    }
+    
+    @Override
+    public String toString() {
+        return String.format("Indent = %d : [%d, %d]", currentIndent, row, column) ;  
+    }
+}


[7/9] jena git commit: Import of lib, io, logging and iterator packages

Posted by an...@apache.org.
http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorArray.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorArray.java b/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorArray.java
new file mode 100644
index 0000000..ee45935
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorArray.java
@@ -0,0 +1,92 @@
+/*
+ * 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.Iterator ;
+import java.util.NoSuchElementException ;
+
+/** Iterator over a Java base array */
+public final class IteratorArray<T> implements Iterator<T>
+{
+    /** Iterator over all the array elements */ 
+    public static <T> IteratorArray<T> create(T[] array)
+    { return new IteratorArray<>(array, 0, array.length) ; }
+    
+    /** Iterator over array elements from start (inclusive) to finish (exclusive) */ 
+    public static <T> IteratorArray<T> create(T[] array, int start, int finish)
+    { return new IteratorArray<>(array, start, finish) ; }
+    
+    private int idx ;
+    private int finishIdx ;
+    private T[] array ;
+    
+    private IteratorArray(T[] array, int start, int finish) 
+    {
+        if ( start < 0 )
+            throw new IllegalArgumentException("Start: "+start) ;
+
+        if ( start > finish )
+            throw new IllegalArgumentException("Start >= finish: "+start+" >= "+finish) ;
+
+// Instead: truncate to array length          
+//        if ( finish > array.length )
+//            throw new IllegalArgumentException("Finish outside array") ;
+//        
+// Instead: immediate end iterator                
+//        if ( start >= array.length )
+//            throw new IllegalArgumentException("Start outside array") ;
+
+        this.array = array ;
+        idx = start ;
+        finishIdx = finish ;
+        if ( idx < 0 )
+            idx = 0 ;
+        if ( finishIdx > array.length ) 
+            finishIdx = array.length ;
+    }
+
+    @Override
+    public boolean hasNext()
+    {
+//        if ( idx < 0 )
+//            return false ;
+        if ( idx >= finishIdx )
+            return false ;
+        return true ;
+    }
+
+    public T current()
+    {
+        if ( idx >= finishIdx )
+            throw new NoSuchElementException() ;
+        return array[idx] ;
+    }
+    
+    @Override
+    public T next()
+    {
+        if ( ! hasNext() )
+            throw new NoSuchElementException() ; 
+        return array[idx++] ;
+    }
+
+    @Override
+    public void remove()
+    { throw new UnsupportedOperationException("ArrayIterator") ; }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorBlockingQueue.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorBlockingQueue.java b/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorBlockingQueue.java
new file mode 100644
index 0000000..c11b262
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorBlockingQueue.java
@@ -0,0 +1,73 @@
+/*
+ * 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.Iterator ;
+import java.util.NoSuchElementException ;
+import java.util.concurrent.BlockingQueue ;
+
+/** Iterator over a blocking queue until queue end seen */
+
+public class IteratorBlockingQueue<T> implements Iterator<T>
+{
+    private BlockingQueue<T> queue ;
+    private boolean finished = false ;
+    private T slot = null ;
+    private T endMarker ; 
+
+    public IteratorBlockingQueue(BlockingQueue<T> queue, T endMarker) { this.queue = queue ; this.endMarker = endMarker ; }
+    
+    @Override
+    public boolean hasNext()
+    {
+        if ( finished ) return false ;
+        if ( slot != null ) return true ;
+        try
+        {
+            slot = queue.take() ;
+            if ( slot == endMarker )
+            {
+                finished = true ;
+                slot = null ;
+                return false ;
+            }
+            return true ;
+            
+        } catch (InterruptedException ex)
+        {
+            ex.printStackTrace();
+            
+        }
+        return false ;
+    }
+
+    @Override
+    public T next()
+    {
+        if ( ! hasNext() )
+            throw new NoSuchElementException() ;
+        T item = slot ;
+        slot = null ;
+        return item ;
+    }
+
+    @Override
+    public void remove()
+    { throw new UnsupportedOperationException() ; }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorConcat.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorConcat.java b/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorConcat.java
new file mode 100644
index 0000000..65e6430
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorConcat.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.iterator;
+
+import java.util.Iterator ;
+import java.util.List ;
+import java.util.NoSuchElementException ;
+
+import org.apache.jena.atlas.lib.DS ;
+
+/** Iterator of Iterators
+ *  IteratorConcat is better when there are lots of iterators to be joined.
+ *  IteratorCons is slightly better for two iterators. 
+ */
+
+public class IteratorConcat<T> implements Iterator<T>
+{
+    private List<Iterator<T>> iterators = DS.list(); 
+    int idx = -1 ;
+    private Iterator<T> current = null ;
+    private Iterator<T> removeFrom = null ;
+    boolean finished = false ;
+    
+    /** 
+     * Usually, it is better to create an IteratorConcat explicitly and add iterator if there are going to be many.
+     * @param iter1
+     * @param iter2
+     * @return Iterator
+     * @see IteratorCons
+     */
+    public static <T> Iterator<T> concat(Iterator<T> iter1, Iterator<T> iter2)
+    {
+        if (iter2 == null) return iter1 ;
+        if (iter1 == null) return iter2 ;
+        IteratorConcat<T> c = new IteratorConcat<>() ;
+        c.add(iter1) ;
+        c.add(iter2) ;
+        return c ;
+    }
+ 
+    public IteratorConcat() {}
+
+    
+    public void add(Iterator<T> iter) { iterators.add(iter) ; }
+    
+    @Override
+    public boolean hasNext()
+    {
+        if ( finished )
+            return false ;
+
+        if ( current != null && current.hasNext() )
+            return true ;
+        
+        while ( idx < iterators.size()-1 )
+        {
+            idx++ ;
+            current = iterators.get(idx) ;
+            if ( current.hasNext() )
+                return true ;
+            // Nothing here - move on.
+            current = null ;
+        }
+        // idx has run off the end.
+        return false ;
+    }
+
+    @Override
+    public T next()
+    {
+        if ( ! hasNext() )
+            throw new NoSuchElementException() ; 
+        removeFrom = current ;
+        return current.next();
+    }
+
+    @Override
+    public void remove()
+    { 
+        if ( null == removeFrom )
+            throw new IllegalStateException("no calls to next() since last call to remove()") ;
+        
+        removeFrom.remove() ;
+        removeFrom = null ;
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorCons.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorCons.java b/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorCons.java
new file mode 100644
index 0000000..c6cfd61
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorCons.java
@@ -0,0 +1,122 @@
+/*
+ * 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.Iterator ;
+import java.util.NoSuchElementException ;
+
+/** IteratorCons : the concatenation of two iterators.
+ * See also {@link IteratorConcat}.
+ * If there potentially many iterators to be joined, it is better to 
+ *  create an IteratorConcat explicitly and add each iterator.
+ *  IteratorCons is slightly better in the two iterator case.
+ */
+public class IteratorCons<T> implements Iterator<T>, Iterable<T>
+{
+    // No - we don't really need IteratorCons and IteratorConcat
+    // Historical - IteratorCons came first.
+    // IteratorConcat is nearly as good as IteratorCons in the small when it
+    // it is hard to see when it woudl matter much.
+    
+    private Iterator<? extends T> iter1 ;
+    private Iterator<? extends T> iter2 ;
+    private Iterator<? extends T> removeFrom ;
+
+    public static <X> Iterator<X> create(Iterator<? extends X> iter1, Iterator<? extends X> iter2)
+    {
+        if ( iter1 == null && iter2 == null )
+            return Iter.nullIterator() ;
+        
+        // The casts are safe because an iterator can only return X, and does not take an X an an assignment.  
+        if ( iter1 == null )
+        {
+            @SuppressWarnings("unchecked")
+            Iterator<X> x = (Iterator<X>)iter2 ;
+            return x ;
+        }
+        
+        if ( iter2 == null )
+        {
+            @SuppressWarnings("unchecked")
+            Iterator<X> x = (Iterator<X>)iter1 ;
+            return x ;
+        }
+        
+        return new IteratorCons<>(iter1, iter2) ;
+    }
+    
+    private IteratorCons(Iterator<? extends T> iter1, Iterator<? extends T> iter2)
+    {
+        this.iter1 = iter1 ;
+        this.iter2 = iter2 ;
+    }
+
+    @Override
+    public boolean hasNext()
+    {
+        if ( iter1 != null )
+        {
+            if ( iter1.hasNext() ) return true ;
+            // Iter1 ends
+            iter1 = null ;
+        }
+        
+        if ( iter2 != null )
+        {
+            if ( iter2.hasNext() ) return true ;
+            // Iter2 ends
+            iter2 = null ;
+        }
+        return false ; 
+    }
+
+    @Override
+    public T next()
+    {
+        if ( ! hasNext() )
+            throw new NoSuchElementException("Iterator2.next") ;
+        if ( iter1 != null )
+        {
+            removeFrom = iter1 ;
+            return iter1.next();
+        }
+        if ( iter2 != null )
+        {
+            removeFrom = iter2 ;
+            return iter2.next();
+        }
+        throw new Error("Iterator2.next") ;
+    }
+
+    @Override
+    public void remove()
+    {
+        if ( null == removeFrom )
+            throw new IllegalStateException("no calls to next() since last call to remove()") ;
+        
+        removeFrom.remove() ;
+        removeFrom = null ;
+    }
+
+    @Override
+    public Iterator<T> iterator()
+    {
+        return this ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorDelayedInitialization.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorDelayedInitialization.java b/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorDelayedInitialization.java
new file mode 100644
index 0000000..807105e
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorDelayedInitialization.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.atlas.iterator;
+
+import java.util.Iterator ;
+import java.util.NoSuchElementException ;
+
+import org.apache.jena.atlas.lib.Closeable ;
+
+/** Class to delay the initialization of an iterator until first call of an Iterator operation. */
+
+public abstract class IteratorDelayedInitialization<T> implements Iterator<T>, Closeable
+{
+    private boolean initialized = false ;
+    private Iterator<T> iterator ; 
+    
+    public IteratorDelayedInitialization() {}
+
+    private void init()
+    {
+        if ( ! initialized )
+        {
+            initialized = true ;
+            iterator = initializeIterator() ;
+        }
+    }
+    
+    // Called exactly once
+    protected abstract Iterator<T> initializeIterator() ;
+    
+    @Override
+    public boolean hasNext()
+    {
+        init() ;
+        boolean b = iterator.hasNext() ;
+        if ( ! b )
+            close() ;
+        return b ;
+    }
+
+    @Override
+    public T next()
+    {
+        init() ;
+        try { return iterator.next() ; }
+        catch (NoSuchElementException ex) { close() ; throw(ex) ; }
+    }
+
+    @Override
+    public void remove()
+    {
+        init() ;
+        iterator.remove() ;
+    }
+    
+    @Override
+    public void close()
+    {
+        Iter.close(iterator) ;
+        iterator = null ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorInteger.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorInteger.java b/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorInteger.java
new file mode 100644
index 0000000..6fff27c
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorInteger.java
@@ -0,0 +1,61 @@
+/*
+ * 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.Iterator ;
+
+public class IteratorInteger implements Iterator<Long>
+{
+    private final long start ;
+    private final long finish ;
+    private long current ;
+
+    public static IteratorInteger range(long start, long finish)
+    {
+        return new IteratorInteger(start, finish) ;
+    }
+    
+    
+    /** [start, finish) */
+    public IteratorInteger(long start, long finish)
+    {
+        this.start = start ;
+        this.finish = finish ;
+        this.current = start ;
+    }
+    
+    @Override
+    public boolean hasNext()
+    {
+        return ( current < finish ) ;
+    }
+
+    @Override
+    public Long next()
+    {
+        Long v = current;
+        current++ ;
+        return v ;
+    }
+
+    @Override
+    public void remove()
+    { throw new UnsupportedOperationException() ; }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorResourceClosing.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorResourceClosing.java b/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorResourceClosing.java
new file mode 100644
index 0000000..89a54aa
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorResourceClosing.java
@@ -0,0 +1,107 @@
+/*
+ * 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.Iterator;
+
+import org.apache.jena.atlas.AtlasException ;
+import org.apache.jena.atlas.io.IO ;
+import org.apache.jena.atlas.lib.Closeable ;
+
+/**
+ * This iterator will automatically close a {@link org.apache.jena.atlas.lib.Closeable} or {@link java.io.Closeable}
+ * resource when the iterator is exhausted.  Alternatively, the resource will be closed when {@link #close()} is
+ * called.  An {@link AtlasException} will be thrown if access is attempted after {@link #close()} has been called.
+ */
+public class IteratorResourceClosing<T> implements Iterator<T>, Closeable
+{
+    private final Iterator<T> iter ;
+    private final Object resource ;
+    private boolean finished;
+    
+    public IteratorResourceClosing(Iterator<T> iter, Closeable resource)
+    {
+        this.iter = iter;
+        this.resource = resource;
+        this.finished = false;
+    }
+    
+    public IteratorResourceClosing(Iterator<T> iter, java.io.Closeable resource)
+    {
+        this.iter = iter;
+        this.resource = resource ;
+        this.finished = false;
+    }
+    
+    private void checkFinished()
+    {
+        if (finished) throw new AtlasException("IteratorResourceClosing is closed, no further operations can be performed on it.") ;
+    }
+
+    @Override
+    public boolean hasNext()
+    {
+        if (finished)
+            return false;
+        
+        boolean toReturn = iter.hasNext();
+        
+        // Clean up if we are done
+        if (!toReturn)
+        {
+            close();
+        }
+        return toReturn ;
+    }
+
+    @Override
+    public T next()
+    {
+        checkFinished();
+        return iter.next() ;
+    }
+
+    @Override
+    public void remove()
+    {
+        checkFinished();
+        iter.remove() ;
+    }
+
+    @Override
+    public void close()
+    {
+        if (!finished)
+        {
+            Iter.close(iter) ;
+            if (null != resource)
+            {
+                if (resource instanceof Closeable)
+                {
+                    ((Closeable)resource).close() ;
+                }
+                else
+                {
+                    IO.close((java.io.Closeable)resource) ;
+                }
+            }
+            finished = true;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorSlotted.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorSlotted.java b/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorSlotted.java
new file mode 100644
index 0000000..fd846dc
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorSlotted.java
@@ -0,0 +1,117 @@
+/*
+ * 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.Iterator ;
+import java.util.NoSuchElementException ;
+
+import org.apache.jena.atlas.lib.Lib ;
+
+/** An Iterator with a one slot lookahead. */  
+public abstract class IteratorSlotted<T> implements Iterator<T>
+{
+    private boolean finished = false ;
+    private boolean slotIsSet = false ;
+    private T slot = null ; 
+
+    protected IteratorSlotted() { }
+
+    // -------- The contract with the subclasses 
+    
+    /** Implement this, not next() or nextBinding() */
+    protected abstract T moveToNext() ;
+    
+    /** Can return true here then null from moveToNext() to indicate end. */ 
+    protected abstract boolean hasMore() ;
+    // alter add a flag to say if null is a legal value.
+    
+    /** Close the iterator. */
+    protected void closeIterator() { }
+   
+    // -------- The contract with the subclasses 
+
+    protected boolean isFinished() { return finished ; }
+
+    @Override
+    public final boolean hasNext()
+    {
+        if ( finished )
+            return false ;
+        if ( slotIsSet )
+            return true ;
+
+        boolean r = hasMore() ;
+        if ( ! r )
+        {
+            close() ;
+            return false ;
+        }
+        
+        slot = moveToNext() ;
+        if ( slot == null ) {
+            close() ;
+            return false ;
+        }
+            
+        slotIsSet = true ;
+        return true ;
+    }
+    
+    /** final - autoclose and registration relies on it - implement moveToNextBinding() */
+    @Override
+    public final T next()
+    {
+        if ( ! hasNext() ) throw new NoSuchElementException(Lib.className(this)) ;
+        
+        T obj = slot ;
+        slot = null ;
+        slotIsSet = false ;
+        return obj ;
+    }
+
+    /** Look at the next element - returns null when there is no element */
+    public final T peek()
+    {
+        return peek(null) ;
+    }
+    
+    /** Look at the next element - returns dft when there is no element */
+    public final T peek(T dft)
+    {
+        hasNext() ;
+        if ( ! slotIsSet ) return dft ;
+        return slot ;
+    }
+    
+    @Override
+    public final void remove()
+    {
+        throw new UnsupportedOperationException(Lib.className(this)+".remove") ;
+    }
+    
+    public final void close()
+    {
+        if ( finished )
+            return ;
+        closeIterator() ;
+        slotIsSet = false ;
+        slot = null ;
+        finished = true ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorTruncate.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorTruncate.java b/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorTruncate.java
new file mode 100644
index 0000000..86ef52e
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorTruncate.java
@@ -0,0 +1,79 @@
+/*
+ * 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.Iterator ;
+import java.util.NoSuchElementException ;
+
+/** Iterate while a condition return true, then stop */
+public class IteratorTruncate<T> implements Iterator<T>
+{
+    static public interface Test { boolean accept(Object object) ; }
+    private Test test ;
+    private T slot = null ;
+    private boolean active = true ;
+    private Iterator<T> iter ;
+
+    public IteratorTruncate (Test test, Iterator<T> iter)
+    { this.test = test ; this.iter = iter ; }
+
+    @Override
+    public boolean hasNext()
+    {
+        if ( ! active ) return false ;
+        if ( slot != null )
+            return true ;
+
+        if ( ! iter.hasNext() )
+        {
+            active = false ;
+            return false ;
+        }
+
+        slot = iter.next() ;
+        if ( test.accept(slot) )
+            return true ;
+        // Once the test goes false, no longer yield anything.
+        Iter.close(iter) ;
+        active = false ;
+        iter = null ;
+        slot = null ;
+        return false ;
+    }
+
+    @Override
+    public T next()
+    {
+        if ( ! hasNext() )
+            throw new NoSuchElementException("IteratorTruncate.next") ;    
+        T x = slot ;
+        slot = null ;
+        return x ;
+    }
+
+    @Override
+    public void remove()
+    { throw new UnsupportedOperationException("IteratorTruncate.remove"); }
+
+    public void close() {
+        if ( iter != null )
+            Iter.close(iter) ; 
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorWithBuffer.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorWithBuffer.java b/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorWithBuffer.java
new file mode 100644
index 0000000..7af9ac8
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorWithBuffer.java
@@ -0,0 +1,132 @@
+/*
+ * 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.ArrayList ;
+import java.util.Iterator ;
+import java.util.List ;
+import java.util.NoSuchElementException ;
+
+/**
+ * Iterator that delays output by N slots so you can react to the output before
+ * it's yielded. See also {@link PeekIterator} (which predates this code). See also
+ * {@link IteratorWithHistory} for an iterator that remembers what it has yielded.
+ * 
+ * @see PeekIterator
+ * @see PushbackIterator
+ * @see IteratorWithHistory
+ */
+public class IteratorWithBuffer<T> implements Iterator<T> {
+    private List<T>     lookahead ;
+    private Iterator<T> iter ;
+    private int         capacity ;
+    private boolean     innerHasEnded = false ;
+
+    public IteratorWithBuffer(Iterator<T> iter, int N) {
+        if ( N < 0 )
+            throw new IllegalArgumentException("Buffering size < 0") ;
+        this.iter = iter ;
+        this.lookahead = new ArrayList<>(N) ;
+        this.capacity = N ;
+        // Fill the lookahead.
+        for ( int i = 0 ; i < N ; i++ ) {
+            if ( !iter.hasNext() ) {
+                atEndInner() ;
+                break ;
+            }
+            T nextItem = iter.next() ;
+            // System.out.println("Fill: "+nextItem) ;
+            lookahead.add(nextItem) ;
+        }
+    }
+
+    @Override
+    public boolean hasNext() {
+        return lookahead.size() > 0 ;
+    }
+
+    @Override
+    public T next() {
+        if ( !hasNext() )
+            throw new NoSuchElementException(this.getClass().getName()) ;
+
+        if ( !iter.hasNext() )
+            atEndInner() ;
+
+        T item = lookahead.remove(0) ;
+        // System.out.println("remove: "+item) ;
+        if ( iter.hasNext() ) {
+            // Should not throw NoSuchElementException.
+            T nextItem = iter.next() ;
+            // System.out.println("add   : "+nextItem) ;
+            lookahead.add(nextItem) ;
+        }
+        return item ;
+    }
+
+    @Override
+    public void remove() {
+        throw new UnsupportedOperationException("remove") ;
+    }
+
+    /**
+     * Look at elements that will be returned by a subsequnet call of .next().
+     * The next element is index 0, then index 1 etc. This operation is valid
+     * immediately after the constructor returns. Returns null for no such
+     * element (underlying iterator didn't yeild enough elements). Throws
+     * IndexOutOfBoundsException if an attempt i smade to go beyond the
+     * buffering window.
+     */
+    public T peek(int idx) {
+        if ( idx < 0 || idx >= capacity )
+            throw new IndexOutOfBoundsException("Index: " + idx) ;
+        if ( idx >= lookahead.size() )
+            return null ;
+        return lookahead.get(idx) ;
+    }
+
+    /**
+     * Return the current size of the lookahead. This can be used to tell the
+     * difference between an iterator returning null and an iterator that is
+     * just short.
+     */
+    public int currentSize() {
+        return lookahead.size() ;
+    }
+
+    /**
+     * Set the element to be returned by a subsequent .next(). Use with care.
+     * The original element to be returned at this position is lost.
+     */
+    public void set(int idx, T item) {
+        lookahead.set(idx, item) ;
+    }
+
+    /** Called when the underlying iterator ends */
+    private void atEndInner() {
+        if ( !innerHasEnded ) {
+            innerHasEnded = true ;
+            endReachedInner() ;
+        }
+    }
+
+    /** Called, once, at the end of the wrapped iterator. */
+    protected void endReachedInner() {}
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorWithHistory.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorWithHistory.java b/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorWithHistory.java
new file mode 100644
index 0000000..b5b800a
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorWithHistory.java
@@ -0,0 +1,109 @@
+/*
+ * 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.ArrayList ;
+import java.util.Iterator ;
+import java.util.List ;
+import java.util.NoSuchElementException ;
+
+/** Remembers the last N yields.
+ * See also {@link IteratorWithBuffer}, for an iterator that looks ahead to what it will yield.
+ * @see IteratorWithBuffer
+ * @see PeekIterator
+ * @see PushbackIterator
+ */
+public class IteratorWithHistory<T> implements Iterator<T>
+{
+    private List<T> history ;
+    private Iterator<T> iter ;
+    private int capacity ;
+    private boolean hasEnded = false ;
+    
+    public IteratorWithHistory(Iterator<T> iter, int N) {
+        this.iter = iter ;
+        this.history = new ArrayList<>(N) ;
+        this.capacity = N ;
+    }
+
+    @Override
+    public boolean hasNext() {
+        boolean b = iter.hasNext() ;
+        if ( !b )
+            atEnd() ;
+        return b ;
+    }
+
+    @Override
+    public T next() {
+        T item = null ;
+        try {
+            item = iter.next() ;
+        }
+        catch (NoSuchElementException ex) {
+            atEnd() ;
+        }
+        // Shuffle up, add at bottom.
+        if ( history.size() >= capacity )
+            history.remove(history.size() - 1) ;
+        history.add(0, item) ;
+        return item ;
+    }
+
+    @Override
+    public void remove()
+    { throw new UnsupportedOperationException("remove") ; }
+
+    /**
+     * return the previous i'th element returned by next(). 0 means last call of
+     * next. History is retained after the end of iteration.
+     * 
+     * @return Element or null for no such element (that is for haven't yielded
+     *         that many elements).
+     * @throws IndexOutOfBoundsException
+     *             if index is negative.
+     */
+    public T getPrevious(int idx) {
+        if ( idx >= capacity || idx < 0 )
+            throw new IndexOutOfBoundsException("Index: " + idx) ;
+        if ( idx >= history.size() )
+            return null ;
+        return history.get(idx) ;
+    }
+
+    /**
+     * Return the current size of the histiory. This can be used to tell the
+     * difference between an iterator returning null and an iterator that is
+     * just short.
+     */
+    public int currentSize() {
+        return history.size() ;
+    }
+
+    /** Called when the underlying iterator ends */
+    protected void atEnd() {
+        if ( !hasEnded ) {
+            hasEnded = true ;
+            endReached() ;
+        }
+    }
+    
+    /** Called, once, at the end */ 
+    protected void endReached() { }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorWrapper.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorWrapper.java b/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorWrapper.java
new file mode 100644
index 0000000..83901f2
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/IteratorWrapper.java
@@ -0,0 +1,41 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.atlas.iterator;
+
+import java.util.Iterator ;
+
+public class IteratorWrapper<T> implements Iterator<T>
+{
+    protected final Iterator<T> iterator ;
+
+    public IteratorWrapper(Iterator<T> iterator)
+    {
+        this.iterator = iterator ;
+    }
+
+    @Override
+    public boolean hasNext()        { return iterator.hasNext() ; }
+
+    @Override
+    public T next()                 { return iterator.next() ; }
+    
+    @Override
+    public void remove()            { iterator.remove() ; }
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/iterator/MapUtils.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/iterator/MapUtils.java b/jena-base/src/main/java/org/apache/jena/atlas/iterator/MapUtils.java
new file mode 100644
index 0000000..3c6492e
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/MapUtils.java
@@ -0,0 +1,36 @@
+/*
+ * 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.Map ;
+
+import org.apache.jena.atlas.lib.ActionKeyValue ;
+
+
+
+public class MapUtils
+{
+     // Map specific operations
+    
+    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()) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/iterator/NullIterator.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/iterator/NullIterator.java b/jena-base/src/main/java/org/apache/jena/atlas/iterator/NullIterator.java
new file mode 100644
index 0000000..ff2b2f1
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/NullIterator.java
@@ -0,0 +1,49 @@
+/*
+ * 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.Iterator ;
+import java.util.NoSuchElementException ;
+
+/** Null Iterator - also guaranteed sharable and immutable */
+public class NullIterator<T> implements Iterator<T>, Iterable<T>
+{
+    @Override
+    public boolean hasNext()
+    {
+        return false ;
+    }
+
+    @Override
+    public T next()
+    {
+        throw new NoSuchElementException("NullIterator.next") ;
+    }
+
+    @Override
+    public void remove()
+    { throw new NoSuchElementException("NullIterator.remove") ;}
+
+    @Override
+    public Iterator<T> iterator()
+    {
+        return this ;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/iterator/PeekIterator.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/iterator/PeekIterator.java b/jena-base/src/main/java/org/apache/jena/atlas/iterator/PeekIterator.java
new file mode 100644
index 0000000..d9c0d2e
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/PeekIterator.java
@@ -0,0 +1,100 @@
+/*
+ * 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.Iterator ;
+import java.util.NoSuchElementException ;
+import java.util.Queue ;
+
+/** PeekIterator - is one slot ahead from the wrapped iterator */ 
+public class PeekIterator<T> implements Iterator<T>
+{
+    private final Iterator<T> iter ;
+    private boolean finished = false ;
+    // Slot always full when iterator active.  Null is a a valid element.
+    private T slot ;
+    
+    public static <T> PeekIterator<T> create(PeekIterator<T> iter) { return iter ; }
+    public static <T> PeekIterator<T> create(Iterator<T> iter)
+    { 
+        if ( iter instanceof PeekIterator<?> )
+            return (PeekIterator<T>)iter ;
+        return new PeekIterator<>(iter) ;
+    }
+
+    public PeekIterator(Iterator<T> iter)
+    {
+        this.iter = iter ;
+        fill() ;
+    }
+    
+    private void fill()
+    {
+        if ( finished ) return ;
+        if ( iter.hasNext() )
+            slot = iter.next();
+        else
+        {
+            finished = true ;
+            slot = null ;
+        }
+    }
+
+    @Override
+    public boolean hasNext()
+    {
+        if ( finished )
+            return false ;
+        return true ;
+    }
+
+    /** Peek the next element or return null
+     *  @see Queue#peek
+     */
+    public T peek()
+    {
+        if ( finished )
+            return null  ;
+        return slot ;
+    }
+    
+    /** Peek the next element or throw  NoSuchElementException */
+    public T element()
+    {
+        if ( finished )
+            throw new NoSuchElementException() ;
+        return slot ;
+    }
+    
+    @Override
+    public T next()
+    {
+        if ( finished )
+            throw new NoSuchElementException() ;
+        T x = slot ;
+        // Move on now so the slot is loaded for peek.
+        fill() ;
+        return x ;
+    }
+
+    @Override
+    public void remove()
+    { throw new UnsupportedOperationException() ; }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/iterator/PushbackIterator.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/iterator/PushbackIterator.java b/jena-base/src/main/java/org/apache/jena/atlas/iterator/PushbackIterator.java
new file mode 100644
index 0000000..355c563
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/PushbackIterator.java
@@ -0,0 +1,62 @@
+/*
+ * 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.ArrayDeque ;
+import java.util.Deque ;
+import java.util.Iterator ;
+
+/** An iterator where you can push items backinto the iterator, to be yielded (LIFO) next time.
+ *  @see PeekIterator
+ */
+public class PushbackIterator<T> implements Iterator<T> {
+    private Deque<T>    items = new ArrayDeque<>() ;
+    private Iterator<T> iter ;
+
+    public PushbackIterator(Iterator<T> iter) {
+        if ( iter == null )
+            throw new IllegalArgumentException("Wrapped iterator can't be null") ;
+        this.iter = iter ;
+    }
+
+    public void pushback(T item) {
+        items.push(item) ;
+    }
+
+    @Override
+    public boolean hasNext() {
+        if ( !items.isEmpty() )
+            return true ;
+        return iter.hasNext() ;
+    }
+
+    @Override
+    public T next() {
+        if ( !items.isEmpty() )
+            return items.pop() ;
+        return iter.next() ;
+    }
+
+    @Override
+    public void remove() {
+        // Need to track if last next() was from the stack or not.
+        throw new UnsupportedOperationException() ;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/iterator/RepeatApplyIterator.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/iterator/RepeatApplyIterator.java b/jena-base/src/main/java/org/apache/jena/atlas/iterator/RepeatApplyIterator.java
new file mode 100644
index 0000000..e504265
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/RepeatApplyIterator.java
@@ -0,0 +1,86 @@
+/*
+ * 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.Iterator ;
+import java.util.NoSuchElementException ;
+
+import org.apache.jena.atlas.lib.Closeable ;
+import org.apache.jena.atlas.lib.Lib ;
+
+public abstract class RepeatApplyIterator<T> implements Iterator<T>, Closeable
+{
+    private Iterator<T> input ;
+    private boolean finished = false ;
+    private Iterator<T> currentStage = null ;
+
+    protected RepeatApplyIterator(Iterator<T> input)
+    {
+        this.input = input ;
+    }
+
+    @Override
+    public boolean hasNext()
+    {
+        if  ( finished )
+            return false ;
+        for ( ;; )
+        {
+            if ( currentStage == null && input.hasNext() )
+            {
+                T nextItem = input.next();
+                currentStage = makeNextStage(nextItem) ;
+            }
+            
+            if ( currentStage == null  )
+            {
+                hasFinished() ;
+                finished = true ;
+                return false ;
+            }
+            
+            if ( currentStage.hasNext() )
+                return true ;
+            
+            currentStage = null ;
+        }
+    }
+
+    protected abstract Iterator<T> makeNextStage(T t) ;
+    
+    protected void hasFinished() {} 
+    
+    @Override
+    public T next()
+    {
+        if ( ! hasNext() )
+            throw new NoSuchElementException(Lib.className(this)+".next()/finished") ;
+        return currentStage.next() ;
+    }
+
+    @Override
+    public final void remove()
+    { throw new UnsupportedOperationException() ; }
+    
+    @Override
+    public void close()
+    {
+        Iter.close(input) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/iterator/SingletonIterator.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/iterator/SingletonIterator.java b/jena-base/src/main/java/org/apache/jena/atlas/iterator/SingletonIterator.java
new file mode 100644
index 0000000..9c49e48
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/SingletonIterator.java
@@ -0,0 +1,51 @@
+/*
+ * 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.Iterator ;
+import java.util.NoSuchElementException ;
+
+public class SingletonIterator<T> implements Iterator<T>
+{
+    private T thing = null ;
+    private boolean yielded = false ;
+    
+    public static <T> SingletonIterator<T> create(T thing) { return new SingletonIterator<>(thing) ; }
+    
+    public SingletonIterator(T thing) { this.thing = thing ; }
+    
+    @Override
+    public boolean hasNext()
+    {
+        return ! yielded ;
+    }
+
+    @Override
+    public T next()
+    {
+        if ( yielded )
+            throw new NoSuchElementException("SingletonIterator.next") ;
+        yielded = true ;
+        return thing ;
+    }
+
+    @Override
+    public void remove()
+    { throw new NoSuchElementException("SingletonIterator.remove") ;}
+}

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

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/iterator/WrapperIterator.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/iterator/WrapperIterator.java b/jena-base/src/main/java/org/apache/jena/atlas/iterator/WrapperIterator.java
new file mode 100644
index 0000000..0a8a0b0
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/iterator/WrapperIterator.java
@@ -0,0 +1,45 @@
+/*
+ * 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.Iterator ;
+
+public class WrapperIterator<T> implements Iterator<T>
+{
+    private final Iterator<T> iter ;
+
+    public WrapperIterator(Iterator<T> iter) { this.iter = iter ; }
+    
+    @Override
+    public boolean hasNext()
+    {
+        return iter.hasNext() ;
+    }
+
+    @Override
+    public T next()
+    {
+        return iter.next() ;
+    }
+
+    @Override
+    public void remove()
+    { iter.remove() ; }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/ActionKeyValue.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/ActionKeyValue.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/ActionKeyValue.java
new file mode 100644
index 0000000..894107a
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/ActionKeyValue.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.lib;
+
+public interface ActionKeyValue<KeyType, ValueType>
+{
+    public void apply(KeyType key, ValueType value) ;
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/AlarmClock.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/AlarmClock.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/AlarmClock.java
new file mode 100644
index 0000000..6210bf1
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/AlarmClock.java
@@ -0,0 +1,69 @@
+/*
+ * 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.lib ;
+
+import java.util.concurrent.ScheduledThreadPoolExecutor ;
+import java.util.concurrent.TimeUnit ;
+
+/**
+ * An AlarmClock is an object that will make a callback (with a value) at a
+ * preset time. Simple abstraction of add/reset/cancel of a Runnable. Currently,
+ * backed by {@link ScheduledThreadPoolExecutor}
+ */
+public class AlarmClock {
+    private ScheduledThreadPoolExecutor timer = new ScheduledThreadPoolExecutor(1) ;
+
+    /* package */AlarmClock() {}
+
+    static private AlarmClock singleton = new AlarmClock() ;
+
+    /** Global singleton for general use */
+    static public AlarmClock get() {
+        return singleton ;
+    }
+
+    /** Add a task to be called after a delay (in milliseconds) */
+    public void add(Runnable task, long delay) {
+        if ( task == null )
+            throw new IllegalArgumentException("Task is null") ;
+        timer.schedule(task, delay, TimeUnit.MILLISECONDS) ;
+    }
+
+    /** Reschedule a task to now run after a different delay from now (in milliseconds) */
+    public void reset(Runnable task, long delay) {
+        if ( task == null )
+            throw new IllegalArgumentException("Task is null") ;
+        cancel(task) ;
+        add(task, delay) ;
+    }
+
+    /** Cancel a task  */
+    public void cancel(Runnable task) {
+        if ( task == null )
+            throw new IllegalArgumentException("Task is null") ;
+        timer.remove(task) ;
+    }
+
+    // public int getCount() { return timer.getQueue().size(); }
+
+    /** Clean up */
+    public void release() {
+        timer.shutdownNow() ;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/Alg.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/Alg.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/Alg.java
new file mode 100644
index 0000000..3a3dacb
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/Alg.java
@@ -0,0 +1,239 @@
+/*
+ * 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.lib;
+
+import java.nio.IntBuffer ;
+import java.util.Comparator ;
+import java.util.List ;
+
+public class Alg
+{
+    // Linear search is really there to test binary search.
+    static int linearSearch(IntBuffer buff, int key)
+    { 
+        return linearSearch(buff, 0, buff.limit(), key) ;
+    }
+
+    static int linearSearch(IntBuffer buff, int low, int high, int key)
+    {
+        int len = buff.limit(); // In int units.
+        check(len, low, high) ;
+        for ( int i = low ; i < high ; i++ )
+        {
+            int k2 = buff.get(i) ;
+            if ( k2 == key )
+                return i ;
+            if ( k2 > key )
+                return encodeIndex(i) ;
+        }
+        return encodeIndex(high) ;
+    }
+    
+    // The encoded offset (insertion point) when not found.
+    public static final int encodeIndex(int i) { return -(i+1) ; } 
+    public static final int decodeIndex(int i) { return -(i+1) ; } 
+
+    
+    /* Both Arrays and Collections have a binary search implementation.
+     * Arrays searches Object[] arrays, and Collections searches List<T>
+     *
+     * But sometime things are not so easy, and it isn't neat as to
+     * what is being searched, like a slice of an NIO Buffer
+     * 
+     * http://en.wikipedia.org/wiki/Binary_search
+     */
+    
+    public static int binarySearch(IntBuffer buff, int value)
+    {
+        return  binarySearch(buff, 0, buff.limit(), value) ;
+    }
+    
+    public static int binarySearch(IntBuffer buff, int low, int high, int value)
+    {
+        // Low is inclusive, high is exclusive.
+        check(buff.limit(), low, high) ;
+        high -- ;   // Convert high to inclusive.
+
+        // Non-tail-recursive form, because tail-recursion removal
+        // is not required by java (unlike scheme).
+        
+        while (low <= high)
+        {
+            int mid = (low + high) >>> 1 ;  // int divide by 2 : better: mid = low + ((high - low) / 2)
+            int k = buff.get(mid) ;
+            
+            // Two comparisons : see wikipedia for one comparison version.
+            if (k < value)
+                low = mid + 1 ;
+            else if ( k > value)
+                high = mid - 1 ;
+            else
+                return mid ;
+        }
+        // On exit, when not finding, low is the least value
+        // above, including off the end of the array.  
+        return encodeIndex(low) ;
+    }
+    
+    // Alt form - no early termination.
+//    public static int binarySearch2(IntBuffer buff, int value, int low, int high)
+//    {
+//        // Low is inclusive, high is exclusive
+//        check(buff.limit(), low, high) ;
+//        int N = high ;
+//        
+//        // Uses high as exclusive index
+//        while (low < high)
+//        {
+//            int mid = (low + high) >>> 1 ;  // int divide by 2 : better: mid = low + ((high - low) / 2)
+//            int k = buff.get(mid) ;
+//            
+//            // Two comparisons : see wikipedia for one comparison version.
+//            if ( k < value)
+//                low = mid + 1 ;
+//            else 
+//                //can't be high = mid-1: here A[mid] >= value,
+//                //so high can't be < mid if A[mid] == value
+//                high = mid;
+//        }
+//        if (low < N && buff.get(low) == value )
+//            return low ;
+//        else
+//            return enc(low) ;
+//    }
+        
+    private static void check(int len, int low, int high)
+    {
+        if ( low > high )
+            throw new IllegalArgumentException("Low index ("+low+") is not less than high index ("+high+")") ; 
+        if ( low < 0 )
+            throw new ArrayIndexOutOfBoundsException("Low index is negative: "+low) ; 
+        if ( high > len )
+            throw new ArrayIndexOutOfBoundsException("High index is too large: "+high) ;
+    }
+
+    // Why isn't this in the java RT?
+    public static <T> int binarySearch( List<T> array, int low, int high, T value, Comparator<T> comparator )
+    {
+        check(array.size(), low, high) ;
+        high -- ;
+
+        while( low <= high )
+        {
+            int mid = ( low + high ) >>> 1 ;
+
+            T k = array.get(mid) ;
+            
+            int x = comparator.compare(k, value) ;
+            if ( x < 0 )
+                low = mid + 1 ;
+            else if ( x > 0 )
+                high = mid - 1 ;
+            else
+                return mid ;
+        }
+        return encodeIndex(low) ;
+    }
+
+    // Why isn't this in the java RT?
+    public static <T extends Comparable<? super T>>
+    int binarySearch(T[] array, int low, int high, T value)
+    {
+        check(array.length, low, high) ;
+        high -- ;
+
+        while( low <= high )
+        {
+            int mid = ( low + high ) >>> 1 ;
+
+            T k = array[mid] ;
+            
+            int x = k.compareTo(value) ; // comparator.compare(k, value) ;
+            if ( x < 0 )
+                low = mid + 1 ;
+            else if ( x > 0 )
+                high = mid - 1 ;
+            else
+                return mid ;
+        }
+        return encodeIndex(low) ;
+    }
+    
+    
+    // Use Arrays.binarySearch functions
+    
+//    public static int binarySearch(int buff[], int value)
+//    { return binarySearch(buff, value, 0, buff.length) ; } 
+//
+//    public static int binarySearch(int buff[], int low, int high, int value)
+//    {
+//        check(buff.length, low, high) ;
+//        // Low is inclusive, high is exclusive.
+//        high -- ;   // Convert high to inclusive.
+//
+//        // Non-tail-recursive form, because tail-recursion removal
+//        // is not required by java (unlike scheme).
+//        
+//        while (low <= high)
+//        {
+//            int mid = (low + high) >>> 1 ;  // int divide by 2 
+//            int k = buff[mid] ;
+//
+//            if (k < value)
+//                low = mid + 1 ;
+//            else if ( k > value)
+//                high = mid - 1 ;
+//            else
+//                return mid ;
+//        }
+//        // On exit, when not finding, low is the least value
+//        // above, including off the end of the array.  
+//        return encodeIndex(low) ;
+//    }
+//
+//    
+//    public static int binarySearch(long buff[], int value)
+//    { return binarySearch(buff, value, 0, buff.length) ; } 
+//    
+//    public static int binarySearch(long buff[], int low, int high, long value)
+//    {
+//        check(buff.length, low, high) ;
+//        // Low is inclusive, high is exclusive.
+//        high -- ;   // Convert high to inclusive.
+//
+//        // Non-tail-recursive form, because tail-recursion removal
+//        // is not required by java (unlike scheme).
+//        
+//        while (low <= high)
+//        {
+//            int mid = (low + high) >>> 1 ;  // int divide by 2 
+//            long k = buff[mid] ;
+//
+//            if (k < value)
+//                low = mid + 1 ;
+//            else if ( k > value)
+//                high = mid - 1 ;
+//            else
+//                return mid ;
+//        }
+//        // On exit, when not finding, low is the least value
+//        // above, including off the end of the array.  
+//        return encodeIndex(low) ;
+//    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/Allocator.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/Allocator.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/Allocator.java
new file mode 100644
index 0000000..84005de
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/Allocator.java
@@ -0,0 +1,28 @@
+/*
+ * 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.lib;
+
+public interface Allocator<T>
+{
+    /** Allocate a new T, different from others allocated */ 
+    public T create() ;
+    
+//    /** Reset the process of allocation - may cause equivalent objects to be created from before the reset */  
+//    public void reset() ;
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/ArrayUtils.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/ArrayUtils.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/ArrayUtils.java
new file mode 100644
index 0000000..d45a107
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/ArrayUtils.java
@@ -0,0 +1,64 @@
+/*
+ * 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.lib;
+
+import java.lang.reflect.Array ;
+import java.util.Arrays ;
+
+/** Collection of array-related operations */
+public class ArrayUtils
+{
+    private ArrayUtils() {}
+    
+    /** Allocate an array of generic type T (initialized to null) */
+    @SuppressWarnings("unchecked")
+    public static <T> T[] alloc(Class<T> cls, int n)
+    {
+        return (T[])Array.newInstance(cls, n) ;
+    }
+    
+ // Compiles but fails at runtime (class cast exception if the reuls is assigned or accessed)
+//        @SuppressWarnings("unchecked")
+//        T[] array = (T[])new Object[n] ;
+// or is T known 
+//        @SuppressWarnings("unchecked")
+//        Set<T> x[] = new Set[length] ;
+//        return array ;
+        
+    /** Allocation space and copy */ 
+    public static <T> T[] copy(T[] array)
+    {
+        return copy(array, 0, array.length) ;
+    }
+    
+    /** Allocation space and copy */ 
+    public static <T> T[] copy(T[] array, int start, int finish)
+    {    
+        return Arrays.copyOfRange(array, start, finish) ;
+
+//        // Java5.
+//        // Fails for arrays of length 0;
+//        if ( array.length <= start )
+//            throw new IllegalArgumentException("Zero length array not supported") ;
+//        @SuppressWarnings("unchecked")
+//        T[] array2 = (T[])Array.newInstance(array[start].getClass(), finish-start) ;
+//        System.arraycopy(array, start, array2, 0, finish-start) ;
+//        return array2 ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/1320f8db/jena-base/src/main/java/org/apache/jena/atlas/lib/BitsInt.java
----------------------------------------------------------------------
diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/BitsInt.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/BitsInt.java
new file mode 100644
index 0000000..1e112a0
--- /dev/null
+++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/BitsInt.java
@@ -0,0 +1,309 @@
+/*
+ * 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.lib;
+
+// NB shifting is "mod 32" -- <<32 is a no-op (not a clear).
+// http://mindprod.com/jgloss/masking.html
+
+/** Utilities for manipulating a bit pattern which are held in a 32 bit int.
+ *  @see BitsLong
+ */ 
+public final class BitsInt
+{
+    private BitsInt() {}
+    
+    private static int IntLen = Integer.SIZE ;
+    
+    /** Extract the value packed into bits start (inclusive) and finish (exclusive).
+     *  The value is returned in the low part of the returned int.
+     *  The low bit is bit zero.
+     * @param bits
+     * @param start
+     * @param finish
+     * @return int  
+     */ 
+    
+    public static final
+    int unpack(int bits, int start, int finish)
+    {
+        check(start, finish) ;
+        if ( finish == 0 ) return 0 ;
+        // Remove top bits by moving up.  Clear bottom bits by them moving down.
+        return (bits<<(IntLen-finish)) >>> ((IntLen-finish)+start) ;
+    }
+
+    /** Place the value into the bit pattern between start and finish
+     *  and returns the new int. 
+     * @param bits
+     * @param value
+     * @param start
+     * @param finish
+     * @return int
+     */
+    public static final
+    int pack(int bits, int value, int start, int finish)
+    {
+        check(start, finish) ;
+        bits = clear$(bits, start, finish) ;
+        bits = bits | (value<<start) ;
+        return bits ;
+    }
+
+    /** Get bits from a hex string.
+     * 
+     * @param str
+     * @param startChar     Index of first character (counted from the left, string style). 
+     * @param finishChar    Index after the last character (counted from the left, string style).
+     * @return int
+     */
+    
+    public static final
+    int unpack(String str, int startChar, int finishChar)
+    {
+        String s = str.substring(startChar, finishChar) ;
+        return Integer.parseInt(s, 16) ;
+    }
+
+    /** Set the bits specificied.
+     * 
+     * @param bits      Pattern
+     * @param bitIndex 
+     * @return          Modified pattern
+     */
+    public static final
+    int set(int bits, int bitIndex)
+    { 
+        check(bitIndex) ;
+        return set$(bits, bitIndex) ;
+    }
+
+    /** Set the bits from start (inc) to finish (exc) to one
+     * 
+     * @param bits      Pattern
+     * @param start     start  (inclusive)
+     * @param finish    finish (exclusive)
+     * @return          Modified pattern
+     */
+    public static final
+    int set(int bits, int start, int finish)
+    { 
+        check(start, finish) ;
+        return set$(bits, start, finish) ;
+    }
+
+    /** Test whether a bit is the same as isSet 
+     * @param bits      Pattern
+     * @param isSet     Test whether is set or not. 
+     * @param bitIndex  Bit index
+     * @return          Boolean
+     */
+    public static final
+    boolean test(int bits, boolean isSet, int bitIndex)
+    {
+        check(bitIndex) ;
+        return test$(bits, isSet, bitIndex) ;
+    }
+    
+    /** Test whether a bit is set 
+     * @param bits      Pattern
+     * @param bitIndex  Bit index
+     * @return          Boolean
+     */
+    public static final
+    boolean isSet(int bits, int bitIndex)
+    {
+        check(bitIndex) ;
+        return test$(bits, true, bitIndex) ;
+    }
+    
+    /** Test whether a range has a specific value or not   
+     * @param bits      Pattern
+     * @param value     Value to test for
+     * @param start     start  (inclusive)
+     * @param finish    finish (exclusive)
+     * @return          Boolean
+     */
+    public static final
+    boolean test(int bits, int value, int start, int finish)
+    {
+        check(start, finish) ;
+        return test$(bits, value, start, finish) ;
+    }
+    
+    /** Get the bits from start (inclusive) to finish (exclusive),
+     *  leaving them aligned in the int.  See also unpack, returns
+     *  the value found at that place.
+     *  @see #unpack(int, int, int)
+     *  @param bits
+     *  @param start
+     *  @param finish
+     *  @return int
+     */
+    
+    public static final
+    int access(int bits, int start, int finish)
+    {
+        check(start, finish) ;
+        return access$(bits, start, finish) ; 
+    }
+    
+    /**
+     * Clear the bits specified.
+     *  @param bits
+     *  @param start
+     *  @param finish
+     *  @return int
+     */
+    public static final
+    int clear(int bits, int start, int finish)
+    {
+        check(start, finish) ;
+        return clear$(bits, start, finish) ;
+    }
+
+    /**
+     * Create a mask that has ones between bit positions start (inc) and finish (exc),
+     * and zeros elsewhere.
+     * @param start
+     * @param finish
+     * @return int
+     */
+    public static final
+    int mask(int start, int finish)
+    {
+        check(start, finish) ;
+        return mask$(start, finish) ;
+    }
+    
+    /**
+     * Create a mask that has zeros between bit positions start (inc) and finish (exc),
+     * and ones elsewhere
+     * @param start
+     * @param finish
+     * @return int
+     */
+    public static final
+    int maskZero(int start, int finish)
+    {
+        check(start, finish) ;
+        return maskZero$(start, finish) ;
+    }
+    
+    private static final
+    int clear$(int bits, int start, int finish)
+    {
+        int mask = maskZero$(start, finish) ;
+        bits = bits & mask ;
+        return bits ;
+    }
+
+    private static final
+    int set$(int bits, int bitIndex)
+    { 
+        int mask = mask$(bitIndex) ;
+        return bits | mask ;
+    }
+
+    private static final
+    int set$(int bits, int start, int finish)
+    { 
+        int mask = mask$(start, finish) ;
+        return bits | mask ;
+    }
+
+    private static
+    boolean test$(int bits, boolean isSet, int bitIndex)
+    {
+        return isSet == access$(bits, bitIndex) ;
+    }
+
+    private static
+    boolean test$(int bits, int value, int start, int finish)
+    {
+        int v = access$(bits, start, finish) ;
+        return v == value ;
+    }
+
+
+    
+    private static final
+    boolean access$(int bits, int bitIndex)
+    {
+        int mask = mask$(bitIndex) ;
+        return (bits & mask) != 0L ;
+    }
+    
+    private static final
+    int access$(int bits, int start, int finish)
+    {
+        // Two ways:
+//        int mask = mask$(start, finish) ;
+//        return bits & mask ;
+        
+        return ( (bits<<(IntLen-finish)) >>> (IntLen-finish+start) ) << start  ;
+    }
+    
+
+    private static final
+    int mask$(int bitIndex)
+    {
+        return 1 << bitIndex ;
+    }
+
+    private static final
+    int mask$(int start, int finish)
+    {
+    //        int mask = 0 ;
+    //        if ( finish == int.SIZE )
+    //            // <<int.SIZE is a no-op 
+    //            mask = -1 ;
+    //        else
+    //            mask = (1L<<finish)-1 ;
+        if ( finish == 0 )
+            // So start is zero and so the mask is zero.
+            return 0 ;
+
+        
+        int mask = -1 ;
+//        mask = mask << (IntLen-finish) >>> (intLen-finish) ;      // Clear the top bits
+//        return mask >>> start << start ;                  // Clear the bottom bits
+        return mask << (IntLen-finish) >>> (IntLen-finish+start) << start ; 
+    }
+
+    private static final
+    int maskZero$(int start, int finish)
+    {
+        return ~mask$(start, finish) ;
+    }
+    
+    private static final
+    void check(int bitIndex)
+    {
+        if ( bitIndex < 0 || bitIndex >= IntLen ) throw new IllegalArgumentException("Illegal bit index: "+bitIndex) ;
+    }
+
+    private static final
+    void check(int start, int finish)
+    {
+        if ( start < 0 || start >= IntLen ) throw new IllegalArgumentException("Illegal start: "+start) ;
+        if ( finish < 0 || finish > IntLen ) throw new IllegalArgumentException("Illegal finish: "+finish) ;
+        if ( start > finish )  throw new IllegalArgumentException("Illegal range: ("+start+", "+finish+")") ;
+    }
+    
+}