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

Reply via email to