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) ; 
+}

Reply via email to