Index: ocean/lib/jdom.jar
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: ocean\lib\jdom.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Index: ocean/lib/commons-lang-2.3.jar
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: ocean\lib\commons-lang-2.3.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Index: ocean/lib/slf4j-api-1.5.2.jar
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: ocean\lib\slf4j-api-1.5.2.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Index: ocean/lib/slf4j-simple-1.5.2.jar
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: ocean\lib\slf4j-simple-1.5.2.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Index: ocean/lib/commons-io-1.3.2.jar
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: ocean\lib\commons-io-1.3.2.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Index: ocean/lib/commons-io-1.3.2.jar
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: ocean\lib\commons-io-1.3.2.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Index: ocean/lib/commons-lang-2.3.jar
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: ocean\lib\commons-lang-2.3.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Index: ocean/lib/jdom.jar
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: ocean\lib\jdom.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Index: ocean/lib/slf4j-api-1.5.2.jar
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: ocean\lib\slf4j-api-1.5.2.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Index: ocean/lib/slf4j-simple-1.5.2.jar
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: ocean\lib\slf4j-simple-1.5.2.jar
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Index: ocean/src/com/imagero/uio/Input.java
===================================================================
--- ocean/src/com/imagero/uio/Input.java	(revision 0)
+++ ocean/src/com/imagero/uio/Input.java	(revision 0)
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio;
+
+import java.io.IOException;
+
+/**
+ * Unfortunately InputStream is not interface.
+ *
+ * @author Andrei Kouznetsov
+ * Date: 08.11.2003
+ * Time: 19:25:33
+ */
+public interface Input {
+	int read() throws IOException;
+	long skip(long n) throws IOException;
+	int read(byte [] b) throws IOException;
+	int read(byte [] b, int off, int len) throws IOException;
+}
Index: ocean/src/com/imagero/uio/FilterDataOutput.java
===================================================================
--- ocean/src/com/imagero/uio/FilterDataOutput.java	(revision 0)
+++ ocean/src/com/imagero/uio/FilterDataOutput.java	(revision 0)
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+ package com.imagero.uio;
+
+import java.io.DataOutput;
+import java.io.IOException;
+
+public class FilterDataOutput implements DataOutput {
+    /**
+     * The underlying output to be filtered.
+     */
+    protected DataOutput out;
+
+    public FilterDataOutput(DataOutput out) {
+        this.out = out;
+    }
+
+    public void write(int b) throws IOException {
+        out.write(b);
+    }
+
+    public void write(byte b[]) throws IOException {
+        write(b, 0, b.length);
+    }
+
+    public void write(byte b[], int off, int len) throws IOException {
+        if ((off | len | (b.length - (len + off)) | (off + len)) < 0)
+            throw new IndexOutOfBoundsException();
+
+        for (int i = 0; i < len; i++) {
+            write(b[off + i]);
+        }
+    }
+
+    public void writeBoolean(boolean v) throws IOException {
+        out.writeBoolean(v);
+    }
+
+    public void writeByte(int v) throws IOException {
+        out.writeByte(v);
+    }
+
+    public void writeShort(int v) throws IOException {
+        out.writeShort(v);
+    }
+
+    public void writeChar(int v) throws IOException {
+        out.writeChar(v);
+    }
+
+    public void writeInt(int v) throws IOException {
+        out.writeInt(v);
+    }
+
+    public void writeLong(long v) throws IOException {
+        out.writeLong(v);
+    }
+
+    public void writeFloat(float v) throws IOException {
+        out.writeFloat(v);
+    }
+
+    public void writeDouble(double v) throws IOException {
+        out.writeDouble(v);
+    }
+
+    public void writeBytes(String s) throws IOException {
+        out.writeBytes(s);
+    }
+
+    public void writeChars(String s) throws IOException {
+        out.writeChars(s);
+    }
+
+    public void writeUTF(String str) throws IOException {
+        out.writeUTF(str);
+    }
+}
Index: ocean/src/com/imagero/uio/ByteArray.java
===================================================================
--- ocean/src/com/imagero/uio/ByteArray.java	(revision 0)
+++ ocean/src/com/imagero/uio/ByteArray.java	(revision 0)
@@ -0,0 +1,22 @@
+package com.imagero.uio;
+
+/**
+ * Date: 09.04.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class ByteArray {
+
+    byte[] buffer;
+
+    int position;
+    int bitOffset;
+
+    public static final int[] N_MASK = {0xFF, 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+    public static final int[] K_MASK = {0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF};
+    public static final int[] I_MASK = {0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF};
+
+    public ByteArray(byte[] buffer) {
+        this.buffer = buffer;
+    }
+}
Index: ocean/src/com/imagero/uio/Transformer.java
===================================================================
--- ocean/src/com/imagero/uio/Transformer.java	(revision 0)
+++ ocean/src/com/imagero/uio/Transformer.java	(revision 0)
@@ -0,0 +1,495 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio;
+
+import com.imagero.uio.io.BitInputStream;
+import com.imagero.uio.io.BitOutputStream;
+import com.imagero.uio.io.ByteArrayOutputStreamExt;
+import com.imagero.uio.RandomAccessInput;
+import com.imagero.uio.xform.XTransformer;
+import com.imagero.uio.xform.ByteToXBE;
+import com.imagero.uio.xform.ByteToXLE;
+import com.imagero.uio.xform.XtoByteBE;
+import com.imagero.uio.xform.XtoByteLE;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+/**
+ * Primitive type conversion, array copying, etc.
+ *
+ * @author Andrey Kuznetsov
+ */
+public class Transformer extends XTransformer {
+
+
+
+    public static final int byteToInt(byte[] source, int sourceOffset, int[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return ByteToXBE.byteToInt(source, sourceOffset, dest, destOffset);
+        } else {
+            return ByteToXLE.byteToInt(source, sourceOffset, dest, destOffset);
+        }
+    }
+
+    public static final void byteToInt(byte[] source, int sourceOffset, int count, int[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            ByteToXBE.byteToInt(source, sourceOffset, count, dest, destOffset);
+        } else {
+            ByteToXLE.byteToInt(source, sourceOffset, count, dest, destOffset);
+        }
+    }
+
+    public static final int intToByte(int[] source, int sourceOffset, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return XtoByteBE.intToByte(source, sourceOffset, dest, destOffset);
+        } else {
+            return XtoByteLE.intToByte(source, sourceOffset, dest, destOffset);
+        }
+    }
+
+    public static final void intToByte(int[] source, int srcOffset, int count, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            XtoByteBE.intToByte(source, srcOffset, count, dest, destOffset);
+        } else {
+            XtoByteLE.intToByte(source, srcOffset, count, dest, destOffset);
+        }
+    }
+
+    public static int intToByte(int v, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return XtoByteBE.intToByte(v, dest, destOffset);
+        } else {
+            return XtoByteLE.intToByte(v, dest, destOffset);
+        }
+    }
+
+    public static final int byteToInt(byte[] source, int sourceOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return ByteToXBE.byteToInt(source, sourceOffset);
+        } else {
+            return ByteToXLE.byteToInt(source, sourceOffset);
+        }
+    }
+
+
+/* **********************************************************************/
+
+    public static int byteToChar(byte[] source, int sourceOffset, char[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return ByteToXBE.byteToChar(source, sourceOffset, dest, destOffset);
+        } else {
+            return ByteToXLE.byteToChar(source, sourceOffset, dest, destOffset);
+        }
+    }
+
+    public static void byteToChar(byte[] source, int sourceOffset, int count, char[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            ByteToXBE.byteToChar(source, sourceOffset, count, dest, destOffset);
+        } else {
+            ByteToXLE.byteToChar(source, sourceOffset, count, dest, destOffset);
+        }
+    }
+
+    public static int byteToChar(byte[] source, int sourceOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return ByteToXBE.byteToChar(source, sourceOffset);
+        } else {
+            return ByteToXLE.byteToChar(source, sourceOffset);
+        }
+    }
+
+/* **********************************************************************/
+    public static int charToByte(char[] source, int srcOffset, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return XtoByteBE.charToByte(source, srcOffset, dest, destOffset);
+        } else {
+            return XtoByteLE.charToByte(source, srcOffset, dest, destOffset);
+        }
+    }
+
+    public static int charToByte(char v, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return XtoByteBE.charToByte(v, dest, destOffset);
+        } else {
+            return XtoByteLE.charToByte(v, dest, destOffset);
+        }
+    }
+
+    public static void charToByte(char[] source, int srcOffset, int count, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            XtoByteBE.charToByte(source, srcOffset, count, dest, destOffset);
+        } else {
+            XtoByteLE.charToByte(source, srcOffset, count, dest, destOffset);
+        }
+    }
+
+/* **********************************************************************/
+
+    public static int byteToDouble(byte[] source, int sourceOffset, double[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return ByteToXBE.byteToDoubleBE(source, sourceOffset, dest, destOffset);
+        } else {
+            return ByteToXLE.byteToDoubleLE(source, sourceOffset, dest, destOffset);
+        }
+    }
+
+    public static void byteToDouble(byte[] source, int sourceOffset, int count, double[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            ByteToXBE.byteToDoubleBE(source, sourceOffset, count, dest, destOffset);
+        } else {
+            ByteToXLE.byteToDoubleLE(source, sourceOffset, count, dest, destOffset);
+        }
+    }
+
+    public static double byteToDouble(byte[] source, int sourceOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return ByteToXBE.byteToDoubleBE(source, sourceOffset);
+        } else {
+            return ByteToXLE.byteToDoubleLE(source, sourceOffset);
+        }
+    }
+
+/* **********************************************************************/
+
+    public static int doubleToByte(double[] source, int srcOffset, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return XtoByteBE.doubleToByteBE(source, srcOffset, dest, destOffset);
+        } else {
+            return XtoByteLE.doubleToByteLE(source, srcOffset, dest, destOffset);
+        }
+    }
+
+    public static void doubleToByte(double[] source, int srcOffset, int count, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            XtoByteBE.doubleToByteBE(source, srcOffset, count, dest, destOffset);
+        } else {
+            XtoByteLE.doubleToByteLE(source, srcOffset, count, dest, destOffset);
+        }
+    }
+
+    public static int doubleToByte(double d, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return XtoByteBE.doubleToByteBE(d, dest, destOffset);
+        } else {
+            return XtoByteLE.doubleToByteLE(d, dest, destOffset);
+        }
+    }
+
+/* **********************************************************************/
+
+    public static int byteToFloat(byte[] source, int sourceOffset, float[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return ByteToXBE.byteToFloatBE(source, sourceOffset, dest, destOffset);
+        } else {
+            return ByteToXLE.byteToFloatLE(source, sourceOffset, dest, destOffset);
+        }
+    }
+
+    public static void byteToFloat(byte[] source, int sourceOffset, int count, float[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            ByteToXBE.byteToFloatBE(source, sourceOffset, count, dest, destOffset);
+        } else {
+            ByteToXLE.byteToFloatLE(source, sourceOffset, count, dest, destOffset);
+        }
+    }
+
+    public static float byteToFloat(byte[] source, int sourceOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return ByteToXBE.byteToFloatBE(source, sourceOffset);
+        } else {
+            return ByteToXLE.byteToFloatLE(source, sourceOffset);
+        }
+    }
+
+/* **********************************************************************/
+
+    public static int floatToByte(float[] source, int offset, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return XtoByteBE.floatToByteBE(source, offset, dest, destOffset);
+        } else {
+            return XtoByteLE.floatToByteLE(source, offset, dest, destOffset);
+        }
+    }
+
+    public static void floatToByte(float[] source, int offset, int count, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            XtoByteBE.floatToByteBE(source, offset, count, dest, destOffset);
+        } else {
+            XtoByteLE.floatToByteLE(source, offset, count, dest, destOffset);
+        }
+    }
+
+    public static int floatToByte(float f, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return XtoByteBE.floatToByteBE(f, dest, destOffset);
+        } else {
+            return XtoByteLE.floatToByteLE(f, dest, destOffset);
+        }
+    }
+
+/* **********************************************************************/
+
+    public static int byteToLong(byte[] source, int sourceOffset, long[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return ByteToXBE.byteToLongBE(source, sourceOffset, dest, destOffset);
+        } else {
+            return ByteToXLE.byteToLongLE(source, sourceOffset, dest, destOffset);
+        }
+    }
+
+    public static void byteToLong(byte[] source, int sourceOffset, int count, long[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            ByteToXBE.byteToLongBE(source, sourceOffset, count, dest, destOffset);
+        } else {
+            ByteToXLE.byteToLongLE(source, sourceOffset, count, dest, destOffset);
+        }
+    }
+
+    public static long byteToLong(byte[] source, int sourceOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return ByteToXBE.byteToLongBE(source, sourceOffset);
+        } else {
+            return ByteToXLE.byteToLongLE(source, sourceOffset);
+        }
+    }
+
+/* **********************************************************************/
+
+    public static int longToByte(long[] source, int offset, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return XtoByteBE.longToByteBE(source, offset, dest, destOffset);
+        } else {
+            return XtoByteLE.longToByteLE(source, offset, dest, destOffset);
+        }
+    }
+
+    public static void longToByte(long[] source, int offset, int count, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            XtoByteBE.longToByteBE(source, offset, count, dest, destOffset);
+        } else {
+            XtoByteLE.longToByteLE(source, offset, count, dest, destOffset);
+        }
+    }
+
+    public static int longToByte(long v, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return XtoByteBE.longToByteBE(v, dest, destOffset);
+        } else {
+            return XtoByteLE.longToByteLE(v, dest, destOffset);
+        }
+    }
+
+/* **********************************************************************/
+
+
+    public static int byteToShort(byte[] source, int sourceOffset, short[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return ByteToXBE.byteToShortBE(source, sourceOffset, dest, destOffset);
+        } else {
+            return ByteToXLE.byteToShortLE(source, sourceOffset, dest, destOffset);
+        }
+    }
+
+    public static void byteToShort(byte[] source, int sourceOffset, int count, short[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            ByteToXBE.byteToShortBE(source, sourceOffset, count, dest, destOffset);
+        } else {
+            ByteToXLE.byteToShortLE(source, sourceOffset, count, dest, destOffset);
+        }
+    }
+
+    public static int byteToShort(byte[] source, int sourceOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return ByteToXBE.byteToShortBE(source, sourceOffset);
+        } else {
+            return ByteToXLE.byteToShortLE(source, sourceOffset);
+        }
+    }
+
+/* **********************************************************************/
+
+    public static int shortToByte(short[] source, int offset, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return XtoByteBE.shortToByteBE(source, offset, dest, destOffset);
+        } else {
+            return XtoByteBE.shortToByteBE(source, offset, dest, destOffset);
+        }
+    }
+
+    public static void shortToByte(short[] source, int offset, int count, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            XtoByteBE.shortToByteBE(source, offset, count, dest, destOffset);
+        } else {
+            XtoByteLE.shortToByteLE(source, offset, count, dest, destOffset);
+        }
+    }
+
+    public static int shortToByte(short v, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return XtoByteBE.shortToByteBE(v, dest, destOffset);
+        } else {
+            return XtoByteLE.shortToByteLE(v, dest, destOffset);
+        }
+    }
+
+    public static final byte[] readByteLine(RandomAccessInput in) throws IOException {
+        long start = in.getFilePointer();
+        long end = start;
+        boolean finished = false;
+        boolean eof = false;
+        int length = 0;
+        while (!finished) {
+            int k = in.read();
+            switch (k) {
+                case -1:
+                    eof = true;
+                    finished = true;
+                    break;
+                case '\n':
+                    finished = true;
+                    end = in.getFilePointer();
+                    length = (int) (end - start);
+                    break;
+                case '\r':
+                    finished = true;
+                    end = in.getFilePointer();
+                    length = (int) (end - start);
+                    if ((in.read()) == '\n') {
+                        end = in.getFilePointer();
+                    }
+                    break;
+                default:
+                    k = 0;
+                    break;
+            }
+        }
+        if (eof && length == 0) {
+            return null;
+        }
+        byte[] b = new byte[length];
+        in.seek(start);
+        in.readFully(b);
+        in.seek(end);
+        return b;
+    }
+
+    public static final int readByteLine(RandomAccessInput in, byte[] dest) throws IOException {
+        long start = in.getFilePointer();
+        long end = start;
+        boolean finished = false;
+        boolean eof = false;
+        int length = 0;
+        int cnt = 0;
+        while (!finished) {
+            if (cnt++ >= dest.length) {
+                finished = true;
+                break;
+            }
+            switch (in.read()) {
+                case -1:
+                    eof = true;
+                    finished = true;
+                    break;
+                case '\n':
+                    finished = true;
+                    end = in.getFilePointer();
+                    length = (int) (end - start);
+                    break;
+                case '\r':
+                    finished = true;
+                    end = in.getFilePointer();
+                    length = (int) (end - start);
+                    if ((in.read()) == '\n') {
+                        end = in.getFilePointer();
+                    }
+                    break;
+            }
+        }
+
+        if (eof && length == 0) {
+            return 0;
+        }
+        if (length == 0) {
+            end = in.getFilePointer();
+            length = Math.min(dest.length, (int) (end - start));
+        }
+        in.seek(start);
+        in.readFully(dest, 0, length);
+        in.seek(end);
+        return length;
+    }
+
+    /**
+     * Make right shift for all bytes of given array
+     * @param src byte array
+     * @param first value used to fill empty bits of first byte in array
+     * @param shift shift amount (from 1 to 7)
+     * @throws java.io.IOException
+     */
+    public static void shiftRight(byte[] src, int first, int shift) throws IOException {
+        ByteArrayOutputStreamExt out = new ByteArrayOutputStreamExt(src.length + 1);
+        BitOutputStream bos = new BitOutputStream(out);
+
+        bos.write(first, shift);
+        for (int i = 0; i < src.length; i++) {
+            bos.write(src[i] & 0xFF);
+        }
+        bos.close();
+
+        byte[] dst = out.drain();
+        System.arraycopy(dst, 0, src, 0, src.length);
+    }
+
+    /**
+     * Make left shift for all bytes in given array
+     * @param src byte array
+     * @param shift shift amount (from 1 to 7)
+     * @throws java.io.IOException
+     */
+    public static void shiftLeft(byte[] src, int shift) throws IOException {
+        ByteArrayInputStream in = new ByteArrayInputStream(src);
+        BitInputStream bis = new BitInputStream(in);
+        bis.read(shift);
+        bis.setBitsToRead(8);
+        int a = bis.read();
+        int p = 0;
+        while (a != -1 && p < src.length) {
+            int b = bis.read();
+            src[p++] = (byte) a;
+            if (b == -1) {
+                break;
+            }
+            a = b;
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/ReadUtil.java
===================================================================
--- ocean/src/com/imagero/uio/ReadUtil.java	(revision 0)
+++ ocean/src/com/imagero/uio/ReadUtil.java	(revision 0)
@@ -0,0 +1,350 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+ package com.imagero.uio;
+
+import com.imagero.uio.io.UnexpectedEOFException;
+
+import java.io.IOException;
+
+/**
+ * Methods to fill data in primitive arrays
+ *
+ * Date: 01.03.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class ReadUtil {
+    public static int read(RandomAccessInput in, short[] dest) throws IOException {
+        return read(in, dest, 0, dest.length, in.getByteOrder());
+    }
+
+    public static int read(RandomAccessInput in, short[] dest, int byteOrder) throws IOException {
+        return read(in, dest, 0, dest.length, byteOrder);
+    }
+
+    public static int read(RandomAccessInput in, short[] dest, int offset, int length) throws IOException {
+        return read(in, dest, offset, length, in.getByteOrder());
+    }
+
+    public static int read(RandomAccessInput in, char[] dest) throws IOException {
+        return read(in, dest, 0, dest.length, in.getByteOrder());
+    }
+
+    public static int read(RandomAccessInput in, char[] dest, int byteOrder) throws IOException {
+        return read(in, dest, 0, dest.length, byteOrder);
+    }
+
+    public static int read(RandomAccessInput in, char[] dest, int offset, int length) throws IOException {
+        return read(in, dest, offset, length, in.getByteOrder());
+    }
+
+    public static int read(RandomAccessInput in, int[] dest) throws IOException {
+        return read(in, dest, 0, dest.length, in.getByteOrder());
+    }
+
+    public static int read(RandomAccessInput in, int[] dest, int byteOrder) throws IOException {
+        return read(in, dest, 0, dest.length, byteOrder);
+    }
+
+    public static int read(RandomAccessInput in, int[] dest, int offset, int length) throws IOException {
+        return read(in, dest, offset, length, in.getByteOrder());
+    }
+
+    public static int read(RandomAccessInput in, long[] dest) throws IOException {
+        return read(in, dest, 0, dest.length, in.getByteOrder());
+    }
+
+    public static int read(RandomAccessInput in, long[] dest, int byteOrder) throws IOException {
+        return read(in, dest, 0, dest.length, byteOrder);
+    }
+
+    public static int read(RandomAccessInput in, long[] dest, int offset, int length) throws IOException {
+        return read(in, dest, offset, length, in.getByteOrder());
+    }
+
+    public static int read(RandomAccessInput in, float[] dest) throws IOException {
+        return read(in, dest, 0, dest.length, in.getByteOrder());
+    }
+
+    public static int read(RandomAccessInput in, float[] dest, int byteOrder) throws IOException {
+        return read(in, dest, 0, dest.length, byteOrder);
+    }
+
+    public static int read(RandomAccessInput in, float[] dest, int offset, int length) throws IOException {
+        return read(in, dest, offset, length, in.getByteOrder());
+    }
+
+    public static int read(RandomAccessInput in, double[] dest) throws IOException {
+        return read(in, dest, 0, dest.length, in.getByteOrder());
+    }
+
+    public static int read(RandomAccessInput in, double[] dest, int byteOrder) throws IOException {
+        return read(in, dest, 0, dest.length, byteOrder);
+    }
+
+    public static int read(RandomAccessInput in, double[] dest, int offset, int length) throws IOException {
+        return read(in, dest, offset, length, in.getByteOrder());
+    }
+
+    public static void readFully(RandomAccessInput in, short[] dest) throws IOException {
+        readFully(in, dest, 0, dest.length, in.getByteOrder());
+    }
+
+    public static void readFully(RandomAccessInput in, short[] dest, int byteOrder) throws IOException {
+        readFully(in, dest, 0, dest.length, byteOrder);
+    }
+
+    public static void readFully(RandomAccessInput in, short[] dest, int destOffset, int len) throws IOException {
+        readFully(in, dest, destOffset, len, in.getByteOrder());
+    }
+
+    public static void readFully(RandomAccessInput in, char[] dest) throws IOException {
+        readFully(in, dest, 0, dest.length, in.getByteOrder());
+    }
+
+    public static void readFully(RandomAccessInput in, char[] dest, int byteOrder) throws IOException {
+        readFully(in, dest, 0, dest.length, byteOrder);
+    }
+
+    public static void readFully(RandomAccessInput in, char[] dest, int destOffset, int len) throws IOException {
+        readFully(in, dest, destOffset, len, in.getByteOrder());
+    }
+
+    public static void readFully(RandomAccessInput in, int[] dest) throws IOException {
+        readFully(in, dest, 0, dest.length, in.getByteOrder());
+    }
+
+    public static void readFully(RandomAccessInput in, int[] dest, int byteOrder) throws IOException {
+        readFully(in, dest, 0, dest.length, byteOrder);
+    }
+
+    public static void readFully(RandomAccessInput in, int[] dest, int destOffset, int len) throws IOException {
+        readFully(in, dest, destOffset, len, in.getByteOrder());
+    }
+
+    public static void readFully(RandomAccessInput in, long[] dest) throws IOException {
+        readFully(in, dest, 0, dest.length, in.getByteOrder());
+    }
+
+    public static void readFully(RandomAccessInput in, long[] dest, int byteOrder) throws IOException {
+        readFully(in, dest, 0, dest.length, byteOrder);
+    }
+
+    public static void readFully(RandomAccessInput in, long[] dest, int destOffset, int len) throws IOException {
+        readFully(in, dest, destOffset, len, in.getByteOrder());
+    }
+
+    public static void readFully(RandomAccessInput in, float[] dest) throws IOException {
+        readFully(in, dest, 0, dest.length, in.getByteOrder());
+    }
+
+    public static void readFully(RandomAccessInput in, float[] dest, int byteOrder) throws IOException {
+        readFully(in, dest, 0, dest.length, byteOrder);
+    }
+
+    public static void readFully(RandomAccessInput in, float[] dest, int destOffset, int len) throws IOException {
+        readFully(in, dest, destOffset, len, in.getByteOrder());
+    }
+
+    public static void readFully(RandomAccessInput in, double[] dest) throws IOException {
+        readFully(in, dest, 0, dest.length, in.getByteOrder());
+    }
+
+    public static void readFully(RandomAccessInput in, double[] dest, int byteOrder) throws IOException {
+        readFully(in, dest, 0, dest.length, byteOrder);
+    }
+
+    public static void readFully(RandomAccessInput in, double[] dest, int destOffset, int len) throws IOException {
+        readFully(in, dest, destOffset, len, in.getByteOrder());
+    }
+
+    public static void readFully(RandomAccessInput in, short[] dest, int offset, int length, int byteOrder) throws IOException {
+        int sum = 0;
+        while (length > 0) {
+            int read = read(in, dest, offset, length, byteOrder);
+            if (read <= 0) {
+                throw new UnexpectedEOFException(sum);
+            }
+            sum += read;
+            length -= read;
+            offset += read;
+        }
+    }
+
+    public static void readFully(RandomAccessInput in, char[] dest, int offset, int length, int byteOrder) throws IOException {
+        int sum = 0;
+        while (length > 0) {
+            int read = read(in, dest, offset, length, byteOrder);
+            if (read <= 0) {
+                throw new UnexpectedEOFException(sum);
+            }
+            sum += read;
+            length -= read;
+            offset += read;
+        }
+    }
+
+    public static void readFully(RandomAccessInput in, int[] dest, int offset, int length, int byteOrder) throws IOException {
+        int sum = 0;
+        while (length > 0) {
+            int read = read(in, dest, offset, length, byteOrder);
+            if (read <= 0) {
+                throw new UnexpectedEOFException(sum);
+            }
+            sum += read;
+            length -= read;
+            offset += read;
+        }
+    }
+
+    public static void readFully(RandomAccessInput in, long[] dest, int offset, int length, int byteOrder) throws IOException {
+        int sum = 0;
+        while (length > 0) {
+            int read = read(in, dest, offset, length, byteOrder);
+            if (read <= 0) {
+                throw new UnexpectedEOFException(sum);
+            }
+            sum += read;
+            length -= read;
+            offset += read;
+        }
+    }
+
+    public static void readFully(RandomAccessInput in, float[] dest, int offset, int length, int byteOrder) throws IOException {
+        int sum = 0;
+        while (length > 0) {
+            int read = read(in, dest, offset, length, byteOrder);
+            if (read <= 0) {
+                throw new UnexpectedEOFException(sum);
+            }
+            sum += read;
+            length -= read;
+            offset += read;
+        }
+    }
+
+    public static void readFully(RandomAccessInput in, double[] dest, int offset, int length, int byteOrder) throws IOException {
+        int sum = 0;
+        while (length > 0) {
+            int read = read(in, dest, offset, length, byteOrder);
+            if (read <= 0) {
+                throw new UnexpectedEOFException(sum);
+            }
+            sum += read;
+            length -= read;
+            offset += read;
+        }
+    }
+
+    public static int read(RandomAccessInput in, short[] dest, int destOffset, int len, int byteOrder) throws IOException {
+        byte[] b = new byte[len * 2];
+        int cnt = in.read(b);
+
+        if ((cnt & 1) != 0) {
+            in.seek(in.getFilePointer() - 1);
+        }
+        final int count = cnt >> 1;
+        Transformer.byteToShort(b, 0, count, dest, destOffset, byteOrder == RandomAccessInput.BIG_ENDIAN);
+        return count;
+    }
+
+    public static int read(RandomAccessInput in, char[] dest, int destOffset, int len, int byteOrder) throws IOException {
+        byte[] b = new byte[len * 2];
+
+        int cnt = in.read(b);
+
+        if ((cnt & 1) != 0) {
+            in.seek(in.getFilePointer() - 1);
+        }
+
+        final int count = cnt >> 1;
+        Transformer.byteToChar(b, 0, count, dest, destOffset, byteOrder == RandomAccessInput.BIG_ENDIAN);
+        return count;
+    }
+
+    public static int read(RandomAccessInput in, int[] dest, int destOffset, int len, int byteOrder) throws IOException {
+        byte[] b = new byte[len * 4];
+
+        int cnt = in.read(b);
+
+        int r3 = cnt & 3;
+        if (r3 != 0) {
+            in.seek(in.getFilePointer() - r3);
+        }
+
+        final int count = cnt >> 2;
+        Transformer.byteToInt(b, 0, count, dest, destOffset, byteOrder == RandomAccessInput.BIG_ENDIAN);
+        return count;
+    }
+
+    public static int read(RandomAccessInput in, float[] dest, int destOffset, int len, int byteOrder) throws IOException {
+        byte[] b = new byte[len * 4];
+        int cnt = in.read(b);
+
+        int r3 = cnt & 3;
+        if (r3 != 0) {
+            in.seek(in.getFilePointer() - r3);
+        }
+
+        final int count = cnt >> 2;
+        Transformer.byteToFloat(b, 0, count, dest, destOffset, byteOrder == RandomAccessInput.BIG_ENDIAN);
+        return count;
+    }
+
+    public static int read(RandomAccessInput in, long[] dest, int destOffset, int len, int byteOrder) throws IOException {
+        byte[] b = new byte[len * 8];
+        int cnt = in.read(b);
+
+        int r7 = cnt & 7;
+        if (r7 != 0) {
+            in.seek(in.getFilePointer() - r7);
+        }
+
+        final int count = cnt >> 3;
+        Transformer.byteToLong(b, 0, count, dest, destOffset, byteOrder == RandomAccessInput.BIG_ENDIAN);
+        return count;
+    }
+
+    public static int read(RandomAccessInput in, double[] dest, int destOffset, int len, int byteOrder) throws IOException {
+        byte[] b = new byte[len * 8];
+        int cnt = in.read(b);
+
+        int r7 = cnt & 7;
+        if (r7 != 0) {
+            in.seek(in.getFilePointer() - r7);
+        }
+
+        final int count = cnt >> 3;
+        Transformer.byteToDouble(b, 0, count, dest, destOffset, byteOrder == RandomAccessInput.BIG_ENDIAN);
+        return count;
+    }
+}
Index: ocean/src/com/imagero/uio/Seekable.java
===================================================================
--- ocean/src/com/imagero/uio/Seekable.java	(revision 0)
+++ ocean/src/com/imagero/uio/Seekable.java	(revision 0)
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+ package com.imagero.uio;
+
+import java.io.IOException;
+
+/**
+ * Date: 01.03.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public interface Seekable {
+    long getFilePointer() throws IOException;
+
+    long length() throws IOException;
+
+    void seek(long offset) throws IOException;
+
+    void close() throws IOException;
+}
Index: ocean/src/com/imagero/uio/Sys.java
===================================================================
--- ocean/src/com/imagero/uio/Sys.java	(revision 0)
+++ ocean/src/com/imagero/uio/Sys.java	(revision 0)
@@ -0,0 +1,335 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio;
+
+import java.io.IOException;
+import java.io.PrintStream;
+
+/**
+ * @author Andrei Kouznetsov
+ *         Date: 05.07.2004
+ *         Time: 15:02:01
+ */
+public class Sys {
+    public static final PrintStreamFilter out = new PrintStreamFilter(java.lang.System.out);
+    public static final PrintStreamFilter err = new PrintStreamFilter(java.lang.System.err);
+
+    public static PrintStreamFilter getErr() {
+        return err;
+    }
+
+    /**
+     * Set PrintStream used for output.
+     * Set to null to disable output.
+     * @param err PrintStream or null
+     */
+    public static void setErr(PrintStream err) {
+        Sys.err.setPs(err);
+    }
+
+    public static PrintStreamFilter getOut() {
+        return out;
+    }
+
+    /**
+     * Set PrintStream used for output.
+     * Set to null to disable output.
+     * @param out PrintStream or null
+     */
+    public static void setOut(PrintStream out) {
+        Sys.out.setPs(out);
+    }
+
+    public static class PrintStreamFilter {
+        PrintStream ps;
+
+        public PrintStreamFilter(PrintStream ps) {
+            this.ps = ps;
+        }
+
+        public PrintStream getPrintStream() {
+            return ps;
+        }
+
+        void setPs(PrintStream ps) {
+            this.ps = ps;
+        }
+
+        public void print(String x) {
+            if (ps == null) {
+                return;
+            }
+            if (x == null) {
+                ps.print(x);
+                return;
+            }
+            char[] chars = new char[x.length()];
+            x.getChars(0, x.length(), chars, 0);
+            int p = chars.length;
+            for (int j = 0; j < p; j++) {
+                char c = chars[j];
+                if (Character.isLetterOrDigit(c)) {
+                    chars[j] = c;
+                } else if (Character.isWhitespace(c)) {
+                    chars[j] = c;
+                } else if (Character.isISOControl(c)) {
+                    chars[j] = '.';
+                } else {
+                    chars[j] = c;
+                }
+            }
+            ps.print(chars);
+        }
+
+        public void println(String x) {
+            print(x);
+            println();
+        }
+
+        public void println() {
+            if (ps == null) {
+                return;
+            }
+            ps.println();
+        }
+
+        public void flush() {
+            if (ps == null) {
+                return;
+            }
+            ps.flush();
+        }
+
+        public void close() {
+            if (ps == null) {
+                return;
+            }
+            ps.close();
+        }
+
+        public boolean checkError() {
+            if (ps == null) {
+                return false;
+            }
+            return ps.checkError();
+        }
+
+        public void write(int b) {
+            if (ps == null) {
+                return;
+            }
+            ps.write(b);
+        }
+
+        public void write(byte buf[], int off, int len) {
+            if (ps == null) {
+                return;
+            }
+            ps.write(buf, off, len);
+        }
+
+        public void print(boolean b) {
+            if (ps == null) {
+                return;
+            }
+            ps.print(b);
+        }
+
+        public void print(char c) {
+            if (ps == null) {
+                return;
+            }
+            ps.print(c);
+        }
+
+        public void print(int i) {
+            if (ps == null) {
+                return;
+            }
+            ps.print(i);
+        }
+
+        public void print(long l) {
+            if (ps == null) {
+                return;
+            }
+            ps.print(l);
+        }
+
+        public void print(float f) {
+            if (ps == null) {
+                return;
+            }
+            ps.print(f);
+        }
+
+        public void print(double d) {
+            if (ps == null) {
+                return;
+            }
+            ps.print(d);
+        }
+
+        public void print(char s[]) {
+            if (ps == null) {
+                return;
+            }
+            ps.print(s);
+        }
+
+        public void print(Object obj) {
+            if (ps == null) {
+                return;
+            }
+            ps.print(obj);
+        }
+
+        public void println(boolean x) {
+            if (ps == null) {
+                return;
+            }
+            ps.println(x);
+        }
+
+        public void println(char x) {
+            if (ps == null) {
+                return;
+            }
+            ps.println(x);
+        }
+
+        public void println(int x) {
+            if (ps == null) {
+                return;
+            }
+            ps.println(x);
+        }
+
+        public void println(long x) {
+            if (ps == null) {
+                return;
+            }
+            ps.println(x);
+        }
+
+        public void println(float x) {
+            if (ps == null) {
+                return;
+            }
+            ps.println(x);
+        }
+
+        public void println(double x) {
+            if (ps == null) {
+                return;
+            }
+            ps.println(x);
+        }
+
+        public void println(char x[]) {
+            if (ps == null) {
+                return;
+            }
+            ps.println(x);
+        }
+
+        public void println(Object x) {
+            if (ps == null) {
+                return;
+            }
+            ps.println(x);
+        }
+
+        public void write(byte b[]) throws IOException {
+            if (ps == null) {
+                return;
+            }
+            ps.write(b);
+        }
+
+        public void println(Object[] objects) {
+            for (int i = 0; i < objects.length; i++) {
+                print(objects[i]);
+            }
+            println();
+        }
+
+        public void println(Object[] objects, String delimiter) {
+            for (int i = 0; i < objects.length; i++) {
+                print(objects[i]);
+                print(delimiter);
+            }
+            println();
+        }
+
+        public void println(long[] longs, String delimiter) {
+            for (int i = 0; i < longs.length; i++) {
+                print(longs[i]);
+                print(delimiter);
+            }
+            println();
+        }
+
+        public void println(int[] numbers, String delimiter) {
+            for (int i = 0; i < numbers.length; i++) {
+                print(numbers[i]);
+                print(delimiter);
+            }
+            println();
+        }
+
+        public void println(char[] chars, String delimiter) {
+            for (int i = 0; i < chars.length; i++) {
+                print(chars[i]);
+                print(delimiter);
+            }
+            println();
+        }
+
+        public void println(short[] shorts, String delimiter) {
+            for (int i = 0; i < shorts.length; i++) {
+                print(shorts[i]);
+                print(delimiter);
+            }
+            println();
+        }
+
+        public void println(byte[] bytes, String delimiter) {
+            for (int i = 0; i < bytes.length; i++) {
+                print(bytes[i]);
+                print(delimiter);
+            }
+            println();
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/UIOStreamBuilder.java
===================================================================
--- ocean/src/com/imagero/uio/UIOStreamBuilder.java	(revision 0)
+++ ocean/src/com/imagero/uio/UIOStreamBuilder.java	(revision 0)
@@ -0,0 +1,782 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio;
+
+import com.imagero.uio.bio.BIOFactory;
+import com.imagero.uio.bio.BufferedRandomAccessIO;
+import com.imagero.uio.bio.ByteArrayRandomAccessIO;
+import com.imagero.uio.bio.IOController;
+import com.imagero.uio.bio.VariableSizeByteBuffer;
+import com.imagero.uio.bio.content.ByteArrayContent;
+import com.imagero.uio.bio.content.CharArrayContent;
+import com.imagero.uio.bio.content.Content;
+import com.imagero.uio.bio.content.DoubleArrayContent;
+import com.imagero.uio.bio.content.FloatArrayContent;
+import com.imagero.uio.bio.content.IntArrayContent;
+import com.imagero.uio.bio.content.LongArrayContent;
+import com.imagero.uio.bio.content.RandomAccessFileContent;
+import com.imagero.uio.bio.content.ShortArrayContent;
+import com.imagero.uio.bio.content.Span;
+import com.imagero.uio.bio.content.SpannedRandomAccessInputContent;
+import com.imagero.uio.bio.content.SpannedRandomAccessIOContent;
+import com.imagero.uio.impl.RandomAccessFileWrapper;
+import com.imagero.uio.impl.RandomAccessFileX;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.net.URL;
+
+/**
+ * <pre>
+ * UIOStreamBuilder is a builder pattern implementation and replacement for RandomAccessFactory.
+ * Usual process looks like
+ * File f = ...;
+ * RandomAccessIO ro = new UIOStreamBuilder(f).setByteOrder(RandomAccessIO.LITTLE_ENDIAN).setBuffered(true).create();
+ * or
+ * RandomAccessIO ra = (RandomAccessIO)new UIOStreamBuilder(f).setMode(UIOStreamBuilder.READ_WRITE).create();
+ *
+ * Defaul values are:
+ * mode - UIOStreamBuilder.READ_ONLY
+ * byte order - RandomAccessIO.BIG_ENDIAN
+ * buffered - false (however some streams are always buffered)
+ *
+ * </pre>
+ * @see #create
+ * @see #setBuffered
+ * @see #setByteOrder
+ * @see #setBufferSize
+ * @see #setCacheFile
+ * @see #setMaxBufferCount
+ * @see #setMode
+ * @see #setStart
+ * @see #setLength
+ *
+ * @author Andrey Kuznetsov
+ */
+public class UIOStreamBuilder {
+
+    public static final String READ_ONLY = "r";
+    public static final String READ_WRITE = "rw";
+
+    String mode = READ_ONLY;
+
+    int byteOrder = RandomAccessInput.BIG_ENDIAN;
+
+    Long start;
+    Long length;
+
+    private boolean buffered = true;
+
+    private Integer maxBufferCount;
+    private Integer bufferSize;
+
+    Creator creator;
+
+    File cache;
+
+    public static int DEFAULT_CHUNK_SIZE = 256 * 1024;
+    public static int DEFAULT_CHUNK_COUNT = 8;
+
+    public boolean isReadOnly() {
+        if (READ_WRITE.equals(mode)) {
+            return false;
+        }
+        return true;
+    }
+
+    public UIOStreamBuilder(String filename) {
+        this(new File(filename));
+    }
+
+    public UIOStreamBuilder(File file) {
+        this.creator = new FileCreator(file);
+    }
+
+    public UIOStreamBuilder(RandomAccessFile rafSource) {
+        this.creator = new RAFCreator(rafSource);
+    }
+
+    public UIOStreamBuilder(RandomAccessIO ra) {
+        this.creator = new RAIOCreator(ra);
+    }
+
+    public UIOStreamBuilder(RandomAccessInput ro) {
+        this.creator = new RAICreator(ro);
+    }
+
+    public UIOStreamBuilder(RandomAccessInput ro, Span [] spans) {
+        this.creator = new SpannedCreator(ro, spans);
+    }
+
+    public UIOStreamBuilder(byte[] byteSource) {
+        this.creator = new ByteCreator(byteSource);
+    }
+
+    public UIOStreamBuilder(byte[][] byteSource) {
+        if (byteSource.length == 1) {
+            this.creator = new ByteCreator(byteSource[0]);
+        } else {
+            this.creator = new Byte2DCreator(byteSource);
+        }
+    }
+
+    public UIOStreamBuilder(short[] shortSource) {
+        creator = new ShortCreator(shortSource);
+    }
+
+    public UIOStreamBuilder(short[][] shortSource) {
+        creator = new ShortCreator(shortSource);
+    }
+
+    public UIOStreamBuilder(char[] charSource) {
+        creator = new CharCreator(charSource);
+    }
+
+    public UIOStreamBuilder(char[][] charSource) {
+        creator = new CharCreator(charSource);
+    }
+
+    public UIOStreamBuilder(int[] intSource) {
+        creator = new IntCreator(intSource);
+    }
+
+    public UIOStreamBuilder(int[][] intSource) {
+        creator = new IntCreator(intSource);
+    }
+
+    public UIOStreamBuilder(long[] longSource) {
+        creator = new LongCreator(longSource);
+    }
+
+    public UIOStreamBuilder(long[][] longSource) {
+        creator = new LongCreator(longSource);
+    }
+
+    public UIOStreamBuilder(float[] floatSource) {
+        creator = new FloatCreator(floatSource);
+    }
+
+    public UIOStreamBuilder(float[][] floatSource) {
+        creator = new FloatCreator(floatSource);
+    }
+
+    public UIOStreamBuilder(double[] doubleSource) {
+        creator = new DoubleCreator(doubleSource);
+    }
+
+    public UIOStreamBuilder(double[][] doubleSource) {
+        creator = new DoubleCreator(doubleSource);
+    }
+
+    static private File getTmpDir() {
+        String name = System.getProperty("uio.temp.dir");
+        if (name != null && name.length() > 0) {
+            File f = new File(name);
+            if (!f.exists()) {
+                f.mkdirs();
+            }
+            if (f.isDirectory()) {
+                return f;
+            }
+        }
+        return null;
+    }
+
+    static File createTempFile(String prefix) {
+        File dir = getTmpDir();
+        if (dir != null) {
+            return new File(dir, prefix + Integer.toHexString(dir.hashCode()));
+        }
+        return null;
+    }
+
+    /**
+     * always buffered
+     * @param url
+     */
+    public UIOStreamBuilder(URL url) {
+        creator = new URLCreator(url);
+    }
+
+    /**
+     * always buffered
+     * @param in
+     */
+    public UIOStreamBuilder(InputStream in) {
+        creator = new ISCreator(in);
+    }
+
+    /**
+     * Always buffered.
+     * Two things are very important:
+     * 1. closing RandomAccessOutput created by this method does not close OutputStream
+     * 2. To write data to OutputStream RandomAccessOutput must be closed or flushed.
+     *
+     * @param out OutputStream
+     */
+    public UIOStreamBuilder(OutputStream out) {
+        creator = new OSCreator(out);
+    }
+
+    /**
+     * set mode (writeable or read only)
+     * @param mode READ_ONLY or READ_WRITE
+     * @return UIOStreamBuilder
+     */
+    public UIOStreamBuilder setMode(String mode) {
+        if (READ_ONLY.equals(mode) || READ_WRITE.equals(mode)) {
+            this.mode = mode;
+            return this;
+        } else {
+            throw new IllegalArgumentException(mode);
+        }
+    }
+
+    /**
+     * set byte order (big endian or little endian)
+     * @param byteOrder LITTLE_ENDIAN or BIG_ENDIAN (default value - BIG_ENDIAN)
+     * @return UIOStreamBuilder
+     */
+    public UIOStreamBuilder setByteOrder(int byteOrder) {
+        switch (byteOrder) {
+            case RandomAccessInput.LITTLE_ENDIAN:
+            case RandomAccessInput.BIG_ENDIAN:
+                this.byteOrder = byteOrder;
+                return this;
+            default:
+                throw new IllegalArgumentException("" + byteOrder);
+        }
+    }
+
+    /**
+     * set start offset
+     * @param start start offset of stream (default value - 0L)
+     * @return UIOStreamBuilder
+     */
+    public UIOStreamBuilder setStart(long start) {
+        if (start < 0) {
+            throw new IllegalArgumentException(" " + start);
+        }
+        this.start = new Long(start);
+        return this;
+    }
+
+    /**
+     * set stream length
+     * @param length stream length
+     * @return UIOStreamBuilder
+     */
+    public UIOStreamBuilder setLength(long length) {
+        if (length < 0) {
+            throw new IllegalArgumentException(" " + length);
+        }
+        this.length = new Long(length);
+        if (start == null) {
+            start = new Long(0L);
+        }
+        return this;
+    }
+
+    /**
+     * set if stream should be buffered or not (rather a hint because some streams are always buffered)
+     * @param buffered true or false (default value - false)
+     * @return UIOStreamBuilder
+     */
+    public UIOStreamBuilder setBuffered(boolean buffered) {
+        this.buffered = buffered;
+        return this;
+    }
+
+    /**
+     * set maxBufferCount for MemoryAccessManager - for unbuffered streams this parameter is ignored.
+     * @param max
+     * @return UIOStreamBuilder
+     */
+    public UIOStreamBuilder setMaxBufferCount(int max) {
+        this.maxBufferCount = new Integer(max);
+        return this;
+    }
+
+    /**
+     * set size for memory chunks used by MemoryAccessManager - for unbuffered streams this parameter is ignored.
+     * @param bufferSize
+     * @return UIOStreamBuilder
+     */
+    public UIOStreamBuilder setBufferSize(int bufferSize) {
+        this.bufferSize = new Integer(bufferSize);
+        return this;
+    }
+
+    /**
+     * Set file which can be used to cache data (only for Streams)
+     * @param f File
+     * @return UIOStreamBuilder
+     */
+    public UIOStreamBuilder setCacheFile(File f) {
+        this.cache = f;
+        return this;
+    }
+
+    /**
+     * finally create desired stream
+     * @return RandomAccessIinput
+     * @throws java.io.IOException
+     */
+    public RandomAccessInput create() throws IOException {
+        if (length != null && length.longValue() == 0) {
+            Sys.err.println("Warning: stream length is 0");
+        }
+        if (buffered) {
+            return creator.createBuffered();
+        } else {
+            return creator.create();
+        }
+    }
+
+    abstract class Creator {
+        abstract RandomAccessInput create() throws IOException;
+
+        abstract RandomAccessInput createBuffered() throws IOException;
+
+        protected int getByteOrder() {
+            return byteOrder != 0 ? byteOrder : RandomAccessInput.BIG_ENDIAN;
+        }
+
+        protected int getBufferCount() {
+            if (maxBufferCount != null) {
+                return maxBufferCount.intValue();
+            }
+            return DEFAULT_CHUNK_COUNT;
+        }
+
+        protected int getBufferSize() {
+            if (bufferSize != null) {
+                return bufferSize.intValue();
+            }
+            return DEFAULT_CHUNK_SIZE;
+        }
+    }
+
+    class FileCreator extends Creator {
+
+        File fileSource;
+
+        public FileCreator(File fileSource) {
+            this.fileSource = fileSource;
+        }
+
+        public RandomAccessInput create() throws IOException {
+            final RandomAccessFileX rafx = new RandomAccessFileX(fileSource, mode);
+            if (start == null && length == null) {
+                return new RandomAccessFileWrapper(rafx, getByteOrder());
+            } else if (length == null) {
+                return new RandomAccessFileWrapper(rafx, start.longValue(), getByteOrder());
+            } else {
+                return new RandomAccessFileWrapper(rafx, start.longValue(), length.longValue(), getByteOrder());
+            }
+        }
+
+        RandomAccessInput createBuffered() throws IOException {
+            Content bc = new RandomAccessFileContent(fileSource, mode);
+            IOController controller = new IOController(DEFAULT_CHUNK_SIZE, bc);
+            BufferedRandomAccessIO bio = new BufferedRandomAccessIO(controller);
+            bio.setByteOrder(byteOrder);
+            return bio;
+        }
+    }
+
+    class RAFCreator extends Creator {
+        RandomAccessFile rafSource;
+
+        public RAFCreator(RandomAccessFile rafSource) {
+            this.rafSource = rafSource;
+        }
+
+        public RandomAccessInput create() throws IOException {
+            if (start == null && length == null) {
+                return new RandomAccessFileWrapper(rafSource, getByteOrder());
+            } else if (length == null) {
+                return new RandomAccessFileWrapper(rafSource, start.longValue(), getByteOrder());
+            } else {
+                return new RandomAccessFileWrapper(rafSource, start.longValue(), length.longValue(), getByteOrder());
+            }
+        }
+
+        RandomAccessInput createBuffered() throws IOException {
+            Content bc = new RandomAccessFileContent(rafSource);
+            IOController controller = new IOController(DEFAULT_CHUNK_SIZE, bc);
+            return new BufferedRandomAccessIO(controller);
+        }
+    }
+
+    class RAICreator extends Creator {
+        RandomAccessInput roSource;
+
+        public RAICreator(RandomAccessInput roSource) {
+            this.roSource = roSource;
+        }
+
+        protected int getByteOrder() {
+            return byteOrder != 0 ? byteOrder : roSource.getByteOrder();
+        }
+
+        public RandomAccessInput create() throws IOException {
+            return roSource.createInputChild(start != null ? start.longValue() : 0L, 0, byteOrder, true);
+        }
+
+        RandomAccessInput createBuffered() throws IOException {
+            return create();
+        }
+    }
+
+    class RAIOCreator extends Creator {
+        RandomAccessIO raSource;
+
+        public RAIOCreator(RandomAccessIO raSource) {
+            this.raSource = raSource;
+        }
+
+        protected int getByteOrder() {
+            return byteOrder != 0 ? byteOrder : raSource.getByteOrder();
+        }
+
+        public RandomAccessInput create() throws IOException {
+            return raSource.createIOChild(start != null ? start.longValue() : 0L, 0, byteOrder, true);
+        }
+
+        RandomAccessInput createBuffered() throws IOException {
+            return create();
+        }
+    }
+
+    class Byte2DCreator extends Creator {
+
+        byte[][] byteSource;
+
+        public Byte2DCreator(byte[][] byteSource) {
+            this.byteSource = byteSource;
+        }
+
+        RandomAccessInput create() throws IOException {
+            return createBuffered();
+        }
+
+        RandomAccessInput createBuffered() throws IOException {
+            Content content = new ByteArrayContent(byteSource);
+            IOController controller = new IOController(DEFAULT_CHUNK_SIZE, content);
+            return new BufferedRandomAccessIO(controller, start != null ? start.longValue() : 0L);
+        }
+    }
+
+    class ByteCreator extends Creator {
+
+        byte[] byteSource;
+
+        public ByteCreator(byte[] byteSource) {
+            this.byteSource = byteSource;
+        }
+
+        RandomAccessInput create() throws IOException {
+            if (start != null) {
+                VariableSizeByteBuffer vsb;
+                vsb = new VariableSizeByteBuffer(byteSource);
+                return new ByteArrayRandomAccessIO(start.intValue(), length != null? length.intValue(): 0, vsb);
+            }
+            return new ByteArrayRandomAccessIO(byteSource);
+        }
+
+        RandomAccessInput createBuffered() throws IOException {
+            return create();
+        }
+    }
+
+    class ShortCreator extends Creator {
+        short[][] shortSource;
+
+        public ShortCreator(short[] shortSource) {
+            this(new short[][]{shortSource});
+        }
+
+        public ShortCreator(short[][] shortSource) {
+            this.shortSource = shortSource;
+        }
+
+        RandomAccessInput create() throws IOException {
+            Content content = new ShortArrayContent(shortSource);
+            IOController controller = new IOController(DEFAULT_CHUNK_SIZE, content);
+            return new BufferedRandomAccessIO(controller, start != null ? start.longValue() : 0L);
+        }
+
+        RandomAccessInput createBuffered() throws IOException {
+            return create();
+        }
+    }
+
+    class CharCreator extends Creator {
+        char[][] charSource;
+
+        public CharCreator(char[] charSource) {
+            this(new char[][]{charSource});
+        }
+
+        public CharCreator(char[][] charSource) {
+            this.charSource = charSource;
+        }
+
+        RandomAccessInput create() throws IOException {
+            Content content = new CharArrayContent(charSource);
+            IOController controller = new IOController(DEFAULT_CHUNK_SIZE, content);
+            return new BufferedRandomAccessIO(controller, start != null ? start.longValue() : 0L);
+        }
+
+        RandomAccessInput createBuffered() throws IOException {
+            return create();
+        }
+    }
+
+    class IntCreator extends Creator {
+        int[][] intSource;
+
+        public IntCreator(int[] intSource) {
+            this(new int[][]{intSource});
+        }
+
+        public IntCreator(int[][] intSource) {
+            this.intSource = intSource;
+        }
+
+        RandomAccessInput create() throws IOException {
+            Content content = new IntArrayContent(intSource);
+            IOController controller = new IOController(DEFAULT_CHUNK_SIZE, content);
+            return new BufferedRandomAccessIO(controller, start != null ? start.longValue() : 0L);
+        }
+
+        RandomAccessInput createBuffered() throws IOException {
+            return create();
+        }
+    }
+
+    class SpannedCreator extends Creator {
+        Span [] spans;
+        RandomAccessInput raiSource;
+
+        public SpannedCreator(RandomAccessInput raiSource, Span[] spans) {
+            this.raiSource = raiSource;
+            this.spans = spans;
+        }
+
+        RandomAccessInput create() throws IOException {
+            Content content;
+            if(mode == READ_ONLY) {
+                RandomAccessInput inputChild = raiSource.createInputChild(0, 0, getByteOrder(), false);
+                content = new SpannedRandomAccessInputContent(inputChild, spans);
+            }
+            else {
+                if(raiSource instanceof RandomAccessIO) {
+                    RandomAccessIO inputChild = ((RandomAccessIO)raiSource).createIOChild(0, 0, getByteOrder(), false);
+                    content = new SpannedRandomAccessIOContent(inputChild, spans);
+                }
+                else {
+                    RandomAccessInput inputChild = raiSource.createInputChild(0, 0, getByteOrder(), false);
+                    content = new SpannedRandomAccessInputContent(inputChild, spans);
+                }
+            }
+            IOController controller = new IOController(DEFAULT_CHUNK_SIZE, content);
+            return new BufferedRandomAccessIO(controller, 0L);
+        }
+
+        protected int getByteOrder() {
+            return byteOrder != 0 ? byteOrder : raiSource.getByteOrder();
+        }
+
+
+        RandomAccessInput createBuffered() throws IOException {
+            return create();
+        }
+    }
+
+    class LongCreator extends Creator {
+        long[][] longSource;
+
+        public LongCreator(long[] longSource) {
+            this(new long[][]{longSource});
+        }
+
+        public LongCreator(long[][] longSource) {
+            this.longSource = longSource;
+        }
+
+        RandomAccessInput create() throws IOException {
+            Content content = new LongArrayContent(longSource);
+            IOController controller = new IOController(DEFAULT_CHUNK_SIZE, content);
+            return new BufferedRandomAccessIO(controller, start != null ? start.longValue() : 0L);
+        }
+
+        RandomAccessInput createBuffered() throws IOException {
+            return create();
+        }
+    }
+
+    class FloatCreator extends Creator {
+        float[][] floatSource;
+
+        public FloatCreator(float[] floatSource) {
+            this(new float[][]{floatSource});
+        }
+
+        public FloatCreator(float[][] floatSource) {
+            this.floatSource = floatSource;
+        }
+
+        RandomAccessInput create() throws IOException {
+            Content content = new FloatArrayContent(floatSource);
+            IOController controller = new IOController(DEFAULT_CHUNK_SIZE, content);
+            return new BufferedRandomAccessIO(controller, start != null ? start.longValue() : 0L);
+        }
+
+        RandomAccessInput createBuffered() throws IOException {
+            return create();
+        }
+    }
+
+    class DoubleCreator extends Creator {
+        double[][] doubleSource;
+
+        public DoubleCreator(double[] doubleSource) {
+            this(new double[][]{doubleSource});
+        }
+
+        public DoubleCreator(double[][] doubleSource) {
+            this.doubleSource = doubleSource;
+        }
+
+        RandomAccessInput create() throws IOException {
+            Content content = new DoubleArrayContent(doubleSource);
+            IOController controller = new IOController(DEFAULT_CHUNK_SIZE, content);
+            return new BufferedRandomAccessIO(controller, start != null ? start.longValue() : 0L);
+        }
+
+        RandomAccessInput createBuffered() throws IOException {
+            return create();
+        }
+    }
+
+    class URLCreator extends Creator {
+        URL url;
+        FileCreator fileCreator;
+
+        public URLCreator(URL url) {
+            this.url = url;
+            final String protocol = url.getProtocol();
+            if ("file".equalsIgnoreCase(protocol)) {
+                File f = new File(url.getFile());
+                fileCreator = new FileCreator(f);
+            }
+        }
+
+        RandomAccessInput create() throws IOException {
+            if (fileCreator == null) {
+                return create0();
+            } else {
+                return fileCreator.create();
+            }
+        }
+
+        RandomAccessInput createBuffered() throws IOException {
+            if (fileCreator == null) {
+                return create0();
+            } else {
+                return fileCreator.createBuffered();
+            }
+        }
+
+        private RandomAccessInput create0() {
+            if (cache == null) {
+                cache = createTempFile("urc");
+            }
+            IOController controller = BIOFactory.createIOController(url, cache, getBufferSize());
+            BufferedRandomAccessIO rio = new BufferedRandomAccessIO(controller);
+            rio.setByteOrder(byteOrder);
+            return rio;
+        }
+    }
+
+    class ISCreator extends Creator {
+        InputStream inputStreamSource;
+
+        public ISCreator(InputStream inputStreamSource) {
+            this.inputStreamSource = inputStreamSource;
+        }
+
+        RandomAccessInput create() throws IOException {
+            if (inputStreamSource instanceof ByteArrayInputStream) {
+                return new BaisWrapper((ByteArrayInputStream) inputStreamSource);
+            } else {
+                if (cache == null) {
+                    cache = createTempFile("isc");
+                }
+                IOController controller = BIOFactory.createIOController(inputStreamSource, cache, getBufferSize());
+                BufferedRandomAccessIO bio = new BufferedRandomAccessIO(controller);
+                bio.setByteOrder(byteOrder);
+                return bio;
+            }
+        }
+
+        RandomAccessInput createBuffered() throws IOException {
+            return create();
+        }
+    }
+
+    class OSCreator extends Creator {
+        OutputStream outputStreamSource;
+
+        public OSCreator(OutputStream outputStreamSource) {
+            this.outputStreamSource = outputStreamSource;
+            setMode(READ_WRITE);
+        }
+
+        RandomAccessInput create() throws IOException {
+            final BufferedRandomAccessIO bio = BIOFactory.create(outputStreamSource);
+            bio.setByteOrder(byteOrder);
+            return bio;
+        }
+
+        RandomAccessInput createBuffered() throws IOException {
+            return create();
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/BaisWrapper.java
===================================================================
--- ocean/src/com/imagero/uio/BaisWrapper.java	(revision 0)
+++ ocean/src/com/imagero/uio/BaisWrapper.java	(revision 0)
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+ package com.imagero.uio;
+
+import com.imagero.uio.impl.AbstractRandomAccessInput;
+import com.imagero.uio.io.IOutils;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+/**
+ * Wrapper for ByteArrayInputStream which gives possibility to use it as AbstractRandomAccessInput.
+ * Date: 19.12.2007
+ *
+ * @author Andrey Kuznetsov
+ */
+public class BaisWrapper extends AbstractRandomAccessInput {
+    long pos;
+    long mark;
+    long count;
+
+    long offset;
+
+    ByteArrayInputStream in;
+
+    public BaisWrapper(ByteArrayInputStream in) {
+        this.in = in;
+        in.mark(0);
+        count = in.available();
+    }
+
+    public BaisWrapper(BaisWrapper in, long offset, int byteOrder) {
+        this.in = in.in;
+        this.offset = offset;
+        count = in.count - (in.offset + (int)offset);
+        setByteOrder(byteOrder);
+    }
+
+    public BaisWrapper(BaisWrapper in, long offset, long length, int byteOrder) {
+        this.in = in.in;
+        this.offset = offset;
+        count = in.count - (in.offset + (int) offset);
+        if(length > 0) {
+            count = Math.min(count, length);
+        }
+        setByteOrder(byteOrder);
+    }
+
+    synchronized public int read() {
+        seek0(pos);
+        int k = in.read();
+        pos++;
+        return k;
+    }
+
+    synchronized public int read(byte b[], int off, int len) {
+        seek0(pos);
+        int read = in.read(b, off, len);
+        pos += read;
+        return read;
+    }
+
+    synchronized public long skip(long n) {
+        seek0(pos);
+        long length = in.skip(n);
+        pos += length;
+        return length;
+    }
+
+    synchronized public void seek(long position) {
+        seek0(position);
+    }
+
+    private void seek0(long position) {
+        in.reset();
+        pos = 0;
+        if(position + offset > 0) {
+            skip(position + offset);
+        }
+    }
+
+    public boolean markSupported() {
+        return true;
+    }
+
+    public synchronized void mark(int readlimit) {
+        mark = pos;
+    }
+
+    public synchronized void reset() {
+        seek0(mark);
+    }
+
+    public long getFilePointer() {
+        return pos;
+    }
+
+    public long length() {
+        return count - offset;
+    }
+
+    public RandomAccessInput createInputChild(long offset, long length, int byteOrder, boolean syncPointer) {
+        return new BaisWrapper(this, offset, length, byteOrder);
+    }
+
+    public void close() {
+        IOutils.closeStream(in);
+    }
+
+    public InputStream createInputStream(long offset) {
+        return new BaisWrapper(this, offset, byteOrder);
+    }
+
+    public long getChildPosition(InputStream child) {
+        if(child instanceof BaisWrapper) {
+            BaisWrapper wrapper = (BaisWrapper) child;
+            return wrapper.getFilePointer();
+        }
+        return -1;
+    }
+
+    public void setChildPosition(InputStream child, long position) {
+        if (child instanceof BaisWrapper) {
+            BaisWrapper wrapper = (BaisWrapper) child;
+            wrapper.seek(position);
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/RandomAccessInput.java
===================================================================
--- ocean/src/com/imagero/uio/RandomAccessInput.java	(revision 0)
+++ ocean/src/com/imagero/uio/RandomAccessInput.java	(revision 0)
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.DataInput;
+
+/**
+ * @author Andrey Kuznetsov
+ */
+public interface RandomAccessInput extends Input, DataInput, Endian, Seekable {
+
+    /**
+     * Create RandomAccessInput child from given offset
+     * @param offset offset in parent stream
+     * @param byteOrder byte order for new stream
+     * @param syncPointer if true then streams will share same stream position
+     * @return RandomAccessInput
+     * @throws IOException
+     */
+    RandomAccessInput createInputChild(long offset, long length, int byteOrder, boolean syncPointer) throws IOException;
+
+    /**
+     * Create InputStream starting from given offset.
+     * @param offset offset in parent (for created InputStream) stream
+     * @return InputStream
+     */
+    InputStream createInputStream(long offset);
+
+    /**
+     * get stream position of child InputStream (created with createInputStream());
+     * @param child child InputStream
+     * @return Stream position or -1 if supplied InputStream is not a child of this stream
+     */
+    long getChildPosition(InputStream child);
+
+    /**
+     * set stream position of child InputStream.
+     * @param child child InputStream
+     * @param position new stream position
+     */ 
+    void setChildPosition(InputStream child, long position);
+
+    /**
+     * Same as readLine, but returns byte array instead of String
+     * @return byte array
+     * @throws IOException
+     */
+    byte [] readByteLine() throws IOException;
+
+    /**
+     * Same as readByteLine(), but read in given byte array and returns how much was read.
+     * @param dest byte array
+     * @return how much bytes read
+     * @throws IOException
+     */
+    int readByteLine(byte [] dest) throws IOException;
+
+    /**
+     * Same as readShort() from DataInput, but uses given byte order instead of streams byte order
+     * @param byteOrder byte order
+     * @return short
+     * @throws IOException
+     */
+    short readShort(int byteOrder) throws IOException;
+
+    /**
+     * Same as readUnsignedShort() from DataInput, but uses given byte order instead of streams byte order
+     * @param byteOrder byte order
+     * @return int
+     * @throws IOException
+     */
+    int readUnsignedShort(int byteOrder) throws IOException;
+
+    /**
+     * Same as readChar() from DataInput, but uses given byte order instead of streams byte order
+     * @param byteOrder byte order
+     * @return char
+     * @throws IOException
+     */
+    char readChar(int byteOrder) throws IOException;
+
+    /**
+     * Same as readInt() from DataInput, but uses given byte order instead of streams byte order
+     * @param byteOrder byte order
+     * @return int
+     * @throws IOException
+     */
+    int readInt(int byteOrder) throws IOException;
+
+    /**
+     * Same as readLong() from DataInput, but uses given byte order instead of streams byte order
+     * @param byteOrder byte order
+     * @return long
+     * @throws IOException
+     */
+    long readLong(int byteOrder) throws IOException;
+
+    /**
+     * Same as readFloat() from DataInput, but uses given byte order instead of streams byte order
+     * @param byteOrder byte order
+     * @return float
+     * @throws IOException
+     */
+    float readFloat(int byteOrder) throws IOException;
+
+    /**
+     * Same as readDouble() from DataInput, but uses given byte order instead of streams byte order
+     * @param byteOrder byte order
+     * @return double
+     * @throws IOException
+     */
+    double readDouble(int byteOrder) throws IOException;
+
+    /**
+     * Reads four input bytes and returns an long value in the range 0 through 0xFFFFFFFF.
+     * @return long
+     * @throws IOException
+     */
+    long readUnsignedInt() throws IOException;
+
+    /**
+     * Same as readUnsignedInt(), but uses given byte order instead of streams byte order
+     * @param byteOrder byte order
+     * @return long
+     * @throws IOException
+     */
+    long readUnsignedInt(int byteOrder) throws IOException;
+
+    boolean isBuffered();
+}
Index: ocean/src/com/imagero/uio/RandomAccessOutput.java
===================================================================
--- ocean/src/com/imagero/uio/RandomAccessOutput.java	(revision 0)
+++ ocean/src/com/imagero/uio/RandomAccessOutput.java	(revision 0)
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.DataOutput;
+
+
+/**
+ * @author Andrey Kuznetsov
+ */
+public interface RandomAccessOutput extends DataOutput, Endian, Seekable {
+
+    RandomAccessOutput createOutputChild(long offset, int byteOrder, boolean syncPointer) throws IOException;
+    OutputStream createOutputStream(long offset);
+    void flush() throws IOException;
+
+    void writeShort(int v, int byteOrder) throws IOException;
+
+    void writeChar(int v, int byteOrder) throws IOException;
+
+    void writeInt(int v, int byteOrder) throws IOException;
+
+    void writeLong(long v, int byteOrder) throws IOException;
+
+    void writeFloat(float v, int byteOrder) throws IOException;
+
+    void writeDouble(double v, int byteOrder) throws IOException;
+
+    /**
+     * Set length of stream.
+     * @param newLength new stream length
+     * @throws IOException
+     */
+    void setLength(long newLength) throws IOException;
+}
Index: ocean/src/com/imagero/uio/WriteUtil.java
===================================================================
--- ocean/src/com/imagero/uio/WriteUtil.java	(revision 0)
+++ ocean/src/com/imagero/uio/WriteUtil.java	(revision 0)
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+ package com.imagero.uio;
+
+import com.imagero.uio.xform.XtoByteBE;
+import com.imagero.uio.xform.XtoByteLE;
+
+import java.io.IOException;
+
+/**
+ * Methods to write data from primitive arrays.
+ *
+ * Date: 01.03.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class WriteUtil {
+    public static void write(RandomAccessOutput io, short[] data) throws IOException {
+        write(io, data, 0, data.length, io.getByteOrder());
+    }
+
+    public static void write(RandomAccessOutput io, short[] data, int byteOrder) throws IOException {
+        write(io, data, 0, data.length, byteOrder);
+    }
+
+    public static void write(RandomAccessOutput io, short[] data, int offset, int length) throws IOException {
+        write(io, data, offset, length, io.getByteOrder());
+    }
+
+    public static void write(RandomAccessOutput io, char[] data) throws IOException {
+        write(io, data, 0, data.length, io.getByteOrder());
+    }
+
+    public static void write(RandomAccessOutput io, char[] data, int byteOrder) throws IOException {
+        write(io, data, 0, data.length, byteOrder);
+    }
+
+    public static void write(RandomAccessOutput io, char[] data, int offset, int length) throws IOException {
+        write(io, data, offset, length, io.getByteOrder());
+    }
+
+    public static void write(RandomAccessOutput io, int[] data) throws IOException {
+        write(io, data, 0, data.length, io.getByteOrder());
+    }
+
+    public static void write(RandomAccessOutput io, int[] data, int byteOrder) throws IOException {
+        write(io, data, 0, data.length, byteOrder);
+    }
+
+    public static void write(RandomAccessOutput io, int[] data, int offset, int length) throws IOException {
+        write(io, data, offset, length, io.getByteOrder());
+    }
+
+    public static void write(RandomAccessOutput io, float[] data) throws IOException {
+        write(io, data, 0, data.length, io.getByteOrder());
+    }
+
+    public static void write(RandomAccessOutput io, float[] data, int byteOrder) throws IOException {
+        write(io, data, 0, data.length, byteOrder);
+    }
+
+    public static void write(RandomAccessOutput io, float[] data, int offset, int length) throws IOException {
+        write(io, data, offset, length, io.getByteOrder());
+    }
+
+    public static void write(RandomAccessOutput io, long[] data) throws IOException {
+        write(io, data, 0, data.length, io.getByteOrder());
+    }
+
+    public static void write(RandomAccessOutput io, long[] data, int byteOrder) throws IOException {
+        write(io, data, 0, data.length, byteOrder);
+    }
+
+    public static void write(RandomAccessOutput io, long[] data, int offset, int length) throws IOException {
+        write(io, data, offset, length, io.getByteOrder());
+    }
+
+    public static void write(RandomAccessOutput io, double[] data) throws IOException {
+        write(io, data, 0, data.length, io.getByteOrder());
+    }
+
+    public static void write(RandomAccessOutput io, double[] data, int byteOrder) throws IOException {
+        write(io, data, 0, data.length, byteOrder);
+    }
+
+    public static void write(RandomAccessOutput io, double[] data, int offset, int length) throws IOException {
+        write(io, data, offset, length, io.getByteOrder());
+    }
+
+    public static void write(RandomAccessOutput io, short[] sh, int offset, int length, int byteOrder) throws IOException {
+        byte[] b = new byte[length << 1];
+        if(byteOrder == RandomAccessInput.BIG_ENDIAN) {
+            XtoByteBE.shortToByteBE(sh, offset, length, b, 0);
+        }
+        else {
+            XtoByteLE.shortToByteLE(sh, offset, length, b, 0);
+        }
+        io.write(b);
+    }
+
+    public static void write(RandomAccessOutput io, char[] sh, int offset, int length, int byteOrder) throws IOException {
+        byte[] b = transform(sh, offset, length, byteOrder);
+        io.write(b);
+    }
+
+    protected static byte[] transform(char[] sh, int offset, int length, int byteOrder) {
+        byte[] b = new byte[length << 1];
+        if(byteOrder == RandomAccessInput.BIG_ENDIAN) {
+            XtoByteBE.charToByte(sh, offset, length, b, 0);
+        }
+        else {
+            XtoByteLE.charToByte(sh, offset, length, b, 0);
+        }
+        return b;
+    }
+
+    public static void write(RandomAccessOutput io, int[] source, int offset, int length, int byteOrder) throws IOException {
+        byte[] b = transform(source, offset, length, byteOrder);
+        io.write(b);
+    }
+
+    protected static byte[] transform(int[] source, int offset, int length, int byteOrder) {
+        byte[] b = new byte[length << 2];
+        if(byteOrder == RandomAccessInput.BIG_ENDIAN) {
+            XtoByteBE.intToByte(source, offset, length, b, 0);
+        }
+        else {
+            XtoByteLE.intToByte(source, offset, length, b, 0);
+        }
+        return b;
+    }
+
+    public static void write(RandomAccessOutput io, float[] source, int offset, int length, int byteOrder) throws IOException {
+        byte[] b = transform(source, offset, length, byteOrder);
+        io.write(b);
+    }
+
+    protected static byte[] transform(float[] source, int offset, int length, int byteOrder) {
+        byte[] b = new byte[length << 2];
+        if(byteOrder == RandomAccessInput.BIG_ENDIAN) {
+            XtoByteBE.floatToByteBE(source, offset, length, b, 0);
+        }
+        else {
+            XtoByteLE.floatToByteLE(source, offset, length, b, 0);
+        }
+        return b;
+    }
+
+    public static void write(RandomAccessOutput io, long[] source, int offset, int length, int byteOrder) throws IOException {
+        byte[] b = transform(source, offset, length, byteOrder);
+        io.write(b);
+    }
+
+    protected static byte[] transform(long[] source, int offset, int length, int byteOrder) {
+        byte[] b = new byte[length << 3];
+        if(byteOrder == RandomAccessInput.BIG_ENDIAN) {
+            XtoByteBE.longToByteBE(source, offset, length, b, 0);
+        }
+        else {
+            XtoByteLE.longToByteLE(source, offset, length, b, 0);
+        }
+        return b;
+    }
+
+    public static void write(RandomAccessOutput io, double[] source, int offset, int length, int byteOrder) throws IOException {
+        byte[] b = transform(source, offset, length, byteOrder);
+        io.write(b);
+    }
+
+    protected static byte[] transform(double[] source, int offset, int length, int byteOrder) {
+        byte[] b = new byte[length << 3];
+        if(byteOrder == RandomAccessInput.BIG_ENDIAN) {
+            XtoByteBE.doubleToByteBE(source, offset, length, b, 0);
+        }
+        else {
+            XtoByteLE.doubleToByteLE(source, offset, length, b, 0);
+        }
+        return b;
+    }
+}
Index: ocean/src/com/imagero/uio/ByteArrayIO.java
===================================================================
--- ocean/src/com/imagero/uio/ByteArrayIO.java	(revision 0)
+++ ocean/src/com/imagero/uio/ByteArrayIO.java	(revision 0)
@@ -0,0 +1,187 @@
+package com.imagero.uio;
+
+
+/**
+ * ByteArrayIO - this class gives the possibility to read from and write to given ByteArray.
+ * It supports bit offsets and bitwise writing.
+ * It does not extends InputStream or OutputStream, but has similar methods.
+ *
+ * Date: 09.04.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class ByteArrayIO {
+
+    public static int read(ByteArray ba) {
+        return read(ba, 8);
+    }
+
+    /**
+     * read up to 8 bits from given ByteArray
+     * @param ba ByteArray
+     * @param nbits bit count to read
+     * @return int
+     */
+    public static int read(ByteArray ba, final int nbits) {
+        int ret = 0;
+        //nothing to read
+        if (nbits == 0) {
+            return 0;
+        }
+        //too many bits requested
+        if (nbits > 8) {
+            throw new IllegalArgumentException("no more then 8 bits can be read at once");
+        }
+        if(ba.bitOffset == 0 && nbits == 8) {
+            return ba.buffer[ba.position++];
+        }
+        ret = ba.buffer[ba.position] & ByteArray.N_MASK[ba.bitOffset];
+        int rshift = (8 - ba.bitOffset) - nbits;
+        if(rshift > 0) {
+            ret = ret >> rshift;
+        }
+        int bitOffset = ba.bitOffset + nbits;
+        if (bitOffset > 7) {
+            bitOffset -= 8;
+            ba.bitOffset = bitOffset;
+            ba.position++;
+            if (bitOffset > 0) {
+                ret = (ret << bitOffset) | (ba.buffer[ba.position] & ByteArray.N_MASK[ba.bitOffset]);
+            }
+        }
+        return ret;
+    }
+
+    public static int read(ByteArray ba, byte b[]) {
+        return read(ba, b, 0, b.length);
+    }
+
+    /**
+     * Reads data from input stream into an byte array.
+     *
+     * @param b the buffer into which the data is read.
+     * @param off the start offset of the data.
+     * @param len the maximum number of bytes read.
+     * @return the total number of bytes read into the buffer, or -1 if the EOF has been reached.
+     * @exception NullPointerException if supplied byte array is null
+     */
+    public static int read(ByteArray ba, byte b[], int off, int len) {
+        if (len <= 0) {
+            return 0;
+        }
+        int c = read(ba);
+        if (c == -1) {
+            return -1;
+        }
+        b[off] = (byte) c;
+
+        int i = 1;
+        for (; i < len; ++i) {
+            c = read(ba);
+            if (c == -1) {
+                break;
+            }
+            b[off + i] = (byte) c;
+        }
+        return i;
+    }
+
+    /**
+     * Skips some bytes from the input stream.
+     * @param n the number of bytes to be skipped.
+     * @return the actual number of bytes skipped.
+     */
+    public static long skipBytes(ByteArray ba, long n) {
+        int max = (int) Math.min(ba.buffer.length - ba.position, n);
+        ba.position += max;
+        return max;
+    }
+
+    /**
+     * skip some bits
+     * @param n bits to skip
+     * @return number of bits skipped
+     */
+    public static int skipBits(ByteArray ba, int n) {
+        int k = n;
+        int nbits = k % 8;
+        int nbytes = k / 8;
+        int bitOffset = ba.bitOffset + nbits;
+        if (bitOffset > 7) {
+            nbytes++;
+            ba.bitOffset = bitOffset - 8;
+        }
+        ba.position += nbytes;
+        return n;
+    }
+
+    public static void seek(ByteArray ba, int pos) {
+        ba.position = pos;
+    }
+
+    public static void seek(ByteArray ba, int pos, int bitPos) {
+        ba.position = pos + bitPos / 8;
+        ba.bitOffset = bitPos % 8;
+    }
+
+    public static int skipToByteBoundary(ByteArray ba) {
+        if (ba.bitOffset > 0) {
+            int ret = 8 - ba.bitOffset;
+            ba.bitOffset = 0;
+            ba.position++;
+            return ret;
+        }
+        return 0;
+    }
+
+    /**
+     * Writes some bits from the specified int to stream.
+     * @param b int which should be written
+     */
+    public static void write(ByteArray ba, int b) {
+        write(ba, b, 8);
+    }
+
+    /**
+     * Writes up to 8 bits from the specified int to stream.
+     * @param N int which should be written
+     * @param N_BITS bit count to write
+     */
+    public static void write(ByteArray ba, int N, int N_BITS) {
+        if (N_BITS == 0) {
+            return;
+        }
+        if (N_BITS > 8) {
+            throw new IllegalArgumentException("no more then 8 bits can be written at once");
+        }
+        if(ba.bitOffset == 0 && N_BITS == 8) {
+            ba.buffer[ba.position++] = (byte) N;
+        }
+        else {
+            write0(ba, N, N_BITS);
+        }
+    }
+
+    protected final static void write0(ByteArray ba, int N, int N_BITS) {
+        int available = 8 - ba.bitOffset;
+        int a = ba.buffer[ba.position] & 0xFF;
+        int b = a;
+
+        a = ((a >> available) << N_BITS) | (N & ByteArray.K_MASK[N_BITS]);
+        if (N_BITS > available) {
+            a = a >> (N_BITS - available);
+            ba.buffer[ba.position++] = (byte) a;
+            ba.bitOffset = 0;
+            N_BITS -= available;
+            write0(ba, N & ByteArray.N_MASK[N_BITS], N_BITS);
+        } else if (available > N_BITS) {
+            a = a << (available - N_BITS);
+            a |= b & ByteArray.K_MASK[available - N_BITS];
+            ba.buffer[ba.position] = (byte) a;
+            ba.bitOffset += N_BITS;
+        } else {
+            ba.buffer[ba.position++] = (byte) a;
+            ba.bitOffset = 0;
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/RandomAccessIO.java
===================================================================
--- ocean/src/com/imagero/uio/RandomAccessIO.java	(revision 0)
+++ ocean/src/com/imagero/uio/RandomAccessIO.java	(revision 0)
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio;
+
+import java.io.IOException;
+
+
+/**
+ * @author Andrey Kuznetsov
+ */
+public interface RandomAccessIO extends RandomAccessInput, RandomAccessOutput {
+    RandomAccessIO createIOChild(long offset, long length, int byteOrder, boolean syncPointer) throws IOException;
+}
Index: ocean/src/com/imagero/uio/Endian.java
===================================================================
--- ocean/src/com/imagero/uio/Endian.java	(revision 0)
+++ ocean/src/com/imagero/uio/Endian.java	(revision 0)
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+ package com.imagero.uio;
+
+/**
+ * Date: 01.03.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public interface Endian {
+    int BIG_ENDIAN = 0x4D4D;
+    int LITTLE_ENDIAN = 0x4949;
+
+    int getByteOrder();
+
+    void setByteOrder(int byteOrder);
+}
Index: ocean/src/com/imagero/uio/BaisWrapper.java
===================================================================
--- ocean/src/com/imagero/uio/BaisWrapper.java	(revision 0)
+++ ocean/src/com/imagero/uio/BaisWrapper.java	(revision 0)
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+ package com.imagero.uio;
+
+import com.imagero.uio.impl.AbstractRandomAccessInput;
+import com.imagero.uio.io.IOutils;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+/**
+ * Wrapper for ByteArrayInputStream which gives possibility to use it as AbstractRandomAccessInput.
+ * Date: 19.12.2007
+ *
+ * @author Andrey Kuznetsov
+ */
+public class BaisWrapper extends AbstractRandomAccessInput {
+    long pos;
+    long mark;
+    long count;
+
+    long offset;
+
+    ByteArrayInputStream in;
+
+    public BaisWrapper(ByteArrayInputStream in) {
+        this.in = in;
+        in.mark(0);
+        count = in.available();
+    }
+
+    public BaisWrapper(BaisWrapper in, long offset, int byteOrder) {
+        this.in = in.in;
+        this.offset = offset;
+        count = in.count - (in.offset + (int)offset);
+        setByteOrder(byteOrder);
+    }
+
+    public BaisWrapper(BaisWrapper in, long offset, long length, int byteOrder) {
+        this.in = in.in;
+        this.offset = offset;
+        count = in.count - (in.offset + (int) offset);
+        if(length > 0) {
+            count = Math.min(count, length);
+        }
+        setByteOrder(byteOrder);
+    }
+
+    synchronized public int read() {
+        seek0(pos);
+        int k = in.read();
+        pos++;
+        return k;
+    }
+
+    synchronized public int read(byte b[], int off, int len) {
+        seek0(pos);
+        int read = in.read(b, off, len);
+        pos += read;
+        return read;
+    }
+
+    synchronized public long skip(long n) {
+        seek0(pos);
+        long length = in.skip(n);
+        pos += length;
+        return length;
+    }
+
+    synchronized public void seek(long position) {
+        seek0(position);
+    }
+
+    private void seek0(long position) {
+        in.reset();
+        pos = 0;
+        if(position + offset > 0) {
+            skip(position + offset);
+        }
+    }
+
+    public boolean markSupported() {
+        return true;
+    }
+
+    public synchronized void mark(int readlimit) {
+        mark = pos;
+    }
+
+    public synchronized void reset() {
+        seek0(mark);
+    }
+
+    public long getFilePointer() {
+        return pos;
+    }
+
+    public long length() {
+        return count - offset;
+    }
+
+    public RandomAccessInput createInputChild(long offset, long length, int byteOrder, boolean syncPointer) {
+        return new BaisWrapper(this, offset, length, byteOrder);
+    }
+
+    public void close() {
+        IOutils.closeStream(in);
+    }
+
+    public InputStream createInputStream(long offset) {
+        return new BaisWrapper(this, offset, byteOrder);
+    }
+
+    public long getChildPosition(InputStream child) {
+        if(child instanceof BaisWrapper) {
+            BaisWrapper wrapper = (BaisWrapper) child;
+            return wrapper.getFilePointer();
+        }
+        return -1;
+    }
+
+    public void setChildPosition(InputStream child, long position) {
+        if (child instanceof BaisWrapper) {
+            BaisWrapper wrapper = (BaisWrapper) child;
+            wrapper.seek(position);
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/bio/Ring.java
===================================================================
--- ocean/src/com/imagero/uio/bio/Ring.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/Ring.java	(revision 0)
@@ -0,0 +1,32 @@
+package com.imagero.uio.bio;
+
+/**
+ * Ring - minimalistic Ring implementation.
+ *
+ * Date: 29.08.2007
+ * @author Andrey Kuznetsov
+ */
+class Ring {
+
+    Object[] elements;
+
+    int size;
+    int index;
+
+    public Ring(int size) {
+        this.size = size;
+        this.elements = new Object[size];
+    }
+
+    /**
+     * add Object to ring
+     * @param o Object
+     * @return Object removed from ring (replaced by new Object) or null
+     */
+    public Object add(Object o) {
+        Object tmp = elements[index];
+        elements[index] = o;
+        index = (index + 1) % size;
+        return tmp;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/BIOFactory.java
===================================================================
--- ocean/src/com/imagero/uio/bio/BIOFactory.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/BIOFactory.java	(revision 0)
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio;
+
+import com.imagero.uio.UIOStreamBuilder;
+import com.imagero.uio.bio.content.Content;
+import com.imagero.uio.bio.content.DummyContent;
+import com.imagero.uio.bio.content.FileCachedInputStreamContent;
+import com.imagero.uio.bio.content.MemoryCachedInputStreamContent;
+import com.imagero.uio.bio.content.FileCachedHTTPContent;
+import com.imagero.uio.bio.content.HTTPContent;
+
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.InputStream;
+import java.io.File;
+import java.net.URL;
+
+/**
+ * This class could be removed in the future.
+ * Use UIOStreamBuilder to create uio streams.
+ * @author Andrey Kuznetsov
+ */
+public class BIOFactory {
+
+    public static BufferedRandomAccessIO create(int chunkSize) {
+        IOController controller = createIOController(chunkSize);
+        BufferedRandomAccessIO out = new BufferedRandomAccessIO(controller);
+        return out;
+    }
+
+    public static BufferedRandomAccessIO create(OutputStream out) {
+        return create(out, UIOStreamBuilder.DEFAULT_CHUNK_SIZE);
+    }
+
+    public static BufferedRandomAccessIO create(final OutputStream out, int chunkSize) {
+        IOController ctrl = createIOController(chunkSize);
+        BufferedRandomAccessIO bio = new BufferedRandomAccessIO(ctrl) {
+            public void close() throws IOException {
+                controller.writeTo(out);
+                super.close();
+            }
+        };
+        return bio;
+    }
+
+    public static BufferedRandomAccessIO create(DataOutput out) {
+        return create(out, UIOStreamBuilder.DEFAULT_CHUNK_SIZE);
+    }
+
+    public static BufferedRandomAccessIO create(final DataOutput out, int chunkSize) {
+        IOController ctrl = createIOController(chunkSize);
+        BufferedRandomAccessIO bio = new BufferedRandomAccessIO(ctrl) {
+            public void close() throws IOException {
+                controller.writeTo(out);
+                super.close();
+            }
+        };
+        return bio;
+    }
+
+    public static IOController createIOController(int chunkSize) {
+        Content bc = new DummyContent();
+        IOController sb = new IOController(chunkSize, bc);
+        return sb;
+    }
+
+    public static IOController createIOController(InputStream in, File tmp, int chunkSize) {
+        Content bc;
+        if (tmp != null) {
+            try {
+                bc = new FileCachedInputStreamContent(in, tmp);
+            } catch (IOException ex) {
+                System.err.println("Unable to use file cache, switching to memory cache.");
+                ex.printStackTrace();
+                bc = new MemoryCachedInputStreamContent(in, chunkSize);
+            }
+        } else {
+            bc = new MemoryCachedInputStreamContent(in, chunkSize);
+        }
+        IOController sb = new IOController(chunkSize, bc);
+        return sb;
+    }
+
+    public static IOController createIOController(URL url) {
+        return createIOController(url, UIOStreamBuilder.DEFAULT_CHUNK_SIZE);
+    }
+
+    public static IOController createIOController(URL url, int chunkSize) {
+        return createIOController(url, null, chunkSize);
+    }
+
+    public static IOController createIOController(URL url, File tmp) {
+        return createIOController(url, tmp, UIOStreamBuilder.DEFAULT_CHUNK_SIZE);
+    }
+
+    public static IOController createIOController(URL url, File tmp, int chunkSize) {
+        Content bc;
+        if (tmp != null) {
+            try {
+                bc = new FileCachedHTTPContent(url, tmp);
+            } catch (IOException ex) {
+                ex.printStackTrace();
+                System.err.println("Unable to use file cache, switching to memory cache.");
+                bc = new HTTPContent(url);
+            }
+        } else {
+            bc = new HTTPContent(url);
+        }
+        IOController sb = new IOController(chunkSize, bc);
+        return sb;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/BufferIndex.java
===================================================================
--- ocean/src/com/imagero/uio/bio/BufferIndex.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/BufferIndex.java	(revision 0)
@@ -0,0 +1,35 @@
+package com.imagero.uio.bio;
+
+/**
+ * Index of Object in 2D array
+ * Date: 14.12.2007
+ *
+ * @author Andrey Kuznetsov
+ */
+class BufferIndex {
+    /**
+     * index of array in 2D array
+     */
+    int arrayIndex;
+    /**
+     * index of object in 1D array
+     */
+    int index;
+
+    /**
+     * @param arrayIndex index of array
+     * @param index index of object
+     */
+    public BufferIndex(int arrayIndex, int index) {
+        this.arrayIndex = arrayIndex;
+        this.index = index;
+    }
+
+    public boolean equals(Object obj) {
+        if(obj != null && obj instanceof BufferIndex) {
+            BufferIndex bi = (BufferIndex) obj;
+            return bi.arrayIndex == arrayIndex && bi.index == index;
+        }
+        return false;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/FSBInputStream.java
===================================================================
--- ocean/src/com/imagero/uio/bio/FSBInputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/FSBInputStream.java	(revision 0)
@@ -0,0 +1,68 @@
+package com.imagero.uio.bio;
+
+import java.io.InputStream;
+import java.io.IOException;
+
+/**
+ * Date: 01.03.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+class FSBInputStream extends InputStream {
+    FixedSizeByteBuffer buffer;
+    BufferPosition position;
+    int mark;
+    long offset;
+
+    public FSBInputStream(FixedSizeByteBuffer buffer) {
+        this.buffer = buffer;
+        position = new BufferPosition(buffer.buf.length);
+    }
+
+    public FSBInputStream(int offset, FixedSizeByteBuffer buffer) {
+        this.buffer = buffer;
+        position = new BufferPosition(Integer.MAX_VALUE);
+        position.pos = offset;
+        this.offset = offset;
+    }
+
+    public int read() {
+        return buffer.read(position);
+    }
+
+    public int read(byte b[]) throws IOException {
+        return buffer.read(b, 0, b.length, position);
+    }
+
+    public int read(byte b[], int off, int len) {
+        return buffer.read(b, off, len, position);
+    }
+
+    public long skip(long n) {
+        return buffer.skip(n, position);
+    }
+
+    public int available() {
+        return buffer.availableForReading(position);
+    }
+
+    public synchronized void mark(int readlimit) {
+        this.mark = position.pos;
+    }
+
+    public synchronized void reset() throws IOException {
+        position.pos = mark;
+    }
+
+    public boolean markSupported() {
+        return true;
+    }
+
+    public long getPosition() {
+        return position.pos - offset;
+    }
+
+    public void setPosition(long pos) {
+        position.pos = (int) (pos + offset);
+    }
+}
Index: ocean/src/com/imagero/uio/bio/IOCInputStream.java
===================================================================
--- ocean/src/com/imagero/uio/bio/IOCInputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/IOCInputStream.java	(revision 0)
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Independent InputStream with shared IOController.
+ *
+ * @author Andrey Kuznetsov
+ */
+public class IOCInputStream extends InputStream {
+
+    FixedSizeByteBuffer buffer;
+    BufferIndex bufferIndex;
+    BufferPosition bufferPosition;
+    IOController controller;
+
+    StreamPosition streamPosition = new StreamPosition();
+    long offset;
+
+    long mark;
+
+    public IOCInputStream(IOController controller) {
+        this.controller = controller;
+    }
+
+    public IOCInputStream(IOController controller, long offset) {
+        this.controller = controller;
+        this.offset = offset;
+        bufferPosition = new BufferPosition(controller.bufferSize);
+        seek(0);
+    }
+
+    public void seek(long offset) {
+        streamPosition.pos = offset + this.offset;
+        bufferPosition.pos = (int) ((streamPosition.pos) % controller.bufferSize);
+ }
+
+    private void prepareBufferForReading() {
+        BufferIndex index = controller.getBufferIndex(streamPosition.pos);
+
+        if (!index.equals(bufferIndex) || buffer == null || buffer.buf == null) {
+            bufferIndex = index;
+            try {
+                buffer = controller.getBuffer(streamPosition.pos, true);
+            } catch (IOException ex) {
+                //ignore
+            }
+        }
+        if (buffer != null) {
+            bufferPosition.pos = (int) ((streamPosition.pos) % controller.bufferSize);
+        }
+    }
+
+    public int read() throws IOException {
+        checkBuffer();
+
+        if (buffer != null) {
+            streamPosition.pos++;
+            return buffer.read(bufferPosition);
+        }
+        return -1;
+    }
+
+    public int available() throws IOException {
+        if(buffer != null) {
+            return buffer.availableForReading(bufferPosition);
+        }
+        return 0;
+    }
+
+    private void checkBuffer() {
+        if (buffer == null || bufferPosition.available() <= 0) {
+            prepareBufferForReading();
+        }
+    }
+
+    public long skip(long n) throws IOException {
+        checkBuffer();
+        if (buffer == null) {
+            return 0;
+        }
+        long skp = buffer.skip(n, bufferPosition);
+        streamPosition.pos += skp;
+        return skp;
+    }
+
+    public int read(byte b[]) throws IOException {
+        return read(b, 0, b.length);
+    }
+
+    public int read(byte[] b, int offset, int length) throws IOException {
+        checkBuffer();
+        if (buffer == null) {
+            return -1;
+        }
+        int rc = buffer.read(b, offset, length, bufferPosition);
+        if (rc > 0) {
+            streamPosition.pos += rc;
+        }
+        return rc;
+    }
+
+    public boolean markSupported() {
+        return true;
+    }
+
+    public synchronized void mark(int readlimit) {
+        this.mark = streamPosition.pos;
+    }
+
+    public synchronized void reset() throws IOException {
+        seek(mark);
+    }
+
+    public void close() throws IOException {
+        if (controller != null) {
+            controller = null;
+        }
+    }
+
+    public long getPosition() {
+        return streamPosition.pos - offset;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/FSBOutputStream.java
===================================================================
--- ocean/src/com/imagero/uio/bio/FSBOutputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/FSBOutputStream.java	(revision 0)
@@ -0,0 +1,40 @@
+package com.imagero.uio.bio;
+
+import java.io.OutputStream;
+import java.io.IOException;
+
+/**
+ * Date: 01.03.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+class FSBOutputStream extends OutputStream {
+    FixedSizeByteBuffer buffer;
+    BufferPosition position;
+
+    public FSBOutputStream(FixedSizeByteBuffer buffer) {
+        this(0, buffer);
+    }
+
+    public FSBOutputStream(int offset, FixedSizeByteBuffer buffer) {
+        this.buffer = buffer;
+        position = new BufferPosition(Integer.MAX_VALUE);
+        position.pos = offset;
+    }
+
+    public void write(int b) throws IOException {
+        buffer.write(b, position);
+    }
+
+    public void write(byte b[]) throws IOException {
+        buffer.write(b, 0, b.length, position);
+    }
+
+    public void write(byte b[], int off, int len) throws IOException {
+        buffer.write(b, off, len, position);
+    }
+
+    public void close() throws IOException {
+        buffer = null;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/IOCOutputStream.java
===================================================================
--- ocean/src/com/imagero/uio/bio/IOCOutputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/IOCOutputStream.java	(revision 0)
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio;
+
+import java.io.OutputStream;
+import java.io.IOException;
+
+/**
+ * OutputStream with shared IOController.
+ *
+ * @author Andrey Kuznetsov
+ */
+public class IOCOutputStream extends OutputStream {
+
+    FixedSizeByteBuffer buffer;
+    BufferIndex bufferIndex;
+    BufferPosition bufferPosition;
+    IOController controller;
+
+    StreamPosition streamPosition = new StreamPosition();
+    long offset;
+
+    long mark;
+
+    public IOCOutputStream(IOController controller) {
+        this.controller = controller;
+    }
+
+    public IOCOutputStream(IOController controller, long offset) {
+        this.controller = controller;
+        this.offset = offset;
+        bufferPosition = new BufferPosition(controller.bufferSize);
+        seek(0);
+    }
+
+    public void seek(long offset) {
+        streamPosition.pos = offset + this.offset;
+    }
+
+    protected void prepareBufferForWriting() throws IOException {
+        BufferIndex index = controller.getBufferIndex(streamPosition.pos);
+
+        if (!index.equals(bufferIndex) || buffer == null || buffer.buf == null) {
+            bufferIndex = index;
+            buffer = controller.getBuffer(streamPosition.pos, false);
+        }
+        bufferPosition.pos = (int) ((streamPosition.pos) % controller.bufferSize);
+        buffer.changed = true;
+    }
+
+    private void checkBuffer() throws IOException {
+        if (buffer == null || !(bufferPosition.available() > 0)) {
+            prepareBufferForWriting();
+        }
+    }
+
+    public void write(int b) throws IOException {
+        checkBuffer();
+        buffer.write(b, bufferPosition);
+        streamPosition.pos++;
+    }
+
+    public void write(byte b[]) throws IOException {
+        write(b, 0, b.length);
+    }
+
+    public void write(byte b[], int offset, int length) throws IOException {
+        while (length > 0) {
+            checkBuffer();
+            int written = buffer.write(b, offset, length, bufferPosition);
+            length -= written;
+            offset += written;
+            streamPosition.pos += written;
+        }
+    }
+
+    public void flush() throws IOException {
+        if (controller != null) {
+            controller.sync();
+        }
+    }
+
+    public void close() throws IOException {
+        flush();
+        controller = null;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/FixedSizeByteBuffer.java
===================================================================
--- ocean/src/com/imagero/uio/bio/FixedSizeByteBuffer.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/FixedSizeByteBuffer.java	(revision 0)
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio;
+
+import com.imagero.uio.RandomAccessIO;
+
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * This class can be used to read from and write to byte array.
+ *
+ * @author Andrey Kuznetsov
+ */
+public class FixedSizeByteBuffer {
+
+    protected byte[] buf;
+    protected int count;
+
+    boolean changed;
+    BufferIndex index;
+
+    protected FixedSizeByteBuffer(byte buf[]) {
+        this.buf = buf;
+    }
+
+    public int read(BufferPosition position) {
+        if (availableForReading(position) > 0) {
+            int v = buf[position.pos++] & 0xFF;
+            return v;
+        }
+        return -1;
+    }
+
+    public long skip(long n, BufferPosition position) {
+        long p = Math.max(0, Math.min(count - position.pos, n));
+        position.pos += p;
+        return p;
+    }
+
+    public BufferPosition createPosition() {
+        return new BufferPosition(buf.length);
+    }
+
+    public int availableForReading(BufferPosition position) {
+        return Math.max(0, count - position.pos);
+    }
+
+    public int availableForWriting(BufferPosition position) {
+        return Math.max(0, buf.length - position.pos);
+    }
+
+    public int read(byte[] dest, int offset, int length, BufferPosition position) {
+        final int available = availableForReading(position);
+        int toCopy = Math.max(0, Math.min(length, available));
+        if (toCopy > 0) {
+            System.arraycopy(buf, position.pos, dest, offset, toCopy);
+            position.pos += toCopy;
+        }
+        return toCopy;
+    }
+
+    /**
+     * write given byte to buffer.
+     *
+     * @param b int to write
+     */
+    public void write(int b, BufferPosition position) {
+        buf[position.pos++] = (byte) b;
+        count = Math.max(position.pos, count);
+    }
+
+    public int getCount() {
+        return count;
+    }
+
+    public int getPosition(BufferPosition position) {
+        return position.pos;
+    }
+
+    public void setCount(int count) {
+        this.count = Math.min(Math.max(count, 0), buf.length);
+    }
+
+    /**
+     * write buffer contents to OutputStream
+     * @param wholeBuffer if true then whole buffer is written, otherwise only getCount() bytes are written
+     */
+    public void writeBuffer(OutputStream out, boolean wholeBuffer) throws IOException {
+        if (wholeBuffer) {
+            out.write(buf);
+        } else {
+            out.write(buf, 0, count);
+        }
+    }
+
+    public void writeBuffer(DataOutput out, boolean wholeBuffer) throws IOException {
+        if (wholeBuffer) {
+            out.write(buf);
+        } else {
+            out.write(buf, 0, count);
+        }
+    }
+
+    /**
+     * write whole buffer contents to OutputStream (count is ignored)
+     */
+    public void writeBuffer(OutputStream out) throws IOException {
+        out.write(buf);
+    }
+
+    public void writeBuffer(DataOutput out) throws IOException {
+        out.write(buf);
+    }
+
+    public int write(byte src[], int offset, int length, BufferPosition position) {
+        int available = availableForWriting(position);
+        int toCopy = Math.max(0, Math.min(length, available));
+        if (toCopy > 0) {
+            System.arraycopy(src, offset, buf, position.pos, toCopy);
+            position.pos += toCopy;
+            count = Math.max(count, position.pos);
+        }
+        return toCopy;
+    }
+
+    public RandomAccessIO create() {
+        return new FSBRandomAccessIO(this);
+    }
+
+    public RandomAccessIO create(int offset, int length) {
+        return new FSBRandomAccessIO(this, offset, length);
+    }
+
+    public static FixedSizeByteBuffer createBuffer(byte buf[]) {
+        return new FixedSizeByteBuffer(buf);
+    }
+}
Index: ocean/src/com/imagero/uio/bio/Buffer.java
===================================================================
--- ocean/src/com/imagero/uio/bio/Buffer.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/Buffer.java	(revision 0)
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio;
+
+/**
+ * @author Andrey Kuznetsov
+ */
+class Buffer {
+    byte [] buffer;
+
+    public Buffer(byte[] buffer) {
+        this.buffer = buffer;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/IOController.java
===================================================================
--- ocean/src/com/imagero/uio/bio/IOController.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/IOController.java	(revision 0)
@@ -0,0 +1,353 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio;
+
+import com.imagero.util.OpenVector;
+import com.imagero.uio.bio.content.SynchronizedContent;
+import com.imagero.uio.bio.content.Content;
+import com.imagero.uio.UIOStreamBuilder;
+
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Enumeration;
+import java.util.NoSuchElementException;
+
+/**
+ * Buffer controller.
+ * IOController loads data from Content and maintains buffers (FixedSizeBuffer).
+ * @author Andrey Kuznetsov
+ */
+public class IOController {
+
+    OpenVector bufs = new OpenVector(100);
+
+    int bufferSize = UIOStreamBuilder.DEFAULT_CHUNK_SIZE;
+    int arrayLength = 1000;
+
+    Content content;
+
+    Ring rs;
+    int maxBufferCount = UIOStreamBuilder.DEFAULT_CHUNK_COUNT;
+
+    long explicitLength;
+
+    public IOController(int bufferSize, Content content) {
+        this.bufferSize = bufferSize;
+        this.content = content;
+        this.rs = new Ring(maxBufferCount);
+    }
+
+    final void setLength(long newLength) {
+        explicitLength = newLength;
+    }
+
+    private Enumeration buffers(final boolean allowNullValues) {
+        return new Enumeration() {
+            final FixedSizeByteBuffer empty = FixedSizeByteBuffer.createBuffer(new byte[bufferSize]);
+            final long length = length();
+            final BufferIndex max = getBufferIndex(length);
+            final BufferIndex bi = new BufferIndex(0, 0);
+
+            public boolean hasMoreElements() {
+                boolean b = bi.arrayIndex < max.arrayIndex;
+                boolean b2 = bi.index <= max.index;
+                return b || b2;
+            }
+
+            public Object nextElement() {
+                if (hasMoreElements()) {
+                    if (!(bi.index < arrayLength)) {
+                        bi.index = 0;
+                        bi.arrayIndex++;
+                    }
+                    FixedSizeByteBuffer fb = getBuffer(bi.arrayIndex, bi.index++);
+                    return fb != null || allowNullValues ? fb : empty;
+                } else {
+                    throw new NoSuchElementException();
+                }
+            }
+        };
+    }
+
+    private Enumeration buffers(final long maxPos, final boolean allowNullValues) {
+        return new Enumeration() {
+            final FixedSizeByteBuffer empty = FixedSizeByteBuffer.createBuffer(new byte[bufferSize]);
+            final long length = Math.min(maxPos, length());
+            final BufferIndex max = getBufferIndex(length);
+            final BufferIndex bi = new BufferIndex(0, 0);
+
+            public boolean hasMoreElements() {
+                boolean b = bi.arrayIndex < max.arrayIndex;
+                boolean b2 = bi.index <= max.index;
+                return b || b2;
+            }
+
+            public Object nextElement() {
+                if (hasMoreElements()) {
+                    if (!(bi.index < arrayLength)) {
+                        bi.index = 0;
+                        bi.arrayIndex++;
+                    }
+                    FixedSizeByteBuffer fb = getBuffer(bi.arrayIndex, bi.index++);
+                    return fb != null || allowNullValues ? fb : empty;
+                } else {
+                    throw new NoSuchElementException();
+                }
+            }
+        };
+    }
+
+
+    long length() {
+        if (explicitLength > 0) {
+            return explicitLength;
+        }
+
+        long contentLength = 0;
+        try {
+            contentLength = content.length();
+        } catch (IOException ex) {
+            ex.printStackTrace();
+        }
+
+        Object[] elements = bufs.getElements();
+        int maxI = elements.length - 1;
+        for (int i = maxI; i >= 0; i--) {
+            BufferArray ba = (BufferArray) elements[i];
+            if (ba != null) {
+                FixedSizeByteBuffer[] buffers = ba.buffers;
+                int maxJ = buffers.length - 1;
+                for (int j = maxJ; j >= 0; j--) {
+                    FixedSizeByteBuffer buffer = buffers[j];
+                    if (buffer != null) {
+                        int count = buffer.getCount();
+                        long startOffset = getStartOffset(buffer.index, bufferSize);
+                        if (count > 0) {
+                            return Math.max(contentLength, startOffset + count);
+                        }
+                    }
+                }
+            }
+        }
+        return contentLength;
+    }
+
+    void writeTo(OutputStream out) throws IOException {
+        Enumeration enums = buffers(false);
+        while (enums.hasMoreElements()) {
+            FixedSizeByteBuffer buffer = (FixedSizeByteBuffer) enums.nextElement();
+            buffer.writeBuffer(out, enums.hasMoreElements());
+        }
+    }
+
+    void writeTo(DataOutput out) throws IOException {
+        Enumeration enums = buffers(false);
+        while (enums.hasMoreElements()) {
+            FixedSizeByteBuffer buffer = (FixedSizeByteBuffer) enums.nextElement();
+            buffer.writeBuffer(out, enums.hasMoreElements());
+        }
+    }
+
+    private class BufferArray {
+        FixedSizeByteBuffer[] buffers;
+
+        public BufferArray() {
+            buffers = new FixedSizeByteBuffer[arrayLength];
+        }
+    }
+
+    private FixedSizeByteBuffer getBuffer(BufferIndex bi) {
+        return getBuffer(bi.arrayIndex, bi.index);
+    }
+
+    private FixedSizeByteBuffer getBuffer(int aIndex, int index) {
+        Object[] objects = bufs.checkSize(aIndex);
+        BufferArray ba = (BufferArray) objects[aIndex];
+        if (ba == null) {
+            ba = new BufferArray();
+            objects[aIndex] = ba;
+        }
+        return ba.buffers[index];
+    }
+
+    protected void setBuffer(BufferIndex index, FixedSizeByteBuffer buffer) {
+        Object[] objects = bufs.checkSize(index.arrayIndex);
+        BufferArray ba = (BufferArray) objects[index.arrayIndex];
+        ba.buffers[index.index] = buffer;
+    }
+
+    public long flushBefore(long pos) {
+        pos = (pos / bufferSize) * bufferSize;
+        Enumeration enums = buffers(pos, true);
+        while (enums.hasMoreElements()) {
+            FixedSizeByteBuffer o = (FixedSizeByteBuffer) enums.nextElement();
+            if (o != null) {
+                o.buf = null;
+            }
+        }
+        return pos;
+    }
+
+    public BufferIndex getBufferIndex(long pos) {
+        if(pos < 0) {
+            throw new IllegalArgumentException("Negative stream position");
+        }
+        long count = pos / bufferSize;
+        long aIndex = count / arrayLength;
+        int index = (int) (count % arrayLength);
+        if (aIndex > Integer.MAX_VALUE) {
+            throw new IndexOutOfBoundsException("Please increase buffer size");
+        }
+        if(index < 0) {
+            throw new IndexOutOfBoundsException("Please increase buffer size");
+        }
+        BufferIndex bi = new BufferIndex((int) aIndex, index);
+        return bi;
+    }
+
+    public FixedSizeByteBuffer getBuffer(long pos) {
+        return getBuffer(getBufferIndex(pos));
+    }
+
+    FixedSizeByteBuffer getBuffer(long pos, boolean load) throws IOException {
+        BufferIndex bi = getBufferIndex(pos);
+        long startOffset = getStartOffset(bi, bufferSize);
+        FixedSizeByteBuffer sb = getBuffer(bi);
+        if (sb == null) {
+            sb = FixedSizeByteBuffer.createBuffer(new byte[bufferSize]);
+            setBuffer(bi, sb);
+            sb.index = bi;
+            if (load) {
+                long max = content.length();
+                if (pos > max) {
+                    return null;
+                }
+
+                int size = content.load(startOffset, sb.buf);
+                sb.count = size;
+            }
+            if (content.canReload()) {
+                checkBuffers(sb);
+            }
+        } else {
+            if (sb.buf == null) {
+                long max = content.length();
+                if (pos > max) {
+                    return null;
+                }
+                sb.buf = new byte[bufferSize];
+                int size = content.load(startOffset, sb.buf);
+                sb.count = size;
+            }
+        }
+        return sb;
+    }
+
+    private void checkBuffers(FixedSizeByteBuffer buffer0) {
+        FixedSizeByteBuffer buffer = (FixedSizeByteBuffer) rs.add(buffer0);
+        if (buffer != null && content.writable()) {
+            if (buffer.changed) {
+                try {
+                    long offset = getStartOffset(buffer.index, bufferSize);
+                    content.save(offset, 0, buffer.buf, buffer.getCount());
+                    buffer.changed = false;
+                } catch (IOException ex) {
+                    ex.printStackTrace();
+                }
+            }
+            setBuffer(buffer.index, null);
+            buffer.buf = null;
+        }
+    }
+
+    void sync() throws IOException {
+        boolean canWrite = content.writable();
+        if (!canWrite) {
+            return;
+        }
+
+        try {
+            long length = content.length(); //may be already closed
+        }
+        catch(IOException ex) {
+            //stream was already closed, so just return
+            return;
+        }
+        Enumeration enums = buffers(true);
+        while (enums.hasMoreElements()) {
+            FixedSizeByteBuffer buffer = (FixedSizeByteBuffer) enums.nextElement();
+            if (buffer != null && buffer.changed) {
+                long offset = getStartOffset(buffer.index, bufferSize);
+                content.save(offset, 0, buffer.buf, buffer.getCount());
+                buffer.changed = false;
+            }
+        }
+    }
+
+    public long getStartOffset(BufferIndex bufferIndex, int bufferSize) {
+        long res = bufferIndex.arrayIndex * arrayLength * bufferSize;
+        res += bufferIndex.index * bufferSize;
+        return res;
+    }
+
+    /**
+     * determine if access to stream content is synchronized
+     */
+    public boolean isSynchronizedContent() {
+        return content instanceof SynchronizedContent;
+    }
+
+    /**
+     * define if access to content should be syncronized.
+     */
+    public void setSynchronizedContent(boolean b) {
+        if(b) {
+            if(!isSynchronizedContent()) {
+                content = new SynchronizedContent(content);
+            }
+        }
+        else {
+            if(isSynchronizedContent()) {
+                SynchronizedContent synchronizedContent = (SynchronizedContent) content;
+                content = synchronizedContent.getContent();
+            }
+        }
+    }
+
+    protected void finalize() throws Throwable {
+        super.finalize();
+        content = null;
+        rs = null;
+        bufs = null;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/ByteArrayRandomAccessIO.java
===================================================================
--- ocean/src/com/imagero/uio/bio/ByteArrayRandomAccessIO.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/ByteArrayRandomAccessIO.java	(revision 0)
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio;
+
+import com.imagero.uio.RandomAccessIO;
+import com.imagero.uio.RandomAccessInput;
+import com.imagero.uio.RandomAccessOutput;
+import com.imagero.uio.impl.AbstractRandomAccessIO;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * ByteArrayRandomAccessIO is like ByteArrayOutputStream and ByteArrayInputStream together.
+ * It implements also DataInput/DataOutput and other advanced interfaces.
+ * @author Andrey Kuznetsov
+ */
+public class ByteArrayRandomAccessIO extends AbstractRandomAccessIO implements RandomAccessIO {
+
+    VariableSizeByteBuffer buffer;
+    BufferPosition position;
+    int _offset;
+    Integer length;
+
+    private static VariableSizeByteBuffer createBuffer(int size) {
+        return new VariableSizeByteBuffer(size);
+    }
+
+    private static VariableSizeByteBuffer createBuffer(byte [] data) {
+        return new VariableSizeByteBuffer(data);
+    }
+    public ByteArrayRandomAccessIO(int initialSize) {
+        this(createBuffer(initialSize));
+    }
+
+    public ByteArrayRandomAccessIO(int offset, int length, VariableSizeByteBuffer buffer) {
+        this._offset = offset;
+        this.buffer = buffer;
+        position = new BufferPosition(Integer.MAX_VALUE);
+        position.pos = offset;
+        if(length > 0) {
+            this.length = new Integer(length);
+        }
+    }
+
+    public ByteArrayRandomAccessIO(byte [] data) {
+        this(createBuffer(data));
+    }
+
+    public ByteArrayRandomAccessIO(VariableSizeByteBuffer buffer) {
+        this.buffer = buffer;
+        position = new BufferPosition(Integer.MAX_VALUE);
+    }
+
+    public int read() throws IOException {
+        return buffer.read(position);
+    }
+
+    public long skip(long n) throws IOException {
+        return buffer.skip(n, position);
+    }
+
+    public int read(byte[] b, int off, int len) throws IOException {
+        return buffer.read(b, off, len, position);
+    }
+
+    public long getFilePointer() throws IOException {
+        return position.pos - _offset;
+    }
+
+    public long length() throws IOException {
+        if(length != null) {
+            return Math.min(length.intValue(), buffer.getCount() - _offset);
+        }
+        else {
+            return buffer.getCount() - _offset;
+        }
+    }
+
+    public void seek(long offset) throws IOException {
+        if (offset + _offset > Integer.MAX_VALUE) {
+            throw new IOException("Offset too big: 0x" + Long.toHexString(offset));
+        }
+        buffer.seek((int) offset + _offset, position);
+    }
+
+    public void setLength(long newLength) throws IOException {
+        if (newLength > Integer.MAX_VALUE) {
+            throw new IOException();
+        }
+        buffer.setCount((int) newLength);
+    }
+
+    public void write(int b) throws IOException {
+        buffer.write(b, position);
+    }
+
+    public void write(byte b[], int offset, int length) throws IOException {
+        buffer.write(b, offset, length, position);
+    }
+
+    public RandomAccessIO createIOChild(long offset, long length, int byteOrder, boolean syncPointer) {
+        ByteArrayRandomAccessIO io = new ByteArrayRandomAccessIO((int) offset, (int) length, buffer);
+        if(syncPointer) {
+            io.buffer = buffer;
+        }
+        io.setByteOrder(byteOrder);
+        return io;
+    }
+
+    public RandomAccessInput createInputChild(long offset, long length, int byteOrder, boolean syncPointer) {
+        return createIOChild(offset, length, byteOrder, syncPointer);
+    }
+
+    public RandomAccessOutput createOutputChild(long offset, int byteOrder, boolean syncPointer) {
+        return createIOChild(offset, 0, byteOrder, syncPointer);
+    }
+
+    public byte [] toByteArray() throws IOException {
+        byte [] b = new byte[(int)length()];
+        int pos = position.pos;
+        buffer.seek(0, position);
+        buffer.read(b, 0, b.length, position);
+        buffer.seek(pos, position);
+        return b;
+    }
+
+    public InputStream createInputStream(long offset) {
+        return buffer.getInputStream((int) offset);
+    }
+
+    public long getChildPosition(InputStream child) {
+        if(child instanceof VSBInputStream) {
+            VSBInputStream vsbis = (VSBInputStream) child;
+            return vsbis.getPosition();
+        }
+        return -1;
+    }
+
+    public void setChildPosition(InputStream child, long pos) {
+        if (child instanceof VSBInputStream) {
+            VSBInputStream vsbis = (VSBInputStream) child;
+            vsbis.setPosition(pos);
+        }
+    }
+
+    public OutputStream createOutputStream(long offset) {
+        return buffer.getOutputStream((int) offset);
+    }
+}
Index: ocean/src/com/imagero/uio/bio/StreamPosition.java
===================================================================
--- ocean/src/com/imagero/uio/bio/StreamPosition.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/StreamPosition.java	(revision 0)
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio;
+
+/**
+ * @author Andrey Kuznetsov
+ */
+public class StreamPosition {
+    public long pos;
+}
Index: ocean/src/com/imagero/uio/bio/FSBRandomAccessIO.java
===================================================================
--- ocean/src/com/imagero/uio/bio/FSBRandomAccessIO.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/FSBRandomAccessIO.java	(revision 0)
@@ -0,0 +1,111 @@
+package com.imagero.uio.bio;
+
+import com.imagero.uio.impl.AbstractRandomAccessIO;
+import com.imagero.uio.RandomAccessIO;
+import com.imagero.uio.RandomAccessInput;
+import com.imagero.uio.RandomAccessOutput;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Date: 01.03.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+class FSBRandomAccessIO extends AbstractRandomAccessIO {
+
+    FixedSizeByteBuffer buffer;
+    BufferPosition position;
+
+    int offset;
+    int length;
+
+    public FSBRandomAccessIO(FixedSizeByteBuffer buffer) {
+        this(buffer, 0, buffer.buf.length);
+    }
+
+    public FSBRandomAccessIO(FixedSizeByteBuffer buffer, int offset) {
+        this(buffer, offset, buffer.buf.length - offset);
+    }
+
+    public FSBRandomAccessIO(FixedSizeByteBuffer buffer, int offset, int length) {
+        this.buffer = buffer;
+        this.offset = offset;
+        if(length > 0) {
+            this.length = length;
+        }
+        else {
+            this.length = buffer.availableForReading(buffer.createPosition());
+        }
+    }
+
+    public int read() throws IOException {
+        return buffer.read(position);
+    }
+
+    public void seek(long pos) {
+        position.pos = (int) Math.min(length, pos + offset);
+    }
+
+    public long length() throws IOException {
+        return length;
+    }
+
+    public long getFilePointer() throws IOException {
+        return position.pos - offset;
+    }
+
+    public void setLength(long newLength) throws IOException {
+        this.length = (int) Math.min(buffer.buf.length, newLength);
+    }
+
+    public void write(int b) throws IOException {
+        buffer.write(b, position);
+    }
+
+    public void write(byte b[], int off, int len) throws IOException {
+        buffer.write(b, off, len, position);
+    }
+
+    public InputStream createInputStream(long offset) {
+        return new FSBInputStream((int) offset, buffer);
+    }
+
+    public long getChildPosition(InputStream child) {
+        if(child instanceof FSBInputStream) {
+            FSBInputStream fsbis = (FSBInputStream) child;
+            return fsbis.getPosition();
+        }
+        return -1;
+    }
+
+    public void setChildPosition(InputStream child, long position) {
+        if (child instanceof FSBInputStream) {
+            FSBInputStream fsbis = (FSBInputStream) child;
+            fsbis.setPosition(position);
+        }
+    }
+
+    public OutputStream createOutputStream(long offset) {
+        return new FSBOutputStream((int) offset, buffer) ;
+    }
+
+    public RandomAccessIO createIOChild(long offset, long length, int byteOrder, boolean syncPointer) throws IOException {
+        FSBRandomAccessIO rio = new FSBRandomAccessIO(buffer, (int) offset, (int) length);
+        if(syncPointer) {
+            rio.position = position;
+        }
+        rio.setByteOrder(byteOrder);
+        return rio;
+    }
+
+    public RandomAccessInput createInputChild(long offset, long length, int byteOrder, boolean syncPointer) throws IOException {
+        return createIOChild(offset, 0, byteOrder, syncPointer);
+    }
+
+    public RandomAccessOutput createOutputChild(long offset, int byteOrder, boolean syncPointer) throws IOException {
+        return createIOChild(offset, 0, byteOrder, syncPointer);
+    }
+}
Index: ocean/src/com/imagero/uio/bio/VSBInputStream.java
===================================================================
--- ocean/src/com/imagero/uio/bio/VSBInputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/VSBInputStream.java	(revision 0)
@@ -0,0 +1,68 @@
+package com.imagero.uio.bio;
+
+import java.io.InputStream;
+import java.io.IOException;
+
+/**
+ * Date: 01.03.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+class VSBInputStream extends InputStream {
+    VariableSizeByteBuffer buffer;
+    BufferPosition position;
+    int mark;
+    long offset;
+
+    public VSBInputStream(VariableSizeByteBuffer buffer) {
+        this.buffer = buffer;
+        position = new BufferPosition(Integer.MAX_VALUE);
+    }
+
+    public VSBInputStream(int offset, VariableSizeByteBuffer buffer) {
+        this.buffer = buffer;
+        position = new BufferPosition(Integer.MAX_VALUE);
+        buffer.seek(offset, position);
+        this.offset = offset;
+    }
+
+    public int read() {
+        return buffer.read(position);
+    }
+
+    public int read(byte b[]) throws IOException {
+        return buffer.read(b, 0, b.length, position);
+    }
+
+    public int read(byte b[], int off, int len) {
+        return buffer.read(b, off, len, position);
+    }
+
+    public long skip(long n) {
+        return buffer.skip(n, position);
+    }
+
+    public int available() {
+        return buffer.availableForReading(position);
+    }
+
+    public synchronized void mark(int readlimit) {
+        this.mark = position.pos;
+    }
+
+    public synchronized void reset() throws IOException {
+        buffer.seek(mark, position);
+    }
+
+    public boolean markSupported() {
+        return true;
+    }
+
+    public long getPosition() {
+        return position.pos - offset;
+    }
+
+    public void setPosition(long pos) {
+        buffer.seek((int) pos, position);
+    }
+}
Index: ocean/src/com/imagero/uio/bio/VSBOutputStream.java
===================================================================
--- ocean/src/com/imagero/uio/bio/VSBOutputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/VSBOutputStream.java	(revision 0)
@@ -0,0 +1,40 @@
+package com.imagero.uio.bio;
+
+import java.io.OutputStream;
+import java.io.IOException;
+
+/**
+ * Date: 01.03.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+class VSBOutputStream extends OutputStream {
+    VariableSizeByteBuffer buffer;
+    BufferPosition position;
+
+    public VSBOutputStream(VariableSizeByteBuffer buffer) {
+        this(0, buffer);
+    }
+
+    public VSBOutputStream(int offset, VariableSizeByteBuffer buffer) {
+        this.buffer = buffer;
+        position = new BufferPosition(Integer.MAX_VALUE);
+        buffer.seek(offset, position);
+    }
+
+    public void write(int b) throws IOException {
+        buffer.write(b, position);
+    }
+
+    public void write(byte b[]) throws IOException {
+        buffer.write(b, 0, b.length, position);
+    }
+
+    public void write(byte b[], int off, int len) throws IOException {
+        buffer.write(b, off, len, position);
+    }
+
+    public void close() throws IOException {
+        buffer = null;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/BufferedRandomAccessIO.java
===================================================================
--- ocean/src/com/imagero/uio/bio/BufferedRandomAccessIO.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/BufferedRandomAccessIO.java	(revision 0)
@@ -0,0 +1,252 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio;
+
+import com.imagero.uio.RandomAccessIO;
+import com.imagero.uio.RandomAccessInput;
+import com.imagero.uio.RandomAccessOutput;
+import com.imagero.uio.impl.AbstractRandomAccessIO;
+
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * BufferedRandomAccessIO - buffered readable and writable stream with random access.
+ * @author Andrey Kuznetsov
+ */
+public class BufferedRandomAccessIO extends AbstractRandomAccessIO {
+
+    FixedSizeByteBuffer buffer;
+    BufferIndex bufferIndex;
+    BufferPosition bufferPosition;
+    IOController controller;
+
+    StreamPosition streamPosition = new StreamPosition();
+    long offset;
+
+
+    public BufferedRandomAccessIO(IOController controller) {
+        this(controller, 0L);
+    }
+
+    public BufferedRandomAccessIO(IOController controller, long offset) {
+        this.controller = controller;
+        this.offset = offset;
+        bufferPosition = new BufferPosition(controller.bufferSize);
+        seek(0);
+    }
+
+    public final void setLength(long newLength) throws IOException {
+        controller.setLength(newLength);
+    }
+
+    public long flushBefore(long pos) {
+        return controller.flushBefore(pos);
+    }
+
+    protected void prepareBufferForReading(BufferIndex index) throws IOException {
+        if (!index.equals(bufferIndex) || buffer == null || buffer.buf == null) {
+            bufferIndex = index;
+            buffer = controller.getBuffer(streamPosition.pos, true);
+        }
+        bufferPosition.pos = (int) ((streamPosition.pos) % controller.bufferSize);
+    }
+
+    protected void prepareBufferForWriting(BufferIndex index) throws IOException {
+        if (!index.equals(bufferIndex) || buffer == null || buffer.buf == null) {
+            bufferIndex = index;
+            buffer = controller.getBuffer(streamPosition.pos, false);
+        }
+        bufferPosition.pos = (int) ((streamPosition.pos) % controller.bufferSize);
+        buffer.changed = true;
+    }
+
+    public long getFilePointer() {
+        return streamPosition.pos - offset;
+    }
+
+    public long length() throws IOException {
+        return controller.length() - offset;
+    }
+
+    public void seek(long pos) {
+        if(pos < 0) {
+            throw new IllegalArgumentException("Negative seek offset");
+        }
+        streamPosition.pos = pos + offset;
+        bufferPosition.pos = (int) ((streamPosition.pos) % controller.bufferSize);
+    }
+
+    public int available() throws IOException {
+        if (buffer != null) {
+            return buffer.availableForReading(bufferPosition);
+        }
+        return 0;
+    }
+
+    public void write(int b) throws IOException {
+        ensureBuffer(false);
+        buffer.write(b, bufferPosition);
+        streamPosition.pos++;
+    }
+
+    public void write(byte b[], int offset, int length) throws IOException {
+        while (length > 0) {
+            ensureBuffer(false);
+            int written = buffer.write(b, offset, length, bufferPosition);
+            length -= written;
+            offset += written;
+            streamPosition.pos += written;
+        }
+    }
+
+    public void close() throws IOException {
+        if (controller != null) {
+            controller.sync();
+            controller = null;
+        }
+    }
+
+    /**
+     * write buffer contents to given OutputStream
+     * @param out OutputStream
+     */
+    public void writeBuffer(OutputStream out) throws IOException {
+        controller.writeTo(out);
+    }
+
+    /**
+     * write buffer contents to DataOutput
+     * @param out OutputStream
+     */
+    public void writeBuffer(DataOutput out) throws IOException {
+        controller.writeTo(out);
+    }
+
+    private void ensureBuffer(boolean read) throws IOException {
+        BufferIndex index = controller.getBufferIndex(streamPosition.pos);
+        if (read) {
+            if (buffer == null || buffer.availableForReading(bufferPosition) <= 0 || bufferIndex != index) {
+                prepareBufferForReading(index);
+            }
+        } else {
+            if (buffer == null || buffer.availableForWriting(bufferPosition) <= 0 || bufferIndex != index) {
+                prepareBufferForWriting(index);
+            }
+        }
+    }
+
+    public int read() throws IOException {
+        try {
+            ensureBuffer(true);
+        } catch (IOException ex) {
+            return -1;
+        }
+        if (buffer != null) {
+            streamPosition.pos++;
+            return buffer.read(bufferPosition);
+        }
+        return -1;
+    }
+
+    public long skip(long n) throws IOException {
+        ensureBuffer(true);
+        if (buffer == null) {
+            return 0;
+        }
+        long skipped = buffer.skip(n, bufferPosition);
+        streamPosition.pos += skipped;
+        return skipped;
+    }
+
+    public int read(byte[] b, int offset, int length) throws IOException {
+        ensureBuffer(true);
+        if (buffer == null) {
+            return 0;
+        }
+        int rc = buffer.read(b, offset, length, bufferPosition);
+        if (rc > 0) {
+            streamPosition.pos += rc;
+        }
+        return rc;
+    }
+
+    public RandomAccessIO createIOChild(long offset, long length, int byteOrder, boolean syncPointer) {
+        BufferedRandomAccessIO io = new BufferedRandomAccessIO(controller, this.offset + offset);
+        io.setByteOrder(byteOrder);
+        if (syncPointer) {
+            io.streamPosition = streamPosition;
+        }
+        return io;
+    }
+
+    public RandomAccessInput createInputChild(long offset, long length, int byteOrder, boolean syncPointer) {
+        return createIOChild(offset, 0, byteOrder, syncPointer);
+    }
+
+    public InputStream createInputStream(long offset) {
+        return new IOCInputStream(controller, this.offset + offset);
+    }
+
+    public RandomAccessOutput createOutputChild(long offset, int byteOrder, boolean syncPointer) {
+        return createIOChild(offset, 0, byteOrder, syncPointer);
+    }
+
+    public OutputStream createOutputStream(long offset) {
+        return new IOCOutputStream(controller, this.offset + offset);
+    }
+
+    public void flush() throws IOException {
+        controller.sync();
+    }
+
+    public boolean isBuffered() {
+        return true;
+    }
+
+    public long getChildPosition(InputStream child) {
+        if(child instanceof IOCInputStream) {
+            IOCInputStream in = (IOCInputStream) child;
+            return in.getPosition();
+        }
+        return -1;
+    }
+
+    public void setChildPosition(InputStream child, long position) {
+        if (child instanceof IOCInputStream) {
+            IOCInputStream in = (IOCInputStream) child;
+            in.seek(position);
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/bio/BufferPosition.java
===================================================================
--- ocean/src/com/imagero/uio/bio/BufferPosition.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/BufferPosition.java	(revision 0)
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio;
+
+/**
+ * @author Andrey Kuznetsov
+ */
+public class BufferPosition {
+
+    public BufferPosition(int bufferSize) {
+        this.bufferSize = bufferSize;
+    }
+
+    int bufferSize;
+    public int pos;
+
+    public int available() {
+        return bufferSize - pos;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/VariableSizeByteBuffer.java
===================================================================
--- ocean/src/com/imagero/uio/bio/VariableSizeByteBuffer.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/VariableSizeByteBuffer.java	(revision 0)
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio;
+
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * This class can be used to read from and write to byte array.
+ *
+ * @author Andrey Kuznetsov
+ */
+public class VariableSizeByteBuffer {
+
+    protected Buffer buf;
+    int count;
+
+    boolean changed;
+
+    public VariableSizeByteBuffer(int size) {
+        this(new byte[size]);
+    }
+
+    public VariableSizeByteBuffer(byte buf[]) {
+        this(new Buffer(buf));
+    }
+
+    VariableSizeByteBuffer(Buffer buf) {
+        this.buf = buf;
+        count = buf.buffer.length;
+    }
+
+    public VariableSizeByteBuffer create() {
+        return new VariableSizeByteBuffer(buf);
+    }
+
+    public void seek(int pos, BufferPosition position) {
+        position.pos = pos;
+    }
+
+    public int read(BufferPosition position) {
+        if (position.pos >= count) {
+            return -1;
+        }
+        return buf.buffer[position.pos++] & 0xFF;
+    }
+
+    public long skip(long n, BufferPosition position) {
+        long p = Math.max(0L, Math.min(n, Integer.MAX_VALUE));
+        position.pos += p;
+        return p;
+    }
+
+    /**
+     * get amount of bytes which may be written without changing buffer size
+     */
+    public int availableForWriting(BufferPosition position) {
+        return buf.buffer.length - position.pos;
+    }
+
+    public int availableForReading(BufferPosition position) {
+        return Math.max(0, count - position.pos);
+    }
+
+    public int read(byte[] dest, int offset, int length, BufferPosition position) {
+        final int available = availableForReading(position);
+        int toRead = Math.max(0, Math.min(length, available));
+        if (toRead > 0) {
+            System.arraycopy(buf.buffer, position.pos, dest, offset, toRead);
+            position.pos += toRead;
+        }
+        return toRead;
+    }
+
+    public int getCount() {
+        return count;
+    }
+
+    public void setCount(int count) {
+        this.count = Math.min(Math.max(count, 0), buf.buffer.length);
+    }
+
+    public void writeBuffer(OutputStream out) throws IOException {
+        out.write(buf.buffer, 0, count);
+    }
+
+    public void writeBuffer(DataOutput out) throws IOException {
+        out.write(buf.buffer, 0, count);
+    }
+
+    public void write(byte b[], int offset, int length, BufferPosition position) {
+        if (length > 0) {
+            checkSize(length, position);
+            System.arraycopy(b, offset, buf.buffer, position.pos, length);
+            position.pos += length;
+            count = Math.max(count, position.pos);
+        }
+    }
+
+    public void write(int b, BufferPosition position) {
+        checkSize(1, position);
+        buf.buffer[position.pos++] = (byte) b;
+        count = Math.max(count, position.pos);
+    }
+
+    private synchronized void checkSize(int k, BufferPosition position) {
+        if (position.pos + k > buf.buffer.length) {
+            byte newbuf[] = new byte[Math.max(buf.buffer.length << 1, position.pos + k)];
+            System.arraycopy(buf.buffer, 0, newbuf, 0, count);
+            buf.buffer = newbuf;
+        }
+    }
+
+    public InputStream getInputStream(int offset) {
+        return new VSBInputStream(offset, this);
+    }
+
+    public OutputStream getOutputStream(int offset) {
+        return new VSBOutputStream(offset, this);
+    }
+}
Index: ocean/src/com/imagero/uio/bio/BIOFactory.java
===================================================================
--- ocean/src/com/imagero/uio/bio/BIOFactory.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/BIOFactory.java	(revision 0)
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio;
+
+import com.imagero.uio.UIOStreamBuilder;
+import com.imagero.uio.bio.content.Content;
+import com.imagero.uio.bio.content.DummyContent;
+import com.imagero.uio.bio.content.FileCachedInputStreamContent;
+import com.imagero.uio.bio.content.MemoryCachedInputStreamContent;
+import com.imagero.uio.bio.content.FileCachedHTTPContent;
+import com.imagero.uio.bio.content.HTTPContent;
+
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.InputStream;
+import java.io.File;
+import java.net.URL;
+
+/**
+ * This class could be removed in the future.
+ * Use UIOStreamBuilder to create uio streams.
+ * @author Andrey Kuznetsov
+ */
+public class BIOFactory {
+
+    public static BufferedRandomAccessIO create(int chunkSize) {
+        IOController controller = createIOController(chunkSize);
+        BufferedRandomAccessIO out = new BufferedRandomAccessIO(controller);
+        return out;
+    }
+
+    public static BufferedRandomAccessIO create(OutputStream out) {
+        return create(out, UIOStreamBuilder.DEFAULT_CHUNK_SIZE);
+    }
+
+    public static BufferedRandomAccessIO create(final OutputStream out, int chunkSize) {
+        IOController ctrl = createIOController(chunkSize);
+        BufferedRandomAccessIO bio = new BufferedRandomAccessIO(ctrl) {
+            public void close() throws IOException {
+                controller.writeTo(out);
+                super.close();
+            }
+        };
+        return bio;
+    }
+
+    public static BufferedRandomAccessIO create(DataOutput out) {
+        return create(out, UIOStreamBuilder.DEFAULT_CHUNK_SIZE);
+    }
+
+    public static BufferedRandomAccessIO create(final DataOutput out, int chunkSize) {
+        IOController ctrl = createIOController(chunkSize);
+        BufferedRandomAccessIO bio = new BufferedRandomAccessIO(ctrl) {
+            public void close() throws IOException {
+                controller.writeTo(out);
+                super.close();
+            }
+        };
+        return bio;
+    }
+
+    public static IOController createIOController(int chunkSize) {
+        Content bc = new DummyContent();
+        IOController sb = new IOController(chunkSize, bc);
+        return sb;
+    }
+
+    public static IOController createIOController(InputStream in, File tmp, int chunkSize) {
+        Content bc;
+        if (tmp != null) {
+            try {
+                bc = new FileCachedInputStreamContent(in, tmp);
+            } catch (IOException ex) {
+                System.err.println("Unable to use file cache, switching to memory cache.");
+                ex.printStackTrace();
+                bc = new MemoryCachedInputStreamContent(in, chunkSize);
+            }
+        } else {
+            bc = new MemoryCachedInputStreamContent(in, chunkSize);
+        }
+        IOController sb = new IOController(chunkSize, bc);
+        return sb;
+    }
+
+    public static IOController createIOController(URL url) {
+        return createIOController(url, UIOStreamBuilder.DEFAULT_CHUNK_SIZE);
+    }
+
+    public static IOController createIOController(URL url, int chunkSize) {
+        return createIOController(url, null, chunkSize);
+    }
+
+    public static IOController createIOController(URL url, File tmp) {
+        return createIOController(url, tmp, UIOStreamBuilder.DEFAULT_CHUNK_SIZE);
+    }
+
+    public static IOController createIOController(URL url, File tmp, int chunkSize) {
+        Content bc;
+        if (tmp != null) {
+            try {
+                bc = new FileCachedHTTPContent(url, tmp);
+            } catch (IOException ex) {
+                ex.printStackTrace();
+                System.err.println("Unable to use file cache, switching to memory cache.");
+                bc = new HTTPContent(url);
+            }
+        } else {
+            bc = new HTTPContent(url);
+        }
+        IOController sb = new IOController(chunkSize, bc);
+        return sb;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/Buffer.java
===================================================================
--- ocean/src/com/imagero/uio/bio/Buffer.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/Buffer.java	(revision 0)
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio;
+
+/**
+ * @author Andrey Kuznetsov
+ */
+class Buffer {
+    byte [] buffer;
+
+    public Buffer(byte[] buffer) {
+        this.buffer = buffer;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/BufferedRandomAccessIO.java
===================================================================
--- ocean/src/com/imagero/uio/bio/BufferedRandomAccessIO.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/BufferedRandomAccessIO.java	(revision 0)
@@ -0,0 +1,252 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio;
+
+import com.imagero.uio.RandomAccessIO;
+import com.imagero.uio.RandomAccessInput;
+import com.imagero.uio.RandomAccessOutput;
+import com.imagero.uio.impl.AbstractRandomAccessIO;
+
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * BufferedRandomAccessIO - buffered readable and writable stream with random access.
+ * @author Andrey Kuznetsov
+ */
+public class BufferedRandomAccessIO extends AbstractRandomAccessIO {
+
+    FixedSizeByteBuffer buffer;
+    BufferIndex bufferIndex;
+    BufferPosition bufferPosition;
+    IOController controller;
+
+    StreamPosition streamPosition = new StreamPosition();
+    long offset;
+
+
+    public BufferedRandomAccessIO(IOController controller) {
+        this(controller, 0L);
+    }
+
+    public BufferedRandomAccessIO(IOController controller, long offset) {
+        this.controller = controller;
+        this.offset = offset;
+        bufferPosition = new BufferPosition(controller.bufferSize);
+        seek(0);
+    }
+
+    public final void setLength(long newLength) throws IOException {
+        controller.setLength(newLength);
+    }
+
+    public long flushBefore(long pos) {
+        return controller.flushBefore(pos);
+    }
+
+    protected void prepareBufferForReading(BufferIndex index) throws IOException {
+        if (!index.equals(bufferIndex) || buffer == null || buffer.buf == null) {
+            bufferIndex = index;
+            buffer = controller.getBuffer(streamPosition.pos, true);
+        }
+        bufferPosition.pos = (int) ((streamPosition.pos) % controller.bufferSize);
+    }
+
+    protected void prepareBufferForWriting(BufferIndex index) throws IOException {
+        if (!index.equals(bufferIndex) || buffer == null || buffer.buf == null) {
+            bufferIndex = index;
+            buffer = controller.getBuffer(streamPosition.pos, false);
+        }
+        bufferPosition.pos = (int) ((streamPosition.pos) % controller.bufferSize);
+        buffer.changed = true;
+    }
+
+    public long getFilePointer() {
+        return streamPosition.pos - offset;
+    }
+
+    public long length() throws IOException {
+        return controller.length() - offset;
+    }
+
+    public void seek(long pos) {
+        if(pos < 0) {
+            throw new IllegalArgumentException("Negative seek offset");
+        }
+        streamPosition.pos = pos + offset;
+        bufferPosition.pos = (int) ((streamPosition.pos) % controller.bufferSize);
+    }
+
+    public int available() throws IOException {
+        if (buffer != null) {
+            return buffer.availableForReading(bufferPosition);
+        }
+        return 0;
+    }
+
+    public void write(int b) throws IOException {
+        ensureBuffer(false);
+        buffer.write(b, bufferPosition);
+        streamPosition.pos++;
+    }
+
+    public void write(byte b[], int offset, int length) throws IOException {
+        while (length > 0) {
+            ensureBuffer(false);
+            int written = buffer.write(b, offset, length, bufferPosition);
+            length -= written;
+            offset += written;
+            streamPosition.pos += written;
+        }
+    }
+
+    public void close() throws IOException {
+        if (controller != null) {
+            controller.sync();
+            controller = null;
+        }
+    }
+
+    /**
+     * write buffer contents to given OutputStream
+     * @param out OutputStream
+     */
+    public void writeBuffer(OutputStream out) throws IOException {
+        controller.writeTo(out);
+    }
+
+    /**
+     * write buffer contents to DataOutput
+     * @param out OutputStream
+     */
+    public void writeBuffer(DataOutput out) throws IOException {
+        controller.writeTo(out);
+    }
+
+    private void ensureBuffer(boolean read) throws IOException {
+        BufferIndex index = controller.getBufferIndex(streamPosition.pos);
+        if (read) {
+            if (buffer == null || buffer.availableForReading(bufferPosition) <= 0 || bufferIndex != index) {
+                prepareBufferForReading(index);
+            }
+        } else {
+            if (buffer == null || buffer.availableForWriting(bufferPosition) <= 0 || bufferIndex != index) {
+                prepareBufferForWriting(index);
+            }
+        }
+    }
+
+    public int read() throws IOException {
+        try {
+            ensureBuffer(true);
+        } catch (IOException ex) {
+            return -1;
+        }
+        if (buffer != null) {
+            streamPosition.pos++;
+            return buffer.read(bufferPosition);
+        }
+        return -1;
+    }
+
+    public long skip(long n) throws IOException {
+        ensureBuffer(true);
+        if (buffer == null) {
+            return 0;
+        }
+        long skipped = buffer.skip(n, bufferPosition);
+        streamPosition.pos += skipped;
+        return skipped;
+    }
+
+    public int read(byte[] b, int offset, int length) throws IOException {
+        ensureBuffer(true);
+        if (buffer == null) {
+            return 0;
+        }
+        int rc = buffer.read(b, offset, length, bufferPosition);
+        if (rc > 0) {
+            streamPosition.pos += rc;
+        }
+        return rc;
+    }
+
+    public RandomAccessIO createIOChild(long offset, long length, int byteOrder, boolean syncPointer) {
+        BufferedRandomAccessIO io = new BufferedRandomAccessIO(controller, this.offset + offset);
+        io.setByteOrder(byteOrder);
+        if (syncPointer) {
+            io.streamPosition = streamPosition;
+        }
+        return io;
+    }
+
+    public RandomAccessInput createInputChild(long offset, long length, int byteOrder, boolean syncPointer) {
+        return createIOChild(offset, 0, byteOrder, syncPointer);
+    }
+
+    public InputStream createInputStream(long offset) {
+        return new IOCInputStream(controller, this.offset + offset);
+    }
+
+    public RandomAccessOutput createOutputChild(long offset, int byteOrder, boolean syncPointer) {
+        return createIOChild(offset, 0, byteOrder, syncPointer);
+    }
+
+    public OutputStream createOutputStream(long offset) {
+        return new IOCOutputStream(controller, this.offset + offset);
+    }
+
+    public void flush() throws IOException {
+        controller.sync();
+    }
+
+    public boolean isBuffered() {
+        return true;
+    }
+
+    public long getChildPosition(InputStream child) {
+        if(child instanceof IOCInputStream) {
+            IOCInputStream in = (IOCInputStream) child;
+            return in.getPosition();
+        }
+        return -1;
+    }
+
+    public void setChildPosition(InputStream child, long position) {
+        if (child instanceof IOCInputStream) {
+            IOCInputStream in = (IOCInputStream) child;
+            in.seek(position);
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/bio/BufferIndex.java
===================================================================
--- ocean/src/com/imagero/uio/bio/BufferIndex.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/BufferIndex.java	(revision 0)
@@ -0,0 +1,35 @@
+package com.imagero.uio.bio;
+
+/**
+ * Index of Object in 2D array
+ * Date: 14.12.2007
+ *
+ * @author Andrey Kuznetsov
+ */
+class BufferIndex {
+    /**
+     * index of array in 2D array
+     */
+    int arrayIndex;
+    /**
+     * index of object in 1D array
+     */
+    int index;
+
+    /**
+     * @param arrayIndex index of array
+     * @param index index of object
+     */
+    public BufferIndex(int arrayIndex, int index) {
+        this.arrayIndex = arrayIndex;
+        this.index = index;
+    }
+
+    public boolean equals(Object obj) {
+        if(obj != null && obj instanceof BufferIndex) {
+            BufferIndex bi = (BufferIndex) obj;
+            return bi.arrayIndex == arrayIndex && bi.index == index;
+        }
+        return false;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/BufferPosition.java
===================================================================
--- ocean/src/com/imagero/uio/bio/BufferPosition.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/BufferPosition.java	(revision 0)
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio;
+
+/**
+ * @author Andrey Kuznetsov
+ */
+public class BufferPosition {
+
+    public BufferPosition(int bufferSize) {
+        this.bufferSize = bufferSize;
+    }
+
+    int bufferSize;
+    public int pos;
+
+    public int available() {
+        return bufferSize - pos;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/ByteArrayRandomAccessIO.java
===================================================================
--- ocean/src/com/imagero/uio/bio/ByteArrayRandomAccessIO.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/ByteArrayRandomAccessIO.java	(revision 0)
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio;
+
+import com.imagero.uio.RandomAccessIO;
+import com.imagero.uio.RandomAccessInput;
+import com.imagero.uio.RandomAccessOutput;
+import com.imagero.uio.impl.AbstractRandomAccessIO;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * ByteArrayRandomAccessIO is like ByteArrayOutputStream and ByteArrayInputStream together.
+ * It implements also DataInput/DataOutput and other advanced interfaces.
+ * @author Andrey Kuznetsov
+ */
+public class ByteArrayRandomAccessIO extends AbstractRandomAccessIO implements RandomAccessIO {
+
+    VariableSizeByteBuffer buffer;
+    BufferPosition position;
+    int _offset;
+    Integer length;
+
+    private static VariableSizeByteBuffer createBuffer(int size) {
+        return new VariableSizeByteBuffer(size);
+    }
+
+    private static VariableSizeByteBuffer createBuffer(byte [] data) {
+        return new VariableSizeByteBuffer(data);
+    }
+    public ByteArrayRandomAccessIO(int initialSize) {
+        this(createBuffer(initialSize));
+    }
+
+    public ByteArrayRandomAccessIO(int offset, int length, VariableSizeByteBuffer buffer) {
+        this._offset = offset;
+        this.buffer = buffer;
+        position = new BufferPosition(Integer.MAX_VALUE);
+        position.pos = offset;
+        if(length > 0) {
+            this.length = new Integer(length);
+        }
+    }
+
+    public ByteArrayRandomAccessIO(byte [] data) {
+        this(createBuffer(data));
+    }
+
+    public ByteArrayRandomAccessIO(VariableSizeByteBuffer buffer) {
+        this.buffer = buffer;
+        position = new BufferPosition(Integer.MAX_VALUE);
+    }
+
+    public int read() throws IOException {
+        return buffer.read(position);
+    }
+
+    public long skip(long n) throws IOException {
+        return buffer.skip(n, position);
+    }
+
+    public int read(byte[] b, int off, int len) throws IOException {
+        return buffer.read(b, off, len, position);
+    }
+
+    public long getFilePointer() throws IOException {
+        return position.pos - _offset;
+    }
+
+    public long length() throws IOException {
+        if(length != null) {
+            return Math.min(length.intValue(), buffer.getCount() - _offset);
+        }
+        else {
+            return buffer.getCount() - _offset;
+        }
+    }
+
+    public void seek(long offset) throws IOException {
+        if (offset + _offset > Integer.MAX_VALUE) {
+            throw new IOException("Offset too big: 0x" + Long.toHexString(offset));
+        }
+        buffer.seek((int) offset + _offset, position);
+    }
+
+    public void setLength(long newLength) throws IOException {
+        if (newLength > Integer.MAX_VALUE) {
+            throw new IOException();
+        }
+        buffer.setCount((int) newLength);
+    }
+
+    public void write(int b) throws IOException {
+        buffer.write(b, position);
+    }
+
+    public void write(byte b[], int offset, int length) throws IOException {
+        buffer.write(b, offset, length, position);
+    }
+
+    public RandomAccessIO createIOChild(long offset, long length, int byteOrder, boolean syncPointer) {
+        ByteArrayRandomAccessIO io = new ByteArrayRandomAccessIO((int) offset, (int) length, buffer);
+        if(syncPointer) {
+            io.buffer = buffer;
+        }
+        io.setByteOrder(byteOrder);
+        return io;
+    }
+
+    public RandomAccessInput createInputChild(long offset, long length, int byteOrder, boolean syncPointer) {
+        return createIOChild(offset, length, byteOrder, syncPointer);
+    }
+
+    public RandomAccessOutput createOutputChild(long offset, int byteOrder, boolean syncPointer) {
+        return createIOChild(offset, 0, byteOrder, syncPointer);
+    }
+
+    public byte [] toByteArray() throws IOException {
+        byte [] b = new byte[(int)length()];
+        int pos = position.pos;
+        buffer.seek(0, position);
+        buffer.read(b, 0, b.length, position);
+        buffer.seek(pos, position);
+        return b;
+    }
+
+    public InputStream createInputStream(long offset) {
+        return buffer.getInputStream((int) offset);
+    }
+
+    public long getChildPosition(InputStream child) {
+        if(child instanceof VSBInputStream) {
+            VSBInputStream vsbis = (VSBInputStream) child;
+            return vsbis.getPosition();
+        }
+        return -1;
+    }
+
+    public void setChildPosition(InputStream child, long pos) {
+        if (child instanceof VSBInputStream) {
+            VSBInputStream vsbis = (VSBInputStream) child;
+            vsbis.setPosition(pos);
+        }
+    }
+
+    public OutputStream createOutputStream(long offset) {
+        return buffer.getOutputStream((int) offset);
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/FloatArrayContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/FloatArrayContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/FloatArrayContent.java	(revision 0)
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import com.imagero.uio.Transformer;
+
+import java.io.IOException;
+
+/**
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class FloatArrayContent extends Content {
+    float[][] data;
+    boolean bigEndian;
+
+    static final int SHIFT = 2;
+
+    public FloatArrayContent(float[][] data) {
+        this(data, true);
+    }
+
+    public FloatArrayContent(float[][] data, boolean bigEndian) {
+        this.data = data;
+        this.bigEndian = bigEndian;
+    }
+
+    public int load(long offset, int bpos, byte[] dest) throws IOException {
+        //to simplify float to byte converting, we require 4 bytes boundary
+        if ((offset & 3) != 0 || ((dest.length - bpos) & 3) != 0) {
+            throw new IOException("Illegal offset or length");
+        }
+        final long off = offset >> SHIFT;
+        final int len = (dest.length - bpos) >> SHIFT;
+
+        IndexAndStart ias = getIAS(off);
+        if (ias == null) {
+            return 0;
+        }
+        int index = ias.index;
+        int pos = (int) (off - ias.start);
+        float[] src = data[index];
+        int toCopy = Math.min(src.length - pos, len);
+        if (toCopy > 0) {
+            com.imagero.uio.Transformer.floatToByte(src, pos, toCopy, dest, bpos, bigEndian);
+            if ((toCopy < len)) {
+                int copiedBytes = (toCopy << SHIFT);
+                return toCopy + load(offset + copiedBytes, bpos + copiedBytes, dest);
+            }
+        }
+        return toCopy;
+    }
+
+    public void close() {
+    }
+
+    private IndexAndStart getIAS(long offset) {
+        long start = 0;
+        for (int i = 0; i < data.length; i++) {
+            float[] src = data[i];
+            int length = src.length;
+            long end = start + length;
+            if (offset >= start && offset <= end) {
+                return new IndexAndStart(i, start);
+            }
+            start += length;
+        }
+        return null;
+    }
+
+    /**
+     * Save data to current content (char array).
+     * All offsets and lengths must be
+     * @param offset
+     * @param spos
+     * @param src
+     * @param length
+     * @throws IOException
+     */
+    public void save(long offset, int spos, byte[] src, int length) throws IOException {
+        //to simplify byte to float converting we require 4 byte boundary
+        if ((offset & 3) != 0 || ((src.length - spos) & 3) != 0) {
+            throw new IOException("Illegal offset or length");
+        }
+        final long off = offset >> SHIFT;
+        final int len = Math.min((src.length - spos), length) >> SHIFT;
+
+        IndexAndStart ias = getIAS(off);
+        if (ias == null) {
+            return;
+        }
+        int index = ias.index;
+        long start = ias.start;
+        int dpos = (int) (off - start);
+        float[] dest = data[index];
+        int request = len;
+        int toCopy = Math.min((dest.length - dpos), request);
+        if (request > 0 && toCopy > 0) {
+            com.imagero.uio.Transformer.byteToFloat(src, spos, toCopy, dest, dpos, true);
+            if (toCopy < request) {
+                int copiedBytes = (toCopy << SHIFT);
+                save(offset + copiedBytes, spos + copiedBytes, src, (request - toCopy) << SHIFT);
+            }
+        }
+    }
+
+    public boolean canReload() {
+        return true;
+    }
+
+    public long length() throws IOException {
+        long length = 0;
+        for (int i = 0; i < data.length; i++) {
+            float[] dest = data[i];
+            length += dest.length;
+        }
+        return length << SHIFT;
+    }
+
+    public boolean writable() {
+        return true;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/Span.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/Span.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/Span.java	(revision 0)
@@ -0,0 +1,16 @@
+package com.imagero.uio.bio.content;
+
+/**
+ * Date: 19.03.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class Span {
+    long offset;
+    long length;
+
+    public Span(long offset, long length) {
+        this.offset = offset;
+        this.length = length;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/FileCachedHTTPContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/FileCachedHTTPContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/FileCachedHTTPContent.java	(revision 0)
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import com.imagero.uio.bio.content.HTTPContent;
+import com.imagero.uio.impl.TmpRandomAccessFile;
+import com.imagero.util.Vector;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+
+/**
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class FileCachedHTTPContent extends HTTPContent {
+
+    File tmp;
+    TmpRandomAccessFile tmpRaf;
+
+    Vector ranges = new Vector();
+
+    public FileCachedHTTPContent(URL url, File tmp) throws IOException {
+        super(url);
+        this.tmp = tmp;
+        tmpRaf = new TmpRandomAccessFile(tmp, "rw");
+    }
+
+    public int load(long offset, int bpos, byte[] buffer) throws IOException {
+        if (contains(offset, buffer.length - bpos)) {
+            tmpRaf.seek(offset);
+            tmpRaf.readFully(buffer, bpos, buffer.length - bpos);
+            return buffer.length - bpos;
+        }
+        int k = super.load(offset, bpos, buffer);
+        if (k > 0) {
+            tmpRaf.seek(offset);
+            tmpRaf.write(buffer, bpos, k);
+            addRange(offset, k);
+        }
+        return k;
+    }
+
+    boolean contains(long offset, int length) {
+        Range r0 = new Range(offset, offset + length);
+        for (int i = 0; i < ranges.size(); i++) {
+            Range r = (Range) ranges.elementAt(i);
+            if (r.contains(r0)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    void addRange(long offset, int length) {
+        Range r0 = new Range(offset, offset + length);
+
+        for (int i = 0; i < ranges.size(); i++) {
+            Range r = (Range) ranges.elementAt(i);
+            if (r.canJoin(r0)) {
+                r.join(r0);
+                for (int j = 0; j < ranges.size(); j++) {
+                    Range r1 = (Range) ranges.elementAt(j);
+                    if (r != r1 && r.canJoin(r1)) {
+                        r.join(r1);
+                        j--;
+                        ranges.remove(r1);
+                    }
+                }
+                return;
+            }
+        }
+        ranges.add(r0);
+    }
+
+    public boolean canReload() {
+        return true;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/DoubleArrayContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/DoubleArrayContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/DoubleArrayContent.java	(revision 0)
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import com.imagero.uio.Transformer;
+
+import java.io.IOException;
+
+/**
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class DoubleArrayContent extends Content {
+    double[][] data;
+    boolean bigEndian;
+
+    static final int SHIFT = 3;
+
+    public DoubleArrayContent(double[][] data) {
+        this(data, true);
+    }
+
+    public DoubleArrayContent(double[][] data, boolean bigEndian) {
+        this.data = data;
+        this.bigEndian = bigEndian;
+    }
+
+    public int load(long offset, int bpos, byte[] dest) throws IOException {
+        //to simplify long to byte converting, we require 8 bytes boundary
+        if ((offset & 7) != 0 || ((dest.length - bpos) & 7) != 0) {
+            throw new IOException("Illegal offset or length");
+        }
+        final long off = offset >> SHIFT;
+        final int len = (dest.length - bpos) >> SHIFT;
+
+        IndexAndStart ias = getIAS(off);
+        if (ias == null) {
+            return 0;
+        }
+        int index = ias.index;
+        int pos = (int) (off - ias.start);
+        double[] src = data[index];
+        int toCopy = Math.min(src.length - pos, len);
+        if (toCopy > 0) {
+            com.imagero.uio.Transformer.doubleToByte(src, pos, toCopy, dest, bpos, bigEndian);
+            if ((toCopy < len)) {
+                int copiedBytes = (toCopy << SHIFT);
+                return toCopy + load(offset + copiedBytes, bpos + copiedBytes, dest);
+            }
+        }
+        return toCopy;
+    }
+
+    public void close() {
+    }
+
+    private IndexAndStart getIAS(long offset) {
+        long start = 0;
+        for (int i = 0; i < data.length; i++) {
+            double[] src = data[i];
+            int length = src.length;
+            long end = start + length;
+            if (offset >= start && offset <= end) {
+                return new IndexAndStart(i, start);
+            }
+            start += length;
+        }
+        return null;
+    }
+
+    /**
+     * Save data to current content (char array).
+     * All offsets and lengths must be
+     * @param offset
+     * @param spos
+     * @param src
+     * @param length
+     * @throws IOException
+     */
+    public void save(long offset, int spos, byte[] src, int length) throws IOException {
+        //to simplify byte to long converting we require 8 byte boundary
+        if ((offset & 7) != 0 || ((src.length - spos) & 7) != 0) {
+            throw new IOException("Illegal offset or length");
+        }
+        final long off = offset >> SHIFT;
+        final int len = Math.min((src.length - spos), length) >> SHIFT;
+
+        IndexAndStart ias = getIAS(off);
+        if (ias == null) {
+            return;
+        }
+        int index = ias.index;
+        long start = ias.start;
+        int dpos = (int) (off - start);
+        double[] dest = data[index];
+        int request = len;
+        int toCopy = Math.min((dest.length - dpos), request);
+        if (request > 0 && toCopy > 0) {
+            com.imagero.uio.Transformer.byteToDouble(src, spos, toCopy, dest, dpos, true);
+            if (toCopy < request) {
+                int copiedBytes = (toCopy << SHIFT);
+                save(offset + copiedBytes, spos + copiedBytes, src, (request - toCopy) << SHIFT);
+            }
+        }
+    }
+
+    public boolean canReload() {
+        return true;
+    }
+
+    public long length() throws IOException {
+        long length = 0;
+        for (int i = 0; i < data.length; i++) {
+            double[] dest = data[i];
+            length += dest.length;
+        }
+        return length << SHIFT;
+    }
+
+    public boolean writable() {
+        return true;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/SpannedRandomAccessFileContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/SpannedRandomAccessFileContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/SpannedRandomAccessFileContent.java	(revision 0)
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import com.imagero.uio.impl.RandomAccessFileX;
+import com.imagero.uio.io.IOutils;
+import com.imagero.uio.io.UnexpectedEOFException;
+
+import java.io.RandomAccessFile;
+import java.io.File;
+import java.io.IOException;
+import java.io.EOFException;
+
+/**
+ * Content with access to one or more predefined areas in File or RandomAccessFile.
+ * Length can not be changed.
+ * UnexpectedEOFException is thrown if we try to write outside our length.
+ *
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class SpannedRandomAccessFileContent extends Content {
+    private RandomAccessFile raf;
+
+    Span[] spans;
+    long length;
+
+    public SpannedRandomAccessFileContent(File f, Span[] spans) throws IOException {
+        this(f, getMode(f), spans);
+    }
+
+    public SpannedRandomAccessFileContent(File f, String mode, Span[] spans) throws IOException {
+        this(new RandomAccessFileX(f, mode), spans);
+
+    }
+
+    static String getMode(File f) {
+        if (!f.exists() || f.canWrite()) {
+            return "rw";
+        } else {
+            return "r";
+        }
+    }
+
+    public SpannedRandomAccessFileContent(RandomAccessFile raf, Span[] spans) throws IOException {
+        this.raf = raf;
+        this.spans = spans;
+        long rafLength = raf.length();
+        for (int i = 0; i < spans.length; i++) {
+            Span span = spans[i];
+            if (span.offset > rafLength || span.offset + span.length > rafLength) {
+                throw new IOException("Illegal span: " + span.offset + " " + span.length);
+            }
+        }
+        for (int i = 0; i < spans.length; i++) {
+            length += spans[i].length;
+        }
+    }
+
+    Span currentSpan;
+    long spanOffset;
+
+    void seek(long offset) throws IOException {
+        int sp = 0;
+        while (offset > 0) {
+            Span span = spans[sp++];
+            if (offset < span.length) {
+                currentSpan = span;
+                spanOffset = offset;
+                raf.seek(span.offset + offset);
+                return;
+            } else {
+                offset -= span.length;
+            }
+        }
+    }
+
+    public int load(long offset, int bpos, byte[] b) throws IOException {
+        seek(offset);
+
+        long available = currentSpan.length - spanOffset;
+        int len = (int) Math.min(available, b.length - bpos);
+        if (len > 0) {
+            raf.readFully(b, bpos, len);
+            return len;
+        }
+        throw new EOFException();
+    }
+
+    public boolean canReload() {
+        return true;
+    }
+
+    public void close() {
+        IOutils.closeStream(raf);
+    }
+
+    public boolean writable() {
+        return true;
+    }
+
+    public void save(long offset, int bpos, byte[] buffer, int length) throws IOException {
+        long len = length;
+        while (len > 0) {
+            seek(offset);
+            long available = currentSpan.length - spanOffset;
+            int w = (int) Math.min(available, len);
+            if (w == 0) {
+                throw new UnexpectedEOFException(length - len);
+            }
+            raf.write(buffer, bpos, w);
+            offset += w;
+            bpos += w;
+            len -= w;
+        }
+    }
+
+    public long length() throws IOException {
+        return length;
+    }
+
+    protected void finalize() throws Throwable {
+        super.finalize();
+        raf = null;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/CharArrayContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/CharArrayContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/CharArrayContent.java	(revision 0)
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import com.imagero.uio.Transformer;
+
+import java.io.IOException;
+
+/**
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class CharArrayContent extends Content {
+    char[][] data;
+    boolean bigEndian;
+
+    static final int SHIFT = 1;
+
+    public CharArrayContent(char[][] data) {
+        this(data, true);
+    }
+
+    public CharArrayContent(char[][] data, boolean bigEndian) {
+        this.data = data;
+        this.bigEndian = bigEndian;
+    }
+
+    public int load(long offset, int bpos, byte[] dest) throws IOException {
+        //to simplify char to byte converting, we require two bytes boundary
+        if ((offset & 1) != 0 || ((dest.length - bpos) & 1) != 0) {
+            throw new IOException("Illegal offset or length");
+        }
+        final long off = offset >> SHIFT;
+        final int len = (dest.length - bpos) >> SHIFT;
+
+        IndexAndStart ias = getIAS(off);
+        if (ias == null) {
+            return 0;
+        }
+        int index = ias.index;
+        int pos = (int) (off - ias.start);
+        char[] src = data[index];
+        int toCopy = Math.min(src.length - pos, len);
+        if (toCopy > 0) {
+            com.imagero.uio.Transformer.charToByte(src, pos, toCopy, dest, bpos, bigEndian);
+            if ((toCopy < len)) {
+                int copiedBytes = (toCopy << SHIFT);
+                return toCopy + load(offset + copiedBytes, bpos + copiedBytes, dest);
+            }
+        }
+        return toCopy;
+    }
+
+    public void close() {
+    }
+
+    private IndexAndStart getIAS(long offset) {
+        long start = 0;
+        for (int i = 0; i < data.length; i++) {
+            char[] src = data[i];
+            int length = src.length;
+            long end = start + length;
+            if (offset >= start && offset <= end) {
+                return new IndexAndStart(i, start);
+            }
+            start += length;
+        }
+        return null;
+    }
+
+    /**
+     * Save data to current content (char array).
+     * All offsets and lengths must be
+     * @param offset
+     * @param spos
+     * @param src
+     * @param length
+     * @throws IOException
+     */
+    public void save(long offset, int spos, byte[] src, int length) throws IOException {
+        //to simplify char to byte converting we require two byte boundary
+        if ((offset & 1) != 0 || ((src.length - spos) & 1) != 0) {
+            throw new IOException("Illegal offset or length");
+        }
+        final long off = offset >> SHIFT;
+        final int len = Math.min((src.length - spos), length) >> SHIFT;
+
+        IndexAndStart ias = getIAS(off);
+        if (ias == null) {
+            return;
+        }
+        int index = ias.index;
+        long start = ias.start;
+        int dpos = (int) (off - start);
+        char[] dest = data[index];
+        int request = len;
+        int toCopy = Math.min((dest.length - dpos), request);
+        if (request > 0 && toCopy > 0) {
+            com.imagero.uio.Transformer.byteToChar(src, spos, toCopy, dest, dpos, true);
+            if (toCopy < request) {
+                int copiedBytes = (toCopy << SHIFT);
+                save(offset + copiedBytes, spos + copiedBytes, src, (request - toCopy) << SHIFT);
+            }
+        }
+    }
+
+    public boolean canReload() {
+        return true;
+    }
+
+    public long length() throws IOException {
+        long length = 0;
+        for (int i = 0; i < data.length; i++) {
+            char[] dest = data[i];
+            length += dest.length;
+        }
+        return length << SHIFT;
+    }
+
+    public boolean writable() {
+        return true;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/IndexAndStart.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/IndexAndStart.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/IndexAndStart.java	(revision 0)
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+/**
+ * Date: 06.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+class IndexAndStart {
+    //array index
+    int index;
+    //offset of first byte of array in stream
+    long start;
+
+    public IndexAndStart(int index, long start) {
+        this.index = index;
+        this.start = start;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/SynchronizedContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/SynchronizedContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/SynchronizedContent.java	(revision 0)
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import java.io.IOException;
+
+/**
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class SynchronizedContent extends Content {
+
+    Content content;
+
+    public SynchronizedContent(Content content) {
+        this.content = content;
+    }
+
+    public synchronized int load(long offset, int bpos, byte[] buffer) throws IOException {
+        return content.load(offset, bpos, buffer);
+    }
+
+    public synchronized void save(long offset, int bpos, byte[] buffer, int length) throws IOException {
+        content.save(offset, bpos, buffer, length);
+    }
+
+    public synchronized long length() throws IOException {
+        return content.length();
+    }
+
+    public void close() {
+        content.close();
+    }
+
+    public boolean canReload() {
+        return content.canReload();
+    }
+
+    public boolean writable() {
+        return content.writable();
+    }
+
+    public Content getContent() {
+        return content;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/HTTPContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/HTTPContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/HTTPContent.java	(revision 0)
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import com.imagero.uio.io.IOutils;
+import com.imagero.uio.io.UnexpectedEOFException;
+
+import java.net.URL;
+import java.net.HttpURLConnection;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class HTTPContent extends Content {
+    URL url;
+
+    long length;
+
+    public HTTPContent(URL url) {
+        String protocol = url.getProtocol();
+        if (!"http".equalsIgnoreCase(protocol)) {
+            throw new IllegalArgumentException("http protokol only");
+        }
+        this.url = url;
+    }
+
+    public int load(long offset, int bpos, byte[] buffer) throws IOException {
+        HttpURLConnection httpcon = (HttpURLConnection) url.openConnection();
+        httpcon.setAllowUserInteraction(true);
+        httpcon.setDoInput(true);
+        httpcon.setDoOutput(true);
+        httpcon.setRequestMethod("GET");
+        httpcon.setUseCaches(false);
+        httpcon.setRequestProperty("Range", "bytes=" + offset + "-" + (offset + buffer.length - bpos));
+        httpcon.connect();
+
+        int responseCode = httpcon.getResponseCode();
+        if (responseCode != 206) {
+            httpcon.disconnect();
+            throw new IOException("byteserving not supported by server");
+        }
+        InputStream in = httpcon.getInputStream();
+
+        int count = 0;
+        try {
+            int len = buffer.length - bpos;
+            IOutils.readFully(in, buffer, bpos, len);
+            count = len;
+        } catch (UnexpectedEOFException ex) {
+            count = (int) ex.getCount();
+        } finally {
+            httpcon.disconnect();
+        }
+        return count;
+    }
+
+    public void close() {
+    }
+
+    public void save(long offset, int bpos, byte[] buffer, int length) throws IOException {
+    }
+
+    public long length() throws IOException {
+        if (length == 0) {
+            HttpURLConnection httpcon = (HttpURLConnection) url.openConnection();
+            httpcon.setRequestMethod("HEAD");
+            httpcon.setUseCaches(false);
+            httpcon.connect();
+            length = httpcon.getContentLength();
+            httpcon.disconnect();
+        }
+        return length;
+    }
+
+    public boolean canReload() {
+        return false;
+    }
+
+    public boolean writable() {
+        return false;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/Content.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/Content.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/Content.java	(revision 0)
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import java.io.IOException;
+
+/**
+ *
+ * @author Andrey Kuznetsov
+ */
+public abstract class Content {
+
+    /**
+     * Load stream content to specified buffer
+     * @param offset stream offset
+     * @param buffer byte array
+     * @return how much bytes were loaded
+     * @throws java.io.IOException
+     */
+    public final int load(long offset, byte[] buffer) throws IOException {
+        return load(offset, 0, buffer);
+    }
+
+    /**
+     * Load stream content to specified buffer
+     * @param offset stream offset
+     * @param bpos buffer position
+     * @param buffer byte array
+     * @return how much bytes were loaded
+     * @throws java.io.IOException
+     */
+    public abstract int load(long offset, int bpos, byte[] buffer) throws IOException;
+
+    /**
+     * Save buffer content to stream.
+     * Not always supported.
+     * @param offset stream offset
+     * @param bpos buffer position
+     * @param buffer byte array
+     * @param length how much bytes should be saved
+     * @throws java.io.IOException
+     */
+    public abstract void save(long offset, int bpos, byte[] buffer, int length) throws IOException;
+
+    /**
+     * Get stream length. Not always known.
+     * @return
+     * @throws java.io.IOException
+     */
+    public abstract long length() throws IOException;
+
+    /**
+     * close stream
+     */
+    public abstract void close();
+
+    /**
+     * Determine if data may be reloaded or not.
+     * For example: data from InputStream cannot be reloaded,
+     * however if content uses file or memory based cache then it is possible to reload data.
+     * @return true if data can be reloaded.
+     */
+    public abstract boolean canReload();
+
+    public abstract boolean writable();
+
+    protected void finalize() throws Throwable {
+        super.finalize();
+        close();
+    }
+
+
+}
Index: ocean/src/com/imagero/uio/bio/content/IntArrayContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/IntArrayContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/IntArrayContent.java	(revision 0)
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import com.imagero.uio.Transformer;
+
+import java.io.IOException;
+
+/**
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class IntArrayContent extends Content {
+    int[][] data;
+    boolean bigEndian;
+
+    static final int SHIFT = 2;
+
+    public IntArrayContent(int[][] data) {
+        this(data, true);
+    }
+
+    public IntArrayContent(int[][] data, boolean bigEndian) {
+        this.data = data;
+        this.bigEndian = bigEndian;
+    }
+
+    public int load(long offset, int bpos, byte[] dest) throws IOException {
+        //to simplify int to byte converting, we require 4 bytes boundary
+        if ((offset & 3) != 0 || ((dest.length - bpos) & 3) != 0) {
+            throw new IOException("Illegal offset or length");
+        }
+        final long off = offset >> SHIFT;
+        final int len = (dest.length - bpos) >> SHIFT;
+
+        IndexAndStart ias = getIAS(off);
+        if (ias == null) {
+            return 0;
+        }
+        int index = ias.index;
+        int pos = (int) (off - ias.start);
+        int[] src = data[index];
+        int toCopy = Math.min(src.length - pos, len);
+        if (toCopy > 0) {
+            com.imagero.uio.Transformer.intToByte(src, pos, toCopy, dest, bpos, bigEndian);
+            if ((toCopy < len)) {
+                int copiedBytes = (toCopy << SHIFT);
+                return toCopy + load(offset + copiedBytes, bpos + copiedBytes, dest);
+            }
+        }
+        return toCopy;
+    }
+
+    public void close() {
+    }
+
+    private IndexAndStart getIAS(long offset) {
+        long start = 0;
+        for (int i = 0; i < data.length; i++) {
+            int[] src = data[i];
+            int length = src.length;
+            long end = start + length;
+            if (offset >= start && offset <= end) {
+                return new IndexAndStart(i, start);
+            }
+            start += length;
+        }
+        return null;
+    }
+
+    /**
+     * Save data to current content (char array).
+     * All offsets and lengths must be
+     * @param offset
+     * @param spos
+     * @param src
+     * @param length
+     * @throws IOException
+     */
+    public void save(long offset, int spos, byte[] src, int length) throws IOException {
+        //to simplify byte to int converting we require 4 byte boundary
+        if ((offset & 3) != 0 || ((src.length - spos) & 3) != 0) {
+            throw new IOException("Illegal offset or length");
+        }
+        final long off = offset >> SHIFT;
+        final int len = Math.min((src.length - spos), length) >> SHIFT;
+
+        IndexAndStart ias = getIAS(off);
+        if (ias == null) {
+            return;
+        }
+        int index = ias.index;
+        long start = ias.start;
+        int dpos = (int) (off - start);
+        int[] dest = data[index];
+        int request = len;
+        int toCopy = Math.min((dest.length - dpos), request);
+        if (request > 0 && toCopy > 0) {
+            com.imagero.uio.Transformer.byteToInt(src, spos, toCopy, dest, dpos, true);
+            if (toCopy < request) {
+                int copiedBytes = (toCopy << SHIFT);
+                save(offset + copiedBytes, spos + copiedBytes, src, (request - toCopy) << SHIFT);
+            }
+        }
+    }
+
+    public boolean canReload() {
+        return true;
+    }
+
+    public long length() throws IOException {
+        long length = 0;
+        for (int i = 0; i < data.length; i++) {
+            int[] dest = data[i];
+            length += dest.length;
+        }
+        return length << SHIFT;
+    }
+
+    public boolean writable() {
+        return true;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/RandomAccessIOContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/RandomAccessIOContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/RandomAccessIOContent.java	(revision 0)
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import com.imagero.uio.RandomAccessIO;
+import com.imagero.uio.RandomAccessInput;
+import com.imagero.uio.io.IOutils;
+
+import java.io.IOException;
+import java.io.EOFException;
+
+/**
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class RandomAccessIOContent extends Content {
+    private RandomAccessIO rio;
+
+    public RandomAccessIOContent(RandomAccessIO rio) throws IOException {
+        this.rio = rio;
+    }
+
+    public int load(long offset, int bpos, byte[] b) throws IOException {
+        long max = rio.length() - offset;
+        int len = (int) Math.min(max, b.length - bpos);
+        if (len > 0) {
+            rio.seek(offset);
+            rio.readFully(b, bpos, len);
+            return len;
+        }
+        throw new EOFException();
+//            return 0;
+    }
+
+    public boolean canReload() {
+        return true;
+    }
+
+    public void close() {
+        IOutils.closeStream((RandomAccessInput) rio);
+        rio = null;
+    }
+
+    public boolean writable() {
+        return true;
+    }
+
+    public void save(long offset, int bpos, byte[] buffer, int length) throws IOException {
+        rio.seek(offset);
+        try {
+            rio.write(buffer, bpos, length);
+        } catch (IndexOutOfBoundsException ex) {
+            ex.printStackTrace();
+        }
+    }
+
+    public long length() throws IOException {
+        return rio.length();
+    }
+
+    protected void finalize() throws Throwable {
+        super.finalize();
+        rio = null;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/SpannedRandomAccessInputContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/SpannedRandomAccessInputContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/SpannedRandomAccessInputContent.java	(revision 0)
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import com.imagero.uio.RandomAccessInput;
+import com.imagero.uio.io.IOutils;
+
+import java.io.EOFException;
+import java.io.IOException;
+
+/**
+ * Content with access to one or more predefined areas in RandomAccessIO.
+ * Length can not be changed.
+ * UnexpectedEOFException is thrown if we try to write outside our length.
+ *
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class SpannedRandomAccessInputContent extends Content {
+    private RandomAccessInput rio;
+
+    Span[] spans;
+    long length;
+
+
+    public SpannedRandomAccessInputContent(RandomAccessInput rio, Span[] spans) throws IOException {
+        this.rio = rio;
+        this.spans = spans;
+        long rafLength = rio.length();
+        for (int i = 0; i < spans.length; i++) {
+            Span span = spans[i];
+            if (span.offset > rafLength || span.offset + span.length > rafLength) {
+                throw new IOException("Illegal span: " + span.offset + " " + span.length);
+            }
+        }
+        for (int i = 0; i < spans.length; i++) {
+            length += spans[i].length;
+        }
+    }
+
+    Span currentSpan;
+    long spanOffset;
+
+    void seek(long offset) throws IOException {
+        int sp = 0;
+        while (offset > 0) {
+            Span span = spans[sp++];
+            if (offset < span.length) {
+                currentSpan = span;
+                spanOffset = offset;
+                rio.seek(span.offset + offset);
+                return;
+            } else {
+                offset -= span.length;
+            }
+        }
+    }
+
+    public int load(long offset, int bpos, byte[] b) throws IOException {
+        seek(offset);
+
+        long available = currentSpan.length - spanOffset;
+        int len = (int) Math.min(available, b.length - bpos);
+        if (len > 0) {
+            rio.readFully(b, bpos, len);
+            return len;
+        }
+        throw new EOFException();
+    }
+
+    public boolean canReload() {
+        return true;
+    }
+
+    public void close() {
+        IOutils.closeStream(rio);
+    }
+
+    public boolean writable() {
+        return false;
+    }
+
+    public void save(long offset, int bpos, byte[] buffer, int length) throws IOException {
+
+    }
+
+    public long length() throws IOException {
+        return length;
+    }
+
+    protected void finalize() throws Throwable {
+        super.finalize();
+        rio = null;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/DummyContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/DummyContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/DummyContent.java	(revision 0)
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import java.io.IOException;
+
+/**
+ * empty content which just tracks length.
+ */
+public class DummyContent extends Content {
+    long length;
+
+    public int load(long offset, int bpos, byte[] buffer) throws IOException {
+        return 0;
+    }
+
+    public void save(long offset, int bpos, byte[] buffer, int length) throws IOException {
+        this.length = Math.max(this.length, offset + length);
+    }
+
+    public long length() throws IOException {
+        return length;
+    }
+
+    public void close() {
+    }
+
+    public boolean canReload() {
+        return false;
+    }
+
+    public boolean writable() {
+        return true;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/MemoryCachedInputStreamContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/MemoryCachedInputStreamContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/MemoryCachedInputStreamContent.java	(revision 0)
@@ -0,0 +1,300 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import com.imagero.uio.io.IOutils;
+import com.imagero.uio.io.UnexpectedEOFException;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.EOFException;
+import java.util.Hashtable;
+
+/**
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class MemoryCachedInputStreamContent extends Content {
+    InputStream in;
+    int chunkSize;
+    boolean finished;
+
+    int overflow = 5;
+
+    Hashtable ht = new Hashtable();
+
+    int lastChunkSize;
+
+    public MemoryCachedInputStreamContent(InputStream in, int chunkSize) {
+        this.in = in;
+        this.chunkSize = chunkSize;
+    }
+
+    public int load(long offset, int destOffset, byte[] dest) throws IOException {
+        long index = offset / chunkSize;
+        if (finished && index >= readCount) {
+            throw new EOFException();
+        }
+        if (!finished) {
+            try {
+                for (long i = readCount; i <= index; i++) {
+                    byte[] b = new byte[chunkSize];
+                    Chunk chunk = addChunk(b, i);   //first add then read
+                    int length = b.length;
+                    lastChunkSize = chunkSize;
+                    try {
+                        IOutils.readFully(in, b);
+                    } catch (UnexpectedEOFException ex) {
+                        length = (int) ex.getCount();
+                    }
+                    if (chunk.src.length != length) {
+                        if (length > 0) {
+                            b = new byte[length];
+                            System.arraycopy(chunk.src, 0, b, 0, length);
+                            chunk.src = b;
+                            lastChunkSize = length;
+                        } else {
+                            ht.remove(new Long(i));
+                        }
+                        finished = true;
+                        break;
+                    }
+                }
+            } catch (IOException ex) {
+                finished = true;
+            }
+        }
+        if (index <= readCount) {
+            return copyData(dest, destOffset, offset);
+        }
+        return 0;
+    }
+
+    protected void prepare() {
+        try {
+            for (long i = readCount; !finished; i++) {
+                byte[] b = new byte[chunkSize];
+                Chunk chunk = addChunk(b, i);   //first add then read
+                int length = b.length;
+                lastChunkSize = chunkSize;
+                try {
+                    IOutils.readFully(in, b);
+                } catch (UnexpectedEOFException ex) {
+                    length = (int) ex.getCount();
+                }
+                if (chunk.src.length != length) {
+                    if (length > 0) {
+                        b = new byte[length];
+                        System.arraycopy(chunk.src, 0, b, 0, length);
+                        chunk.src = b;
+                        lastChunkSize = length;
+                    } else {
+                        ht.remove(new Long(i));
+                    }
+                    finished = true;
+                    break;
+                }
+            }
+        } catch (IOException ex) {
+            finished = true;
+        }
+    }
+
+    private int copyData(byte[] dest, int destOffset, long streamOffset) {
+        long index = streamOffset / chunkSize;
+        Chunk chunk = (Chunk) ht.get(new Long(index));
+        if (chunk != null) {
+            return chunk.copyInterval(dest, destOffset, streamOffset);
+        }
+        return 0;
+    }
+
+    public void close() {
+    }
+
+    long readCount;
+
+    private Chunk addChunk(byte[] buf, long index) {
+        long start = index * chunkSize;
+        Chunk helper = new Chunk(buf, index, start);
+        readCount++;
+        ht.put(new Long(index), helper);
+        return helper;
+    }
+
+    class Chunk {
+        byte[] src;
+        long index;
+
+        long start;
+
+        private Chunk parent;
+
+        private Chunk left;
+        private Chunk right;
+
+        public Chunk(byte[] buf, long index, long start) {
+            this.src = buf;
+            this.index = index;
+            this.start = start;
+        }
+
+        /**
+         *
+         * @param dest destination array
+         * @param destOffset start offset in destination array
+         * @param absOffset absolute offset in stream
+         * @return how much bytes was copied
+         */
+        int copyInterval(byte[] dest, int destOffset, long absOffset) {
+            if (src != null) {
+                if ((start > absOffset) || (absOffset > start + src.length)) {
+                    throw new IndexOutOfBoundsException("Given offset is out of chunk bounds");
+                }
+                if (destOffset < 0 || destOffset > dest.length) {
+                    throw new IndexOutOfBoundsException("Illegal destination offset: " + destOffset);
+                }
+                int srcOffset = (int) (absOffset - start);
+                int length = Math.min(dest.length - destOffset, src.length - srcOffset);
+                System.arraycopy(src, srcOffset, dest, destOffset, length);
+                if (srcOffset == 0 && length == src.length) {
+                    free();
+                } else {
+                    if (srcOffset == 0) {
+                        //right part leftover
+                        byte[] buf = new byte[src.length - length];
+                        System.arraycopy(src, length, buf, 0, buf.length);
+                        src = buf;
+                    } else if (srcOffset + length == src.length) {
+                        //left part leftover
+                        byte[] buf = new byte[src.length - length];
+                        System.arraycopy(src, 0, buf, 0, buf.length);
+                        src = buf;
+                    } else {
+                        byte[] leftBuf = new byte[srcOffset];
+                        System.arraycopy(src, 0, leftBuf, 0, leftBuf.length);
+                        left = new Chunk(leftBuf, -1, start);
+                        left.parent = this;
+
+                        byte[] rightBuf = new byte[src.length - (srcOffset + length)];
+                        System.arraycopy(src, srcOffset + length, rightBuf, 0, rightBuf.length);
+                        right = new Chunk(rightBuf, -1, start + srcOffset + length);
+                        right.parent = this;
+
+                        src = null;
+                    }
+                }
+                return length;
+            } else {
+                Chunk chunk = getChild(absOffset);
+                if (chunk != null) {
+                    return chunk.copyInterval(dest, destOffset, absOffset);
+                }
+            }
+            return 0;
+        }
+
+        private Chunk getChild(long absOffset) {
+            if (right.start <= absOffset) {
+                if (right.src != null) {
+                    return right;
+                } else {
+                    return right.getChild(absOffset);
+                }
+            } else if (left.start <= absOffset) {
+                if (left.src != null) {
+                    return left;
+                } else {
+                    return left.getChild(absOffset);
+                }
+            }
+            return null;
+        }
+
+        private void free() {
+            if (parent != null) {
+                parent.removeChild(this);
+            } else {
+                ht.remove(new Long(index));
+            }
+        }
+
+        private void removeChild(Chunk c) {
+            if (c == null && c.parent != this) {
+                return;
+            }
+            if (c == left) {
+                left = null;
+            } else if (c == right) {
+                right = null;
+            } else {
+                return;
+            }
+
+            if (left == null && right == null) {
+                free();
+            } else {
+                if (left != null) {
+                    connectChild(left);
+                } else if (right != null) {
+                    connectChild(right);
+                }
+            }
+        }
+
+        private void connectChild(Chunk c) {
+            src = c.src;
+            left = c.left;
+            right = c.right;
+        }
+    }
+
+
+    public boolean canReload() {
+        return false;
+    }
+
+    public void save(long offset, int bpos, byte[] buffer, int length) throws IOException {
+    }
+
+    public long length() throws IOException {
+        if (!finished) {
+            prepare();
+        }
+        return (readCount - 1) * chunkSize + lastChunkSize;
+    }
+
+    public boolean writable() {
+        return false;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/ShortArrayContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/ShortArrayContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/ShortArrayContent.java	(revision 0)
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import com.imagero.uio.Transformer;
+
+import java.io.IOException;
+
+/**
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class ShortArrayContent extends Content {
+    short[][] data;
+    boolean bigEndian;
+
+    static final int SHIFT = 1;
+
+    public ShortArrayContent(short[][] data) {
+        this(data, true);
+    }
+
+    public ShortArrayContent(short[][] data, boolean bigEndian) {
+        this.data = data;
+        this.bigEndian = bigEndian;
+    }
+
+    public int load(long offset, int bpos, byte[] dest) throws IOException {
+        //to simplify short to byte converting, we require two bytes boundary
+        if ((offset & 1) != 0 || ((dest.length - bpos) & 1) != 0) {
+            throw new IOException("Illegal offset or length");
+        }
+        final long off = offset >> SHIFT;
+        final int len = (dest.length - bpos) >> SHIFT;
+
+        IndexAndStart ias = getIAS(off);
+        if (ias == null) {
+            return 0;
+        }
+        int index = ias.index;
+        int pos = (int) (off - ias.start);
+        short[] src = data[index];
+        int toCopy = Math.min(src.length - pos, len);
+        if (toCopy > 0) {
+            com.imagero.uio.Transformer.shortToByte(src, pos, toCopy, dest, bpos, bigEndian);
+            if ((toCopy < len)) {
+                int copiedBytes = (toCopy << SHIFT);
+                return toCopy + load(offset + copiedBytes, bpos + copiedBytes, dest);
+            }
+        }
+        return toCopy;
+    }
+
+    public void close() {
+    }
+
+    private IndexAndStart getIAS(long offset) {
+        long start = 0;
+        for (int i = 0; i < data.length; i++) {
+            short[] src = data[i];
+            int length = src.length;
+            long end = start + length;
+            if (offset >= start && offset <= end) {
+                return new IndexAndStart(i, start);
+            }
+            start += length;
+        }
+        return null;
+    }
+
+    /**
+     * Save data to current content (char array).
+     * All offsets and lengths must be
+     * @param offset
+     * @param spos
+     * @param src
+     * @param length
+     * @throws IOException
+     */
+    public void save(long offset, int spos, byte[] src, int length) throws IOException {
+        //to simplify byte to short converting we require two byte boundary
+        if ((offset & 1) != 0 || ((src.length - spos) & 1) != 0) {
+            throw new IOException("Illegal offset or length");
+        }
+        final long off = offset >> SHIFT;
+        final int len = Math.min((src.length - spos), length) >> SHIFT;
+
+        IndexAndStart ias = getIAS(off);
+        if (ias == null) {
+            return;
+        }
+        int index = ias.index;
+        long start = ias.start;
+        int dpos = (int) (off - start);
+        short[] dest = data[index];
+        int request = len;
+        int toCopy = Math.min((dest.length - dpos), request);
+        if (request > 0 && toCopy > 0) {
+            com.imagero.uio.Transformer.byteToShort(src, spos, toCopy, dest, dpos, true);
+            if (toCopy < request) {
+                int copiedBytes = (toCopy << SHIFT);
+                save(offset + copiedBytes, spos + copiedBytes, src, (request - toCopy) << SHIFT);
+            }
+        }
+    }
+
+    public boolean canReload() {
+        return true;
+    }
+
+    public long length() throws IOException {
+        long length = 0;
+        for (int i = 0; i < data.length; i++) {
+            short[] dest = data[i];
+            length += dest.length;
+        }
+        return length << SHIFT;
+    }
+
+    public boolean writable() {
+        return true;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/LongArrayContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/LongArrayContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/LongArrayContent.java	(revision 0)
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import com.imagero.uio.Transformer;
+
+import java.io.IOException;
+
+/**
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class LongArrayContent extends Content {
+    long[][] data;
+    boolean bigEndian;
+
+    static final int SHIFT = 3;
+
+    public LongArrayContent(long[][] data) {
+        this(data, true);
+    }
+
+    public LongArrayContent(long[][] data, boolean bigEndian) {
+        this.data = data;
+        this.bigEndian = bigEndian;
+    }
+
+    public int load(long offset, int bpos, byte[] dest) throws IOException {
+        //to simplify long to byte converting, we require 8 bytes boundary
+        if ((offset & 7) != 0 || ((dest.length - bpos) & 7) != 0) {
+            throw new IOException("Illegal offset or length");
+        }
+        final long off = offset >> SHIFT;
+        final int len = (dest.length - bpos) >> SHIFT;
+
+        IndexAndStart ias = getIAS(off);
+        if (ias == null) {
+            return 0;
+        }
+        int index = ias.index;
+        int pos = (int) (off - ias.start);
+        long[] src = data[index];
+        int toCopy = Math.min(src.length - pos, len);
+        if (toCopy > 0) {
+            com.imagero.uio.Transformer.longToByte(src, pos, toCopy, dest, bpos, bigEndian);
+            if ((toCopy < len)) {
+                int copiedBytes = (toCopy << SHIFT);
+                return toCopy + load(offset + copiedBytes, bpos + copiedBytes, dest);
+            }
+        }
+        return toCopy;
+    }
+
+    public void close() {
+    }
+
+    private IndexAndStart getIAS(long offset) {
+        long start = 0;
+        for (int i = 0; i < data.length; i++) {
+            long[] src = data[i];
+            int length = src.length;
+            long end = start + length;
+            if (offset >= start && offset <= end) {
+                return new IndexAndStart(i, start);
+            }
+            start += length;
+        }
+        return null;
+    }
+
+    /**
+     * Save data to current content (char array).
+     * All offsets and lengths must be
+     * @param offset
+     * @param spos
+     * @param src
+     * @param length
+     * @throws IOException
+     */
+    public void save(long offset, int spos, byte[] src, int length) throws IOException {
+        //to simplify byte to long converting we require 8 byte boundary
+        if ((offset & 7) != 0 || ((src.length - spos) & 7) != 0) {
+            throw new IOException("Illegal offset or length");
+        }
+        final long off = offset >> SHIFT;
+        final int len = Math.min((src.length - spos), length) >> SHIFT;
+
+        IndexAndStart ias = getIAS(off);
+        if (ias == null) {
+            return;
+        }
+        int index = ias.index;
+        long start = ias.start;
+        int dpos = (int) (off - start);
+        long[] dest = data[index];
+        int request = len;
+        int toCopy = Math.min((dest.length - dpos), request);
+        if (request > 0 && toCopy > 0) {
+            com.imagero.uio.Transformer.byteToLong(src, spos, toCopy, dest, dpos, true);
+            if (toCopy < request) {
+                int copiedBytes = (toCopy << SHIFT);
+                save(offset + copiedBytes, spos + copiedBytes, src, (request - toCopy) << SHIFT);
+            }
+        }
+    }
+
+    public boolean canReload() {
+        return true;
+    }
+
+    public long length() throws IOException {
+        long length = 0;
+        for (int i = 0; i < data.length; i++) {
+            long[] dest = data[i];
+            length += dest.length;
+        }
+        return length << SHIFT;
+    }
+
+    public boolean writable() {
+        return true;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/Range.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/Range.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/Range.java	(revision 0)
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+/**
+ * This class helps to track which interval was already filed with data.
+ * @author Andrey Kuznetsov
+ */
+public class Range {
+    long first;
+    long last;
+
+    public Range(long first, long last) {
+        if(first == last) {
+            throw new IllegalArgumentException("Empty range");
+        }
+        this.first = Math.min(first, last);
+        this.last = Math.max(first, last);
+    }
+
+    public boolean contains(Range r) {
+        return (r.first >= first && r.last <= last);
+    }
+
+    public boolean isOverlap(Range r) {
+        return ((r.first >= first && r.first <= last) || (r.last >= first && r.last <= last));
+    }
+
+    public boolean isNeighbor(Range r) {
+        return ((r.last + 1 == first) || (r.first - 1 == last));
+    }
+
+    public boolean canJoin(Range r) {
+        return isOverlap(r) || isNeighbor(r);
+    }
+
+    public void join(Range r) {
+        if(canJoin(r)) {
+            this.first = Math.min(first, r.first);
+            this.last = Math.max(last, r.last);
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/RandomAccessFileContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/RandomAccessFileContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/RandomAccessFileContent.java	(revision 0)
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import com.imagero.uio.impl.RandomAccessFileX;
+import com.imagero.uio.io.IOutils;
+
+import java.io.RandomAccessFile;
+import java.io.File;
+import java.io.IOException;
+import java.io.EOFException;
+
+/**
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class RandomAccessFileContent extends Content {
+    private RandomAccessFile raf;
+
+    public RandomAccessFileContent(File f) throws IOException {
+        this(f, getMode(f));
+    }
+
+    public RandomAccessFileContent(File f, String mode) throws IOException {
+        this(new RandomAccessFileX(f, mode));
+    }
+
+    static String getMode(File f) {
+        if (!f.exists() || f.canWrite()) {
+            return "rw";
+        } else {
+            return "r";
+        }
+    }
+
+    public RandomAccessFileContent(RandomAccessFile raf) {
+        this.raf = raf;
+    }
+
+    public int load(long offset, int bpos, byte[] b) throws IOException {
+        long max = raf.length() - offset;
+        int len = (int) Math.min(max, b.length - bpos);
+        if (len > 0) {
+            raf.seek(offset);
+            raf.readFully(b, bpos, len);
+            return len;
+        }
+        throw new EOFException();
+//            return 0;
+    }
+
+    public boolean canReload() {
+        return true;
+    }
+
+    public void close() {
+        IOutils.closeStream(raf);
+    }
+
+    public boolean writable() {
+        return true;
+    }
+
+    public void save(long offset, int bpos, byte[] buffer, int length) throws IOException {
+        raf.seek(offset);
+        try {
+            raf.write(buffer, bpos, length);
+        } catch (IndexOutOfBoundsException ex) {
+            ex.printStackTrace();
+        }
+    }
+
+    public long length() throws IOException {
+        return raf.length();
+    }
+
+    protected void finalize() throws Throwable {
+        super.finalize();
+        raf = null;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/ByteArrayContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/ByteArrayContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/ByteArrayContent.java	(revision 0)
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import java.io.IOException;
+
+/**
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class ByteArrayContent extends Content {
+    byte[][] data;
+
+    public ByteArrayContent(byte[][] data) {
+        this.data = data;
+    }
+
+    public int load(long offset, int bpos, byte[] buffer) throws IOException {
+        IndexAndStart ias = getIAS(offset);
+        if (ias == null) {
+            return 0;
+        }
+        int index = ias.index;
+        long start = ias.start;
+        int pos = (int) (offset - start);
+        byte[] src = data[index];
+        int toCopy = Math.min(src.length - pos, buffer.length - bpos);
+        if (toCopy > 0) {
+            System.arraycopy(src, pos, buffer, bpos, toCopy);
+            if ((toCopy < buffer.length - bpos)) {
+                return toCopy + load(offset + toCopy, bpos + toCopy, buffer);
+            }
+        }
+        return toCopy;
+    }
+
+    public void close() {
+    }
+
+    private IndexAndStart getIAS(long offset) {
+        long start = 0;
+        for (int i = 0; i < data.length; i++) {
+            byte[] src = data[i];
+            int length = src.length;
+            long end = start + length;
+            if (offset >= start && offset <= end) {
+                return new IndexAndStart(i, start);
+            }
+            start += length;
+        }
+        return null;
+    }
+
+    public void save(long offset, int spos, byte[] src, int length) throws IOException {
+        IndexAndStart ias = getIAS(offset);
+        if (ias == null) {
+            return;
+        }
+        int index = ias.index;
+        long start = ias.start;
+        int dpos = (int) (offset - start);
+        byte[] dest = data[index];
+        int request = Math.min(length, src.length - spos);
+        int toCopy = Math.min(dest.length - dpos, request);
+        if (request > 0 && toCopy > 0) {
+            System.arraycopy(src, spos, dest, dpos, toCopy);
+            if (toCopy < request) {
+                save(offset + toCopy, spos + toCopy, src, request - toCopy);
+            }
+        }
+    }
+
+    public boolean canReload() {
+        return true;
+    }
+
+    public long length() throws IOException {
+        long length = 0;
+        for (int i = 0; i < data.length; i++) {
+            byte[] dest = data[i];
+            length += dest.length;
+        }
+        return length;
+    }
+
+    public boolean writable() {
+        return true;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/FileCachedInputStreamContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/FileCachedInputStreamContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/FileCachedInputStreamContent.java	(revision 0)
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import com.imagero.uio.impl.TmpRandomAccessFile;
+import com.imagero.uio.io.IOutils;
+
+import java.io.InputStream;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class FileCachedInputStreamContent extends Content {
+    InputStream in;
+    File tmp;
+    TmpRandomAccessFile tmpRaf;
+
+    public FileCachedInputStreamContent(InputStream in, File tmp) throws IOException {
+        this.in = in;
+        this.tmp = tmp;
+        tmpRaf = new TmpRandomAccessFile(tmp, "rw");
+    }
+
+    public int load(long offset, int bpos, byte[] buffer) throws IOException {
+        long length = tmpRaf.length();
+        long nl = offset + buffer.length - bpos;
+        if (length < nl) {
+            tmpRaf.seek(length);
+            IOutils.copy(nl - length, in, tmpRaf);
+        }
+        tmpRaf.seek(offset);
+        tmpRaf.readFully(buffer, bpos, buffer.length - bpos);
+        return buffer.length - bpos;
+    }
+
+    public void save(long offset, int bpos, byte[] buffer, int length) throws IOException {
+    }
+
+    public long length() throws IOException {
+        return tmp.length() + in.available();
+    }
+
+    public void close() {
+        IOutils.closeStream(tmpRaf);
+    }
+
+    protected void finalize() throws Throwable {
+        super.finalize();
+        IOutils.closeStream(tmpRaf);
+        tmpRaf = null;
+    }
+
+    public boolean canReload() {
+        return true;
+    }
+
+    public boolean writable() {
+        return false;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/SpannedRandomAccessIOContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/SpannedRandomAccessIOContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/SpannedRandomAccessIOContent.java	(revision 0)
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import com.imagero.uio.RandomAccessIO;
+import com.imagero.uio.RandomAccessInput;
+import com.imagero.uio.io.IOutils;
+import com.imagero.uio.io.UnexpectedEOFException;
+
+import java.io.EOFException;
+import java.io.IOException;
+
+/**
+ * Content with access to one or more predefined areas in RandomAccessIO.
+ * Length can not be changed.
+ * UnexpectedEOFException is thrown if we try to write outside our length.
+ *
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class SpannedRandomAccessIOContent extends Content {
+    private RandomAccessIO rio;
+
+    Span[] spans;
+    long length;
+
+
+    public SpannedRandomAccessIOContent(RandomAccessIO rio, Span[] spans) throws IOException {
+        this.rio = rio;
+        this.spans = spans;
+        long rafLength = rio.length();
+        for (int i = 0; i < spans.length; i++) {
+            Span span = spans[i];
+            if (span.offset > rafLength || span.offset + span.length > rafLength) {
+                throw new IOException("Illegal span: " + span.offset + " " + span.length);
+            }
+        }
+        for (int i = 0; i < spans.length; i++) {
+            length += spans[i].length;
+        }
+    }
+
+    Span currentSpan;
+    long spanOffset;
+
+    void seek(long offset) throws IOException {
+        int sp = 0;
+        while (offset >= 0) {
+            Span span = spans[sp++];
+            if (offset < span.length) {
+                currentSpan = span;
+                spanOffset = offset;
+                rio.seek(span.offset + offset);
+                return;
+            } else {
+                offset -= span.length;
+            }
+        }
+    }
+
+    public int load(long offset, int bpos, byte[] b) throws IOException {
+        seek(offset);
+
+        long available = currentSpan.length - spanOffset;
+        int len = (int) Math.min(available, b.length - bpos);
+        if (len > 0) {
+            rio.readFully(b, bpos, len);
+            return len;
+        }
+        throw new EOFException();
+    }
+
+    public boolean canReload() {
+        return true;
+    }
+
+    public void close() {
+        IOutils.closeStream((RandomAccessInput) rio);
+    }
+
+    public boolean writable() {
+        return true;
+    }
+
+    public void save(long offset, int bpos, byte[] buffer, int length) throws IOException {
+        long len = length;
+        while (len > 0) {
+            seek(offset);
+            long available = currentSpan.length - spanOffset;
+            int w = (int) Math.min(available, len);
+            if (w == 0) {
+                throw new UnexpectedEOFException(length - len);
+            }
+            rio.write(buffer, bpos, w);
+            offset += w;
+            bpos += w;
+            len -= w;
+        }
+    }
+
+    public long length() throws IOException {
+        return length;
+    }
+
+    protected void finalize() throws Throwable {
+        super.finalize();
+        rio = null;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/ByteArrayContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/ByteArrayContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/ByteArrayContent.java	(revision 0)
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import java.io.IOException;
+
+/**
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class ByteArrayContent extends Content {
+    byte[][] data;
+
+    public ByteArrayContent(byte[][] data) {
+        this.data = data;
+    }
+
+    public int load(long offset, int bpos, byte[] buffer) throws IOException {
+        IndexAndStart ias = getIAS(offset);
+        if (ias == null) {
+            return 0;
+        }
+        int index = ias.index;
+        long start = ias.start;
+        int pos = (int) (offset - start);
+        byte[] src = data[index];
+        int toCopy = Math.min(src.length - pos, buffer.length - bpos);
+        if (toCopy > 0) {
+            System.arraycopy(src, pos, buffer, bpos, toCopy);
+            if ((toCopy < buffer.length - bpos)) {
+                return toCopy + load(offset + toCopy, bpos + toCopy, buffer);
+            }
+        }
+        return toCopy;
+    }
+
+    public void close() {
+    }
+
+    private IndexAndStart getIAS(long offset) {
+        long start = 0;
+        for (int i = 0; i < data.length; i++) {
+            byte[] src = data[i];
+            int length = src.length;
+            long end = start + length;
+            if (offset >= start && offset <= end) {
+                return new IndexAndStart(i, start);
+            }
+            start += length;
+        }
+        return null;
+    }
+
+    public void save(long offset, int spos, byte[] src, int length) throws IOException {
+        IndexAndStart ias = getIAS(offset);
+        if (ias == null) {
+            return;
+        }
+        int index = ias.index;
+        long start = ias.start;
+        int dpos = (int) (offset - start);
+        byte[] dest = data[index];
+        int request = Math.min(length, src.length - spos);
+        int toCopy = Math.min(dest.length - dpos, request);
+        if (request > 0 && toCopy > 0) {
+            System.arraycopy(src, spos, dest, dpos, toCopy);
+            if (toCopy < request) {
+                save(offset + toCopy, spos + toCopy, src, request - toCopy);
+            }
+        }
+    }
+
+    public boolean canReload() {
+        return true;
+    }
+
+    public long length() throws IOException {
+        long length = 0;
+        for (int i = 0; i < data.length; i++) {
+            byte[] dest = data[i];
+            length += dest.length;
+        }
+        return length;
+    }
+
+    public boolean writable() {
+        return true;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/CharArrayContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/CharArrayContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/CharArrayContent.java	(revision 0)
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import com.imagero.uio.Transformer;
+
+import java.io.IOException;
+
+/**
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class CharArrayContent extends Content {
+    char[][] data;
+    boolean bigEndian;
+
+    static final int SHIFT = 1;
+
+    public CharArrayContent(char[][] data) {
+        this(data, true);
+    }
+
+    public CharArrayContent(char[][] data, boolean bigEndian) {
+        this.data = data;
+        this.bigEndian = bigEndian;
+    }
+
+    public int load(long offset, int bpos, byte[] dest) throws IOException {
+        //to simplify char to byte converting, we require two bytes boundary
+        if ((offset & 1) != 0 || ((dest.length - bpos) & 1) != 0) {
+            throw new IOException("Illegal offset or length");
+        }
+        final long off = offset >> SHIFT;
+        final int len = (dest.length - bpos) >> SHIFT;
+
+        IndexAndStart ias = getIAS(off);
+        if (ias == null) {
+            return 0;
+        }
+        int index = ias.index;
+        int pos = (int) (off - ias.start);
+        char[] src = data[index];
+        int toCopy = Math.min(src.length - pos, len);
+        if (toCopy > 0) {
+            com.imagero.uio.Transformer.charToByte(src, pos, toCopy, dest, bpos, bigEndian);
+            if ((toCopy < len)) {
+                int copiedBytes = (toCopy << SHIFT);
+                return toCopy + load(offset + copiedBytes, bpos + copiedBytes, dest);
+            }
+        }
+        return toCopy;
+    }
+
+    public void close() {
+    }
+
+    private IndexAndStart getIAS(long offset) {
+        long start = 0;
+        for (int i = 0; i < data.length; i++) {
+            char[] src = data[i];
+            int length = src.length;
+            long end = start + length;
+            if (offset >= start && offset <= end) {
+                return new IndexAndStart(i, start);
+            }
+            start += length;
+        }
+        return null;
+    }
+
+    /**
+     * Save data to current content (char array).
+     * All offsets and lengths must be
+     * @param offset
+     * @param spos
+     * @param src
+     * @param length
+     * @throws IOException
+     */
+    public void save(long offset, int spos, byte[] src, int length) throws IOException {
+        //to simplify char to byte converting we require two byte boundary
+        if ((offset & 1) != 0 || ((src.length - spos) & 1) != 0) {
+            throw new IOException("Illegal offset or length");
+        }
+        final long off = offset >> SHIFT;
+        final int len = Math.min((src.length - spos), length) >> SHIFT;
+
+        IndexAndStart ias = getIAS(off);
+        if (ias == null) {
+            return;
+        }
+        int index = ias.index;
+        long start = ias.start;
+        int dpos = (int) (off - start);
+        char[] dest = data[index];
+        int request = len;
+        int toCopy = Math.min((dest.length - dpos), request);
+        if (request > 0 && toCopy > 0) {
+            com.imagero.uio.Transformer.byteToChar(src, spos, toCopy, dest, dpos, true);
+            if (toCopy < request) {
+                int copiedBytes = (toCopy << SHIFT);
+                save(offset + copiedBytes, spos + copiedBytes, src, (request - toCopy) << SHIFT);
+            }
+        }
+    }
+
+    public boolean canReload() {
+        return true;
+    }
+
+    public long length() throws IOException {
+        long length = 0;
+        for (int i = 0; i < data.length; i++) {
+            char[] dest = data[i];
+            length += dest.length;
+        }
+        return length << SHIFT;
+    }
+
+    public boolean writable() {
+        return true;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/Content.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/Content.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/Content.java	(revision 0)
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import java.io.IOException;
+
+/**
+ *
+ * @author Andrey Kuznetsov
+ */
+public abstract class Content {
+
+    /**
+     * Load stream content to specified buffer
+     * @param offset stream offset
+     * @param buffer byte array
+     * @return how much bytes were loaded
+     * @throws java.io.IOException
+     */
+    public final int load(long offset, byte[] buffer) throws IOException {
+        return load(offset, 0, buffer);
+    }
+
+    /**
+     * Load stream content to specified buffer
+     * @param offset stream offset
+     * @param bpos buffer position
+     * @param buffer byte array
+     * @return how much bytes were loaded
+     * @throws java.io.IOException
+     */
+    public abstract int load(long offset, int bpos, byte[] buffer) throws IOException;
+
+    /**
+     * Save buffer content to stream.
+     * Not always supported.
+     * @param offset stream offset
+     * @param bpos buffer position
+     * @param buffer byte array
+     * @param length how much bytes should be saved
+     * @throws java.io.IOException
+     */
+    public abstract void save(long offset, int bpos, byte[] buffer, int length) throws IOException;
+
+    /**
+     * Get stream length. Not always known.
+     * @return
+     * @throws java.io.IOException
+     */
+    public abstract long length() throws IOException;
+
+    /**
+     * close stream
+     */
+    public abstract void close();
+
+    /**
+     * Determine if data may be reloaded or not.
+     * For example: data from InputStream cannot be reloaded,
+     * however if content uses file or memory based cache then it is possible to reload data.
+     * @return true if data can be reloaded.
+     */
+    public abstract boolean canReload();
+
+    public abstract boolean writable();
+
+    protected void finalize() throws Throwable {
+        super.finalize();
+        close();
+    }
+
+
+}
Index: ocean/src/com/imagero/uio/bio/content/DoubleArrayContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/DoubleArrayContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/DoubleArrayContent.java	(revision 0)
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import com.imagero.uio.Transformer;
+
+import java.io.IOException;
+
+/**
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class DoubleArrayContent extends Content {
+    double[][] data;
+    boolean bigEndian;
+
+    static final int SHIFT = 3;
+
+    public DoubleArrayContent(double[][] data) {
+        this(data, true);
+    }
+
+    public DoubleArrayContent(double[][] data, boolean bigEndian) {
+        this.data = data;
+        this.bigEndian = bigEndian;
+    }
+
+    public int load(long offset, int bpos, byte[] dest) throws IOException {
+        //to simplify long to byte converting, we require 8 bytes boundary
+        if ((offset & 7) != 0 || ((dest.length - bpos) & 7) != 0) {
+            throw new IOException("Illegal offset or length");
+        }
+        final long off = offset >> SHIFT;
+        final int len = (dest.length - bpos) >> SHIFT;
+
+        IndexAndStart ias = getIAS(off);
+        if (ias == null) {
+            return 0;
+        }
+        int index = ias.index;
+        int pos = (int) (off - ias.start);
+        double[] src = data[index];
+        int toCopy = Math.min(src.length - pos, len);
+        if (toCopy > 0) {
+            com.imagero.uio.Transformer.doubleToByte(src, pos, toCopy, dest, bpos, bigEndian);
+            if ((toCopy < len)) {
+                int copiedBytes = (toCopy << SHIFT);
+                return toCopy + load(offset + copiedBytes, bpos + copiedBytes, dest);
+            }
+        }
+        return toCopy;
+    }
+
+    public void close() {
+    }
+
+    private IndexAndStart getIAS(long offset) {
+        long start = 0;
+        for (int i = 0; i < data.length; i++) {
+            double[] src = data[i];
+            int length = src.length;
+            long end = start + length;
+            if (offset >= start && offset <= end) {
+                return new IndexAndStart(i, start);
+            }
+            start += length;
+        }
+        return null;
+    }
+
+    /**
+     * Save data to current content (char array).
+     * All offsets and lengths must be
+     * @param offset
+     * @param spos
+     * @param src
+     * @param length
+     * @throws IOException
+     */
+    public void save(long offset, int spos, byte[] src, int length) throws IOException {
+        //to simplify byte to long converting we require 8 byte boundary
+        if ((offset & 7) != 0 || ((src.length - spos) & 7) != 0) {
+            throw new IOException("Illegal offset or length");
+        }
+        final long off = offset >> SHIFT;
+        final int len = Math.min((src.length - spos), length) >> SHIFT;
+
+        IndexAndStart ias = getIAS(off);
+        if (ias == null) {
+            return;
+        }
+        int index = ias.index;
+        long start = ias.start;
+        int dpos = (int) (off - start);
+        double[] dest = data[index];
+        int request = len;
+        int toCopy = Math.min((dest.length - dpos), request);
+        if (request > 0 && toCopy > 0) {
+            com.imagero.uio.Transformer.byteToDouble(src, spos, toCopy, dest, dpos, true);
+            if (toCopy < request) {
+                int copiedBytes = (toCopy << SHIFT);
+                save(offset + copiedBytes, spos + copiedBytes, src, (request - toCopy) << SHIFT);
+            }
+        }
+    }
+
+    public boolean canReload() {
+        return true;
+    }
+
+    public long length() throws IOException {
+        long length = 0;
+        for (int i = 0; i < data.length; i++) {
+            double[] dest = data[i];
+            length += dest.length;
+        }
+        return length << SHIFT;
+    }
+
+    public boolean writable() {
+        return true;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/DummyContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/DummyContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/DummyContent.java	(revision 0)
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import java.io.IOException;
+
+/**
+ * empty content which just tracks length.
+ */
+public class DummyContent extends Content {
+    long length;
+
+    public int load(long offset, int bpos, byte[] buffer) throws IOException {
+        return 0;
+    }
+
+    public void save(long offset, int bpos, byte[] buffer, int length) throws IOException {
+        this.length = Math.max(this.length, offset + length);
+    }
+
+    public long length() throws IOException {
+        return length;
+    }
+
+    public void close() {
+    }
+
+    public boolean canReload() {
+        return false;
+    }
+
+    public boolean writable() {
+        return true;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/FileCachedHTTPContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/FileCachedHTTPContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/FileCachedHTTPContent.java	(revision 0)
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import com.imagero.uio.bio.content.HTTPContent;
+import com.imagero.uio.impl.TmpRandomAccessFile;
+import com.imagero.util.Vector;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+
+/**
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class FileCachedHTTPContent extends HTTPContent {
+
+    File tmp;
+    TmpRandomAccessFile tmpRaf;
+
+    Vector ranges = new Vector();
+
+    public FileCachedHTTPContent(URL url, File tmp) throws IOException {
+        super(url);
+        this.tmp = tmp;
+        tmpRaf = new TmpRandomAccessFile(tmp, "rw");
+    }
+
+    public int load(long offset, int bpos, byte[] buffer) throws IOException {
+        if (contains(offset, buffer.length - bpos)) {
+            tmpRaf.seek(offset);
+            tmpRaf.readFully(buffer, bpos, buffer.length - bpos);
+            return buffer.length - bpos;
+        }
+        int k = super.load(offset, bpos, buffer);
+        if (k > 0) {
+            tmpRaf.seek(offset);
+            tmpRaf.write(buffer, bpos, k);
+            addRange(offset, k);
+        }
+        return k;
+    }
+
+    boolean contains(long offset, int length) {
+        Range r0 = new Range(offset, offset + length);
+        for (int i = 0; i < ranges.size(); i++) {
+            Range r = (Range) ranges.elementAt(i);
+            if (r.contains(r0)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    void addRange(long offset, int length) {
+        Range r0 = new Range(offset, offset + length);
+
+        for (int i = 0; i < ranges.size(); i++) {
+            Range r = (Range) ranges.elementAt(i);
+            if (r.canJoin(r0)) {
+                r.join(r0);
+                for (int j = 0; j < ranges.size(); j++) {
+                    Range r1 = (Range) ranges.elementAt(j);
+                    if (r != r1 && r.canJoin(r1)) {
+                        r.join(r1);
+                        j--;
+                        ranges.remove(r1);
+                    }
+                }
+                return;
+            }
+        }
+        ranges.add(r0);
+    }
+
+    public boolean canReload() {
+        return true;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/FileCachedInputStreamContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/FileCachedInputStreamContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/FileCachedInputStreamContent.java	(revision 0)
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import com.imagero.uio.impl.TmpRandomAccessFile;
+import com.imagero.uio.io.IOutils;
+
+import java.io.InputStream;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class FileCachedInputStreamContent extends Content {
+    InputStream in;
+    File tmp;
+    TmpRandomAccessFile tmpRaf;
+
+    public FileCachedInputStreamContent(InputStream in, File tmp) throws IOException {
+        this.in = in;
+        this.tmp = tmp;
+        tmpRaf = new TmpRandomAccessFile(tmp, "rw");
+    }
+
+    public int load(long offset, int bpos, byte[] buffer) throws IOException {
+        long length = tmpRaf.length();
+        long nl = offset + buffer.length - bpos;
+        if (length < nl) {
+            tmpRaf.seek(length);
+            IOutils.copy(nl - length, in, tmpRaf);
+        }
+        tmpRaf.seek(offset);
+        tmpRaf.readFully(buffer, bpos, buffer.length - bpos);
+        return buffer.length - bpos;
+    }
+
+    public void save(long offset, int bpos, byte[] buffer, int length) throws IOException {
+    }
+
+    public long length() throws IOException {
+        return tmp.length() + in.available();
+    }
+
+    public void close() {
+        IOutils.closeStream(tmpRaf);
+    }
+
+    protected void finalize() throws Throwable {
+        super.finalize();
+        IOutils.closeStream(tmpRaf);
+        tmpRaf = null;
+    }
+
+    public boolean canReload() {
+        return true;
+    }
+
+    public boolean writable() {
+        return false;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/FloatArrayContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/FloatArrayContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/FloatArrayContent.java	(revision 0)
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import com.imagero.uio.Transformer;
+
+import java.io.IOException;
+
+/**
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class FloatArrayContent extends Content {
+    float[][] data;
+    boolean bigEndian;
+
+    static final int SHIFT = 2;
+
+    public FloatArrayContent(float[][] data) {
+        this(data, true);
+    }
+
+    public FloatArrayContent(float[][] data, boolean bigEndian) {
+        this.data = data;
+        this.bigEndian = bigEndian;
+    }
+
+    public int load(long offset, int bpos, byte[] dest) throws IOException {
+        //to simplify float to byte converting, we require 4 bytes boundary
+        if ((offset & 3) != 0 || ((dest.length - bpos) & 3) != 0) {
+            throw new IOException("Illegal offset or length");
+        }
+        final long off = offset >> SHIFT;
+        final int len = (dest.length - bpos) >> SHIFT;
+
+        IndexAndStart ias = getIAS(off);
+        if (ias == null) {
+            return 0;
+        }
+        int index = ias.index;
+        int pos = (int) (off - ias.start);
+        float[] src = data[index];
+        int toCopy = Math.min(src.length - pos, len);
+        if (toCopy > 0) {
+            com.imagero.uio.Transformer.floatToByte(src, pos, toCopy, dest, bpos, bigEndian);
+            if ((toCopy < len)) {
+                int copiedBytes = (toCopy << SHIFT);
+                return toCopy + load(offset + copiedBytes, bpos + copiedBytes, dest);
+            }
+        }
+        return toCopy;
+    }
+
+    public void close() {
+    }
+
+    private IndexAndStart getIAS(long offset) {
+        long start = 0;
+        for (int i = 0; i < data.length; i++) {
+            float[] src = data[i];
+            int length = src.length;
+            long end = start + length;
+            if (offset >= start && offset <= end) {
+                return new IndexAndStart(i, start);
+            }
+            start += length;
+        }
+        return null;
+    }
+
+    /**
+     * Save data to current content (char array).
+     * All offsets and lengths must be
+     * @param offset
+     * @param spos
+     * @param src
+     * @param length
+     * @throws IOException
+     */
+    public void save(long offset, int spos, byte[] src, int length) throws IOException {
+        //to simplify byte to float converting we require 4 byte boundary
+        if ((offset & 3) != 0 || ((src.length - spos) & 3) != 0) {
+            throw new IOException("Illegal offset or length");
+        }
+        final long off = offset >> SHIFT;
+        final int len = Math.min((src.length - spos), length) >> SHIFT;
+
+        IndexAndStart ias = getIAS(off);
+        if (ias == null) {
+            return;
+        }
+        int index = ias.index;
+        long start = ias.start;
+        int dpos = (int) (off - start);
+        float[] dest = data[index];
+        int request = len;
+        int toCopy = Math.min((dest.length - dpos), request);
+        if (request > 0 && toCopy > 0) {
+            com.imagero.uio.Transformer.byteToFloat(src, spos, toCopy, dest, dpos, true);
+            if (toCopy < request) {
+                int copiedBytes = (toCopy << SHIFT);
+                save(offset + copiedBytes, spos + copiedBytes, src, (request - toCopy) << SHIFT);
+            }
+        }
+    }
+
+    public boolean canReload() {
+        return true;
+    }
+
+    public long length() throws IOException {
+        long length = 0;
+        for (int i = 0; i < data.length; i++) {
+            float[] dest = data[i];
+            length += dest.length;
+        }
+        return length << SHIFT;
+    }
+
+    public boolean writable() {
+        return true;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/HTTPContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/HTTPContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/HTTPContent.java	(revision 0)
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import com.imagero.uio.io.IOutils;
+import com.imagero.uio.io.UnexpectedEOFException;
+
+import java.net.URL;
+import java.net.HttpURLConnection;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class HTTPContent extends Content {
+    URL url;
+
+    long length;
+
+    public HTTPContent(URL url) {
+        String protocol = url.getProtocol();
+        if (!"http".equalsIgnoreCase(protocol)) {
+            throw new IllegalArgumentException("http protokol only");
+        }
+        this.url = url;
+    }
+
+    public int load(long offset, int bpos, byte[] buffer) throws IOException {
+        HttpURLConnection httpcon = (HttpURLConnection) url.openConnection();
+        httpcon.setAllowUserInteraction(true);
+        httpcon.setDoInput(true);
+        httpcon.setDoOutput(true);
+        httpcon.setRequestMethod("GET");
+        httpcon.setUseCaches(false);
+        httpcon.setRequestProperty("Range", "bytes=" + offset + "-" + (offset + buffer.length - bpos));
+        httpcon.connect();
+
+        int responseCode = httpcon.getResponseCode();
+        if (responseCode != 206) {
+            httpcon.disconnect();
+            throw new IOException("byteserving not supported by server");
+        }
+        InputStream in = httpcon.getInputStream();
+
+        int count = 0;
+        try {
+            int len = buffer.length - bpos;
+            IOutils.readFully(in, buffer, bpos, len);
+            count = len;
+        } catch (UnexpectedEOFException ex) {
+            count = (int) ex.getCount();
+        } finally {
+            httpcon.disconnect();
+        }
+        return count;
+    }
+
+    public void close() {
+    }
+
+    public void save(long offset, int bpos, byte[] buffer, int length) throws IOException {
+    }
+
+    public long length() throws IOException {
+        if (length == 0) {
+            HttpURLConnection httpcon = (HttpURLConnection) url.openConnection();
+            httpcon.setRequestMethod("HEAD");
+            httpcon.setUseCaches(false);
+            httpcon.connect();
+            length = httpcon.getContentLength();
+            httpcon.disconnect();
+        }
+        return length;
+    }
+
+    public boolean canReload() {
+        return false;
+    }
+
+    public boolean writable() {
+        return false;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/IndexAndStart.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/IndexAndStart.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/IndexAndStart.java	(revision 0)
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+/**
+ * Date: 06.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+class IndexAndStart {
+    //array index
+    int index;
+    //offset of first byte of array in stream
+    long start;
+
+    public IndexAndStart(int index, long start) {
+        this.index = index;
+        this.start = start;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/IntArrayContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/IntArrayContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/IntArrayContent.java	(revision 0)
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import com.imagero.uio.Transformer;
+
+import java.io.IOException;
+
+/**
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class IntArrayContent extends Content {
+    int[][] data;
+    boolean bigEndian;
+
+    static final int SHIFT = 2;
+
+    public IntArrayContent(int[][] data) {
+        this(data, true);
+    }
+
+    public IntArrayContent(int[][] data, boolean bigEndian) {
+        this.data = data;
+        this.bigEndian = bigEndian;
+    }
+
+    public int load(long offset, int bpos, byte[] dest) throws IOException {
+        //to simplify int to byte converting, we require 4 bytes boundary
+        if ((offset & 3) != 0 || ((dest.length - bpos) & 3) != 0) {
+            throw new IOException("Illegal offset or length");
+        }
+        final long off = offset >> SHIFT;
+        final int len = (dest.length - bpos) >> SHIFT;
+
+        IndexAndStart ias = getIAS(off);
+        if (ias == null) {
+            return 0;
+        }
+        int index = ias.index;
+        int pos = (int) (off - ias.start);
+        int[] src = data[index];
+        int toCopy = Math.min(src.length - pos, len);
+        if (toCopy > 0) {
+            com.imagero.uio.Transformer.intToByte(src, pos, toCopy, dest, bpos, bigEndian);
+            if ((toCopy < len)) {
+                int copiedBytes = (toCopy << SHIFT);
+                return toCopy + load(offset + copiedBytes, bpos + copiedBytes, dest);
+            }
+        }
+        return toCopy;
+    }
+
+    public void close() {
+    }
+
+    private IndexAndStart getIAS(long offset) {
+        long start = 0;
+        for (int i = 0; i < data.length; i++) {
+            int[] src = data[i];
+            int length = src.length;
+            long end = start + length;
+            if (offset >= start && offset <= end) {
+                return new IndexAndStart(i, start);
+            }
+            start += length;
+        }
+        return null;
+    }
+
+    /**
+     * Save data to current content (char array).
+     * All offsets and lengths must be
+     * @param offset
+     * @param spos
+     * @param src
+     * @param length
+     * @throws IOException
+     */
+    public void save(long offset, int spos, byte[] src, int length) throws IOException {
+        //to simplify byte to int converting we require 4 byte boundary
+        if ((offset & 3) != 0 || ((src.length - spos) & 3) != 0) {
+            throw new IOException("Illegal offset or length");
+        }
+        final long off = offset >> SHIFT;
+        final int len = Math.min((src.length - spos), length) >> SHIFT;
+
+        IndexAndStart ias = getIAS(off);
+        if (ias == null) {
+            return;
+        }
+        int index = ias.index;
+        long start = ias.start;
+        int dpos = (int) (off - start);
+        int[] dest = data[index];
+        int request = len;
+        int toCopy = Math.min((dest.length - dpos), request);
+        if (request > 0 && toCopy > 0) {
+            com.imagero.uio.Transformer.byteToInt(src, spos, toCopy, dest, dpos, true);
+            if (toCopy < request) {
+                int copiedBytes = (toCopy << SHIFT);
+                save(offset + copiedBytes, spos + copiedBytes, src, (request - toCopy) << SHIFT);
+            }
+        }
+    }
+
+    public boolean canReload() {
+        return true;
+    }
+
+    public long length() throws IOException {
+        long length = 0;
+        for (int i = 0; i < data.length; i++) {
+            int[] dest = data[i];
+            length += dest.length;
+        }
+        return length << SHIFT;
+    }
+
+    public boolean writable() {
+        return true;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/LongArrayContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/LongArrayContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/LongArrayContent.java	(revision 0)
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import com.imagero.uio.Transformer;
+
+import java.io.IOException;
+
+/**
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class LongArrayContent extends Content {
+    long[][] data;
+    boolean bigEndian;
+
+    static final int SHIFT = 3;
+
+    public LongArrayContent(long[][] data) {
+        this(data, true);
+    }
+
+    public LongArrayContent(long[][] data, boolean bigEndian) {
+        this.data = data;
+        this.bigEndian = bigEndian;
+    }
+
+    public int load(long offset, int bpos, byte[] dest) throws IOException {
+        //to simplify long to byte converting, we require 8 bytes boundary
+        if ((offset & 7) != 0 || ((dest.length - bpos) & 7) != 0) {
+            throw new IOException("Illegal offset or length");
+        }
+        final long off = offset >> SHIFT;
+        final int len = (dest.length - bpos) >> SHIFT;
+
+        IndexAndStart ias = getIAS(off);
+        if (ias == null) {
+            return 0;
+        }
+        int index = ias.index;
+        int pos = (int) (off - ias.start);
+        long[] src = data[index];
+        int toCopy = Math.min(src.length - pos, len);
+        if (toCopy > 0) {
+            com.imagero.uio.Transformer.longToByte(src, pos, toCopy, dest, bpos, bigEndian);
+            if ((toCopy < len)) {
+                int copiedBytes = (toCopy << SHIFT);
+                return toCopy + load(offset + copiedBytes, bpos + copiedBytes, dest);
+            }
+        }
+        return toCopy;
+    }
+
+    public void close() {
+    }
+
+    private IndexAndStart getIAS(long offset) {
+        long start = 0;
+        for (int i = 0; i < data.length; i++) {
+            long[] src = data[i];
+            int length = src.length;
+            long end = start + length;
+            if (offset >= start && offset <= end) {
+                return new IndexAndStart(i, start);
+            }
+            start += length;
+        }
+        return null;
+    }
+
+    /**
+     * Save data to current content (char array).
+     * All offsets and lengths must be
+     * @param offset
+     * @param spos
+     * @param src
+     * @param length
+     * @throws IOException
+     */
+    public void save(long offset, int spos, byte[] src, int length) throws IOException {
+        //to simplify byte to long converting we require 8 byte boundary
+        if ((offset & 7) != 0 || ((src.length - spos) & 7) != 0) {
+            throw new IOException("Illegal offset or length");
+        }
+        final long off = offset >> SHIFT;
+        final int len = Math.min((src.length - spos), length) >> SHIFT;
+
+        IndexAndStart ias = getIAS(off);
+        if (ias == null) {
+            return;
+        }
+        int index = ias.index;
+        long start = ias.start;
+        int dpos = (int) (off - start);
+        long[] dest = data[index];
+        int request = len;
+        int toCopy = Math.min((dest.length - dpos), request);
+        if (request > 0 && toCopy > 0) {
+            com.imagero.uio.Transformer.byteToLong(src, spos, toCopy, dest, dpos, true);
+            if (toCopy < request) {
+                int copiedBytes = (toCopy << SHIFT);
+                save(offset + copiedBytes, spos + copiedBytes, src, (request - toCopy) << SHIFT);
+            }
+        }
+    }
+
+    public boolean canReload() {
+        return true;
+    }
+
+    public long length() throws IOException {
+        long length = 0;
+        for (int i = 0; i < data.length; i++) {
+            long[] dest = data[i];
+            length += dest.length;
+        }
+        return length << SHIFT;
+    }
+
+    public boolean writable() {
+        return true;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/MemoryCachedInputStreamContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/MemoryCachedInputStreamContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/MemoryCachedInputStreamContent.java	(revision 0)
@@ -0,0 +1,300 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import com.imagero.uio.io.IOutils;
+import com.imagero.uio.io.UnexpectedEOFException;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.EOFException;
+import java.util.Hashtable;
+
+/**
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class MemoryCachedInputStreamContent extends Content {
+    InputStream in;
+    int chunkSize;
+    boolean finished;
+
+    int overflow = 5;
+
+    Hashtable ht = new Hashtable();
+
+    int lastChunkSize;
+
+    public MemoryCachedInputStreamContent(InputStream in, int chunkSize) {
+        this.in = in;
+        this.chunkSize = chunkSize;
+    }
+
+    public int load(long offset, int destOffset, byte[] dest) throws IOException {
+        long index = offset / chunkSize;
+        if (finished && index >= readCount) {
+            throw new EOFException();
+        }
+        if (!finished) {
+            try {
+                for (long i = readCount; i <= index; i++) {
+                    byte[] b = new byte[chunkSize];
+                    Chunk chunk = addChunk(b, i);   //first add then read
+                    int length = b.length;
+                    lastChunkSize = chunkSize;
+                    try {
+                        IOutils.readFully(in, b);
+                    } catch (UnexpectedEOFException ex) {
+                        length = (int) ex.getCount();
+                    }
+                    if (chunk.src.length != length) {
+                        if (length > 0) {
+                            b = new byte[length];
+                            System.arraycopy(chunk.src, 0, b, 0, length);
+                            chunk.src = b;
+                            lastChunkSize = length;
+                        } else {
+                            ht.remove(new Long(i));
+                        }
+                        finished = true;
+                        break;
+                    }
+                }
+            } catch (IOException ex) {
+                finished = true;
+            }
+        }
+        if (index <= readCount) {
+            return copyData(dest, destOffset, offset);
+        }
+        return 0;
+    }
+
+    protected void prepare() {
+        try {
+            for (long i = readCount; !finished; i++) {
+                byte[] b = new byte[chunkSize];
+                Chunk chunk = addChunk(b, i);   //first add then read
+                int length = b.length;
+                lastChunkSize = chunkSize;
+                try {
+                    IOutils.readFully(in, b);
+                } catch (UnexpectedEOFException ex) {
+                    length = (int) ex.getCount();
+                }
+                if (chunk.src.length != length) {
+                    if (length > 0) {
+                        b = new byte[length];
+                        System.arraycopy(chunk.src, 0, b, 0, length);
+                        chunk.src = b;
+                        lastChunkSize = length;
+                    } else {
+                        ht.remove(new Long(i));
+                    }
+                    finished = true;
+                    break;
+                }
+            }
+        } catch (IOException ex) {
+            finished = true;
+        }
+    }
+
+    private int copyData(byte[] dest, int destOffset, long streamOffset) {
+        long index = streamOffset / chunkSize;
+        Chunk chunk = (Chunk) ht.get(new Long(index));
+        if (chunk != null) {
+            return chunk.copyInterval(dest, destOffset, streamOffset);
+        }
+        return 0;
+    }
+
+    public void close() {
+    }
+
+    long readCount;
+
+    private Chunk addChunk(byte[] buf, long index) {
+        long start = index * chunkSize;
+        Chunk helper = new Chunk(buf, index, start);
+        readCount++;
+        ht.put(new Long(index), helper);
+        return helper;
+    }
+
+    class Chunk {
+        byte[] src;
+        long index;
+
+        long start;
+
+        private Chunk parent;
+
+        private Chunk left;
+        private Chunk right;
+
+        public Chunk(byte[] buf, long index, long start) {
+            this.src = buf;
+            this.index = index;
+            this.start = start;
+        }
+
+        /**
+         *
+         * @param dest destination array
+         * @param destOffset start offset in destination array
+         * @param absOffset absolute offset in stream
+         * @return how much bytes was copied
+         */
+        int copyInterval(byte[] dest, int destOffset, long absOffset) {
+            if (src != null) {
+                if ((start > absOffset) || (absOffset > start + src.length)) {
+                    throw new IndexOutOfBoundsException("Given offset is out of chunk bounds");
+                }
+                if (destOffset < 0 || destOffset > dest.length) {
+                    throw new IndexOutOfBoundsException("Illegal destination offset: " + destOffset);
+                }
+                int srcOffset = (int) (absOffset - start);
+                int length = Math.min(dest.length - destOffset, src.length - srcOffset);
+                System.arraycopy(src, srcOffset, dest, destOffset, length);
+                if (srcOffset == 0 && length == src.length) {
+                    free();
+                } else {
+                    if (srcOffset == 0) {
+                        //right part leftover
+                        byte[] buf = new byte[src.length - length];
+                        System.arraycopy(src, length, buf, 0, buf.length);
+                        src = buf;
+                    } else if (srcOffset + length == src.length) {
+                        //left part leftover
+                        byte[] buf = new byte[src.length - length];
+                        System.arraycopy(src, 0, buf, 0, buf.length);
+                        src = buf;
+                    } else {
+                        byte[] leftBuf = new byte[srcOffset];
+                        System.arraycopy(src, 0, leftBuf, 0, leftBuf.length);
+                        left = new Chunk(leftBuf, -1, start);
+                        left.parent = this;
+
+                        byte[] rightBuf = new byte[src.length - (srcOffset + length)];
+                        System.arraycopy(src, srcOffset + length, rightBuf, 0, rightBuf.length);
+                        right = new Chunk(rightBuf, -1, start + srcOffset + length);
+                        right.parent = this;
+
+                        src = null;
+                    }
+                }
+                return length;
+            } else {
+                Chunk chunk = getChild(absOffset);
+                if (chunk != null) {
+                    return chunk.copyInterval(dest, destOffset, absOffset);
+                }
+            }
+            return 0;
+        }
+
+        private Chunk getChild(long absOffset) {
+            if (right.start <= absOffset) {
+                if (right.src != null) {
+                    return right;
+                } else {
+                    return right.getChild(absOffset);
+                }
+            } else if (left.start <= absOffset) {
+                if (left.src != null) {
+                    return left;
+                } else {
+                    return left.getChild(absOffset);
+                }
+            }
+            return null;
+        }
+
+        private void free() {
+            if (parent != null) {
+                parent.removeChild(this);
+            } else {
+                ht.remove(new Long(index));
+            }
+        }
+
+        private void removeChild(Chunk c) {
+            if (c == null && c.parent != this) {
+                return;
+            }
+            if (c == left) {
+                left = null;
+            } else if (c == right) {
+                right = null;
+            } else {
+                return;
+            }
+
+            if (left == null && right == null) {
+                free();
+            } else {
+                if (left != null) {
+                    connectChild(left);
+                } else if (right != null) {
+                    connectChild(right);
+                }
+            }
+        }
+
+        private void connectChild(Chunk c) {
+            src = c.src;
+            left = c.left;
+            right = c.right;
+        }
+    }
+
+
+    public boolean canReload() {
+        return false;
+    }
+
+    public void save(long offset, int bpos, byte[] buffer, int length) throws IOException {
+    }
+
+    public long length() throws IOException {
+        if (!finished) {
+            prepare();
+        }
+        return (readCount - 1) * chunkSize + lastChunkSize;
+    }
+
+    public boolean writable() {
+        return false;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/RandomAccessFileContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/RandomAccessFileContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/RandomAccessFileContent.java	(revision 0)
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import com.imagero.uio.impl.RandomAccessFileX;
+import com.imagero.uio.io.IOutils;
+
+import java.io.RandomAccessFile;
+import java.io.File;
+import java.io.IOException;
+import java.io.EOFException;
+
+/**
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class RandomAccessFileContent extends Content {
+    private RandomAccessFile raf;
+
+    public RandomAccessFileContent(File f) throws IOException {
+        this(f, getMode(f));
+    }
+
+    public RandomAccessFileContent(File f, String mode) throws IOException {
+        this(new RandomAccessFileX(f, mode));
+    }
+
+    static String getMode(File f) {
+        if (!f.exists() || f.canWrite()) {
+            return "rw";
+        } else {
+            return "r";
+        }
+    }
+
+    public RandomAccessFileContent(RandomAccessFile raf) {
+        this.raf = raf;
+    }
+
+    public int load(long offset, int bpos, byte[] b) throws IOException {
+        long max = raf.length() - offset;
+        int len = (int) Math.min(max, b.length - bpos);
+        if (len > 0) {
+            raf.seek(offset);
+            raf.readFully(b, bpos, len);
+            return len;
+        }
+        throw new EOFException();
+//            return 0;
+    }
+
+    public boolean canReload() {
+        return true;
+    }
+
+    public void close() {
+        IOutils.closeStream(raf);
+    }
+
+    public boolean writable() {
+        return true;
+    }
+
+    public void save(long offset, int bpos, byte[] buffer, int length) throws IOException {
+        raf.seek(offset);
+        try {
+            raf.write(buffer, bpos, length);
+        } catch (IndexOutOfBoundsException ex) {
+            ex.printStackTrace();
+        }
+    }
+
+    public long length() throws IOException {
+        return raf.length();
+    }
+
+    protected void finalize() throws Throwable {
+        super.finalize();
+        raf = null;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/RandomAccessIOContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/RandomAccessIOContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/RandomAccessIOContent.java	(revision 0)
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import com.imagero.uio.RandomAccessIO;
+import com.imagero.uio.RandomAccessInput;
+import com.imagero.uio.io.IOutils;
+
+import java.io.IOException;
+import java.io.EOFException;
+
+/**
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class RandomAccessIOContent extends Content {
+    private RandomAccessIO rio;
+
+    public RandomAccessIOContent(RandomAccessIO rio) throws IOException {
+        this.rio = rio;
+    }
+
+    public int load(long offset, int bpos, byte[] b) throws IOException {
+        long max = rio.length() - offset;
+        int len = (int) Math.min(max, b.length - bpos);
+        if (len > 0) {
+            rio.seek(offset);
+            rio.readFully(b, bpos, len);
+            return len;
+        }
+        throw new EOFException();
+//            return 0;
+    }
+
+    public boolean canReload() {
+        return true;
+    }
+
+    public void close() {
+        IOutils.closeStream((RandomAccessInput) rio);
+        rio = null;
+    }
+
+    public boolean writable() {
+        return true;
+    }
+
+    public void save(long offset, int bpos, byte[] buffer, int length) throws IOException {
+        rio.seek(offset);
+        try {
+            rio.write(buffer, bpos, length);
+        } catch (IndexOutOfBoundsException ex) {
+            ex.printStackTrace();
+        }
+    }
+
+    public long length() throws IOException {
+        return rio.length();
+    }
+
+    protected void finalize() throws Throwable {
+        super.finalize();
+        rio = null;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/Range.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/Range.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/Range.java	(revision 0)
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+/**
+ * This class helps to track which interval was already filed with data.
+ * @author Andrey Kuznetsov
+ */
+public class Range {
+    long first;
+    long last;
+
+    public Range(long first, long last) {
+        if(first == last) {
+            throw new IllegalArgumentException("Empty range");
+        }
+        this.first = Math.min(first, last);
+        this.last = Math.max(first, last);
+    }
+
+    public boolean contains(Range r) {
+        return (r.first >= first && r.last <= last);
+    }
+
+    public boolean isOverlap(Range r) {
+        return ((r.first >= first && r.first <= last) || (r.last >= first && r.last <= last));
+    }
+
+    public boolean isNeighbor(Range r) {
+        return ((r.last + 1 == first) || (r.first - 1 == last));
+    }
+
+    public boolean canJoin(Range r) {
+        return isOverlap(r) || isNeighbor(r);
+    }
+
+    public void join(Range r) {
+        if(canJoin(r)) {
+            this.first = Math.min(first, r.first);
+            this.last = Math.max(last, r.last);
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/ShortArrayContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/ShortArrayContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/ShortArrayContent.java	(revision 0)
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import com.imagero.uio.Transformer;
+
+import java.io.IOException;
+
+/**
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class ShortArrayContent extends Content {
+    short[][] data;
+    boolean bigEndian;
+
+    static final int SHIFT = 1;
+
+    public ShortArrayContent(short[][] data) {
+        this(data, true);
+    }
+
+    public ShortArrayContent(short[][] data, boolean bigEndian) {
+        this.data = data;
+        this.bigEndian = bigEndian;
+    }
+
+    public int load(long offset, int bpos, byte[] dest) throws IOException {
+        //to simplify short to byte converting, we require two bytes boundary
+        if ((offset & 1) != 0 || ((dest.length - bpos) & 1) != 0) {
+            throw new IOException("Illegal offset or length");
+        }
+        final long off = offset >> SHIFT;
+        final int len = (dest.length - bpos) >> SHIFT;
+
+        IndexAndStart ias = getIAS(off);
+        if (ias == null) {
+            return 0;
+        }
+        int index = ias.index;
+        int pos = (int) (off - ias.start);
+        short[] src = data[index];
+        int toCopy = Math.min(src.length - pos, len);
+        if (toCopy > 0) {
+            com.imagero.uio.Transformer.shortToByte(src, pos, toCopy, dest, bpos, bigEndian);
+            if ((toCopy < len)) {
+                int copiedBytes = (toCopy << SHIFT);
+                return toCopy + load(offset + copiedBytes, bpos + copiedBytes, dest);
+            }
+        }
+        return toCopy;
+    }
+
+    public void close() {
+    }
+
+    private IndexAndStart getIAS(long offset) {
+        long start = 0;
+        for (int i = 0; i < data.length; i++) {
+            short[] src = data[i];
+            int length = src.length;
+            long end = start + length;
+            if (offset >= start && offset <= end) {
+                return new IndexAndStart(i, start);
+            }
+            start += length;
+        }
+        return null;
+    }
+
+    /**
+     * Save data to current content (char array).
+     * All offsets and lengths must be
+     * @param offset
+     * @param spos
+     * @param src
+     * @param length
+     * @throws IOException
+     */
+    public void save(long offset, int spos, byte[] src, int length) throws IOException {
+        //to simplify byte to short converting we require two byte boundary
+        if ((offset & 1) != 0 || ((src.length - spos) & 1) != 0) {
+            throw new IOException("Illegal offset or length");
+        }
+        final long off = offset >> SHIFT;
+        final int len = Math.min((src.length - spos), length) >> SHIFT;
+
+        IndexAndStart ias = getIAS(off);
+        if (ias == null) {
+            return;
+        }
+        int index = ias.index;
+        long start = ias.start;
+        int dpos = (int) (off - start);
+        short[] dest = data[index];
+        int request = len;
+        int toCopy = Math.min((dest.length - dpos), request);
+        if (request > 0 && toCopy > 0) {
+            com.imagero.uio.Transformer.byteToShort(src, spos, toCopy, dest, dpos, true);
+            if (toCopy < request) {
+                int copiedBytes = (toCopy << SHIFT);
+                save(offset + copiedBytes, spos + copiedBytes, src, (request - toCopy) << SHIFT);
+            }
+        }
+    }
+
+    public boolean canReload() {
+        return true;
+    }
+
+    public long length() throws IOException {
+        long length = 0;
+        for (int i = 0; i < data.length; i++) {
+            short[] dest = data[i];
+            length += dest.length;
+        }
+        return length << SHIFT;
+    }
+
+    public boolean writable() {
+        return true;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/Span.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/Span.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/Span.java	(revision 0)
@@ -0,0 +1,16 @@
+package com.imagero.uio.bio.content;
+
+/**
+ * Date: 19.03.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class Span {
+    long offset;
+    long length;
+
+    public Span(long offset, long length) {
+        this.offset = offset;
+        this.length = length;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/SpannedRandomAccessFileContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/SpannedRandomAccessFileContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/SpannedRandomAccessFileContent.java	(revision 0)
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import com.imagero.uio.impl.RandomAccessFileX;
+import com.imagero.uio.io.IOutils;
+import com.imagero.uio.io.UnexpectedEOFException;
+
+import java.io.RandomAccessFile;
+import java.io.File;
+import java.io.IOException;
+import java.io.EOFException;
+
+/**
+ * Content with access to one or more predefined areas in File or RandomAccessFile.
+ * Length can not be changed.
+ * UnexpectedEOFException is thrown if we try to write outside our length.
+ *
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class SpannedRandomAccessFileContent extends Content {
+    private RandomAccessFile raf;
+
+    Span[] spans;
+    long length;
+
+    public SpannedRandomAccessFileContent(File f, Span[] spans) throws IOException {
+        this(f, getMode(f), spans);
+    }
+
+    public SpannedRandomAccessFileContent(File f, String mode, Span[] spans) throws IOException {
+        this(new RandomAccessFileX(f, mode), spans);
+
+    }
+
+    static String getMode(File f) {
+        if (!f.exists() || f.canWrite()) {
+            return "rw";
+        } else {
+            return "r";
+        }
+    }
+
+    public SpannedRandomAccessFileContent(RandomAccessFile raf, Span[] spans) throws IOException {
+        this.raf = raf;
+        this.spans = spans;
+        long rafLength = raf.length();
+        for (int i = 0; i < spans.length; i++) {
+            Span span = spans[i];
+            if (span.offset > rafLength || span.offset + span.length > rafLength) {
+                throw new IOException("Illegal span: " + span.offset + " " + span.length);
+            }
+        }
+        for (int i = 0; i < spans.length; i++) {
+            length += spans[i].length;
+        }
+    }
+
+    Span currentSpan;
+    long spanOffset;
+
+    void seek(long offset) throws IOException {
+        int sp = 0;
+        while (offset > 0) {
+            Span span = spans[sp++];
+            if (offset < span.length) {
+                currentSpan = span;
+                spanOffset = offset;
+                raf.seek(span.offset + offset);
+                return;
+            } else {
+                offset -= span.length;
+            }
+        }
+    }
+
+    public int load(long offset, int bpos, byte[] b) throws IOException {
+        seek(offset);
+
+        long available = currentSpan.length - spanOffset;
+        int len = (int) Math.min(available, b.length - bpos);
+        if (len > 0) {
+            raf.readFully(b, bpos, len);
+            return len;
+        }
+        throw new EOFException();
+    }
+
+    public boolean canReload() {
+        return true;
+    }
+
+    public void close() {
+        IOutils.closeStream(raf);
+    }
+
+    public boolean writable() {
+        return true;
+    }
+
+    public void save(long offset, int bpos, byte[] buffer, int length) throws IOException {
+        long len = length;
+        while (len > 0) {
+            seek(offset);
+            long available = currentSpan.length - spanOffset;
+            int w = (int) Math.min(available, len);
+            if (w == 0) {
+                throw new UnexpectedEOFException(length - len);
+            }
+            raf.write(buffer, bpos, w);
+            offset += w;
+            bpos += w;
+            len -= w;
+        }
+    }
+
+    public long length() throws IOException {
+        return length;
+    }
+
+    protected void finalize() throws Throwable {
+        super.finalize();
+        raf = null;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/SpannedRandomAccessInputContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/SpannedRandomAccessInputContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/SpannedRandomAccessInputContent.java	(revision 0)
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import com.imagero.uio.RandomAccessInput;
+import com.imagero.uio.io.IOutils;
+
+import java.io.EOFException;
+import java.io.IOException;
+
+/**
+ * Content with access to one or more predefined areas in RandomAccessIO.
+ * Length can not be changed.
+ * UnexpectedEOFException is thrown if we try to write outside our length.
+ *
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class SpannedRandomAccessInputContent extends Content {
+    private RandomAccessInput rio;
+
+    Span[] spans;
+    long length;
+
+
+    public SpannedRandomAccessInputContent(RandomAccessInput rio, Span[] spans) throws IOException {
+        this.rio = rio;
+        this.spans = spans;
+        long rafLength = rio.length();
+        for (int i = 0; i < spans.length; i++) {
+            Span span = spans[i];
+            if (span.offset > rafLength || span.offset + span.length > rafLength) {
+                throw new IOException("Illegal span: " + span.offset + " " + span.length);
+            }
+        }
+        for (int i = 0; i < spans.length; i++) {
+            length += spans[i].length;
+        }
+    }
+
+    Span currentSpan;
+    long spanOffset;
+
+    void seek(long offset) throws IOException {
+        int sp = 0;
+        while (offset > 0) {
+            Span span = spans[sp++];
+            if (offset < span.length) {
+                currentSpan = span;
+                spanOffset = offset;
+                rio.seek(span.offset + offset);
+                return;
+            } else {
+                offset -= span.length;
+            }
+        }
+    }
+
+    public int load(long offset, int bpos, byte[] b) throws IOException {
+        seek(offset);
+
+        long available = currentSpan.length - spanOffset;
+        int len = (int) Math.min(available, b.length - bpos);
+        if (len > 0) {
+            rio.readFully(b, bpos, len);
+            return len;
+        }
+        throw new EOFException();
+    }
+
+    public boolean canReload() {
+        return true;
+    }
+
+    public void close() {
+        IOutils.closeStream(rio);
+    }
+
+    public boolean writable() {
+        return false;
+    }
+
+    public void save(long offset, int bpos, byte[] buffer, int length) throws IOException {
+
+    }
+
+    public long length() throws IOException {
+        return length;
+    }
+
+    protected void finalize() throws Throwable {
+        super.finalize();
+        rio = null;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/SpannedRandomAccessIOContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/SpannedRandomAccessIOContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/SpannedRandomAccessIOContent.java	(revision 0)
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import com.imagero.uio.RandomAccessIO;
+import com.imagero.uio.RandomAccessInput;
+import com.imagero.uio.io.IOutils;
+import com.imagero.uio.io.UnexpectedEOFException;
+
+import java.io.EOFException;
+import java.io.IOException;
+
+/**
+ * Content with access to one or more predefined areas in RandomAccessIO.
+ * Length can not be changed.
+ * UnexpectedEOFException is thrown if we try to write outside our length.
+ *
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class SpannedRandomAccessIOContent extends Content {
+    private RandomAccessIO rio;
+
+    Span[] spans;
+    long length;
+
+
+    public SpannedRandomAccessIOContent(RandomAccessIO rio, Span[] spans) throws IOException {
+        this.rio = rio;
+        this.spans = spans;
+        long rafLength = rio.length();
+        for (int i = 0; i < spans.length; i++) {
+            Span span = spans[i];
+            if (span.offset > rafLength || span.offset + span.length > rafLength) {
+                throw new IOException("Illegal span: " + span.offset + " " + span.length);
+            }
+        }
+        for (int i = 0; i < spans.length; i++) {
+            length += spans[i].length;
+        }
+    }
+
+    Span currentSpan;
+    long spanOffset;
+
+    void seek(long offset) throws IOException {
+        int sp = 0;
+        while (offset >= 0) {
+            Span span = spans[sp++];
+            if (offset < span.length) {
+                currentSpan = span;
+                spanOffset = offset;
+                rio.seek(span.offset + offset);
+                return;
+            } else {
+                offset -= span.length;
+            }
+        }
+    }
+
+    public int load(long offset, int bpos, byte[] b) throws IOException {
+        seek(offset);
+
+        long available = currentSpan.length - spanOffset;
+        int len = (int) Math.min(available, b.length - bpos);
+        if (len > 0) {
+            rio.readFully(b, bpos, len);
+            return len;
+        }
+        throw new EOFException();
+    }
+
+    public boolean canReload() {
+        return true;
+    }
+
+    public void close() {
+        IOutils.closeStream((RandomAccessInput) rio);
+    }
+
+    public boolean writable() {
+        return true;
+    }
+
+    public void save(long offset, int bpos, byte[] buffer, int length) throws IOException {
+        long len = length;
+        while (len > 0) {
+            seek(offset);
+            long available = currentSpan.length - spanOffset;
+            int w = (int) Math.min(available, len);
+            if (w == 0) {
+                throw new UnexpectedEOFException(length - len);
+            }
+            rio.write(buffer, bpos, w);
+            offset += w;
+            bpos += w;
+            len -= w;
+        }
+    }
+
+    public long length() throws IOException {
+        return length;
+    }
+
+    protected void finalize() throws Throwable {
+        super.finalize();
+        rio = null;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/content/SynchronizedContent.java
===================================================================
--- ocean/src/com/imagero/uio/bio/content/SynchronizedContent.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/content/SynchronizedContent.java	(revision 0)
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio.content;
+
+import java.io.IOException;
+
+/**
+ * Date: 05.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class SynchronizedContent extends Content {
+
+    Content content;
+
+    public SynchronizedContent(Content content) {
+        this.content = content;
+    }
+
+    public synchronized int load(long offset, int bpos, byte[] buffer) throws IOException {
+        return content.load(offset, bpos, buffer);
+    }
+
+    public synchronized void save(long offset, int bpos, byte[] buffer, int length) throws IOException {
+        content.save(offset, bpos, buffer, length);
+    }
+
+    public synchronized long length() throws IOException {
+        return content.length();
+    }
+
+    public void close() {
+        content.close();
+    }
+
+    public boolean canReload() {
+        return content.canReload();
+    }
+
+    public boolean writable() {
+        return content.writable();
+    }
+
+    public Content getContent() {
+        return content;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/FixedSizeByteBuffer.java
===================================================================
--- ocean/src/com/imagero/uio/bio/FixedSizeByteBuffer.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/FixedSizeByteBuffer.java	(revision 0)
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio;
+
+import com.imagero.uio.RandomAccessIO;
+
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * This class can be used to read from and write to byte array.
+ *
+ * @author Andrey Kuznetsov
+ */
+public class FixedSizeByteBuffer {
+
+    protected byte[] buf;
+    protected int count;
+
+    boolean changed;
+    BufferIndex index;
+
+    protected FixedSizeByteBuffer(byte buf[]) {
+        this.buf = buf;
+    }
+
+    public int read(BufferPosition position) {
+        if (availableForReading(position) > 0) {
+            int v = buf[position.pos++] & 0xFF;
+            return v;
+        }
+        return -1;
+    }
+
+    public long skip(long n, BufferPosition position) {
+        long p = Math.max(0, Math.min(count - position.pos, n));
+        position.pos += p;
+        return p;
+    }
+
+    public BufferPosition createPosition() {
+        return new BufferPosition(buf.length);
+    }
+
+    public int availableForReading(BufferPosition position) {
+        return Math.max(0, count - position.pos);
+    }
+
+    public int availableForWriting(BufferPosition position) {
+        return Math.max(0, buf.length - position.pos);
+    }
+
+    public int read(byte[] dest, int offset, int length, BufferPosition position) {
+        final int available = availableForReading(position);
+        int toCopy = Math.max(0, Math.min(length, available));
+        if (toCopy > 0) {
+            System.arraycopy(buf, position.pos, dest, offset, toCopy);
+            position.pos += toCopy;
+        }
+        return toCopy;
+    }
+
+    /**
+     * write given byte to buffer.
+     *
+     * @param b int to write
+     */
+    public void write(int b, BufferPosition position) {
+        buf[position.pos++] = (byte) b;
+        count = Math.max(position.pos, count);
+    }
+
+    public int getCount() {
+        return count;
+    }
+
+    public int getPosition(BufferPosition position) {
+        return position.pos;
+    }
+
+    public void setCount(int count) {
+        this.count = Math.min(Math.max(count, 0), buf.length);
+    }
+
+    /**
+     * write buffer contents to OutputStream
+     * @param wholeBuffer if true then whole buffer is written, otherwise only getCount() bytes are written
+     */
+    public void writeBuffer(OutputStream out, boolean wholeBuffer) throws IOException {
+        if (wholeBuffer) {
+            out.write(buf);
+        } else {
+            out.write(buf, 0, count);
+        }
+    }
+
+    public void writeBuffer(DataOutput out, boolean wholeBuffer) throws IOException {
+        if (wholeBuffer) {
+            out.write(buf);
+        } else {
+            out.write(buf, 0, count);
+        }
+    }
+
+    /**
+     * write whole buffer contents to OutputStream (count is ignored)
+     */
+    public void writeBuffer(OutputStream out) throws IOException {
+        out.write(buf);
+    }
+
+    public void writeBuffer(DataOutput out) throws IOException {
+        out.write(buf);
+    }
+
+    public int write(byte src[], int offset, int length, BufferPosition position) {
+        int available = availableForWriting(position);
+        int toCopy = Math.max(0, Math.min(length, available));
+        if (toCopy > 0) {
+            System.arraycopy(src, offset, buf, position.pos, toCopy);
+            position.pos += toCopy;
+            count = Math.max(count, position.pos);
+        }
+        return toCopy;
+    }
+
+    public RandomAccessIO create() {
+        return new FSBRandomAccessIO(this);
+    }
+
+    public RandomAccessIO create(int offset, int length) {
+        return new FSBRandomAccessIO(this, offset, length);
+    }
+
+    public static FixedSizeByteBuffer createBuffer(byte buf[]) {
+        return new FixedSizeByteBuffer(buf);
+    }
+}
Index: ocean/src/com/imagero/uio/bio/FSBInputStream.java
===================================================================
--- ocean/src/com/imagero/uio/bio/FSBInputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/FSBInputStream.java	(revision 0)
@@ -0,0 +1,68 @@
+package com.imagero.uio.bio;
+
+import java.io.InputStream;
+import java.io.IOException;
+
+/**
+ * Date: 01.03.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+class FSBInputStream extends InputStream {
+    FixedSizeByteBuffer buffer;
+    BufferPosition position;
+    int mark;
+    long offset;
+
+    public FSBInputStream(FixedSizeByteBuffer buffer) {
+        this.buffer = buffer;
+        position = new BufferPosition(buffer.buf.length);
+    }
+
+    public FSBInputStream(int offset, FixedSizeByteBuffer buffer) {
+        this.buffer = buffer;
+        position = new BufferPosition(Integer.MAX_VALUE);
+        position.pos = offset;
+        this.offset = offset;
+    }
+
+    public int read() {
+        return buffer.read(position);
+    }
+
+    public int read(byte b[]) throws IOException {
+        return buffer.read(b, 0, b.length, position);
+    }
+
+    public int read(byte b[], int off, int len) {
+        return buffer.read(b, off, len, position);
+    }
+
+    public long skip(long n) {
+        return buffer.skip(n, position);
+    }
+
+    public int available() {
+        return buffer.availableForReading(position);
+    }
+
+    public synchronized void mark(int readlimit) {
+        this.mark = position.pos;
+    }
+
+    public synchronized void reset() throws IOException {
+        position.pos = mark;
+    }
+
+    public boolean markSupported() {
+        return true;
+    }
+
+    public long getPosition() {
+        return position.pos - offset;
+    }
+
+    public void setPosition(long pos) {
+        position.pos = (int) (pos + offset);
+    }
+}
Index: ocean/src/com/imagero/uio/bio/FSBOutputStream.java
===================================================================
--- ocean/src/com/imagero/uio/bio/FSBOutputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/FSBOutputStream.java	(revision 0)
@@ -0,0 +1,40 @@
+package com.imagero.uio.bio;
+
+import java.io.OutputStream;
+import java.io.IOException;
+
+/**
+ * Date: 01.03.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+class FSBOutputStream extends OutputStream {
+    FixedSizeByteBuffer buffer;
+    BufferPosition position;
+
+    public FSBOutputStream(FixedSizeByteBuffer buffer) {
+        this(0, buffer);
+    }
+
+    public FSBOutputStream(int offset, FixedSizeByteBuffer buffer) {
+        this.buffer = buffer;
+        position = new BufferPosition(Integer.MAX_VALUE);
+        position.pos = offset;
+    }
+
+    public void write(int b) throws IOException {
+        buffer.write(b, position);
+    }
+
+    public void write(byte b[]) throws IOException {
+        buffer.write(b, 0, b.length, position);
+    }
+
+    public void write(byte b[], int off, int len) throws IOException {
+        buffer.write(b, off, len, position);
+    }
+
+    public void close() throws IOException {
+        buffer = null;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/FSBRandomAccessIO.java
===================================================================
--- ocean/src/com/imagero/uio/bio/FSBRandomAccessIO.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/FSBRandomAccessIO.java	(revision 0)
@@ -0,0 +1,111 @@
+package com.imagero.uio.bio;
+
+import com.imagero.uio.impl.AbstractRandomAccessIO;
+import com.imagero.uio.RandomAccessIO;
+import com.imagero.uio.RandomAccessInput;
+import com.imagero.uio.RandomAccessOutput;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Date: 01.03.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+class FSBRandomAccessIO extends AbstractRandomAccessIO {
+
+    FixedSizeByteBuffer buffer;
+    BufferPosition position;
+
+    int offset;
+    int length;
+
+    public FSBRandomAccessIO(FixedSizeByteBuffer buffer) {
+        this(buffer, 0, buffer.buf.length);
+    }
+
+    public FSBRandomAccessIO(FixedSizeByteBuffer buffer, int offset) {
+        this(buffer, offset, buffer.buf.length - offset);
+    }
+
+    public FSBRandomAccessIO(FixedSizeByteBuffer buffer, int offset, int length) {
+        this.buffer = buffer;
+        this.offset = offset;
+        if(length > 0) {
+            this.length = length;
+        }
+        else {
+            this.length = buffer.availableForReading(buffer.createPosition());
+        }
+    }
+
+    public int read() throws IOException {
+        return buffer.read(position);
+    }
+
+    public void seek(long pos) {
+        position.pos = (int) Math.min(length, pos + offset);
+    }
+
+    public long length() throws IOException {
+        return length;
+    }
+
+    public long getFilePointer() throws IOException {
+        return position.pos - offset;
+    }
+
+    public void setLength(long newLength) throws IOException {
+        this.length = (int) Math.min(buffer.buf.length, newLength);
+    }
+
+    public void write(int b) throws IOException {
+        buffer.write(b, position);
+    }
+
+    public void write(byte b[], int off, int len) throws IOException {
+        buffer.write(b, off, len, position);
+    }
+
+    public InputStream createInputStream(long offset) {
+        return new FSBInputStream((int) offset, buffer);
+    }
+
+    public long getChildPosition(InputStream child) {
+        if(child instanceof FSBInputStream) {
+            FSBInputStream fsbis = (FSBInputStream) child;
+            return fsbis.getPosition();
+        }
+        return -1;
+    }
+
+    public void setChildPosition(InputStream child, long position) {
+        if (child instanceof FSBInputStream) {
+            FSBInputStream fsbis = (FSBInputStream) child;
+            fsbis.setPosition(position);
+        }
+    }
+
+    public OutputStream createOutputStream(long offset) {
+        return new FSBOutputStream((int) offset, buffer) ;
+    }
+
+    public RandomAccessIO createIOChild(long offset, long length, int byteOrder, boolean syncPointer) throws IOException {
+        FSBRandomAccessIO rio = new FSBRandomAccessIO(buffer, (int) offset, (int) length);
+        if(syncPointer) {
+            rio.position = position;
+        }
+        rio.setByteOrder(byteOrder);
+        return rio;
+    }
+
+    public RandomAccessInput createInputChild(long offset, long length, int byteOrder, boolean syncPointer) throws IOException {
+        return createIOChild(offset, 0, byteOrder, syncPointer);
+    }
+
+    public RandomAccessOutput createOutputChild(long offset, int byteOrder, boolean syncPointer) throws IOException {
+        return createIOChild(offset, 0, byteOrder, syncPointer);
+    }
+}
Index: ocean/src/com/imagero/uio/bio/IOCInputStream.java
===================================================================
--- ocean/src/com/imagero/uio/bio/IOCInputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/IOCInputStream.java	(revision 0)
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Independent InputStream with shared IOController.
+ *
+ * @author Andrey Kuznetsov
+ */
+public class IOCInputStream extends InputStream {
+
+    FixedSizeByteBuffer buffer;
+    BufferIndex bufferIndex;
+    BufferPosition bufferPosition;
+    IOController controller;
+
+    StreamPosition streamPosition = new StreamPosition();
+    long offset;
+
+    long mark;
+
+    public IOCInputStream(IOController controller) {
+        this.controller = controller;
+    }
+
+    public IOCInputStream(IOController controller, long offset) {
+        this.controller = controller;
+        this.offset = offset;
+        bufferPosition = new BufferPosition(controller.bufferSize);
+        seek(0);
+    }
+
+    public void seek(long offset) {
+        streamPosition.pos = offset + this.offset;
+        bufferPosition.pos = (int) ((streamPosition.pos) % controller.bufferSize);
+ }
+
+    private void prepareBufferForReading() {
+        BufferIndex index = controller.getBufferIndex(streamPosition.pos);
+
+        if (!index.equals(bufferIndex) || buffer == null || buffer.buf == null) {
+            bufferIndex = index;
+            try {
+                buffer = controller.getBuffer(streamPosition.pos, true);
+            } catch (IOException ex) {
+                //ignore
+            }
+        }
+        if (buffer != null) {
+            bufferPosition.pos = (int) ((streamPosition.pos) % controller.bufferSize);
+        }
+    }
+
+    public int read() throws IOException {
+        checkBuffer();
+
+        if (buffer != null) {
+            streamPosition.pos++;
+            return buffer.read(bufferPosition);
+        }
+        return -1;
+    }
+
+    public int available() throws IOException {
+        if(buffer != null) {
+            return buffer.availableForReading(bufferPosition);
+        }
+        return 0;
+    }
+
+    private void checkBuffer() {
+        if (buffer == null || bufferPosition.available() <= 0) {
+            prepareBufferForReading();
+        }
+    }
+
+    public long skip(long n) throws IOException {
+        checkBuffer();
+        if (buffer == null) {
+            return 0;
+        }
+        long skp = buffer.skip(n, bufferPosition);
+        streamPosition.pos += skp;
+        return skp;
+    }
+
+    public int read(byte b[]) throws IOException {
+        return read(b, 0, b.length);
+    }
+
+    public int read(byte[] b, int offset, int length) throws IOException {
+        checkBuffer();
+        if (buffer == null) {
+            return -1;
+        }
+        int rc = buffer.read(b, offset, length, bufferPosition);
+        if (rc > 0) {
+            streamPosition.pos += rc;
+        }
+        return rc;
+    }
+
+    public boolean markSupported() {
+        return true;
+    }
+
+    public synchronized void mark(int readlimit) {
+        this.mark = streamPosition.pos;
+    }
+
+    public synchronized void reset() throws IOException {
+        seek(mark);
+    }
+
+    public void close() throws IOException {
+        if (controller != null) {
+            controller = null;
+        }
+    }
+
+    public long getPosition() {
+        return streamPosition.pos - offset;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/IOController.java
===================================================================
--- ocean/src/com/imagero/uio/bio/IOController.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/IOController.java	(revision 0)
@@ -0,0 +1,353 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio;
+
+import com.imagero.util.OpenVector;
+import com.imagero.uio.bio.content.SynchronizedContent;
+import com.imagero.uio.bio.content.Content;
+import com.imagero.uio.UIOStreamBuilder;
+
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Enumeration;
+import java.util.NoSuchElementException;
+
+/**
+ * Buffer controller.
+ * IOController loads data from Content and maintains buffers (FixedSizeBuffer).
+ * @author Andrey Kuznetsov
+ */
+public class IOController {
+
+    OpenVector bufs = new OpenVector(100);
+
+    int bufferSize = UIOStreamBuilder.DEFAULT_CHUNK_SIZE;
+    int arrayLength = 1000;
+
+    Content content;
+
+    Ring rs;
+    int maxBufferCount = UIOStreamBuilder.DEFAULT_CHUNK_COUNT;
+
+    long explicitLength;
+
+    public IOController(int bufferSize, Content content) {
+        this.bufferSize = bufferSize;
+        this.content = content;
+        this.rs = new Ring(maxBufferCount);
+    }
+
+    final void setLength(long newLength) {
+        explicitLength = newLength;
+    }
+
+    private Enumeration buffers(final boolean allowNullValues) {
+        return new Enumeration() {
+            final FixedSizeByteBuffer empty = FixedSizeByteBuffer.createBuffer(new byte[bufferSize]);
+            final long length = length();
+            final BufferIndex max = getBufferIndex(length);
+            final BufferIndex bi = new BufferIndex(0, 0);
+
+            public boolean hasMoreElements() {
+                boolean b = bi.arrayIndex < max.arrayIndex;
+                boolean b2 = bi.index <= max.index;
+                return b || b2;
+            }
+
+            public Object nextElement() {
+                if (hasMoreElements()) {
+                    if (!(bi.index < arrayLength)) {
+                        bi.index = 0;
+                        bi.arrayIndex++;
+                    }
+                    FixedSizeByteBuffer fb = getBuffer(bi.arrayIndex, bi.index++);
+                    return fb != null || allowNullValues ? fb : empty;
+                } else {
+                    throw new NoSuchElementException();
+                }
+            }
+        };
+    }
+
+    private Enumeration buffers(final long maxPos, final boolean allowNullValues) {
+        return new Enumeration() {
+            final FixedSizeByteBuffer empty = FixedSizeByteBuffer.createBuffer(new byte[bufferSize]);
+            final long length = Math.min(maxPos, length());
+            final BufferIndex max = getBufferIndex(length);
+            final BufferIndex bi = new BufferIndex(0, 0);
+
+            public boolean hasMoreElements() {
+                boolean b = bi.arrayIndex < max.arrayIndex;
+                boolean b2 = bi.index <= max.index;
+                return b || b2;
+            }
+
+            public Object nextElement() {
+                if (hasMoreElements()) {
+                    if (!(bi.index < arrayLength)) {
+                        bi.index = 0;
+                        bi.arrayIndex++;
+                    }
+                    FixedSizeByteBuffer fb = getBuffer(bi.arrayIndex, bi.index++);
+                    return fb != null || allowNullValues ? fb : empty;
+                } else {
+                    throw new NoSuchElementException();
+                }
+            }
+        };
+    }
+
+
+    long length() {
+        if (explicitLength > 0) {
+            return explicitLength;
+        }
+
+        long contentLength = 0;
+        try {
+            contentLength = content.length();
+        } catch (IOException ex) {
+            ex.printStackTrace();
+        }
+
+        Object[] elements = bufs.getElements();
+        int maxI = elements.length - 1;
+        for (int i = maxI; i >= 0; i--) {
+            BufferArray ba = (BufferArray) elements[i];
+            if (ba != null) {
+                FixedSizeByteBuffer[] buffers = ba.buffers;
+                int maxJ = buffers.length - 1;
+                for (int j = maxJ; j >= 0; j--) {
+                    FixedSizeByteBuffer buffer = buffers[j];
+                    if (buffer != null) {
+                        int count = buffer.getCount();
+                        long startOffset = getStartOffset(buffer.index, bufferSize);
+                        if (count > 0) {
+                            return Math.max(contentLength, startOffset + count);
+                        }
+                    }
+                }
+            }
+        }
+        return contentLength;
+    }
+
+    void writeTo(OutputStream out) throws IOException {
+        Enumeration enums = buffers(false);
+        while (enums.hasMoreElements()) {
+            FixedSizeByteBuffer buffer = (FixedSizeByteBuffer) enums.nextElement();
+            buffer.writeBuffer(out, enums.hasMoreElements());
+        }
+    }
+
+    void writeTo(DataOutput out) throws IOException {
+        Enumeration enums = buffers(false);
+        while (enums.hasMoreElements()) {
+            FixedSizeByteBuffer buffer = (FixedSizeByteBuffer) enums.nextElement();
+            buffer.writeBuffer(out, enums.hasMoreElements());
+        }
+    }
+
+    private class BufferArray {
+        FixedSizeByteBuffer[] buffers;
+
+        public BufferArray() {
+            buffers = new FixedSizeByteBuffer[arrayLength];
+        }
+    }
+
+    private FixedSizeByteBuffer getBuffer(BufferIndex bi) {
+        return getBuffer(bi.arrayIndex, bi.index);
+    }
+
+    private FixedSizeByteBuffer getBuffer(int aIndex, int index) {
+        Object[] objects = bufs.checkSize(aIndex);
+        BufferArray ba = (BufferArray) objects[aIndex];
+        if (ba == null) {
+            ba = new BufferArray();
+            objects[aIndex] = ba;
+        }
+        return ba.buffers[index];
+    }
+
+    protected void setBuffer(BufferIndex index, FixedSizeByteBuffer buffer) {
+        Object[] objects = bufs.checkSize(index.arrayIndex);
+        BufferArray ba = (BufferArray) objects[index.arrayIndex];
+        ba.buffers[index.index] = buffer;
+    }
+
+    public long flushBefore(long pos) {
+        pos = (pos / bufferSize) * bufferSize;
+        Enumeration enums = buffers(pos, true);
+        while (enums.hasMoreElements()) {
+            FixedSizeByteBuffer o = (FixedSizeByteBuffer) enums.nextElement();
+            if (o != null) {
+                o.buf = null;
+            }
+        }
+        return pos;
+    }
+
+    public BufferIndex getBufferIndex(long pos) {
+        if(pos < 0) {
+            throw new IllegalArgumentException("Negative stream position");
+        }
+        long count = pos / bufferSize;
+        long aIndex = count / arrayLength;
+        int index = (int) (count % arrayLength);
+        if (aIndex > Integer.MAX_VALUE) {
+            throw new IndexOutOfBoundsException("Please increase buffer size");
+        }
+        if(index < 0) {
+            throw new IndexOutOfBoundsException("Please increase buffer size");
+        }
+        BufferIndex bi = new BufferIndex((int) aIndex, index);
+        return bi;
+    }
+
+    public FixedSizeByteBuffer getBuffer(long pos) {
+        return getBuffer(getBufferIndex(pos));
+    }
+
+    FixedSizeByteBuffer getBuffer(long pos, boolean load) throws IOException {
+        BufferIndex bi = getBufferIndex(pos);
+        long startOffset = getStartOffset(bi, bufferSize);
+        FixedSizeByteBuffer sb = getBuffer(bi);
+        if (sb == null) {
+            sb = FixedSizeByteBuffer.createBuffer(new byte[bufferSize]);
+            setBuffer(bi, sb);
+            sb.index = bi;
+            if (load) {
+                long max = content.length();
+                if (pos > max) {
+                    return null;
+                }
+
+                int size = content.load(startOffset, sb.buf);
+                sb.count = size;
+            }
+            if (content.canReload()) {
+                checkBuffers(sb);
+            }
+        } else {
+            if (sb.buf == null) {
+                long max = content.length();
+                if (pos > max) {
+                    return null;
+                }
+                sb.buf = new byte[bufferSize];
+                int size = content.load(startOffset, sb.buf);
+                sb.count = size;
+            }
+        }
+        return sb;
+    }
+
+    private void checkBuffers(FixedSizeByteBuffer buffer0) {
+        FixedSizeByteBuffer buffer = (FixedSizeByteBuffer) rs.add(buffer0);
+        if (buffer != null && content.writable()) {
+            if (buffer.changed) {
+                try {
+                    long offset = getStartOffset(buffer.index, bufferSize);
+                    content.save(offset, 0, buffer.buf, buffer.getCount());
+                    buffer.changed = false;
+                } catch (IOException ex) {
+                    ex.printStackTrace();
+                }
+            }
+            setBuffer(buffer.index, null);
+            buffer.buf = null;
+        }
+    }
+
+    void sync() throws IOException {
+        boolean canWrite = content.writable();
+        if (!canWrite) {
+            return;
+        }
+
+        try {
+            long length = content.length(); //may be already closed
+        }
+        catch(IOException ex) {
+            //stream was already closed, so just return
+            return;
+        }
+        Enumeration enums = buffers(true);
+        while (enums.hasMoreElements()) {
+            FixedSizeByteBuffer buffer = (FixedSizeByteBuffer) enums.nextElement();
+            if (buffer != null && buffer.changed) {
+                long offset = getStartOffset(buffer.index, bufferSize);
+                content.save(offset, 0, buffer.buf, buffer.getCount());
+                buffer.changed = false;
+            }
+        }
+    }
+
+    public long getStartOffset(BufferIndex bufferIndex, int bufferSize) {
+        long res = bufferIndex.arrayIndex * arrayLength * bufferSize;
+        res += bufferIndex.index * bufferSize;
+        return res;
+    }
+
+    /**
+     * determine if access to stream content is synchronized
+     */
+    public boolean isSynchronizedContent() {
+        return content instanceof SynchronizedContent;
+    }
+
+    /**
+     * define if access to content should be syncronized.
+     */
+    public void setSynchronizedContent(boolean b) {
+        if(b) {
+            if(!isSynchronizedContent()) {
+                content = new SynchronizedContent(content);
+            }
+        }
+        else {
+            if(isSynchronizedContent()) {
+                SynchronizedContent synchronizedContent = (SynchronizedContent) content;
+                content = synchronizedContent.getContent();
+            }
+        }
+    }
+
+    protected void finalize() throws Throwable {
+        super.finalize();
+        content = null;
+        rs = null;
+        bufs = null;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/IOCOutputStream.java
===================================================================
--- ocean/src/com/imagero/uio/bio/IOCOutputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/IOCOutputStream.java	(revision 0)
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio;
+
+import java.io.OutputStream;
+import java.io.IOException;
+
+/**
+ * OutputStream with shared IOController.
+ *
+ * @author Andrey Kuznetsov
+ */
+public class IOCOutputStream extends OutputStream {
+
+    FixedSizeByteBuffer buffer;
+    BufferIndex bufferIndex;
+    BufferPosition bufferPosition;
+    IOController controller;
+
+    StreamPosition streamPosition = new StreamPosition();
+    long offset;
+
+    long mark;
+
+    public IOCOutputStream(IOController controller) {
+        this.controller = controller;
+    }
+
+    public IOCOutputStream(IOController controller, long offset) {
+        this.controller = controller;
+        this.offset = offset;
+        bufferPosition = new BufferPosition(controller.bufferSize);
+        seek(0);
+    }
+
+    public void seek(long offset) {
+        streamPosition.pos = offset + this.offset;
+    }
+
+    protected void prepareBufferForWriting() throws IOException {
+        BufferIndex index = controller.getBufferIndex(streamPosition.pos);
+
+        if (!index.equals(bufferIndex) || buffer == null || buffer.buf == null) {
+            bufferIndex = index;
+            buffer = controller.getBuffer(streamPosition.pos, false);
+        }
+        bufferPosition.pos = (int) ((streamPosition.pos) % controller.bufferSize);
+        buffer.changed = true;
+    }
+
+    private void checkBuffer() throws IOException {
+        if (buffer == null || !(bufferPosition.available() > 0)) {
+            prepareBufferForWriting();
+        }
+    }
+
+    public void write(int b) throws IOException {
+        checkBuffer();
+        buffer.write(b, bufferPosition);
+        streamPosition.pos++;
+    }
+
+    public void write(byte b[]) throws IOException {
+        write(b, 0, b.length);
+    }
+
+    public void write(byte b[], int offset, int length) throws IOException {
+        while (length > 0) {
+            checkBuffer();
+            int written = buffer.write(b, offset, length, bufferPosition);
+            length -= written;
+            offset += written;
+            streamPosition.pos += written;
+        }
+    }
+
+    public void flush() throws IOException {
+        if (controller != null) {
+            controller.sync();
+        }
+    }
+
+    public void close() throws IOException {
+        flush();
+        controller = null;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/Ring.java
===================================================================
--- ocean/src/com/imagero/uio/bio/Ring.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/Ring.java	(revision 0)
@@ -0,0 +1,32 @@
+package com.imagero.uio.bio;
+
+/**
+ * Ring - minimalistic Ring implementation.
+ *
+ * Date: 29.08.2007
+ * @author Andrey Kuznetsov
+ */
+class Ring {
+
+    Object[] elements;
+
+    int size;
+    int index;
+
+    public Ring(int size) {
+        this.size = size;
+        this.elements = new Object[size];
+    }
+
+    /**
+     * add Object to ring
+     * @param o Object
+     * @return Object removed from ring (replaced by new Object) or null
+     */
+    public Object add(Object o) {
+        Object tmp = elements[index];
+        elements[index] = o;
+        index = (index + 1) % size;
+        return tmp;
+    }
+}
Index: ocean/src/com/imagero/uio/bio/StreamPosition.java
===================================================================
--- ocean/src/com/imagero/uio/bio/StreamPosition.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/StreamPosition.java	(revision 0)
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio;
+
+/**
+ * @author Andrey Kuznetsov
+ */
+public class StreamPosition {
+    public long pos;
+}
Index: ocean/src/com/imagero/uio/bio/VariableSizeByteBuffer.java
===================================================================
--- ocean/src/com/imagero/uio/bio/VariableSizeByteBuffer.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/VariableSizeByteBuffer.java	(revision 0)
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.bio;
+
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * This class can be used to read from and write to byte array.
+ *
+ * @author Andrey Kuznetsov
+ */
+public class VariableSizeByteBuffer {
+
+    protected Buffer buf;
+    int count;
+
+    boolean changed;
+
+    public VariableSizeByteBuffer(int size) {
+        this(new byte[size]);
+    }
+
+    public VariableSizeByteBuffer(byte buf[]) {
+        this(new Buffer(buf));
+    }
+
+    VariableSizeByteBuffer(Buffer buf) {
+        this.buf = buf;
+        count = buf.buffer.length;
+    }
+
+    public VariableSizeByteBuffer create() {
+        return new VariableSizeByteBuffer(buf);
+    }
+
+    public void seek(int pos, BufferPosition position) {
+        position.pos = pos;
+    }
+
+    public int read(BufferPosition position) {
+        if (position.pos >= count) {
+            return -1;
+        }
+        return buf.buffer[position.pos++] & 0xFF;
+    }
+
+    public long skip(long n, BufferPosition position) {
+        long p = Math.max(0L, Math.min(n, Integer.MAX_VALUE));
+        position.pos += p;
+        return p;
+    }
+
+    /**
+     * get amount of bytes which may be written without changing buffer size
+     */
+    public int availableForWriting(BufferPosition position) {
+        return buf.buffer.length - position.pos;
+    }
+
+    public int availableForReading(BufferPosition position) {
+        return Math.max(0, count - position.pos);
+    }
+
+    public int read(byte[] dest, int offset, int length, BufferPosition position) {
+        final int available = availableForReading(position);
+        int toRead = Math.max(0, Math.min(length, available));
+        if (toRead > 0) {
+            System.arraycopy(buf.buffer, position.pos, dest, offset, toRead);
+            position.pos += toRead;
+        }
+        return toRead;
+    }
+
+    public int getCount() {
+        return count;
+    }
+
+    public void setCount(int count) {
+        this.count = Math.min(Math.max(count, 0), buf.buffer.length);
+    }
+
+    public void writeBuffer(OutputStream out) throws IOException {
+        out.write(buf.buffer, 0, count);
+    }
+
+    public void writeBuffer(DataOutput out) throws IOException {
+        out.write(buf.buffer, 0, count);
+    }
+
+    public void write(byte b[], int offset, int length, BufferPosition position) {
+        if (length > 0) {
+            checkSize(length, position);
+            System.arraycopy(b, offset, buf.buffer, position.pos, length);
+            position.pos += length;
+            count = Math.max(count, position.pos);
+        }
+    }
+
+    public void write(int b, BufferPosition position) {
+        checkSize(1, position);
+        buf.buffer[position.pos++] = (byte) b;
+        count = Math.max(count, position.pos);
+    }
+
+    private synchronized void checkSize(int k, BufferPosition position) {
+        if (position.pos + k > buf.buffer.length) {
+            byte newbuf[] = new byte[Math.max(buf.buffer.length << 1, position.pos + k)];
+            System.arraycopy(buf.buffer, 0, newbuf, 0, count);
+            buf.buffer = newbuf;
+        }
+    }
+
+    public InputStream getInputStream(int offset) {
+        return new VSBInputStream(offset, this);
+    }
+
+    public OutputStream getOutputStream(int offset) {
+        return new VSBOutputStream(offset, this);
+    }
+}
Index: ocean/src/com/imagero/uio/bio/VSBInputStream.java
===================================================================
--- ocean/src/com/imagero/uio/bio/VSBInputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/VSBInputStream.java	(revision 0)
@@ -0,0 +1,68 @@
+package com.imagero.uio.bio;
+
+import java.io.InputStream;
+import java.io.IOException;
+
+/**
+ * Date: 01.03.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+class VSBInputStream extends InputStream {
+    VariableSizeByteBuffer buffer;
+    BufferPosition position;
+    int mark;
+    long offset;
+
+    public VSBInputStream(VariableSizeByteBuffer buffer) {
+        this.buffer = buffer;
+        position = new BufferPosition(Integer.MAX_VALUE);
+    }
+
+    public VSBInputStream(int offset, VariableSizeByteBuffer buffer) {
+        this.buffer = buffer;
+        position = new BufferPosition(Integer.MAX_VALUE);
+        buffer.seek(offset, position);
+        this.offset = offset;
+    }
+
+    public int read() {
+        return buffer.read(position);
+    }
+
+    public int read(byte b[]) throws IOException {
+        return buffer.read(b, 0, b.length, position);
+    }
+
+    public int read(byte b[], int off, int len) {
+        return buffer.read(b, off, len, position);
+    }
+
+    public long skip(long n) {
+        return buffer.skip(n, position);
+    }
+
+    public int available() {
+        return buffer.availableForReading(position);
+    }
+
+    public synchronized void mark(int readlimit) {
+        this.mark = position.pos;
+    }
+
+    public synchronized void reset() throws IOException {
+        buffer.seek(mark, position);
+    }
+
+    public boolean markSupported() {
+        return true;
+    }
+
+    public long getPosition() {
+        return position.pos - offset;
+    }
+
+    public void setPosition(long pos) {
+        buffer.seek((int) pos, position);
+    }
+}
Index: ocean/src/com/imagero/uio/bio/VSBOutputStream.java
===================================================================
--- ocean/src/com/imagero/uio/bio/VSBOutputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/bio/VSBOutputStream.java	(revision 0)
@@ -0,0 +1,40 @@
+package com.imagero.uio.bio;
+
+import java.io.OutputStream;
+import java.io.IOException;
+
+/**
+ * Date: 01.03.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+class VSBOutputStream extends OutputStream {
+    VariableSizeByteBuffer buffer;
+    BufferPosition position;
+
+    public VSBOutputStream(VariableSizeByteBuffer buffer) {
+        this(0, buffer);
+    }
+
+    public VSBOutputStream(int offset, VariableSizeByteBuffer buffer) {
+        this.buffer = buffer;
+        position = new BufferPosition(Integer.MAX_VALUE);
+        buffer.seek(offset, position);
+    }
+
+    public void write(int b) throws IOException {
+        buffer.write(b, position);
+    }
+
+    public void write(byte b[]) throws IOException {
+        buffer.write(b, 0, b.length, position);
+    }
+
+    public void write(byte b[], int off, int len) throws IOException {
+        buffer.write(b, off, len, position);
+    }
+
+    public void close() throws IOException {
+        buffer = null;
+    }
+}
Index: ocean/src/com/imagero/uio/blob/BlobInputStream.java
===================================================================
--- ocean/src/com/imagero/uio/blob/BlobInputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/blob/BlobInputStream.java	(revision 0)
@@ -0,0 +1,86 @@
+package com.imagero.uio.blob;
+
+import com.imagero.uio.blob.Blob;
+import com.imagero.uio.io.UnexpectedEOFException;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+/**
+ * BlobInputStream.
+ * Read data from Blob.
+ *
+ * Data from Blob first filled into internal buffer and then read.
+ *
+ * Date: 23.07.2007
+ *
+ * @author Andrey Kuznetsov
+ */
+public class BlobInputStream extends ByteArrayInputStream {
+    Blob blob;
+
+    long start;
+
+    boolean finished;
+
+    public BlobInputStream(Blob blob) throws IOException {
+        super(new byte[2048]);
+        this.blob = blob;
+        fillBuffer();
+    }
+
+    public int read() {
+        if (finished) {
+            return -1;
+        }
+        if (available() > 0) {
+            return super.read();
+        }
+        return -1;
+    }
+
+    public synchronized long skip(long n) {
+        int k = available();
+        if (k < n) {
+            fillBuffer();
+        }
+        return super.skip(n);
+    }
+
+    public synchronized int available() {
+        int k = super.available();
+        if (k > 0) {
+            return k;
+        } else {
+            fillBuffer();
+            return super.available();
+        }
+    }
+
+    public synchronized int read(byte b[], int off, int len) {
+        if (finished) {
+            return -1;
+        }
+        int k = available();
+        return super.read(b, off, Math.min(k, len));
+    }
+
+    private void fillBuffer() {
+        try {
+            count = blob.get(start, buf);
+        } catch (UnexpectedEOFException ex) {
+            count = (int) ex.getCount();
+        } catch (IOException ex) {
+            finished = true;
+            ex.printStackTrace();
+        }
+        if (count <= 0) {
+            count = 0;
+            pos = 0;
+            finished = true;
+            return;
+        }
+        start += count;
+        pos = 0;
+    }
+}
Index: ocean/src/com/imagero/uio/blob/Blob.java
===================================================================
--- ocean/src/com/imagero/uio/blob/Blob.java	(revision 0)
+++ ocean/src/com/imagero/uio/blob/Blob.java	(revision 0)
@@ -0,0 +1,318 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.blob;
+
+import com.imagero.uio.RandomAccessIO;
+import com.imagero.uio.RandomAccessInput;
+import com.imagero.uio.io.UnexpectedEOFException;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Hashtable;
+
+/**
+ * Blob - Object which encapsulates (possible deferred)
+ * data which may come from different sources.
+ *
+ * @author Andrey Kuznetsov
+ */
+public abstract class Blob {
+
+    long length;
+
+    private Hashtable properties = new Hashtable();
+
+    /**
+     * retrieve data from this Blob
+     * @param start start offset
+     * @param length how much bytes to get
+     * @return byte array with data
+     * @throws java.io.IOException
+     */
+    public abstract byte[] get(long start, int length) throws IOException;
+
+    /**
+     * retrieve data from this Blob
+     * @param start start offset
+     * @param dest where to copy data
+     * @return how much byte were copied
+     * @throws java.io.IOException
+     */
+    public abstract int get(long start, byte[] dest) throws IOException;
+
+    /**
+     * determine if this Blob is writable and method set(long, byte[]) can be used to change content of this Blob
+     * @return true if Blob is writable
+     */
+    public abstract boolean writable();
+
+    /**
+     * set data (work only if writable returns true)
+     * @param start start in destination
+     * @param data new data
+     * @throws java.io.IOException
+     */
+    public abstract void set(long start, byte[] data) throws IOException;
+
+    /**
+     * release reloadable resources
+     */
+    public abstract void clear();
+
+    protected boolean lengthKnown() {
+        return length > 0;
+    }
+
+    protected abstract long computeLength() throws IOException;
+
+    public long getLength() {
+        if (!lengthKnown()) {
+            try {
+                length = computeLength();
+            } catch (IOException ex) {
+                System.err.println("Can't compute Blob length. " + ex.getMessage());
+            }
+        }
+        return length;
+    }
+
+    public InputStream getInputStream() throws IOException {
+        return new BlobInputStream(this);
+    }
+
+    public Object getProperty(Object key) {
+        return properties.get(key);
+    }
+
+    public void setProperty(Object key, Object property) {
+        properties.put(key, property);
+    }
+
+    public static class BaBlob extends Blob {
+        int offset;
+
+        byte[] blob;
+
+        public BaBlob(byte[] blob) {
+            this(blob, 0, blob.length);
+        }
+
+        public BaBlob(byte[] blob, int offset, int length) {
+            this.offset = offset;
+            this.length = length;
+            this.blob = blob;
+        }
+
+        protected long computeLength() {
+            return length;
+        }
+
+        static final byte[] empty = new byte[0];
+
+        public byte[] get(long start, int length) {
+            int len = (int) Math.max(Math.min(length, this.length - start), 0);
+            if (len == 0) {
+                return empty;
+            }
+            byte[] dest = new byte[len];
+            System.arraycopy(blob, (int) start + offset, dest, 0, len);
+            return dest;
+        }
+
+        public int get(long start, byte[] dest) {
+            long max = Math.max(Math.min(dest.length, this.length - start), 0);
+            if (max > 0) {
+                System.arraycopy(blob, (int) start + offset, dest, 0, (int) max);
+            }
+            return (int) max;
+        }
+
+        public boolean writable() {
+            return true;
+        }
+
+        public void set(long start, byte[] data) {
+            System.arraycopy(data, 0, blob, (int) start, data.length);
+        }
+
+        public void clear() {
+        }
+
+        public InputStream getInputStream() throws IOException {
+            return new ByteArrayInputStream(blob, offset, (int) length);
+        }
+    }
+
+    public static class RoBlob extends Blob {
+        RandomAccessInput ro;
+        long start;
+
+        public RoBlob(RandomAccessInput ro, long start, long length) {
+            this.ro = ro;
+            this.start = start;
+            this.length = length;
+        }
+
+        protected long computeLength() {
+            return length;
+        }
+
+        public byte[] get(long start, int length) throws IOException {
+            long pos = ro.getFilePointer();
+            try {
+                byte[] dest = new byte[length];
+                ro.seek(this.start + start);
+                ro.readFully(dest);
+                return dest;
+            } finally {
+                ro.seek(pos);
+            }
+        }
+
+        public int get(long start, byte[] dest) throws IOException {
+            long max = Math.min(dest.length, length - start);
+            long pos = ro.getFilePointer();
+            try {
+                ro.seek(this.start + start);
+                ro.readFully(dest, 0, (int) max);
+            } catch (UnexpectedEOFException ex) {
+                return (int) ex.getCount();
+            } finally {
+                ro.seek(pos);
+            }
+            return (int) max;
+        }
+
+        public boolean writable() {
+            return ro instanceof RandomAccessIO;
+        }
+
+        public void set(long start, byte[] data) throws IOException {
+            if (writable()) {
+                RandomAccessIO ra = (RandomAccessIO) ro;
+                ra.seek(start);
+                ra.write(data);
+            }
+        }
+
+        public void clear() {
+        }
+    }
+
+//    public static class IaBlob extends Blob {
+//
+//        int offset;
+//        int length4;
+//        int[] blob;
+//
+////        int mask;
+////        private int bytesPerInt;
+//
+//        RandomAccessInput in;
+//        SkipBytesInputStream skip;
+//
+//        IntArrayContent content;
+//
+//        public IaBlob(int[] blob) throws IOException {
+//            this(blob, 0, blob.length);
+//        }
+//
+//        public IaBlob(int[] blob, int offset, int length/*, int mask, int[] shifts*/) throws IOException {
+//            this.offset = offset;
+//            this.length4 = length;
+//            this.blob = blob;
+////            this.mask = mask;
+//            final int[] shiftsBE = new int[4];
+//            final int[] shiftsLE = new int[4];
+////            for (int i = 0; i < shifts.length; i++) {
+////                shiftsBE[i] = shifts[i];
+////                shiftsLE[i] = shifts[3 - i];
+////            }
+//            in = new UIOStreamBuilder(blob).setStart(offset).setLength(length).setByteOrder(ISeekable.BIG_ENDIAN).create();
+//            in = new XIntArrayInputStream(blob, offset, length, shiftsBE, shiftsLE, ISeekable.BIG_ENDIAN);
+////            skip = new SkipBytesInputStream(in, mask, 4);
+////            if (mask == 0) {
+////                bytesPerInt = 4;
+////            } else {
+////                bytesPerInt = XtoByteBE.getBytesPerNumber(mask);
+////            }
+//        }
+//
+//        public boolean writable() {
+//            return false;
+//        }
+//
+//        protected long computeLength() throws IOException {
+//            InputStream in = getInputStream();
+//            long count = 0;
+//            while (in.read() >= 0) {
+//                count++;
+//            }
+//            return count;
+//        }
+//
+//        public byte[] get(long start, int length) throws IOException {
+//            int len = (int) Math.max(Math.min(this.length4 * 4 - start, length), 0);
+//            byte[] b = new byte[len];
+//            if (len > 0) {
+//                in.seek(start);
+//                IOutils.readFully(skip, b);
+//            }
+//            return b;
+//        }
+//
+//        public int get(long start, byte[] dest) throws IOException {
+//            int len = (int) Math.max(Math.min(this.length4 * 4 - start, dest.length), 0);
+//            if (len > 0) {
+//                in.seek(start);
+//                IOutils.readFully(skip, dest, 0, len);
+//            }
+//            return len;
+//        }
+//
+//        public InputStream getInputStream() throws IOException {
+//            InputStream in = new BlobInputStream(this);
+////            InputStream skip = new SkipBytesInputStream(in, mask, 4);
+//            return in;
+//        }
+//
+//        public void set(long start, byte[] data) throws IOException {
+//            throw new RuntimeException("Unsupported operation");
+//        }
+//
+//
+//        public void clear() {
+//        }
+//    }
+}
Index: ocean/src/com/imagero/uio/blob/Blob.java
===================================================================
--- ocean/src/com/imagero/uio/blob/Blob.java	(revision 0)
+++ ocean/src/com/imagero/uio/blob/Blob.java	(revision 0)
@@ -0,0 +1,318 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.blob;
+
+import com.imagero.uio.RandomAccessIO;
+import com.imagero.uio.RandomAccessInput;
+import com.imagero.uio.io.UnexpectedEOFException;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Hashtable;
+
+/**
+ * Blob - Object which encapsulates (possible deferred)
+ * data which may come from different sources.
+ *
+ * @author Andrey Kuznetsov
+ */
+public abstract class Blob {
+
+    long length;
+
+    private Hashtable properties = new Hashtable();
+
+    /**
+     * retrieve data from this Blob
+     * @param start start offset
+     * @param length how much bytes to get
+     * @return byte array with data
+     * @throws java.io.IOException
+     */
+    public abstract byte[] get(long start, int length) throws IOException;
+
+    /**
+     * retrieve data from this Blob
+     * @param start start offset
+     * @param dest where to copy data
+     * @return how much byte were copied
+     * @throws java.io.IOException
+     */
+    public abstract int get(long start, byte[] dest) throws IOException;
+
+    /**
+     * determine if this Blob is writable and method set(long, byte[]) can be used to change content of this Blob
+     * @return true if Blob is writable
+     */
+    public abstract boolean writable();
+
+    /**
+     * set data (work only if writable returns true)
+     * @param start start in destination
+     * @param data new data
+     * @throws java.io.IOException
+     */
+    public abstract void set(long start, byte[] data) throws IOException;
+
+    /**
+     * release reloadable resources
+     */
+    public abstract void clear();
+
+    protected boolean lengthKnown() {
+        return length > 0;
+    }
+
+    protected abstract long computeLength() throws IOException;
+
+    public long getLength() {
+        if (!lengthKnown()) {
+            try {
+                length = computeLength();
+            } catch (IOException ex) {
+                System.err.println("Can't compute Blob length. " + ex.getMessage());
+            }
+        }
+        return length;
+    }
+
+    public InputStream getInputStream() throws IOException {
+        return new BlobInputStream(this);
+    }
+
+    public Object getProperty(Object key) {
+        return properties.get(key);
+    }
+
+    public void setProperty(Object key, Object property) {
+        properties.put(key, property);
+    }
+
+    public static class BaBlob extends Blob {
+        int offset;
+
+        byte[] blob;
+
+        public BaBlob(byte[] blob) {
+            this(blob, 0, blob.length);
+        }
+
+        public BaBlob(byte[] blob, int offset, int length) {
+            this.offset = offset;
+            this.length = length;
+            this.blob = blob;
+        }
+
+        protected long computeLength() {
+            return length;
+        }
+
+        static final byte[] empty = new byte[0];
+
+        public byte[] get(long start, int length) {
+            int len = (int) Math.max(Math.min(length, this.length - start), 0);
+            if (len == 0) {
+                return empty;
+            }
+            byte[] dest = new byte[len];
+            System.arraycopy(blob, (int) start + offset, dest, 0, len);
+            return dest;
+        }
+
+        public int get(long start, byte[] dest) {
+            long max = Math.max(Math.min(dest.length, this.length - start), 0);
+            if (max > 0) {
+                System.arraycopy(blob, (int) start + offset, dest, 0, (int) max);
+            }
+            return (int) max;
+        }
+
+        public boolean writable() {
+            return true;
+        }
+
+        public void set(long start, byte[] data) {
+            System.arraycopy(data, 0, blob, (int) start, data.length);
+        }
+
+        public void clear() {
+        }
+
+        public InputStream getInputStream() throws IOException {
+            return new ByteArrayInputStream(blob, offset, (int) length);
+        }
+    }
+
+    public static class RoBlob extends Blob {
+        RandomAccessInput ro;
+        long start;
+
+        public RoBlob(RandomAccessInput ro, long start, long length) {
+            this.ro = ro;
+            this.start = start;
+            this.length = length;
+        }
+
+        protected long computeLength() {
+            return length;
+        }
+
+        public byte[] get(long start, int length) throws IOException {
+            long pos = ro.getFilePointer();
+            try {
+                byte[] dest = new byte[length];
+                ro.seek(this.start + start);
+                ro.readFully(dest);
+                return dest;
+            } finally {
+                ro.seek(pos);
+            }
+        }
+
+        public int get(long start, byte[] dest) throws IOException {
+            long max = Math.min(dest.length, length - start);
+            long pos = ro.getFilePointer();
+            try {
+                ro.seek(this.start + start);
+                ro.readFully(dest, 0, (int) max);
+            } catch (UnexpectedEOFException ex) {
+                return (int) ex.getCount();
+            } finally {
+                ro.seek(pos);
+            }
+            return (int) max;
+        }
+
+        public boolean writable() {
+            return ro instanceof RandomAccessIO;
+        }
+
+        public void set(long start, byte[] data) throws IOException {
+            if (writable()) {
+                RandomAccessIO ra = (RandomAccessIO) ro;
+                ra.seek(start);
+                ra.write(data);
+            }
+        }
+
+        public void clear() {
+        }
+    }
+
+//    public static class IaBlob extends Blob {
+//
+//        int offset;
+//        int length4;
+//        int[] blob;
+//
+////        int mask;
+////        private int bytesPerInt;
+//
+//        RandomAccessInput in;
+//        SkipBytesInputStream skip;
+//
+//        IntArrayContent content;
+//
+//        public IaBlob(int[] blob) throws IOException {
+//            this(blob, 0, blob.length);
+//        }
+//
+//        public IaBlob(int[] blob, int offset, int length/*, int mask, int[] shifts*/) throws IOException {
+//            this.offset = offset;
+//            this.length4 = length;
+//            this.blob = blob;
+////            this.mask = mask;
+//            final int[] shiftsBE = new int[4];
+//            final int[] shiftsLE = new int[4];
+////            for (int i = 0; i < shifts.length; i++) {
+////                shiftsBE[i] = shifts[i];
+////                shiftsLE[i] = shifts[3 - i];
+////            }
+//            in = new UIOStreamBuilder(blob).setStart(offset).setLength(length).setByteOrder(ISeekable.BIG_ENDIAN).create();
+//            in = new XIntArrayInputStream(blob, offset, length, shiftsBE, shiftsLE, ISeekable.BIG_ENDIAN);
+////            skip = new SkipBytesInputStream(in, mask, 4);
+////            if (mask == 0) {
+////                bytesPerInt = 4;
+////            } else {
+////                bytesPerInt = XtoByteBE.getBytesPerNumber(mask);
+////            }
+//        }
+//
+//        public boolean writable() {
+//            return false;
+//        }
+//
+//        protected long computeLength() throws IOException {
+//            InputStream in = getInputStream();
+//            long count = 0;
+//            while (in.read() >= 0) {
+//                count++;
+//            }
+//            return count;
+//        }
+//
+//        public byte[] get(long start, int length) throws IOException {
+//            int len = (int) Math.max(Math.min(this.length4 * 4 - start, length), 0);
+//            byte[] b = new byte[len];
+//            if (len > 0) {
+//                in.seek(start);
+//                IOutils.readFully(skip, b);
+//            }
+//            return b;
+//        }
+//
+//        public int get(long start, byte[] dest) throws IOException {
+//            int len = (int) Math.max(Math.min(this.length4 * 4 - start, dest.length), 0);
+//            if (len > 0) {
+//                in.seek(start);
+//                IOutils.readFully(skip, dest, 0, len);
+//            }
+//            return len;
+//        }
+//
+//        public InputStream getInputStream() throws IOException {
+//            InputStream in = new BlobInputStream(this);
+////            InputStream skip = new SkipBytesInputStream(in, mask, 4);
+//            return in;
+//        }
+//
+//        public void set(long start, byte[] data) throws IOException {
+//            throw new RuntimeException("Unsupported operation");
+//        }
+//
+//
+//        public void clear() {
+//        }
+//    }
+}
Index: ocean/src/com/imagero/uio/blob/BlobInputStream.java
===================================================================
--- ocean/src/com/imagero/uio/blob/BlobInputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/blob/BlobInputStream.java	(revision 0)
@@ -0,0 +1,86 @@
+package com.imagero.uio.blob;
+
+import com.imagero.uio.blob.Blob;
+import com.imagero.uio.io.UnexpectedEOFException;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+/**
+ * BlobInputStream.
+ * Read data from Blob.
+ *
+ * Data from Blob first filled into internal buffer and then read.
+ *
+ * Date: 23.07.2007
+ *
+ * @author Andrey Kuznetsov
+ */
+public class BlobInputStream extends ByteArrayInputStream {
+    Blob blob;
+
+    long start;
+
+    boolean finished;
+
+    public BlobInputStream(Blob blob) throws IOException {
+        super(new byte[2048]);
+        this.blob = blob;
+        fillBuffer();
+    }
+
+    public int read() {
+        if (finished) {
+            return -1;
+        }
+        if (available() > 0) {
+            return super.read();
+        }
+        return -1;
+    }
+
+    public synchronized long skip(long n) {
+        int k = available();
+        if (k < n) {
+            fillBuffer();
+        }
+        return super.skip(n);
+    }
+
+    public synchronized int available() {
+        int k = super.available();
+        if (k > 0) {
+            return k;
+        } else {
+            fillBuffer();
+            return super.available();
+        }
+    }
+
+    public synchronized int read(byte b[], int off, int len) {
+        if (finished) {
+            return -1;
+        }
+        int k = available();
+        return super.read(b, off, Math.min(k, len));
+    }
+
+    private void fillBuffer() {
+        try {
+            count = blob.get(start, buf);
+        } catch (UnexpectedEOFException ex) {
+            count = (int) ex.getCount();
+        } catch (IOException ex) {
+            finished = true;
+            ex.printStackTrace();
+        }
+        if (count <= 0) {
+            count = 0;
+            pos = 0;
+            finished = true;
+            return;
+        }
+        start += count;
+        pos = 0;
+    }
+}
Index: ocean/src/com/imagero/uio/ByteArray.java
===================================================================
--- ocean/src/com/imagero/uio/ByteArray.java	(revision 0)
+++ ocean/src/com/imagero/uio/ByteArray.java	(revision 0)
@@ -0,0 +1,22 @@
+package com.imagero.uio;
+
+/**
+ * Date: 09.04.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class ByteArray {
+
+    byte[] buffer;
+
+    int position;
+    int bitOffset;
+
+    public static final int[] N_MASK = {0xFF, 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+    public static final int[] K_MASK = {0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF};
+    public static final int[] I_MASK = {0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 0xFF};
+
+    public ByteArray(byte[] buffer) {
+        this.buffer = buffer;
+    }
+}
Index: ocean/src/com/imagero/uio/ByteArrayIO.java
===================================================================
--- ocean/src/com/imagero/uio/ByteArrayIO.java	(revision 0)
+++ ocean/src/com/imagero/uio/ByteArrayIO.java	(revision 0)
@@ -0,0 +1,187 @@
+package com.imagero.uio;
+
+
+/**
+ * ByteArrayIO - this class gives the possibility to read from and write to given ByteArray.
+ * It supports bit offsets and bitwise writing.
+ * It does not extends InputStream or OutputStream, but has similar methods.
+ *
+ * Date: 09.04.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class ByteArrayIO {
+
+    public static int read(ByteArray ba) {
+        return read(ba, 8);
+    }
+
+    /**
+     * read up to 8 bits from given ByteArray
+     * @param ba ByteArray
+     * @param nbits bit count to read
+     * @return int
+     */
+    public static int read(ByteArray ba, final int nbits) {
+        int ret = 0;
+        //nothing to read
+        if (nbits == 0) {
+            return 0;
+        }
+        //too many bits requested
+        if (nbits > 8) {
+            throw new IllegalArgumentException("no more then 8 bits can be read at once");
+        }
+        if(ba.bitOffset == 0 && nbits == 8) {
+            return ba.buffer[ba.position++];
+        }
+        ret = ba.buffer[ba.position] & ByteArray.N_MASK[ba.bitOffset];
+        int rshift = (8 - ba.bitOffset) - nbits;
+        if(rshift > 0) {
+            ret = ret >> rshift;
+        }
+        int bitOffset = ba.bitOffset + nbits;
+        if (bitOffset > 7) {
+            bitOffset -= 8;
+            ba.bitOffset = bitOffset;
+            ba.position++;
+            if (bitOffset > 0) {
+                ret = (ret << bitOffset) | (ba.buffer[ba.position] & ByteArray.N_MASK[ba.bitOffset]);
+            }
+        }
+        return ret;
+    }
+
+    public static int read(ByteArray ba, byte b[]) {
+        return read(ba, b, 0, b.length);
+    }
+
+    /**
+     * Reads data from input stream into an byte array.
+     *
+     * @param b the buffer into which the data is read.
+     * @param off the start offset of the data.
+     * @param len the maximum number of bytes read.
+     * @return the total number of bytes read into the buffer, or -1 if the EOF has been reached.
+     * @exception NullPointerException if supplied byte array is null
+     */
+    public static int read(ByteArray ba, byte b[], int off, int len) {
+        if (len <= 0) {
+            return 0;
+        }
+        int c = read(ba);
+        if (c == -1) {
+            return -1;
+        }
+        b[off] = (byte) c;
+
+        int i = 1;
+        for (; i < len; ++i) {
+            c = read(ba);
+            if (c == -1) {
+                break;
+            }
+            b[off + i] = (byte) c;
+        }
+        return i;
+    }
+
+    /**
+     * Skips some bytes from the input stream.
+     * @param n the number of bytes to be skipped.
+     * @return the actual number of bytes skipped.
+     */
+    public static long skipBytes(ByteArray ba, long n) {
+        int max = (int) Math.min(ba.buffer.length - ba.position, n);
+        ba.position += max;
+        return max;
+    }
+
+    /**
+     * skip some bits
+     * @param n bits to skip
+     * @return number of bits skipped
+     */
+    public static int skipBits(ByteArray ba, int n) {
+        int k = n;
+        int nbits = k % 8;
+        int nbytes = k / 8;
+        int bitOffset = ba.bitOffset + nbits;
+        if (bitOffset > 7) {
+            nbytes++;
+            ba.bitOffset = bitOffset - 8;
+        }
+        ba.position += nbytes;
+        return n;
+    }
+
+    public static void seek(ByteArray ba, int pos) {
+        ba.position = pos;
+    }
+
+    public static void seek(ByteArray ba, int pos, int bitPos) {
+        ba.position = pos + bitPos / 8;
+        ba.bitOffset = bitPos % 8;
+    }
+
+    public static int skipToByteBoundary(ByteArray ba) {
+        if (ba.bitOffset > 0) {
+            int ret = 8 - ba.bitOffset;
+            ba.bitOffset = 0;
+            ba.position++;
+            return ret;
+        }
+        return 0;
+    }
+
+    /**
+     * Writes some bits from the specified int to stream.
+     * @param b int which should be written
+     */
+    public static void write(ByteArray ba, int b) {
+        write(ba, b, 8);
+    }
+
+    /**
+     * Writes up to 8 bits from the specified int to stream.
+     * @param N int which should be written
+     * @param N_BITS bit count to write
+     */
+    public static void write(ByteArray ba, int N, int N_BITS) {
+        if (N_BITS == 0) {
+            return;
+        }
+        if (N_BITS > 8) {
+            throw new IllegalArgumentException("no more then 8 bits can be written at once");
+        }
+        if(ba.bitOffset == 0 && N_BITS == 8) {
+            ba.buffer[ba.position++] = (byte) N;
+        }
+        else {
+            write0(ba, N, N_BITS);
+        }
+    }
+
+    protected final static void write0(ByteArray ba, int N, int N_BITS) {
+        int available = 8 - ba.bitOffset;
+        int a = ba.buffer[ba.position] & 0xFF;
+        int b = a;
+
+        a = ((a >> available) << N_BITS) | (N & ByteArray.K_MASK[N_BITS]);
+        if (N_BITS > available) {
+            a = a >> (N_BITS - available);
+            ba.buffer[ba.position++] = (byte) a;
+            ba.bitOffset = 0;
+            N_BITS -= available;
+            write0(ba, N & ByteArray.N_MASK[N_BITS], N_BITS);
+        } else if (available > N_BITS) {
+            a = a << (available - N_BITS);
+            a |= b & ByteArray.K_MASK[available - N_BITS];
+            ba.buffer[ba.position] = (byte) a;
+            ba.bitOffset += N_BITS;
+        } else {
+            ba.buffer[ba.position++] = (byte) a;
+            ba.bitOffset = 0;
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/Endian.java
===================================================================
--- ocean/src/com/imagero/uio/Endian.java	(revision 0)
+++ ocean/src/com/imagero/uio/Endian.java	(revision 0)
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+ package com.imagero.uio;
+
+/**
+ * Date: 01.03.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public interface Endian {
+    int BIG_ENDIAN = 0x4D4D;
+    int LITTLE_ENDIAN = 0x4949;
+
+    int getByteOrder();
+
+    void setByteOrder(int byteOrder);
+}
Index: ocean/src/com/imagero/uio/FilterDataOutput.java
===================================================================
--- ocean/src/com/imagero/uio/FilterDataOutput.java	(revision 0)
+++ ocean/src/com/imagero/uio/FilterDataOutput.java	(revision 0)
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+ package com.imagero.uio;
+
+import java.io.DataOutput;
+import java.io.IOException;
+
+public class FilterDataOutput implements DataOutput {
+    /**
+     * The underlying output to be filtered.
+     */
+    protected DataOutput out;
+
+    public FilterDataOutput(DataOutput out) {
+        this.out = out;
+    }
+
+    public void write(int b) throws IOException {
+        out.write(b);
+    }
+
+    public void write(byte b[]) throws IOException {
+        write(b, 0, b.length);
+    }
+
+    public void write(byte b[], int off, int len) throws IOException {
+        if ((off | len | (b.length - (len + off)) | (off + len)) < 0)
+            throw new IndexOutOfBoundsException();
+
+        for (int i = 0; i < len; i++) {
+            write(b[off + i]);
+        }
+    }
+
+    public void writeBoolean(boolean v) throws IOException {
+        out.writeBoolean(v);
+    }
+
+    public void writeByte(int v) throws IOException {
+        out.writeByte(v);
+    }
+
+    public void writeShort(int v) throws IOException {
+        out.writeShort(v);
+    }
+
+    public void writeChar(int v) throws IOException {
+        out.writeChar(v);
+    }
+
+    public void writeInt(int v) throws IOException {
+        out.writeInt(v);
+    }
+
+    public void writeLong(long v) throws IOException {
+        out.writeLong(v);
+    }
+
+    public void writeFloat(float v) throws IOException {
+        out.writeFloat(v);
+    }
+
+    public void writeDouble(double v) throws IOException {
+        out.writeDouble(v);
+    }
+
+    public void writeBytes(String s) throws IOException {
+        out.writeBytes(s);
+    }
+
+    public void writeChars(String s) throws IOException {
+        out.writeChars(s);
+    }
+
+    public void writeUTF(String str) throws IOException {
+        out.writeUTF(str);
+    }
+}
Index: ocean/src/com/imagero/uio/impl/RandomAccessFileWrapper.java
===================================================================
--- ocean/src/com/imagero/uio/impl/RandomAccessFileWrapper.java	(revision 0)
+++ ocean/src/com/imagero/uio/impl/RandomAccessFileWrapper.java	(revision 0)
@@ -0,0 +1,253 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.impl;
+
+import com.imagero.uio.RandomAccessIO;
+import com.imagero.uio.RandomAccessInput;
+import com.imagero.uio.RandomAccessOutput;
+import com.imagero.uio.UIOStreamBuilder;
+import com.imagero.uio.bio.BufferedRandomAccessIO;
+import com.imagero.uio.bio.IOCInputStream;
+import com.imagero.uio.bio.IOCOutputStream;
+import com.imagero.uio.bio.IOController;
+import com.imagero.uio.bio.content.Content;
+import com.imagero.uio.bio.content.RandomAccessFileContent;
+import com.imagero.uio.io.IOutils;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.lang.reflect.Method;
+
+/**
+ * Wrap RandomAccessFile in RandomAccessIO<br>
+ * Attention - this class is not buffered.
+ * That means if you make extensive use of writeInt, writeLong, writeChar, ...,
+ * then performance will be pretty poor. Use buffered classes instead.
+ *
+ * @author Andrei Kouznetsov
+ *         Date: 08.11.2003
+ *         Time: 13:04:44
+ */
+public class RandomAccessFileWrapper extends AbstractRandomAccessIO {
+
+    RandomAccessFile in;
+
+    IOController controller;
+
+    long offset;
+    Long length;
+
+    public RandomAccessFileWrapper(RandomAccessFile in, int byteOrder) throws IOException {
+        this.in = in;
+        setByteOrder(byteOrder);
+    }
+
+    public RandomAccessFileWrapper(RandomAccessFile in, long offset, int byteOrder) throws IOException {
+        if (offset < 0 || offset >= in.length()) {
+            throw new IndexOutOfBoundsException();
+        }
+        this.in = in;
+        this.offset = offset;
+        setByteOrder(byteOrder);
+    }
+
+    public RandomAccessFileWrapper(RandomAccessFile in, long offset, long length, int byteOrder) throws IOException {
+        if (offset < 0 || offset >= in.length()) {
+            throw new IndexOutOfBoundsException();
+        }
+        this.in = in;
+        this.offset = offset;
+        this.length = new Long(length);
+        setByteOrder(byteOrder);
+    }
+
+    protected int _read() throws IOException {
+        if (getFilePointer() >= length()) {
+            throw new EOFException();
+        }
+        int i = in.read();
+        if (i < 0) {
+            throw new EOFException();
+        }
+        return i;
+    }
+
+    public void write(int b) throws IOException {
+        in.write(b);
+    }
+
+    public void write(byte b[], int off, int len) throws IOException {
+        int max = len;
+        if (length != null) {
+            max = (int) Math.min(Math.max(length() - getFilePointer(), 0), len);
+        }
+        in.write(b, off, max);
+    }
+
+    public long getFilePointer() throws IOException {
+        if (length == null) {
+            return in.getFilePointer() - offset;
+        }
+        else {
+            return Math.min(length.longValue(), in.getFilePointer() - offset);
+        }
+    }
+
+    public long length() throws IOException {
+        if (length == null) {
+            return in.length() - offset;
+        }
+        else {
+            return Math.min(length.longValue(), in.length() - offset);
+        }
+    }
+
+    public void seek(long pos) throws IOException {
+        long max = pos;
+        if (length != null) {
+            max = Math.min(Math.max(pos, 0), length.longValue());
+        }
+        in.seek(max + offset);
+    }
+
+    public int read() throws IOException {
+        if (getFilePointer() >= length()) {
+            return -1;
+        }
+        return in.read();
+    }
+
+    public long skip(long n) throws IOException {
+        int max = (int) Math.min(Math.max(length() - getFilePointer(), 0), n);
+        return in.skipBytes(max);
+    }
+
+    public void close() throws IOException {
+        IOutils.closeStream(in);
+        in = null;
+    }
+
+    public int read(byte[] b, int off, int len) throws IOException {
+        int max = (int) Math.min(Math.max(length() - getFilePointer(), 0), len);
+        if(max == 0) {
+            return -1;
+        }
+        return in.read(b, off, max);
+    }
+
+    /**
+     * Set length - only possible if length was not set in constructor.
+     * With 1.2 and later this method works as expected,
+     * with 1.1 it can only grow the file, but can not truncate it.
+     *
+     * @param newLength
+     *
+     * @throws java.io.IOException
+     */
+    public void setLength(long newLength) throws IOException {
+        if (length == null) {
+            try {
+                Class aClass = Class.forName("java.io.RandomAccessFile");
+                Method method = aClass.getMethod("setLength", new Class[]{Long.class});
+                method.invoke(in, new Object[]{new Long(newLength + offset)});
+            }
+            catch (Exception ex) {
+                if (newLength > in.length()) {
+                    long pos = getFilePointer();
+                    seek(newLength);
+                    write(0);
+                    seek(pos);
+                }
+            }
+        }
+    }
+
+    public void readFully(byte b[], int off, int len) throws IOException {
+        in.readFully(b, off, len);
+    }
+
+    public RandomAccessIO createIOChild(long offset, long length, int byteOrder, boolean syncPointer) throws IOException {
+        if (controller == null) {
+            Content content = new RandomAccessFileContent(in);
+            controller = new IOController(UIOStreamBuilder.DEFAULT_CHUNK_SIZE, content);
+
+        }
+        BufferedRandomAccessIO rio = new BufferedRandomAccessIO(controller, offset);
+        if(length > 0) {
+            rio.setLength(length);
+        }
+        return rio;
+    }
+
+    public RandomAccessInput createInputChild(long offset, long length, int byteOrder, boolean syncPointer) throws IOException {
+        return createIOChild(offset, length, byteOrder, syncPointer);
+    }
+
+    public RandomAccessOutput createOutputChild(long offset, int byteOrder, boolean syncPointer) throws IOException {
+        return createIOChild(offset, 0, byteOrder, syncPointer);
+    }
+
+    public InputStream createInputStream(long offset) {
+        if(controller == null) {
+            Content bc = new RandomAccessFileContent(in);
+            controller = new IOController(UIOStreamBuilder.DEFAULT_CHUNK_SIZE, bc);
+        }
+        return new IOCInputStream(controller, offset);
+    }
+
+    public long getChildPosition(InputStream child) {
+        if(child instanceof IOCInputStream) {
+            IOCInputStream iocis = (IOCInputStream) child;
+            return iocis.getPosition();
+        }
+        return -1;
+    }
+
+    public void setChildPosition(InputStream child, long position) {
+        if (child instanceof IOCInputStream) {
+            IOCInputStream iocis = (IOCInputStream) child;
+            iocis.seek(position);
+        }
+    }
+
+    public OutputStream createOutputStream(long offset) {
+        if (controller == null) {
+            Content bc = new RandomAccessFileContent(in);
+            controller = new IOController(UIOStreamBuilder.DEFAULT_CHUNK_SIZE, bc);
+        }
+        return new IOCOutputStream(controller, offset);
+    }
+}
Index: ocean/src/com/imagero/uio/impl/TmpRandomAccessFile.java
===================================================================
--- ocean/src/com/imagero/uio/impl/TmpRandomAccessFile.java	(revision 0)
+++ ocean/src/com/imagero/uio/impl/TmpRandomAccessFile.java	(revision 0)
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.impl;
+
+import com.imagero.uio.io.IOutils;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * TmpRandomAccessFile closes itself on finalization and tries to delete file.
+ * @author Andrey Kuznetsov
+ */
+public class TmpRandomAccessFile extends RandomAccessFileX {
+    File f;
+
+    public TmpRandomAccessFile(String name, String mode) throws IOException {
+        this(new File(name), mode);
+    }
+
+    public TmpRandomAccessFile(File file, String mode) throws IOException {
+        super(file, mode);
+        this.f = file;
+    }
+
+    public void close() throws IOException {
+        super.close();
+        f.delete();
+    }
+
+    protected void finalize() throws Throwable {
+        IOutils.closeStream(this);
+        if (f.exists()) {
+            f.delete();
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/impl/AbstractRandomAccessInput.java
===================================================================
--- ocean/src/com/imagero/uio/impl/AbstractRandomAccessInput.java	(revision 0)
+++ ocean/src/com/imagero/uio/impl/AbstractRandomAccessInput.java	(revision 0)
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.impl;
+
+import com.imagero.uio.RandomAccessInput;
+import com.imagero.uio.Transformer;
+import com.imagero.uio.io.UnexpectedEOFException;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @author Andrey Kuznetsov
+ */
+public abstract class AbstractRandomAccessInput extends InputStream implements RandomAccessInput {
+    protected int byteOrder = BIG_ENDIAN;
+
+    public int getByteOrder() {
+        return byteOrder;
+    }
+
+    public void setByteOrder(int byteOrder) {
+        switch (byteOrder) {
+            case RandomAccessInput.BIG_ENDIAN:
+            case RandomAccessInput.LITTLE_ENDIAN:
+                this.byteOrder = byteOrder;
+                break;
+            default:
+                throw new IllegalArgumentException("" + Integer.toHexString(byteOrder));
+        }
+    }
+
+    public boolean isBuffered() {
+        return false;
+    }
+
+    public final int read(byte[] b) throws IOException {
+        return read(b, 0, b.length);
+    }
+
+    public final void readFully(byte b[]) throws IOException {
+        readFully(b, 0, b.length);
+    }
+
+    public int skipBytes(int n) throws IOException {
+        return (int) skip(n);
+    }
+
+    public final boolean readBoolean() throws IOException {
+        return read() != 0;
+    }
+
+    public final byte readByte() throws IOException {
+        return (byte) read();
+    }
+
+    public final int readUnsignedByte() throws IOException {
+        return read();
+    }
+
+    public final short readShort() throws IOException {
+        return readShort(byteOrder);
+    }
+
+    public final int readUnsignedShort() throws IOException {
+        return readUnsignedShort(byteOrder);
+    }
+
+    public final int readUnsignedShort(int byteOrder) throws IOException {
+        byte[] b0 = new byte[2];
+        readFully(b0);
+        return Transformer.byteToShort(b0, 0, byteOrder == BIG_ENDIAN);
+    }
+
+    public final short readShort(int byteOrder) throws IOException {
+        byte[] b0 = new byte[2];
+        readFully(b0);
+        return (short) Transformer.byteToShort(b0, 0, byteOrder == BIG_ENDIAN);
+    }
+
+    public final char readChar() throws IOException {
+        return readChar(byteOrder);
+    }
+
+    public final char readChar(int byteOrder) throws IOException {
+        byte[] b0 = new byte[2];
+        readFully(b0);
+        return (char) Transformer.byteToChar(b0, 0, byteOrder == BIG_ENDIAN);
+    }
+
+    public final int readInt() throws IOException {
+        return readInt(byteOrder);
+    }
+
+    public final int readInt(int byteOrder) throws IOException {
+        byte[] b0 = new byte[4];
+        readFully(b0);
+        return Transformer.byteToInt(b0, 0, byteOrder == BIG_ENDIAN);
+    }
+
+    public long readUnsignedInt() throws IOException {
+        return ((long) readInt()) & 0xFFFFFFFFL;
+    }
+
+    public long readUnsignedInt(int byteOrder) throws IOException {
+        return ((long) readInt(byteOrder)) & 0xFFFFFFFFL;
+    }
+
+    public final long readLong() throws IOException {
+        return readLong(byteOrder);
+    }
+
+    public final long readLong(int byteOrder) throws IOException {
+        byte[] b0 = new byte[8];
+        readFully(b0);
+        return Transformer.byteToLong(b0, 0, byteOrder == BIG_ENDIAN);
+    }
+
+    public final float readFloat() throws IOException {
+        return readFloat(byteOrder);
+    }
+
+    public final float readFloat(int byteOrder) throws IOException {
+        byte[] b0 = new byte[4];
+        readFully(b0);
+        return Transformer.byteToFloat(b0, 0, byteOrder == BIG_ENDIAN);
+    }
+
+    public final double readDouble() throws IOException {
+        return readDouble(byteOrder);
+    }
+
+    public final double readDouble(int byteOrder) throws IOException {
+        byte[] b0 = new byte[8];
+        readFully(b0);
+        return Transformer.byteToDouble(b0, 0, byteOrder == BIG_ENDIAN);
+    }
+
+    public final String readLine() throws IOException {
+        return new String(readByteLine());
+    }
+
+    public final String readUTF() throws IOException {
+        return DataInputStream.readUTF(this);
+    }
+
+    public final byte[] readByteLine() throws IOException {
+        return Transformer.readByteLine(this);
+    }
+
+    public final int readByteLine(byte[] dest) throws IOException {
+        return Transformer.readByteLine(this, dest);
+    }
+
+    public void readFully(byte[] b, int off, int len) throws IOException {
+        int n = 0;
+        while (n < len) {
+            int count = read(b, off + n, len - n);
+            if (count <= 0) {
+                throw new UnexpectedEOFException(n);
+            }
+            n += count;
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/impl/AbstractRandomAccessOutput.java
===================================================================
--- ocean/src/com/imagero/uio/impl/AbstractRandomAccessOutput.java	(revision 0)
+++ ocean/src/com/imagero/uio/impl/AbstractRandomAccessOutput.java	(revision 0)
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.impl;
+
+import com.imagero.uio.RandomAccessOutput;
+import com.imagero.uio.RandomAccessInput;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * @author Andrey Kuznetsov
+ */
+public abstract class AbstractRandomAccessOutput extends OutputStream implements RandomAccessOutput {
+
+    protected int byteOrder = RandomAccessInput.BIG_ENDIAN;
+
+    public final void write(byte b[]) throws IOException {
+        write(b, 0, b.length);
+    }
+
+    public final void writeBoolean(boolean v) throws IOException {
+        write(v ? 1 : 0);
+    }
+
+    public final void writeByte(int v) throws IOException {
+        write(v);
+    }
+
+    public final void writeShort(int v) throws IOException {
+        writeShort(v, byteOrder);
+    }
+
+    public final void writeShort(int v, int byteOrder) throws IOException {
+        byte[] dest = new byte[2];
+        com.imagero.uio.Transformer.shortToByte((short) v, dest, 0, byteOrder == RandomAccessInput.BIG_ENDIAN);
+        write(dest);
+    }
+
+    public final void writeChar(int v) throws IOException {
+        writeChar(v, byteOrder);
+    }
+
+    public final void writeChar(int v, int byteOrder) throws IOException {
+        byte[] dest = new byte[2];
+        com.imagero.uio.Transformer.charToByte((char) v, dest, 0, byteOrder == RandomAccessInput.BIG_ENDIAN);
+        write(dest);
+    }
+
+    public final void writeInt(int v) throws IOException {
+        writeInt(v, byteOrder);
+    }
+
+    public final void writeInt(int v, int byteOrder) throws IOException {
+        byte[] dest = new byte[4];
+        com.imagero.uio.Transformer.intToByte(v, dest, 0, byteOrder == RandomAccessInput.BIG_ENDIAN);
+        write(dest);
+    }
+
+    public final void writeLong(long v) throws IOException {
+        writeLong(v, byteOrder);
+    }
+
+    public final void writeLong(long v, int byteOrder) throws IOException {
+        byte[] dest = new byte[8];
+        com.imagero.uio.Transformer.longToByte(v, dest, 0, byteOrder == RandomAccessInput.BIG_ENDIAN);
+        write(dest);
+    }
+
+    public final void writeFloat(float v) throws IOException {
+        writeFloat(v, byteOrder);
+    }
+
+    public final void writeFloat(float v, int byteOrder) throws IOException {
+        byte[] dest = new byte[4];
+        com.imagero.uio.Transformer.floatToByte(v, dest, 0, byteOrder == RandomAccessInput.BIG_ENDIAN);
+        write(dest);
+    }
+
+    public final void writeDouble(double v) throws IOException {
+        writeDouble(v, byteOrder);
+    }
+
+    public final void writeDouble(double v, int byteOrder) throws IOException {
+        byte[] dest = new byte[8];
+        com.imagero.uio.Transformer.doubleToByte(v, dest, 0, byteOrder == RandomAccessInput.BIG_ENDIAN);
+        write(dest);
+    }
+
+    public final void writeBytes(String s) throws IOException {
+        write(s.getBytes());
+    }
+
+    public final void writeChars(String s) throws IOException {
+        for (int i = 0; i < s.length(); i++) {
+            writeChar(s.charAt(i));
+        }
+    }
+
+    public final void writeUTF(String str) throws IOException {
+        ByteArrayOutputStream out = new ByteArrayOutputStream(str.length());
+        DataOutputStream dataOut = new DataOutputStream(out);
+        dataOut.writeUTF(str);
+        dataOut.flush();
+        dataOut.close();
+        byte[] b = out.toByteArray();
+        write(b);
+    }
+
+    public boolean isBuffered() {
+        return false;
+    }
+}
Index: ocean/src/com/imagero/uio/impl/OffsetRandomAccessFile.java
===================================================================
--- ocean/src/com/imagero/uio/impl/OffsetRandomAccessFile.java	(revision 0)
+++ ocean/src/com/imagero/uio/impl/OffsetRandomAccessFile.java	(revision 0)
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.impl;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * OffsetRandomAccessFile.java
+ * <br>
+ * Usefull for reading EXIF files<br>
+ * However this class has bad performance, bacause the data is unbuffered.
+ *
+ * @author Andrei Kouznetsov
+ */
+public class OffsetRandomAccessFile extends RandomAccessFileX {
+	protected long offset;
+	protected long length;
+
+	public OffsetRandomAccessFile(File file, String mode, long offset) throws IOException {
+		this(file, mode, offset, file.length() - offset);
+	}
+
+	public OffsetRandomAccessFile(File file, String mode, long offset, long length) throws IOException {
+		super(file, mode);
+		this.offset = offset;
+		this.length = length;
+		seek(0);
+	}
+
+	public OffsetRandomAccessFile(String name, String mode, long offset) throws IOException {
+		this(new File(name), mode, offset);
+	}
+
+	public OffsetRandomAccessFile(String name, String mode, long offset, long length) throws IOException {
+		this(new File(name), mode, offset, length);
+	}
+
+	public void seek(long pos) throws IOException {
+		if(pos < 0) {
+			throw new IOException();
+		}
+		super.seek(pos + offset);
+	}
+
+	public int read() throws IOException {
+		if(getFilePointer() >= length) {
+			return -1;
+		}
+		return super.read();
+	}
+
+	public long length() throws IOException {
+		return length;
+	}
+
+	public long getFilePointer() throws IOException {
+		return super.getFilePointer() - offset;
+	}
+
+	public int skip(int n) throws IOException {
+		return skipBytes(n);
+	}
+}
Index: ocean/src/com/imagero/uio/impl/RandomAccessFileX.java
===================================================================
--- ocean/src/com/imagero/uio/impl/RandomAccessFileX.java	(revision 0)
+++ ocean/src/com/imagero/uio/impl/RandomAccessFileX.java	(revision 0)
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.impl;
+
+import com.imagero.uio.io.IOutils;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+/**
+ * Enhanced RandomAccessFile which
+ * a) closes itself on finalization
+ * and
+ * b) knows if it's writable or not
+ *
+ * @author Andrey Kuznetsov
+ */
+public class RandomAccessFileX extends RandomAccessFile {
+
+    String mode;
+
+    public RandomAccessFileX(String name, String mode) throws IOException {
+        super(name, mode);
+        this.mode = mode;
+    }
+
+    public RandomAccessFileX(File file, String mode) throws IOException {
+        super(file, mode);
+        this.mode = mode;
+    }
+
+    protected void finalize() throws Throwable {
+        super.finalize();
+        IOutils.closeStream(this);
+    }
+
+    public String getMode() {
+        return mode;
+    }
+
+    public boolean writable() {
+        return "rw".equals(mode);
+    }
+}
Index: ocean/src/com/imagero/uio/impl/AbstractRandomAccessIO.java
===================================================================
--- ocean/src/com/imagero/uio/impl/AbstractRandomAccessIO.java	(revision 0)
+++ ocean/src/com/imagero/uio/impl/AbstractRandomAccessIO.java	(revision 0)
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.impl;
+
+import com.imagero.uio.RandomAccessIO;
+import com.imagero.uio.Transformer;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+/**
+ * @author Andrey Kuznetsov
+ */
+public abstract class AbstractRandomAccessIO extends AbstractRandomAccessInput implements RandomAccessIO {
+
+    public final void write(byte b[]) throws IOException {
+        write(b, 0, b.length);
+    }
+
+    public final void writeBoolean(boolean v) throws IOException {
+        write(v ? 1 : 0);
+    }
+
+    public final void writeByte(int v) throws IOException {
+        write(v);
+    }
+
+    public final void writeShort(int v) throws IOException {
+        writeShort(v, byteOrder);
+    }
+
+    public final void writeShort(int v, int byteOrder) throws IOException {
+        byte[] dest = new byte[2];
+        Transformer.shortToByte((short) v, dest, 0, byteOrder == BIG_ENDIAN);
+        write(dest);
+    }
+
+    public final void writeChar(int v) throws IOException {
+        writeChar(v, byteOrder);
+    }
+
+    public final void writeChar(int v, int byteOrder) throws IOException {
+        byte[] dest = new byte[2];
+        Transformer.charToByte((char) v, dest, 0, byteOrder == BIG_ENDIAN);
+        write(dest);
+    }
+
+    public final void writeInt(int v) throws IOException {
+        writeInt(v, byteOrder);
+    }
+
+    public final void writeInt(int v, int byteOrder) throws IOException {
+        byte[] dest = new byte[4];
+        Transformer.intToByte(v, dest, 0, byteOrder == BIG_ENDIAN);
+        write(dest);
+    }
+
+    public final void writeLong(long v) throws IOException {
+        writeLong(v, byteOrder);
+    }
+
+    public final void writeLong(long v, int byteOrder) throws IOException {
+        byte[] dest = new byte[8];
+        Transformer.longToByte(v, dest, 0, byteOrder == BIG_ENDIAN);
+        write(dest);
+    }
+
+    public final void writeFloat(float v) throws IOException {
+        writeFloat(v, byteOrder);
+    }
+
+    public final void writeFloat(float v, int byteOrder) throws IOException {
+        byte[] dest = new byte[4];
+        Transformer.floatToByte(v, dest, 0, byteOrder == BIG_ENDIAN);
+        write(dest);
+    }
+
+    public final void writeDouble(double v) throws IOException {
+        writeDouble(v, byteOrder);
+    }
+
+    public final void writeDouble(double v, int byteOrder) throws IOException {
+        byte[] dest = new byte[8];
+        Transformer.doubleToByte(v, dest, 0, byteOrder == BIG_ENDIAN);
+        write(dest);
+    }
+
+    public final void writeBytes(String s) throws IOException {
+        write(s.getBytes());
+    }
+
+    public final void writeChars(String s) throws IOException {
+        for (int i = 0; i < s.length(); i++) {
+            writeChar(s.charAt(i));
+        }
+    }
+
+    public final void writeUTF(String str) throws IOException {
+        ByteArrayOutputStream out = new ByteArrayOutputStream(str.length());
+        DataOutputStream dataOut = new DataOutputStream(out);
+        dataOut.writeUTF(str);
+        dataOut.flush();
+        dataOut.close();
+        byte[] b = out.toByteArray();
+        write(b);
+    }
+
+    public void flush() throws IOException {
+    }
+}
Index: ocean/src/com/imagero/uio/impl/AbstractRandomAccessInput.java
===================================================================
--- ocean/src/com/imagero/uio/impl/AbstractRandomAccessInput.java	(revision 0)
+++ ocean/src/com/imagero/uio/impl/AbstractRandomAccessInput.java	(revision 0)
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.impl;
+
+import com.imagero.uio.RandomAccessInput;
+import com.imagero.uio.Transformer;
+import com.imagero.uio.io.UnexpectedEOFException;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @author Andrey Kuznetsov
+ */
+public abstract class AbstractRandomAccessInput extends InputStream implements RandomAccessInput {
+    protected int byteOrder = BIG_ENDIAN;
+
+    public int getByteOrder() {
+        return byteOrder;
+    }
+
+    public void setByteOrder(int byteOrder) {
+        switch (byteOrder) {
+            case RandomAccessInput.BIG_ENDIAN:
+            case RandomAccessInput.LITTLE_ENDIAN:
+                this.byteOrder = byteOrder;
+                break;
+            default:
+                throw new IllegalArgumentException("" + Integer.toHexString(byteOrder));
+        }
+    }
+
+    public boolean isBuffered() {
+        return false;
+    }
+
+    public final int read(byte[] b) throws IOException {
+        return read(b, 0, b.length);
+    }
+
+    public final void readFully(byte b[]) throws IOException {
+        readFully(b, 0, b.length);
+    }
+
+    public int skipBytes(int n) throws IOException {
+        return (int) skip(n);
+    }
+
+    public final boolean readBoolean() throws IOException {
+        return read() != 0;
+    }
+
+    public final byte readByte() throws IOException {
+        return (byte) read();
+    }
+
+    public final int readUnsignedByte() throws IOException {
+        return read();
+    }
+
+    public final short readShort() throws IOException {
+        return readShort(byteOrder);
+    }
+
+    public final int readUnsignedShort() throws IOException {
+        return readUnsignedShort(byteOrder);
+    }
+
+    public final int readUnsignedShort(int byteOrder) throws IOException {
+        byte[] b0 = new byte[2];
+        readFully(b0);
+        return Transformer.byteToShort(b0, 0, byteOrder == BIG_ENDIAN);
+    }
+
+    public final short readShort(int byteOrder) throws IOException {
+        byte[] b0 = new byte[2];
+        readFully(b0);
+        return (short) Transformer.byteToShort(b0, 0, byteOrder == BIG_ENDIAN);
+    }
+
+    public final char readChar() throws IOException {
+        return readChar(byteOrder);
+    }
+
+    public final char readChar(int byteOrder) throws IOException {
+        byte[] b0 = new byte[2];
+        readFully(b0);
+        return (char) Transformer.byteToChar(b0, 0, byteOrder == BIG_ENDIAN);
+    }
+
+    public final int readInt() throws IOException {
+        return readInt(byteOrder);
+    }
+
+    public final int readInt(int byteOrder) throws IOException {
+        byte[] b0 = new byte[4];
+        readFully(b0);
+        return Transformer.byteToInt(b0, 0, byteOrder == BIG_ENDIAN);
+    }
+
+    public long readUnsignedInt() throws IOException {
+        return ((long) readInt()) & 0xFFFFFFFFL;
+    }
+
+    public long readUnsignedInt(int byteOrder) throws IOException {
+        return ((long) readInt(byteOrder)) & 0xFFFFFFFFL;
+    }
+
+    public final long readLong() throws IOException {
+        return readLong(byteOrder);
+    }
+
+    public final long readLong(int byteOrder) throws IOException {
+        byte[] b0 = new byte[8];
+        readFully(b0);
+        return Transformer.byteToLong(b0, 0, byteOrder == BIG_ENDIAN);
+    }
+
+    public final float readFloat() throws IOException {
+        return readFloat(byteOrder);
+    }
+
+    public final float readFloat(int byteOrder) throws IOException {
+        byte[] b0 = new byte[4];
+        readFully(b0);
+        return Transformer.byteToFloat(b0, 0, byteOrder == BIG_ENDIAN);
+    }
+
+    public final double readDouble() throws IOException {
+        return readDouble(byteOrder);
+    }
+
+    public final double readDouble(int byteOrder) throws IOException {
+        byte[] b0 = new byte[8];
+        readFully(b0);
+        return Transformer.byteToDouble(b0, 0, byteOrder == BIG_ENDIAN);
+    }
+
+    public final String readLine() throws IOException {
+        return new String(readByteLine());
+    }
+
+    public final String readUTF() throws IOException {
+        return DataInputStream.readUTF(this);
+    }
+
+    public final byte[] readByteLine() throws IOException {
+        return Transformer.readByteLine(this);
+    }
+
+    public final int readByteLine(byte[] dest) throws IOException {
+        return Transformer.readByteLine(this, dest);
+    }
+
+    public void readFully(byte[] b, int off, int len) throws IOException {
+        int n = 0;
+        while (n < len) {
+            int count = read(b, off + n, len - n);
+            if (count <= 0) {
+                throw new UnexpectedEOFException(n);
+            }
+            n += count;
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/impl/AbstractRandomAccessIO.java
===================================================================
--- ocean/src/com/imagero/uio/impl/AbstractRandomAccessIO.java	(revision 0)
+++ ocean/src/com/imagero/uio/impl/AbstractRandomAccessIO.java	(revision 0)
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.impl;
+
+import com.imagero.uio.RandomAccessIO;
+import com.imagero.uio.Transformer;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+/**
+ * @author Andrey Kuznetsov
+ */
+public abstract class AbstractRandomAccessIO extends AbstractRandomAccessInput implements RandomAccessIO {
+
+    public final void write(byte b[]) throws IOException {
+        write(b, 0, b.length);
+    }
+
+    public final void writeBoolean(boolean v) throws IOException {
+        write(v ? 1 : 0);
+    }
+
+    public final void writeByte(int v) throws IOException {
+        write(v);
+    }
+
+    public final void writeShort(int v) throws IOException {
+        writeShort(v, byteOrder);
+    }
+
+    public final void writeShort(int v, int byteOrder) throws IOException {
+        byte[] dest = new byte[2];
+        Transformer.shortToByte((short) v, dest, 0, byteOrder == BIG_ENDIAN);
+        write(dest);
+    }
+
+    public final void writeChar(int v) throws IOException {
+        writeChar(v, byteOrder);
+    }
+
+    public final void writeChar(int v, int byteOrder) throws IOException {
+        byte[] dest = new byte[2];
+        Transformer.charToByte((char) v, dest, 0, byteOrder == BIG_ENDIAN);
+        write(dest);
+    }
+
+    public final void writeInt(int v) throws IOException {
+        writeInt(v, byteOrder);
+    }
+
+    public final void writeInt(int v, int byteOrder) throws IOException {
+        byte[] dest = new byte[4];
+        Transformer.intToByte(v, dest, 0, byteOrder == BIG_ENDIAN);
+        write(dest);
+    }
+
+    public final void writeLong(long v) throws IOException {
+        writeLong(v, byteOrder);
+    }
+
+    public final void writeLong(long v, int byteOrder) throws IOException {
+        byte[] dest = new byte[8];
+        Transformer.longToByte(v, dest, 0, byteOrder == BIG_ENDIAN);
+        write(dest);
+    }
+
+    public final void writeFloat(float v) throws IOException {
+        writeFloat(v, byteOrder);
+    }
+
+    public final void writeFloat(float v, int byteOrder) throws IOException {
+        byte[] dest = new byte[4];
+        Transformer.floatToByte(v, dest, 0, byteOrder == BIG_ENDIAN);
+        write(dest);
+    }
+
+    public final void writeDouble(double v) throws IOException {
+        writeDouble(v, byteOrder);
+    }
+
+    public final void writeDouble(double v, int byteOrder) throws IOException {
+        byte[] dest = new byte[8];
+        Transformer.doubleToByte(v, dest, 0, byteOrder == BIG_ENDIAN);
+        write(dest);
+    }
+
+    public final void writeBytes(String s) throws IOException {
+        write(s.getBytes());
+    }
+
+    public final void writeChars(String s) throws IOException {
+        for (int i = 0; i < s.length(); i++) {
+            writeChar(s.charAt(i));
+        }
+    }
+
+    public final void writeUTF(String str) throws IOException {
+        ByteArrayOutputStream out = new ByteArrayOutputStream(str.length());
+        DataOutputStream dataOut = new DataOutputStream(out);
+        dataOut.writeUTF(str);
+        dataOut.flush();
+        dataOut.close();
+        byte[] b = out.toByteArray();
+        write(b);
+    }
+
+    public void flush() throws IOException {
+    }
+}
Index: ocean/src/com/imagero/uio/impl/AbstractRandomAccessOutput.java
===================================================================
--- ocean/src/com/imagero/uio/impl/AbstractRandomAccessOutput.java	(revision 0)
+++ ocean/src/com/imagero/uio/impl/AbstractRandomAccessOutput.java	(revision 0)
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.impl;
+
+import com.imagero.uio.RandomAccessOutput;
+import com.imagero.uio.RandomAccessInput;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * @author Andrey Kuznetsov
+ */
+public abstract class AbstractRandomAccessOutput extends OutputStream implements RandomAccessOutput {
+
+    protected int byteOrder = RandomAccessInput.BIG_ENDIAN;
+
+    public final void write(byte b[]) throws IOException {
+        write(b, 0, b.length);
+    }
+
+    public final void writeBoolean(boolean v) throws IOException {
+        write(v ? 1 : 0);
+    }
+
+    public final void writeByte(int v) throws IOException {
+        write(v);
+    }
+
+    public final void writeShort(int v) throws IOException {
+        writeShort(v, byteOrder);
+    }
+
+    public final void writeShort(int v, int byteOrder) throws IOException {
+        byte[] dest = new byte[2];
+        com.imagero.uio.Transformer.shortToByte((short) v, dest, 0, byteOrder == RandomAccessInput.BIG_ENDIAN);
+        write(dest);
+    }
+
+    public final void writeChar(int v) throws IOException {
+        writeChar(v, byteOrder);
+    }
+
+    public final void writeChar(int v, int byteOrder) throws IOException {
+        byte[] dest = new byte[2];
+        com.imagero.uio.Transformer.charToByte((char) v, dest, 0, byteOrder == RandomAccessInput.BIG_ENDIAN);
+        write(dest);
+    }
+
+    public final void writeInt(int v) throws IOException {
+        writeInt(v, byteOrder);
+    }
+
+    public final void writeInt(int v, int byteOrder) throws IOException {
+        byte[] dest = new byte[4];
+        com.imagero.uio.Transformer.intToByte(v, dest, 0, byteOrder == RandomAccessInput.BIG_ENDIAN);
+        write(dest);
+    }
+
+    public final void writeLong(long v) throws IOException {
+        writeLong(v, byteOrder);
+    }
+
+    public final void writeLong(long v, int byteOrder) throws IOException {
+        byte[] dest = new byte[8];
+        com.imagero.uio.Transformer.longToByte(v, dest, 0, byteOrder == RandomAccessInput.BIG_ENDIAN);
+        write(dest);
+    }
+
+    public final void writeFloat(float v) throws IOException {
+        writeFloat(v, byteOrder);
+    }
+
+    public final void writeFloat(float v, int byteOrder) throws IOException {
+        byte[] dest = new byte[4];
+        com.imagero.uio.Transformer.floatToByte(v, dest, 0, byteOrder == RandomAccessInput.BIG_ENDIAN);
+        write(dest);
+    }
+
+    public final void writeDouble(double v) throws IOException {
+        writeDouble(v, byteOrder);
+    }
+
+    public final void writeDouble(double v, int byteOrder) throws IOException {
+        byte[] dest = new byte[8];
+        com.imagero.uio.Transformer.doubleToByte(v, dest, 0, byteOrder == RandomAccessInput.BIG_ENDIAN);
+        write(dest);
+    }
+
+    public final void writeBytes(String s) throws IOException {
+        write(s.getBytes());
+    }
+
+    public final void writeChars(String s) throws IOException {
+        for (int i = 0; i < s.length(); i++) {
+            writeChar(s.charAt(i));
+        }
+    }
+
+    public final void writeUTF(String str) throws IOException {
+        ByteArrayOutputStream out = new ByteArrayOutputStream(str.length());
+        DataOutputStream dataOut = new DataOutputStream(out);
+        dataOut.writeUTF(str);
+        dataOut.flush();
+        dataOut.close();
+        byte[] b = out.toByteArray();
+        write(b);
+    }
+
+    public boolean isBuffered() {
+        return false;
+    }
+}
Index: ocean/src/com/imagero/uio/impl/OffsetRandomAccessFile.java
===================================================================
--- ocean/src/com/imagero/uio/impl/OffsetRandomAccessFile.java	(revision 0)
+++ ocean/src/com/imagero/uio/impl/OffsetRandomAccessFile.java	(revision 0)
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.impl;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * OffsetRandomAccessFile.java
+ * <br>
+ * Usefull for reading EXIF files<br>
+ * However this class has bad performance, bacause the data is unbuffered.
+ *
+ * @author Andrei Kouznetsov
+ */
+public class OffsetRandomAccessFile extends RandomAccessFileX {
+	protected long offset;
+	protected long length;
+
+	public OffsetRandomAccessFile(File file, String mode, long offset) throws IOException {
+		this(file, mode, offset, file.length() - offset);
+	}
+
+	public OffsetRandomAccessFile(File file, String mode, long offset, long length) throws IOException {
+		super(file, mode);
+		this.offset = offset;
+		this.length = length;
+		seek(0);
+	}
+
+	public OffsetRandomAccessFile(String name, String mode, long offset) throws IOException {
+		this(new File(name), mode, offset);
+	}
+
+	public OffsetRandomAccessFile(String name, String mode, long offset, long length) throws IOException {
+		this(new File(name), mode, offset, length);
+	}
+
+	public void seek(long pos) throws IOException {
+		if(pos < 0) {
+			throw new IOException();
+		}
+		super.seek(pos + offset);
+	}
+
+	public int read() throws IOException {
+		if(getFilePointer() >= length) {
+			return -1;
+		}
+		return super.read();
+	}
+
+	public long length() throws IOException {
+		return length;
+	}
+
+	public long getFilePointer() throws IOException {
+		return super.getFilePointer() - offset;
+	}
+
+	public int skip(int n) throws IOException {
+		return skipBytes(n);
+	}
+}
Index: ocean/src/com/imagero/uio/impl/RandomAccessFileWrapper.java
===================================================================
--- ocean/src/com/imagero/uio/impl/RandomAccessFileWrapper.java	(revision 0)
+++ ocean/src/com/imagero/uio/impl/RandomAccessFileWrapper.java	(revision 0)
@@ -0,0 +1,253 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.impl;
+
+import com.imagero.uio.RandomAccessIO;
+import com.imagero.uio.RandomAccessInput;
+import com.imagero.uio.RandomAccessOutput;
+import com.imagero.uio.UIOStreamBuilder;
+import com.imagero.uio.bio.BufferedRandomAccessIO;
+import com.imagero.uio.bio.IOCInputStream;
+import com.imagero.uio.bio.IOCOutputStream;
+import com.imagero.uio.bio.IOController;
+import com.imagero.uio.bio.content.Content;
+import com.imagero.uio.bio.content.RandomAccessFileContent;
+import com.imagero.uio.io.IOutils;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.lang.reflect.Method;
+
+/**
+ * Wrap RandomAccessFile in RandomAccessIO<br>
+ * Attention - this class is not buffered.
+ * That means if you make extensive use of writeInt, writeLong, writeChar, ...,
+ * then performance will be pretty poor. Use buffered classes instead.
+ *
+ * @author Andrei Kouznetsov
+ *         Date: 08.11.2003
+ *         Time: 13:04:44
+ */
+public class RandomAccessFileWrapper extends AbstractRandomAccessIO {
+
+    RandomAccessFile in;
+
+    IOController controller;
+
+    long offset;
+    Long length;
+
+    public RandomAccessFileWrapper(RandomAccessFile in, int byteOrder) throws IOException {
+        this.in = in;
+        setByteOrder(byteOrder);
+    }
+
+    public RandomAccessFileWrapper(RandomAccessFile in, long offset, int byteOrder) throws IOException {
+        if (offset < 0 || offset >= in.length()) {
+            throw new IndexOutOfBoundsException();
+        }
+        this.in = in;
+        this.offset = offset;
+        setByteOrder(byteOrder);
+    }
+
+    public RandomAccessFileWrapper(RandomAccessFile in, long offset, long length, int byteOrder) throws IOException {
+        if (offset < 0 || offset >= in.length()) {
+            throw new IndexOutOfBoundsException();
+        }
+        this.in = in;
+        this.offset = offset;
+        this.length = new Long(length);
+        setByteOrder(byteOrder);
+    }
+
+    protected int _read() throws IOException {
+        if (getFilePointer() >= length()) {
+            throw new EOFException();
+        }
+        int i = in.read();
+        if (i < 0) {
+            throw new EOFException();
+        }
+        return i;
+    }
+
+    public void write(int b) throws IOException {
+        in.write(b);
+    }
+
+    public void write(byte b[], int off, int len) throws IOException {
+        int max = len;
+        if (length != null) {
+            max = (int) Math.min(Math.max(length() - getFilePointer(), 0), len);
+        }
+        in.write(b, off, max);
+    }
+
+    public long getFilePointer() throws IOException {
+        if (length == null) {
+            return in.getFilePointer() - offset;
+        }
+        else {
+            return Math.min(length.longValue(), in.getFilePointer() - offset);
+        }
+    }
+
+    public long length() throws IOException {
+        if (length == null) {
+            return in.length() - offset;
+        }
+        else {
+            return Math.min(length.longValue(), in.length() - offset);
+        }
+    }
+
+    public void seek(long pos) throws IOException {
+        long max = pos;
+        if (length != null) {
+            max = Math.min(Math.max(pos, 0), length.longValue());
+        }
+        in.seek(max + offset);
+    }
+
+    public int read() throws IOException {
+        if (getFilePointer() >= length()) {
+            return -1;
+        }
+        return in.read();
+    }
+
+    public long skip(long n) throws IOException {
+        int max = (int) Math.min(Math.max(length() - getFilePointer(), 0), n);
+        return in.skipBytes(max);
+    }
+
+    public void close() throws IOException {
+        IOutils.closeStream(in);
+        in = null;
+    }
+
+    public int read(byte[] b, int off, int len) throws IOException {
+        int max = (int) Math.min(Math.max(length() - getFilePointer(), 0), len);
+        if(max == 0) {
+            return -1;
+        }
+        return in.read(b, off, max);
+    }
+
+    /**
+     * Set length - only possible if length was not set in constructor.
+     * With 1.2 and later this method works as expected,
+     * with 1.1 it can only grow the file, but can not truncate it.
+     *
+     * @param newLength
+     *
+     * @throws java.io.IOException
+     */
+    public void setLength(long newLength) throws IOException {
+        if (length == null) {
+            try {
+                Class aClass = Class.forName("java.io.RandomAccessFile");
+                Method method = aClass.getMethod("setLength", new Class[]{Long.class});
+                method.invoke(in, new Object[]{new Long(newLength + offset)});
+            }
+            catch (Exception ex) {
+                if (newLength > in.length()) {
+                    long pos = getFilePointer();
+                    seek(newLength);
+                    write(0);
+                    seek(pos);
+                }
+            }
+        }
+    }
+
+    public void readFully(byte b[], int off, int len) throws IOException {
+        in.readFully(b, off, len);
+    }
+
+    public RandomAccessIO createIOChild(long offset, long length, int byteOrder, boolean syncPointer) throws IOException {
+        if (controller == null) {
+            Content content = new RandomAccessFileContent(in);
+            controller = new IOController(UIOStreamBuilder.DEFAULT_CHUNK_SIZE, content);
+
+        }
+        BufferedRandomAccessIO rio = new BufferedRandomAccessIO(controller, offset);
+        if(length > 0) {
+            rio.setLength(length);
+        }
+        return rio;
+    }
+
+    public RandomAccessInput createInputChild(long offset, long length, int byteOrder, boolean syncPointer) throws IOException {
+        return createIOChild(offset, length, byteOrder, syncPointer);
+    }
+
+    public RandomAccessOutput createOutputChild(long offset, int byteOrder, boolean syncPointer) throws IOException {
+        return createIOChild(offset, 0, byteOrder, syncPointer);
+    }
+
+    public InputStream createInputStream(long offset) {
+        if(controller == null) {
+            Content bc = new RandomAccessFileContent(in);
+            controller = new IOController(UIOStreamBuilder.DEFAULT_CHUNK_SIZE, bc);
+        }
+        return new IOCInputStream(controller, offset);
+    }
+
+    public long getChildPosition(InputStream child) {
+        if(child instanceof IOCInputStream) {
+            IOCInputStream iocis = (IOCInputStream) child;
+            return iocis.getPosition();
+        }
+        return -1;
+    }
+
+    public void setChildPosition(InputStream child, long position) {
+        if (child instanceof IOCInputStream) {
+            IOCInputStream iocis = (IOCInputStream) child;
+            iocis.seek(position);
+        }
+    }
+
+    public OutputStream createOutputStream(long offset) {
+        if (controller == null) {
+            Content bc = new RandomAccessFileContent(in);
+            controller = new IOController(UIOStreamBuilder.DEFAULT_CHUNK_SIZE, bc);
+        }
+        return new IOCOutputStream(controller, offset);
+    }
+}
Index: ocean/src/com/imagero/uio/impl/RandomAccessFileX.java
===================================================================
--- ocean/src/com/imagero/uio/impl/RandomAccessFileX.java	(revision 0)
+++ ocean/src/com/imagero/uio/impl/RandomAccessFileX.java	(revision 0)
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.impl;
+
+import com.imagero.uio.io.IOutils;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+/**
+ * Enhanced RandomAccessFile which
+ * a) closes itself on finalization
+ * and
+ * b) knows if it's writable or not
+ *
+ * @author Andrey Kuznetsov
+ */
+public class RandomAccessFileX extends RandomAccessFile {
+
+    String mode;
+
+    public RandomAccessFileX(String name, String mode) throws IOException {
+        super(name, mode);
+        this.mode = mode;
+    }
+
+    public RandomAccessFileX(File file, String mode) throws IOException {
+        super(file, mode);
+        this.mode = mode;
+    }
+
+    protected void finalize() throws Throwable {
+        super.finalize();
+        IOutils.closeStream(this);
+    }
+
+    public String getMode() {
+        return mode;
+    }
+
+    public boolean writable() {
+        return "rw".equals(mode);
+    }
+}
Index: ocean/src/com/imagero/uio/impl/TmpRandomAccessFile.java
===================================================================
--- ocean/src/com/imagero/uio/impl/TmpRandomAccessFile.java	(revision 0)
+++ ocean/src/com/imagero/uio/impl/TmpRandomAccessFile.java	(revision 0)
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.impl;
+
+import com.imagero.uio.io.IOutils;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * TmpRandomAccessFile closes itself on finalization and tries to delete file.
+ * @author Andrey Kuznetsov
+ */
+public class TmpRandomAccessFile extends RandomAccessFileX {
+    File f;
+
+    public TmpRandomAccessFile(String name, String mode) throws IOException {
+        this(new File(name), mode);
+    }
+
+    public TmpRandomAccessFile(File file, String mode) throws IOException {
+        super(file, mode);
+        this.f = file;
+    }
+
+    public void close() throws IOException {
+        super.close();
+        f.delete();
+    }
+
+    protected void finalize() throws Throwable {
+        IOutils.closeStream(this);
+        if (f.exists()) {
+            f.delete();
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/Input.java
===================================================================
--- ocean/src/com/imagero/uio/Input.java	(revision 0)
+++ ocean/src/com/imagero/uio/Input.java	(revision 0)
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio;
+
+import java.io.IOException;
+
+/**
+ * Unfortunately InputStream is not interface.
+ *
+ * @author Andrei Kouznetsov
+ * Date: 08.11.2003
+ * Time: 19:25:33
+ */
+public interface Input {
+	int read() throws IOException;
+	long skip(long n) throws IOException;
+	int read(byte [] b) throws IOException;
+	int read(byte [] b, int off, int len) throws IOException;
+}
Index: ocean/src/com/imagero/uio/io/RandomAccessOutputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/RandomAccessOutputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/RandomAccessOutputStream.java	(revision 0)
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import com.imagero.uio.RandomAccessIO;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * RandomAccessOutputStream.java
+ * Can be used as bridge between RandomAccessFile and OutputStream.
+ * @author Andrey Kuznetsov
+ */
+public class RandomAccessOutputStream extends OutputStream {
+
+    protected RandomAccessIO ra;
+    protected long pos;
+
+    public RandomAccessOutputStream(RandomAccessIO ra) {
+        this(ra, 0L);
+    }
+
+    public RandomAccessOutputStream(RandomAccessIO ra, long startPos) {
+        this.ra = ra;
+        this.pos = startPos;
+    }
+
+    protected void checkPos() throws IOException {
+        long fp = ra.getFilePointer();
+        if (fp != pos) {
+            ra.seek(pos);
+        }
+    }
+
+    public void write(int b) throws IOException {
+        checkPos();
+        writeImpl(b);
+        pos++;
+    }
+
+    private void writeImpl(int b) throws IOException {
+        ra.write(b);
+    }
+
+    public void write(byte b[]) throws IOException {
+        write(b, 0, b.length);
+    }
+
+    public void write(byte b[], int off, int len) throws IOException {
+        checkPos();
+        ra.write(b, off, len);
+        pos += len;
+    }
+
+    public void close() throws IOException {
+        ra = null;
+    }
+}
Index: ocean/src/com/imagero/uio/io/ASCII85InputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/ASCII85InputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/ASCII85InputStream.java	(revision 0)
@@ -0,0 +1,241 @@
+/*
+ * Copyright (c) 2003, www.pdfbox.org
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of pdfbox; nor the names of its
+ *    contributors may be used to endorse or promote products derived from this
+ *    software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://www.pdfbox.org
+ *
+ */
+package com.imagero.uio.io;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * This class represents an ASCII85 stream.
+ *
+ * @author Ben Litchfield
+ * @version $Revision: 1.2 $
+ */
+public class ASCII85InputStream extends FilterInputStream {
+    private int index;
+    private int n;
+    private boolean eof;
+
+    private byte[] ascii;
+    private byte[] b;
+
+    /**
+     * Constructor.
+     *
+     * @param is The input stream to actually read from.
+     */
+    public ASCII85InputStream(InputStream is) {
+        super(is);
+        ascii = new byte[5];
+        b = new byte[4];
+    }
+
+    /**
+     * This will read the next byte from the stream.
+     *
+     * @return The next byte read from the stream.
+     *
+     * @throws IOException If there is an error reading from the wrapped stream.
+     */
+    public final int read() throws IOException {
+        if (index >= n) {
+            if (eof) {
+                return -1;
+            }
+            index = 0;
+            int k;
+            byte z;
+            do {
+                int zz = (byte) in.read();
+                if (zz == -1) {
+                    eof = true;
+                    return -1;
+                }
+                z = (byte) zz;
+            }
+            while (z == '\n' || z == '\r' || z == ' ');
+
+            if (z == '~' || z == 'x') {
+                eof = true;
+                ascii = b = null;
+                n = 0;
+                return -1;
+            }
+            else if (z == 'z') {
+                b[0] = b[1] = b[2] = b[3] = 0;
+                n = 4;
+            }
+            else {
+                ascii[0] = z; // may be EOF here....
+                for (k = 1; k < 5; ++k) {
+                    do {
+                        int zz = (byte) in.read();
+                        if (zz == -1) {
+                            eof = true;
+                            return -1;
+                        }
+                        z = (byte) zz;
+                    }
+                    while (z == '\n' || z == '\r' || z == ' ');
+                    ascii[k] = z;
+                    if (z == '~' || z == 'x') {
+                        break;
+                    }
+                }
+                n = k - 1;
+                if (n == 0) {
+                    eof = true;
+                    ascii = null;
+                    b = null;
+                    return -1;
+                }
+                if (k < 5) {
+                    for (++k; k < 5; ++k) {
+                        ascii[k] = 0x21;
+                    }
+                    eof = true;
+                }
+                // decode stream
+                long t = 0;
+                for (k = 0; k < 5; ++k) {
+                    z = (byte) (ascii[k] - 0x21);
+                    if (z < 0 || z > 93) {
+                        n = 0;
+                        eof = true;
+                        ascii = null;
+                        b = null;
+                        throw new IOException("Invalid data in Ascii85 stream");
+                    }
+                    t = (t * 85L) + z;
+                }
+                for (k = 3; k >= 0; --k) {
+                    b[k] = (byte) (t & 0xFFL);
+                    t >>>= 8;
+                }
+            }
+        }
+        return b[index++] & 0xFF;
+    }
+
+    /**
+     * This will read a chunk of data.
+     *
+     * @param data The buffer to write data to.
+     * @param offset The offset into the data stream.
+     * @param len The number of byte to attempt to read.
+     *
+     * @return The number of bytes actually read.
+     *
+     * @throws IOException If there is an error reading data from the underlying stream.
+     */
+    public final int read(byte[] data, int offset, int len) throws IOException {
+        if (eof && index >= n) {
+            return -1;
+        }
+        for (int i = 0; i < len; i++) {
+            if (index < n) {
+                data[i + offset] = b[index++];
+            }
+            else {
+                int t = read();
+                if (t == -1) {
+                    return i;
+                }
+                data[i + offset] = (byte) t;
+            }
+        }
+        return len;
+    }
+
+    /**
+     * This will close the underlying stream and release any resources.
+     *
+     * @throws IOException If there is an error closing the underlying stream.
+     */
+    public void close() throws IOException {
+        ascii = null;
+        eof = true;
+        b = null;
+        super.close();
+    }
+
+    /**
+     * non supported interface methods.
+     *
+     * @return False always.
+     */
+    public boolean markSupported() {
+        return false;
+    }
+
+    /**
+     * Unsupported.
+     *
+     * @param nValue ignored.
+     *
+     * @return Always zero.
+     */
+    public long skip(long nValue) {
+        return 0;
+    }
+
+    /**
+     * Unsupported.
+     *
+     * @return Always zero.
+     */
+    public int available() {
+        if (eof) {
+            return 0;
+        }
+        else {
+            return 1;
+        }
+    }
+
+    /**
+     * Unsupported.
+     *
+     * @param readlimit ignored.
+     */
+    public void mark(int readlimit) {
+    }
+
+    /**
+     * Unsupported.
+     *
+     * @throws IOException telling that this is an unsupported action.
+     */
+    public void reset() throws IOException {
+        throw new IOException("Reset is not supported");
+    }
+}
Index: ocean/src/com/imagero/uio/io/RLE4InputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/RLE4InputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/RLE4InputStream.java	(revision 0)
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import java.io.IOException;
+
+/**
+ * PackBits decoder
+ *
+ * @author Andrei Kouznetsov
+ */
+public class RLE4InputStream extends RLEInputStream {
+
+    byte[] value = new byte[2];
+
+    BitInputStream bin;
+
+    byte[] buffer = new byte[128];
+    int bufferStart;
+    int bufferLength;
+
+    ByteArrayOutputStreamExt bout;
+    BitOutputStream bitOut;
+
+    public RLE4InputStream(BitInputStream in) {
+        super(in);
+        bin = in;
+        bin.setBitsToRead(4);
+        bout = new ByteArrayOutputStreamExt();
+        bitOut = new BitOutputStream(bout);
+    }
+
+    public int read() throws IOException {
+        if (bufferStart >= bufferLength) {
+            fillBuffer();
+        }
+        if (bufferStart >= bufferLength) {
+            return -1;
+        }
+        return buffer[bufferStart++] & 0xFF;
+    }
+
+    private void fillBuffer() throws IOException {
+        if (bout.size() == 0) {
+            fillBufferImpl();
+        }
+        bufferStart = 0;
+        bufferLength = bout.drain(buffer);
+    }
+
+    private void fillBufferImpl() throws IOException {
+        int len = bin.read(8);
+        if (len == 0) {
+            int value = bin.read(8);
+            switch (value) {
+                case 0:
+                    throw new EndOfLineException();
+                case 1:
+                    finished = true;
+                    throw new EndOfBitmapException();
+                case 2:
+                    int x = bin.read(8);
+                    int y = bin.read(8);
+                    throw new DeltaRecordException(x, y);
+                default:
+                    int skipCount = 0;
+                    if ((value & 3) != 0) {
+                        skipCount = (value + 3) / 4 * 4 - value;
+                    }
+                    for (int i = 0; i < value; i++) {
+                        bitOut.write(bin.read(4), 4);
+                    }
+                    for (int i = 0; i < skipCount; i++) {
+                        /*int ignored = */bin.read(4);
+                    }
+            }
+        }
+        else {
+            value[0] = (byte) bin.read(4);
+            value[1] = (byte) bin.read(4);
+
+            for (int i = 0; i < len; i++) {
+                bitOut.write(value[i & 1], 4);
+            }
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/io/BitDataOutputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/BitDataOutputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/BitDataOutputStream.java	(revision 0)
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrei Kouznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import com.imagero.uio.FilterDataOutput;
+
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * adds ability to write streams bitwise
+ * @author Andrey Kuznetsov
+ */
+public class BitDataOutputStream extends FilterDataOutput {
+
+    protected static final int[] mask = new int[64];
+
+    static {
+        for (int i = 0; i < mask.length; i++) {
+            mask[i] =  (1 << (i + 1)) - 1;
+        }
+    }
+
+    protected int bitbuf;
+    protected int vbits;
+
+    private int bitsToWrite = 8;
+
+    protected byte [] flipTable = BitInputStream.getFlipTable();
+
+    protected boolean invertBitOrder;
+
+    protected int fillByte = 0;
+
+    public BitDataOutputStream(DataOutput out) {
+        super(out);
+    }
+
+    public int getBitsToWrite() {
+        return bitsToWrite;
+    }
+
+    /**
+     * set how much bits should be written to stream every write() call
+     * @param bitsToWrite
+     */
+    public void setBitsToWrite(int bitsToWrite) {
+        this.bitsToWrite = bitsToWrite;
+    }
+
+    public boolean isInvertBitOrder() {
+        return invertBitOrder;
+    }
+
+    public void setInvertBitOrder(boolean invertBitOrder) {
+        this.invertBitOrder = invertBitOrder;
+    }
+
+    /**
+     * Writes some bits from the specified int to stream.
+     * @param b int which should be written
+     * @throws IOException if an I/O error occurs
+     * @see #setBitsToWrite
+     * @see #getBitsToWrite
+     */
+    public void write(int b) throws IOException {
+        write(b, bitsToWrite);
+    }
+
+    /**
+     * Writes some bits from the specified int to stream.
+     * @param b int which should be written
+     * @param nbits bit count to write
+     * @throws IOException if an I/O error occurs
+     */
+    public void write(int b, int nbits) throws IOException {
+        if (nbits == 0) {
+            return;
+        }
+        final int k = b & mask[nbits];
+        bitbuf = (bitbuf << nbits) | k;
+        vbits += nbits;
+
+        write8();
+    }
+
+    protected void write8() throws IOException {
+        while (vbits > 8) {
+            int c = (int) (bitbuf << (32 - vbits) >>> 24);
+            vbits -= 8;
+            if(invertBitOrder) {
+                c = flipTable[c] & 0xFF;
+            }
+            out.write(c);
+        }
+    }
+
+    /**
+     * get fill byte used to adjust stream to byte boundary.
+     * @return int
+     */
+    public int getFillByte() {
+        return fillByte;
+    }
+
+    /**
+     * set fill byte used to adjust stream to byte boundary
+     * @param fillByte int
+     */
+    public void setFillByte(int fillByte) {
+        this.fillByte = fillByte & 0xFF;
+    }
+
+    /**
+     * writes bits from buffer to output stream
+     * @throws IOException if I/O error occurs
+     */
+    public void flush() throws IOException {
+        write8();   //rather not needed, just to ensure
+        if(vbits > 0) {
+            write(fillByte, 8);
+        }
+        vbits = 0;
+        bitbuf = 0;
+    }
+}
Index: ocean/src/com/imagero/uio/io/RLE8InputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/RLE8InputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/RLE8InputStream.java	(revision 0)
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * PackBits decoder
+ *
+ * @author Andrei Kouznetsov
+ */
+public class RLE8InputStream extends RLEInputStream {
+
+    int numSamples, value;
+    boolean copyLiter;
+    boolean ignoreByte;
+
+    public RLE8InputStream(InputStream in) {
+        super(in);
+    }
+
+    public int read() throws IOException {
+        if (numSamples == 0) {
+            if (ignoreByte) {
+                ignoreByte = false;
+                /*int ignored = */in.read();
+            }
+            int len = in.read();
+            if (len == 0) {
+                value = in.read();
+                switch (value) {
+                    case 0:
+                        throw new EndOfLineException();
+                    case 1:
+                        finished = true;
+                        throw new EndOfBitmapException();
+                    case 2:
+                        int x = in.read();
+                        int y = in.read();
+                        throw new DeltaRecordException(x, y);
+                    default:
+                        copyLiter = true;
+                        numSamples = value;
+                        if ((numSamples & 1) != 0) {
+                            ignoreByte = true;
+                        }
+                }
+            }
+            else {
+                numSamples = len;
+                copyLiter = false;
+                value = in.read();
+            }
+        }
+        numSamples--;
+        if (copyLiter) {
+            return in.read();
+        }
+        else {
+            return value;
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/io/TIFFStripInputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/TIFFStripInputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/TIFFStripInputStream.java	(revision 0)
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import com.imagero.uio.RandomAccessIO;
+
+import java.io.IOException;
+
+/**
+ * InputStream over all strips
+ * @author Andrei Kouznetsov
+ */
+public class TIFFStripInputStream extends RandomAccessInputStream {
+
+	int[] stripOffsets, stripByteCount;
+
+	int currentStrip = -1;
+	long stripLimit;
+	int markStrip;
+
+	public TIFFStripInputStream(RandomAccessIO ra, int[] stripByteCount, int[] stripOffsets) {
+		super(ra);
+		this.stripByteCount = stripByteCount;
+		this.stripOffsets = stripOffsets;
+	}
+
+	public TIFFStripInputStream(RandomAccessIO ra, long startPos, int[] stripByteCount, int[] stripOffsets) {
+		super(ra, startPos);
+		this.stripByteCount = stripByteCount;
+		this.stripOffsets = stripOffsets;
+	}
+
+	protected void checkPos() throws IOException {
+		if(pos > stripLimit || currentStrip == -1) {
+//			Sys.out.println("currentStrip:" + currentStrip);
+			if(currentStrip >= stripOffsets.length) {
+				throw new IOException();
+			}
+			currentStrip++;
+			stripLimit = stripOffsets[currentStrip] + stripByteCount[currentStrip];
+			pos = stripOffsets[currentStrip];
+
+//			File ft = new File(TiffReader.workDir + "tables" + currentStrip + ".jpg");
+//			FileOutputStream fout = new FileOutputStream(ft);
+//			in.seek(pos);
+//			byte [] b = new byte[256];
+//			for(int i = 0; i < stripByteCount[currentStrip];) {
+//				int r = in.read(b);
+//				if(r < 0) {
+//					break;
+//				}
+//				fout.write(b, 0, r);
+//				i =+ r;
+//			}
+//			fout.close();
+		}
+		super.checkPos();
+	}
+
+	synchronized public int read(byte[] b, int off, int len) throws IOException {
+		return super.read(b, off, Math.min(len, (int)(stripLimit - pos)));
+	}
+
+	public void mark(int i) {
+		super.mark(i);
+		markStrip = currentStrip;
+	}
+
+	public void reset() throws IOException {
+		super.reset();
+		currentStrip = markStrip;
+	}
+
+	public long skip(long l) throws IOException {
+		int lsi = stripOffsets.length - 1;
+		long limit = stripOffsets[lsi] + stripByteCount[lsi];
+		long remaining = l;
+		while(remaining > 0) {
+			checkPos();
+			long cs = Math.min(remaining, stripLimit - pos);
+			if(cs > limit - pos) {
+				cs = limit - pos;
+				remaining -= cs;
+				pos += cs;
+				break;
+			}
+			remaining -= cs;
+			pos += cs;
+		}
+		return l - remaining;
+	}
+
+	public int available() {
+		try {
+			return super.available();
+		}
+		catch(IOException e) {
+			e.printStackTrace();
+		}
+		return 0;
+	}
+
+	public long getPos() {
+		long res = 0;
+		for(int i = 0; i < currentStrip; i++) {
+			res += stripByteCount[i];
+		}
+		res += stripByteCount[currentStrip]
+			- (stripByteCount[currentStrip] + stripOffsets[currentStrip] - pos);
+		return res;
+	}
+}
Index: ocean/src/com/imagero/uio/io/SkipBytesInputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/SkipBytesInputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/SkipBytesInputStream.java	(revision 0)
@@ -0,0 +1,68 @@
+package com.imagero.uio.io;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Date: 30.07.2007
+ *
+ * @author Andrey Kuznetsov
+ */
+public class SkipBytesInputStream extends FilterInputStream {
+
+    static long[] msk = createMask();
+
+    private static long[] createMask() {
+        long[] m = new long[64];
+        m[0] = 1;
+        for (int i = 1; i < m.length; i++) {
+            m[i] = m[i - 1] << 1;
+        }
+        return m;
+    }
+
+    public SkipBytesInputStream(InputStream in, long mask, int mod) {
+        super(in);
+        this.mask = mask;
+        this.mod = mod;
+    }
+
+    int fp;
+    long mask;
+    int mod;
+
+    public int read() throws IOException {
+        if (mask == 0) {
+            return super.read();
+        } else {
+            int a = in.read();
+            long k = msk[(fp++ % mod)] & mask;
+            if (k != 0) {
+                return a;
+            } else {
+                return read();
+            }
+        }
+    }
+
+    public int read(byte b[], int off, int len) throws IOException {
+        int k = in.read(b, off, len);
+        return read0(b, off, k);
+    }
+
+    private int read0(byte[] b, int off, int len) {
+        if (mask == 0) {
+            return len;
+        }
+        int len0 = 0;
+        int p = off;
+        for (int i = 0; i < len; i++) {
+            if ((msk[(fp++ % mod)] & mask) != 0) {
+                b[p++] = b[i];
+                len0++;
+            }
+        }
+        return len0;
+    }
+}
Index: ocean/src/com/imagero/uio/io/MultiByteArrayOutputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/MultiByteArrayOutputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/MultiByteArrayOutputStream.java	(revision 0)
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+
+import com.imagero.uio.Sys;
+
+import java.io.OutputStream;
+import java.util.Vector;
+
+/**
+ * MultiByteArrayOutputStream.java
+ * <br>
+ * It's like ByteArrayOutputStream, but with multiple arrays <br>
+ * Array size is defined through <code>sizeX</code>;
+ *
+ * @author Kouznetsov Andrei
+ */
+public class MultiByteArrayOutputStream extends OutputStream {
+
+    Vector v;
+    int totalCount;
+
+    int sizeX = 1024;
+
+    protected byte buf[];
+
+    protected int pos;
+
+
+    public MultiByteArrayOutputStream() {
+        this(1024);
+    }
+
+    public MultiByteArrayOutputStream(int sizeX) {
+        this.sizeX = sizeX;
+        this.v = new Vector();
+        nextArray();
+    }
+
+    protected void nextArray() {
+        byte[] b = new byte[this.sizeX];
+        v.addElement(b);
+        this.buf = b;
+        this.pos = 0;
+    }
+
+    public synchronized void write(byte b[], int off, int len) {
+        for(int i = off; i < len; i++) {
+            write(b[off + i]);
+        }
+    }
+
+    public synchronized void write(int b) {
+        if(pos == sizeX) {
+            nextArray();
+        }
+        totalCount++;
+        buf[pos++] = (byte) (b & 0xFF);
+    }
+
+    public static void printHex(int value) {
+        value = value & 0xFF;
+        String s = Integer.toHexString(value);
+        if(s.length() == 1) {
+            Sys.out.print("0");
+        }
+        Sys.out.print(s);
+        Sys.out.print(" ");
+    }
+
+    public void reset() {
+        totalCount = 0;
+        v = new Vector();
+        nextArray();
+    }
+
+    public Vector getVector() {
+
+        int lastIndex = this.v.size() - 1;
+        byte[] b = (byte[]) this.v.elementAt(lastIndex);
+        byte[] b2 = new byte[pos];
+        System.arraycopy(b, 0, b2, 0, pos);
+        this.v.setElementAt(b2, lastIndex);
+
+        return this.v;
+    }
+
+    public void flush() {
+    }
+
+    public void close() {
+    }
+
+    public int length() {
+        return (this.v.size() - 1) * this.sizeX + this.pos;
+    }
+}
Index: ocean/src/com/imagero/uio/io/BitInputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/BitInputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/BitInputStream.java	(revision 0)
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * adds ability to read streams bitewise and also to read predefined amount of bits every read() call
+ * @author Andrey Kuznetsov
+ */
+public class BitInputStream extends FilterInputStream {
+
+    int vbits = 0;
+    int bitbuf = 0;
+
+    int markBitbuf;
+    int markVbits;
+
+    private int bitsToRead = 8;
+    boolean invertBitOrder;
+
+    public BitInputStream(InputStream in) {
+        super(in);
+    }
+
+    /**
+     * how much bits is read every read() call (default - 8)
+     * @return
+     */
+    public int getBitsToRead() {
+        return bitsToRead;
+    }
+
+    /**
+     * set how much bits is read every read() call (max 8)
+     * @param bitsToRead
+     */
+    public void setBitsToRead(int bitsToRead) {
+        if(bitsToRead > 32) {
+            throw new IllegalArgumentException("" + bitsToRead);
+        }
+        this.bitsToRead = bitsToRead;
+    }
+
+    public boolean isInvertBitOrder() {
+        return invertBitOrder;
+    }
+
+    public void setInvertBitOrder(boolean invertBitOrder) {
+        this.invertBitOrder = invertBitOrder;
+        if(invertBitOrder) {
+            createFlipTable();
+        }
+    }
+
+    public int read() throws IOException {
+        return read(bitsToRead);
+    }
+
+    public int read(int nbits) throws IOException {
+        int ret;
+        //nothing to read
+        if (nbits == 0) {
+            return 0;
+        }
+        //too many bits requested
+        if(nbits > 32) {
+            throw new IllegalArgumentException("only 32 bit can be read at once");
+        }
+        if (nbits > 24) {
+            int nbits0 = nbits / 2;
+            int nbits1 = nbits - nbits0;
+            return (read(nbits0) << nbits1) | read(nbits1);
+        }
+        //not anough bits in buffer
+        if (nbits > vbits) {
+            fillBuffer(nbits);
+        }
+        //buffer still empty => we are reached EOF
+        if (vbits == 0) {
+            return -1;
+        }
+        ret = bitbuf << (32 - vbits) >>> (32 - nbits);
+        vbits -= nbits;
+
+        if(vbits < 0) {
+            vbits = 0;
+        }
+
+        return ret;
+    }
+
+    public int read(byte b[]) throws IOException {
+        return read(b, 0, b.length);
+    }
+
+    /**
+     * Reads data from input stream into an byte array.
+     *
+     * @param b the buffer into which the data is read.
+     * @param off the start offset of the data.
+     * @param len the maximum number of bytes read.
+     * @return the total number of bytes read into the buffer, or -1 if the EOF has been reached.
+     * @exception IOException if an I/O error occurs.
+     * @exception NullPointerException if supplied byte array is null
+     */
+    public int read(byte b[], int off, int len) throws IOException {
+        if (len <= 0) {
+            return 0;
+        }
+        int c = read();
+        if (c == -1) {
+            return -1;
+        }
+        b[off] = (byte) c;
+
+        int i = 1;
+        for (; i < len; ++i) {
+            c = read();
+            if (c == -1) {
+                break;
+            }
+            b[off + i] = (byte) c;
+        }
+        return i;
+    }
+
+    /**
+     * empties bit buffer.
+     */
+    public void resetBuffer() {
+        vbits = 0;
+        bitbuf = 0;
+    }
+
+    /**
+     * Skips some bytes from the input stream.
+     * If bit buffer is not empty, n - (vbits + 8) / 8 bytes skipped,
+     * then buffer is resetted and filled with same amount of bits as it has before skipping.
+     * @param n the number of bytes to be skipped.
+     * @return the actual number of bytes skipped.
+     * @exception IOException if an I/O error occurs.
+     */
+    public long skip(long n) throws IOException {
+        if (vbits == 0) {
+            return in.skip(n);
+        }
+        else {
+            int b = (vbits + 7) / 8;
+            in.skip(n - b);
+            int vbits = this.vbits;
+            resetBuffer();
+            fillBuffer(vbits);
+            return n;
+        }
+    }
+
+    /**
+     *
+     * @param n bits to skip
+     * @return number of bits skipped
+     */
+    public int skipBits(int n) throws IOException {
+        int k = n;
+        int nbits = k % 8;
+        read(nbits);
+        k -= nbits;
+        while(k > 0) {
+            try {
+                read(8);
+                k -= 8;
+            }
+            catch(IOException ex) {
+                break;
+            }
+        }
+        return n;
+    }
+
+    public int skipToByteBoundary() throws IOException {
+        int nbits = vbits % 8;
+        read(nbits);
+        return nbits;
+    }
+
+    private void fillBuffer(int nbits) throws IOException {
+        int c;
+        while (vbits < nbits) {
+            c = in.read();
+            if (c == -1) {
+                break;
+            }
+            if(invertBitOrder) {
+                c = flipTable[c] & 0xFF;
+            }
+            bitbuf = (bitbuf << 8) + (c & 0xFF);
+            vbits += 8;
+        }
+    }
+
+    public int getBitOffset() {
+        return 7 - (vbits % 8);
+    }
+
+    public synchronized void mark(int readlimit) {
+        in.mark(readlimit);
+        markBitbuf = bitbuf;
+        markVbits = vbits;
+    }
+
+    public synchronized void reset() throws IOException {
+        in.reset();
+        bitbuf = markBitbuf;
+        vbits = markVbits;
+    }
+
+    static private byte[] flipTable;
+
+    static byte [] getFlipTable() {
+        if(flipTable == null) {
+            createFlipTable();
+        }
+        return flipTable;
+    }
+
+    static private void createFlipTable() {
+        flipTable = new byte[256];
+        for (int i = 0; i < flipTable.length; i++) {
+            int b = 0;
+            for (int j = 0; j < 8; j++) {
+                int k = (i >> j) & 1;
+                b = (b << 1) | k;
+            }
+            flipTable[i] = (byte) b;
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/io/BitOutputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/BitOutputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/BitOutputStream.java	(revision 0)
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrei Kouznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * adds ability to write streams bitewise
+ * @author Andrey Kuznetsov
+ */
+public class BitOutputStream extends FilterOutputStream {
+
+    protected static final int[] mask = new int[32];
+
+    static {
+        for (int i = 0; i < mask.length; i++) {
+            mask[i] =  (1 << (i + 1)) - 1;
+        }
+    }
+
+    protected int bitbuf;
+    protected int vbits;
+
+    private int bitsToWrite = 8;
+
+    protected byte [] flipTable = BitInputStream.getFlipTable();
+
+    protected boolean invertBitOrder;
+
+    protected int fillByte = 0;
+
+    public BitOutputStream(OutputStream out) {
+        super(out);
+    }
+
+    public int getBitsToWrite() {
+        return bitsToWrite;
+    }
+
+    /**
+     * set how much bits should be written to stream every write() call
+     * @param bitsToWrite
+     */
+    public void setBitsToWrite(int bitsToWrite) {
+        this.bitsToWrite = bitsToWrite;
+    }
+
+    public boolean isInvertBitOrder() {
+        return invertBitOrder;
+    }
+
+    public void setInvertBitOrder(boolean invertBitOrder) {
+        this.invertBitOrder = invertBitOrder;
+    }
+
+    /**
+     * Writes some bits from the specified int to stream.
+     * @param b int which should be written
+     * @throws IOException if an I/O error occurs
+     * @see #setBitsToWrite
+     * @see #getBitsToWrite
+     */
+    public void write(int b) throws IOException {
+        write(b, bitsToWrite);
+    }
+
+    /**
+     * Writes some bits from the specified int to stream.
+     * @param b int which should be written
+     * @param nbits bit count to write
+     * @throws IOException if an I/O error occurs
+     */
+    public void write(int b, int nbits) throws IOException {
+        if (nbits == 0) {
+            return;
+        }
+        final int k = b & mask[nbits];
+        bitbuf = (bitbuf << nbits) | k;
+        vbits += nbits;
+
+        write8();
+    }
+
+    protected void write8() throws IOException {
+        while (vbits > 8) {
+            int c = (int) (bitbuf << (32 - vbits) >>> 24);
+            vbits -= 8;
+            if(invertBitOrder) {
+                c = flipTable[c] & 0xFF;
+            }
+            out.write(c);
+        }
+    }
+
+    /**
+     * get fill byte used to adjust stream to byte boundary.
+     * @return int
+     */
+    public int getFillByte() {
+        return fillByte;
+    }
+
+    /**
+     * set fill byte used to adjust stream to byte boundary
+     * @param fillByte int
+     */
+    public void setFillByte(int fillByte) {
+        this.fillByte = fillByte & 0xFF;
+    }
+
+    /**
+     * writes bits from buffer to output stream
+     * @throws IOException if I/O error occurs
+     */
+    public void flush() throws IOException {
+        write8();   //rather not needed, just to ensure
+        if(vbits > 0) {
+            write(fillByte, 8);
+        }
+        vbits = 0;
+        bitbuf = 0;
+        out.flush();
+    }
+}
Index: ocean/src/com/imagero/uio/io/UnexpectedEOFException.java
===================================================================
--- ocean/src/com/imagero/uio/io/UnexpectedEOFException.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/UnexpectedEOFException.java	(revision 0)
@@ -0,0 +1,26 @@
+package com.imagero.uio.io;
+
+import java.io.EOFException;
+
+/**
+ * If EOFException is thrown during readFully() it is still useful to know how much bytes were read.
+ * Date: 22.12.2007
+ *
+ * @author Andrey Kuznetsov
+ */
+public class UnexpectedEOFException extends EOFException {
+    private long count;
+
+    public UnexpectedEOFException(long count) {
+        this("EOF", count);
+    }
+
+    public UnexpectedEOFException(String s, long count) {
+        super(s);
+        this.count = count;
+    }
+
+    public long getCount() {
+        return count;
+    }
+}
Index: ocean/src/com/imagero/uio/io/RLEInputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/RLEInputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/RLEInputStream.java	(revision 0)
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @author Andrey Kuznetsov
+ */
+public abstract class RLEInputStream extends FilterInputStream {
+    boolean finished;
+
+    public RLEInputStream(InputStream in) {
+        super(in);
+    }
+
+    public int available() throws IOException {
+        if (finished) {
+            return 0;
+        }
+        return 1;
+    }
+
+    public void close() throws IOException {
+    }
+
+    public boolean markSupported() {
+        return false;
+    }
+
+    public synchronized void mark(int readlimit) {
+    }
+
+    public synchronized void reset() throws IOException {
+        throw new IOException("mark/reset not supported");
+    }
+
+    public int read(byte b[]) throws IOException {
+        return read(b, 0, b.length);
+    }
+
+    public int read(byte b[], int off, int len) throws IOException {
+        int i = off;
+
+        try {
+            for (; i < off + len; i++) {
+                int a = read();
+                if (a == -1) {
+                    i--;
+                    break;
+                }
+                b[i] = (byte) a;
+            }
+        }
+        catch (EndOfLineException ex) {
+            //ignore
+        }
+        catch(EndOfBitmapException ex) {
+            //ignore
+        }
+        catch(IOException ex) {
+            ex.printStackTrace();
+        }
+        return i - off;
+    }
+
+    public abstract int read() throws IOException;
+
+    public static class EndOfLineException extends IOException {
+        public EndOfLineException() {
+            super("EndOfLineException");
+        }
+    }
+
+    public static class EndOfBitmapException extends IOException {
+        public EndOfBitmapException() {
+            super("EndOfBitmapException");
+        }
+    }
+
+    public static class DeltaRecordException extends IOException {
+        public final int dx;
+        public final int dy;
+
+        public DeltaRecordException(int dx, int dy) {
+            this.dx = dx;
+            this.dy = dy;
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/io/App13InputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/App13InputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/App13InputStream.java	(revision 0)
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Read one or more App13 block(s)
+ * @author Andrey Kuznetsov
+ */
+public class App13InputStream extends FilterInputStream implements JpegConstants {
+
+    private static final byte[] ID8 = "8BIM".getBytes();
+    private static final byte[] PHOTOSHOP = "Photoshop3.0".getBytes();
+
+    boolean finished;
+
+    /**
+     * Create new App13InputStream.
+     * Note that <code>in</code> should support <code>mark()</code>
+     * @param in InputStream
+     * @throws IOException
+     */
+    public App13InputStream(InputStream in) throws IOException {
+        super(in);
+    }
+
+    void initBlock() throws IOException {
+        in.mark(3);
+        int marker = in.read();
+        int app13 = in.read();
+        if (marker != MARKER || app13 != APP_13) {
+            finished = true;
+            in.reset();
+            return;
+        }
+        length = (in.read() << 8) | (in.read() & 0xFF) - 2;
+
+        in.mark(4);
+        byte[] b = new byte[4];
+        for (int i = 0; i < 4; i++) {
+            b[i] = (byte) in.read();
+        }
+        in.reset();
+
+        boolean photoshop = true;
+
+        //some applications "forget" to write 'Photoshop 3.0' Identifier
+        for (int i = 0; i < b.length; i++) {
+            if (b[i] != PHOTOSHOP[i]) {
+                photoshop = false;
+                break;
+            }
+        }
+
+        if (photoshop) {
+            for (int i = 0; i < 14; i++) {
+                in.read();
+            }
+            length -= 14;
+        }
+        else {
+            for (int i = 0; i < b.length; i++) {
+                if (b[i] != ID8[i]) {
+                    throw new IOException("not App13 stream");
+                }
+            }
+        }
+    }
+
+    int length;
+
+    public int read() throws IOException {
+        if (finished) {
+            return -1;
+        }
+        if (length == 0) {
+            initBlock();
+            if (finished) {
+                return -1;
+            }
+        }
+        length--;
+        return super.read();
+    }
+
+    public int read(byte b[]) throws IOException {
+        return read(b, 0, b.length);
+    }
+
+    public int read(byte b[], int off, int len) throws IOException {
+        if (b == null) {
+            throw new NullPointerException();
+        }
+        if (off + len > b.length || off < 0) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        int read = 1;
+        int a = read();
+        if (a == -1) {
+            return -1;
+        }
+        b[off] = (byte) a;
+        for (int i = off + 1; i < len; i++) {
+            a = read();
+            if (a == -1) {
+                break;
+            }
+            read++;
+            b[i] = (byte) a;
+        }
+        return read;
+    }
+
+    public long skip(long n) throws IOException {
+        long remaining = n;
+        while (remaining > 0) {
+            int a = read();
+            if (a == -1) {
+                break;
+            }
+            remaining--;
+        }
+        return n - remaining;
+    }
+
+    public int available() throws IOException {
+        if (finished) {
+            return 0;
+        }
+        if (length == 0) {
+            initBlock();
+            if (finished) {
+                return 0;
+            }
+        }
+        return length;
+    }
+
+    public synchronized void mark(int readlimit) {
+
+    }
+
+    public synchronized void reset() throws IOException {
+
+    }
+
+    public boolean markSupported() {
+        return false;
+    }
+}
Index: ocean/src/com/imagero/uio/io/ByteArrayOutputStreamExt.java
===================================================================
--- ocean/src/com/imagero/uio/io/ByteArrayOutputStreamExt.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/ByteArrayOutputStreamExt.java	(revision 0)
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.io;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * ByteArrayOutputStreamExt extends ByteArrayOutputStream with posibility to drain off data into user specified buffer.
+ * ActionEvent is fired if buffer is full and going to grow.
+ * @author Andrey Kuznetsov
+ */
+public class ByteArrayOutputStreamExt extends ByteArrayOutputStream {
+
+    public static final String BUFFER_FULL = "buffer full";
+
+    ActionListener bufferListener;
+
+    public ByteArrayOutputStreamExt() {
+        this(1024);
+    }
+
+    public ByteArrayOutputStreamExt(int size) {
+        super(size);
+    }
+
+    public ByteArrayOutputStreamExt(ActionListener l) {
+        this.bufferListener = l;
+    }
+
+    public ByteArrayOutputStreamExt(int size, ActionListener l) {
+        super(size);
+        this.bufferListener = l;
+    }
+
+    protected void fireBufferFullEvent() {
+        drained = false;
+        if (bufferListener != null) {
+            ActionEvent e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, BUFFER_FULL);
+            bufferListener.actionPerformed(e);
+        }
+    }
+
+    /**
+     * Fill destination buffer with data which is removed from start of this buffer.
+     * @param dest destination buffer
+     * @return how much bytes was moved from this buffer into destination buffer.
+     */
+    public synchronized int drain(byte[] dest) {
+        int length = Math.min(dest.length, count);
+        if (length > 0) {
+            System.arraycopy(buf, 0, dest, 0, length);
+            int len = count - length;
+            if (len > 0) {
+                System.arraycopy(buf, length, buf, 0, len);
+            }
+            count -= length;
+        }
+        drained = true;
+        return length;
+    }
+
+    boolean drained;
+
+    public synchronized void write(int b) {
+        if (bufferListener != null) {
+            int newcount = count + 1;
+            if (newcount > buf.length) {
+                fireBufferFullEvent();
+            }
+        }
+        super.write(b);
+    }
+
+    /**
+     * Writes len bytes from given byte array starting at offset off to this buffer.
+     * If capacity of buffer is not enough for incoming data,
+     * then, at-first, buffer filled with data,
+     * then fired "buffer is full" event,
+     * thus giving the user possibility to "drain" buffer.
+     * If buffer was drained, then rest of data is written to buffer,
+     * otherwise buffer capacity is increased before writing.
+     * @param b
+     * @param off
+     * @param len
+     */
+    public synchronized void write(byte b[], int off, int len) {
+        int max = buf.length - count;
+        if (max > len || bufferListener == null) {
+            super.write(b, off, len);
+        } else {
+            super.write(b, off, max);
+            fireBufferFullEvent();
+            write2(b, off + max, len - max);
+        }
+    }
+
+    private void write2(byte b[], int off, int len) {
+        if (!drained) {
+            super.write(b, off, len);
+        } else {
+            drained = false;
+            int max = buf.length - count;
+            if (max > len) {
+                super.write(b, off, len);
+            } else {
+                super.write(b, off, max);
+                fireBufferFullEvent();
+                write2(b, off + max, len - max);
+            }
+        }
+    }
+
+    public void close() throws IOException {
+        fireBufferFullEvent();
+        super.close();
+    }
+
+    public void setBufferListener(ActionListener bufferListener) {
+        this.bufferListener = bufferListener;
+    }
+
+    /**
+     * Retrieve internal buffer.
+     * Recommended use - immediately after receiving "buffer full" event.
+     * Internal buffer is returned and replaced with new empty buffer.
+     */
+    public synchronized byte[] drain() {
+        byte[] tmp = buf;
+        buf = new byte[0];
+        count = 0;
+        drained = true;
+        return tmp;
+    }
+
+    public synchronized void writeTo(DataOutput out) throws IOException {
+        out.write(buf, 0, count);
+    }
+}
Index: ocean/src/com/imagero/uio/io/HexInputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/HexInputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/HexInputStream.java	(revision 0)
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @author Andrey Kuznetsov
+ */
+public class HexInputStream extends FilterInputStream {
+
+    private static final char encodeTable[] = {
+        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+    };
+
+    public static final int decodeTable [] = new int[0x100];
+
+    static {
+        for (int i = 0; i < decodeTable.length; i++) {
+            decodeTable[i] = -1;
+        }
+        for (int j = 0; j < encodeTable.length; j++) {
+            decodeTable[encodeTable[j]] = j;
+        }
+    }
+
+    boolean finished;
+
+    byte[] buffer = new byte[80];
+    int count;
+    int pos;
+
+    public HexInputStream(InputStream in) {
+        super(in);
+    }
+
+    public int read() throws IOException {
+        if (pos >= count) {
+            if (finished) {
+                return -1;
+            }
+            else {
+                fillBuffer();
+            }
+        }
+        if (pos < count) {
+            return buffer[pos++] & 0xFF;
+        }
+        return -1;
+    }
+
+    protected void fillBuffer() {
+        int k = 0;
+        try {
+            for (; k < buffer.length; k++) {
+                int b0 = in.read();
+                if (b0 == 13 || b0 == 10) {
+                    k--;
+                    continue;
+                }
+                if (b0 == '>') {
+                    k++;
+                    finished = true;
+                    break;
+                }
+                int b1 = in.read();
+                int d0 = decodeTable[b0];
+                int d1 = decodeTable[b1];
+                if (d0 == -1 || d1 == -1) {
+                    k--;
+                    continue;
+                }
+                buffer[k] = (byte) (d0 * 16 + d1);
+            }
+        }
+        catch (Throwable ex) {
+//            ex.printStackTrace();
+            System.err.println(ex.getMessage());
+        }
+        count = k;
+        pos = 0;
+    }
+
+    public long skip(long n) throws IOException {
+        long i = 0;
+        for (; i < n; i++) {
+            int a = read();
+            if (a == -1) {
+                break;
+            }
+        }
+        return i;
+    }
+
+    public int read(byte b[]) throws IOException {
+        return read(b, 0, b.length);
+    }
+
+    public int read(byte b[], int off, int len) throws IOException {
+        if (b == null) {
+            return (int) skip(len);
+        }
+        if (len <= 0) {
+            return 0;
+        }
+        int i = 0;
+        try {
+            for (; i < len; i++) {
+                int a = read();
+                if (a == -1) {
+                    break;
+                }
+                b[i] = (byte) a;
+            }
+        }
+        catch (IOException ex) {
+        }
+        return i == 0 ? -1 : i;
+    }
+}
Index: ocean/src/com/imagero/uio/io/App13OutputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/App13OutputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/App13OutputStream.java	(revision 0)
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Utility to write data into App13 block(s).
+ * If data is longer as given App13 size then multiple App13 blocks are written.
+ * @author Andrey Kuznetsov
+ */
+public class App13OutputStream extends FilterOutputStream {
+
+    int count;
+    byte[] buffer;
+    private byte[] header = {(byte) 0xFF, (byte) 0xED, 0, 0, 'P', 'h', 'o', 't', 'o', 's', 'h', 'o', 'p', ' ', '3', '.', '0', 0};
+
+    /**
+     * create new App13OutputStream with default App13 size (32000)
+     * @param out OutputStream
+     */
+    public App13OutputStream(OutputStream out) {
+        this(out, 32000);
+    }
+
+    /**
+     * create App13OutputStream with user defined App13 size
+     * @param out OutputStream
+     * @param length length of App13
+     */
+    public App13OutputStream(OutputStream out, int length) {
+        super(out);
+        buffer = new byte[length];
+        for (int i = 0; i < header.length; i++) {
+            buffer[i] = header[i];
+        }
+        count = header.length;
+    }
+
+    public synchronized void write(int b) throws IOException {
+        if (count >= buffer.length) {
+            flushBuffer();
+        }
+        buffer[count++] = (byte) b;
+    }
+
+    protected void flushBuffer() throws IOException {
+        if (count > header.length) {
+            int cnt = count - 2;
+            buffer[2] = (byte) ((cnt >> 8) & 0xFF);
+            buffer[3] = (byte) (cnt & 0xFF);
+            out.write(buffer, 0, count);
+            count = header.length;
+        }
+    }
+
+    public void write(byte b[]) throws IOException {
+        write(b, 0, b.length);
+    }
+
+    public void write(byte b[], int off, int len) throws IOException {
+        for (int i = off; i < len; i++) {
+            write(b[i]);
+        }
+    }
+
+    public void flush() throws IOException {
+        flushBuffer();
+        out.flush();
+    }
+}
Index: ocean/src/com/imagero/uio/io/ByteArrayOutputStream2.java
===================================================================
--- ocean/src/com/imagero/uio/io/ByteArrayOutputStream2.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/ByteArrayOutputStream2.java	(revision 0)
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import java.io.ByteArrayOutputStream;
+
+/**
+ * ByteArrayOutputStream which writes to external buffer.
+ * Length of this external buffer can't be changed.
+ *
+ * @author Andrey Kuznetsov
+ */
+public class ByteArrayOutputStream2 extends ByteArrayOutputStream {
+    public ByteArrayOutputStream2(byte[] buffer) {
+        super(0);
+        buf = buffer;
+    }
+
+    /**
+     * write given byte to buffer.
+     *
+     * @param b byte to write
+     * @throws ArrayIndexOutOfBoundsException if new byte count would exceed length of buffer after this operation
+     */
+    public synchronized void write(int b) {
+        int newcount = count + 1;
+        if (newcount > buf.length) {
+            throw new ArrayIndexOutOfBoundsException(newcount);
+        }
+        buf[count] = (byte) b;
+        count = newcount;
+    }
+
+    /**
+     * combine (OR) current value with given byte using supplied mask.
+     * resulting value is (b & mask) | (currentValue & ~mask)
+     * @param b byte to combine
+     * @param mask 8 bit mask
+     * @throws ArrayIndexOutOfBoundsException
+     */
+    public synchronized void write(int b, int mask) {
+        int newcount = count + 1;
+        if (newcount > buf.length) {
+            throw new ArrayIndexOutOfBoundsException(newcount);
+        }
+        buf[count] = (byte) ((b & mask) | (buf[count] & ~mask));
+        count = newcount;
+    }
+
+    /**
+     * Writes bytes from the specified byte array to buffer
+     * @param b byte array
+     * @param off start offset
+     * @param len number of bytes to write
+     * @throws ArrayIndexOutOfBoundsException if new byte count would exceed length of buffer after this operation (however the max possible byte count is written first)
+     */
+    public synchronized void write(byte b[], int off, int len) {
+        if (len == 0) {
+            return;
+        }
+        if ((off < 0) || (len < 0) || ((off + len) > b.length)) {
+            throw new IndexOutOfBoundsException();
+        }
+        int newcount = count + len;
+        if (newcount > buf.length) {
+            int max = buf.length - count;
+            System.arraycopy(b, off, buf, count, max);
+            throw new ArrayIndexOutOfBoundsException(newcount);
+        }
+        System.arraycopy(b, off, buf, count, len);
+        count = newcount;
+    }
+
+    public int getCount() {
+        return count;
+    }
+
+    /**
+     * Skip some bytes.
+     * Negative skip is possible.
+     * @param n byte count to skip
+     * @return how much bytes were skipped
+     */
+    public int skip(int n) {
+        int p = this.count;
+        seek(p + n);
+        return this.count - p;
+//        if (count + n > buf.length) {
+//            n = buf.length - count;
+//        }
+//        if (n < 0) {
+//            return 0;
+//        }
+//        count += n;
+//        return n;
+    }
+
+    public void seek(int pos) {
+        this.count = Math.min(Math.max(pos, 0), buf.length - 1);
+    }
+}
Index: ocean/src/com/imagero/uio/io/LimitedInputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/LimitedInputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/LimitedInputStream.java	(revision 0)
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * InputStream that reads specified number of bytes
+ *
+ * @author Kouznetsov Andrei
+ */
+public class LimitedInputStream extends FilterInputStream {
+    protected int limit;
+
+    /**
+     * create new LimitedInputStream
+     *
+     * @param in InputStream
+     * @param limit read limit
+     */
+    public LimitedInputStream(InputStream in, int limit) {
+        super(in);
+        this.limit = limit;
+    }
+
+    public int available() throws IOException {
+        return limit;
+    }
+
+    public int read() throws IOException {
+        if(limit-- <= 0) {
+            return -1;
+        }
+        return in.read();
+    }
+
+    public int read(byte b[]) throws IOException {
+        return in.read(b, 0, b.length);
+    }
+
+    public int read(byte b[], int off, int len) throws IOException {
+        if(limit > 0) {
+            int length = Math.min(len, limit);
+            int res = in.read(b, off, length);
+            if(res > 0) {
+                limit -= res;
+            }
+            return res;
+        }
+        return -1;
+    }
+
+    public long skip(long n) throws IOException {
+        if(limit > 0) {
+            long length = Math.min(n, limit);
+            long res = in.skip(length);
+            if(res > 0) {
+                limit -= res;
+            }
+            return res;
+        }
+        return -1;
+    }
+
+    int mark;
+
+    public synchronized void mark(int readlimit) {
+        in.mark(readlimit);
+        mark = limit;
+    }
+
+    public synchronized void reset() throws IOException {
+        in.reset();
+        limit = mark;
+    }
+}
\ No newline at end of file
Index: ocean/src/com/imagero/uio/io/Base64DInputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/Base64DInputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/Base64DInputStream.java	(revision 0)
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://res.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * InputStream for decoding data from base64 encoded String array
+ * @author Andrey Kuznetsov
+ */
+public class Base64DInputStream extends InputStream {
+
+    boolean finished;
+
+    protected byte[] buffer;
+    String[] s;
+
+    int current;
+
+    int p;
+
+    public Base64DInputStream(String[] s) {
+        this.s = s;
+    }
+
+    /**
+     * decode next byte
+     * @return int
+     * @throws IOException
+     */
+    public int read() throws IOException {
+        if (finished) {
+            return -1;
+        }
+        if (buffer == null || p >= buffer.length) {
+            next();
+        }
+        if (buffer == null) {
+            finished = true;
+            return -1;
+        }
+        try {
+            return buffer[p++] & 0xFF;
+        }
+        catch (ArrayIndexOutOfBoundsException ex) {
+            System.err.println(p + " " + buffer.length);
+            throw ex;
+        }
+    }
+
+    /**
+     * switch to next String
+     */
+    protected void next() throws IOException {
+        if (finished) {
+            buffer = null;
+            p = 0;
+            return;
+        }
+        if (current < s.length) {
+            String s = this.s[current++];
+            buffer = Base64.base64Decode(s);
+            p = 0;
+        }
+        else {
+            finished = true;
+            buffer = null;
+            p = 0;
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/io/Base64.java
===================================================================
--- ocean/src/com/imagero/uio/io/Base64.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/Base64.java	(revision 0)
@@ -0,0 +1,217 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+
+/**
+ * This class shows how simple and straightforward can be implementation
+ * of base 64 codec using BitInputStream and BitOutputStream
+ * @author Andrey Kuznetsov
+ */
+public class Base64 {
+
+    private static final char encodeTable[] = {
+        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+        'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+        'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+        'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
+    };
+
+    static int decodeTable [] = new int[0x100];
+    static int lineLength = 72;
+
+    public static int getLineLength() {
+        return lineLength;
+    }
+
+    public static void setLineLength(int lineLength) {
+        Base64.lineLength = lineLength;
+    }
+
+
+    static {
+        for (int i = 0; i < decodeTable.length; i++) {
+            decodeTable[i] = -1;
+        }
+        for (int j = 0; j < encodeTable.length; j++) {
+            decodeTable[encodeTable[j]] = j;
+        }
+    }
+
+    private static final char [] crlf = "\n".toCharArray();
+
+    /**
+     * base 64 encode
+     * @param b data to encode
+     * @return base 64 encoded String
+     * @throws IOException
+     */
+    public static String base64Encode(byte[] b) throws IOException {
+        BitInputStream bis = new BitInputStream(new ByteArrayInputStream(b));
+        bis.setBitsToRead(6);
+        StringBuffer sb = new StringBuffer();
+        int cnt = 0;
+        while (true) {
+            int c = bis.read();
+            //EOF reached
+            if (c == -1) {
+                break;
+            }
+            sb.append(encodeTable[c]);
+            if(++cnt == lineLength) {
+                sb.append("\n");
+                cnt = 0;
+            }
+        }
+        while ((sb.length() % 4) != 0) {
+            //padding
+            sb.append('=');
+        }
+        return sb.toString();
+    }
+
+    /**
+     * base64 encode data from InputStream and write ict to given character stream
+     * @param in InputStream
+     * @param out Writer
+     * @throws IOException
+     */
+    public static void base64Encode(InputStream in, Writer out) throws IOException {
+        BitInputStream bis = new BitInputStream(in);
+        char[] buffer = new char[4000];
+        bis.setBitsToRead(6);
+        int cnt = 0;
+        int bufferPtr = 0;
+        while (true) {
+            int c = bis.read();
+            //EOF reached
+            if (c == -1) {
+                break;
+            }
+            buffer[bufferPtr++] = encodeTable[c];
+            if(++cnt == lineLength) {
+                if(bufferPtr + crlf.length >= buffer.length) {
+                    out.write(buffer, 0, bufferPtr);
+                    bufferPtr = 0;
+                }
+                for (int i = 0; i < crlf.length; i++) {
+                   buffer[bufferPtr++] = crlf[i];
+                }
+                cnt = 0;
+            }
+            if (bufferPtr == buffer.length) {
+                out.write(buffer);
+                bufferPtr = 0;
+            }
+        }
+        while ((bufferPtr % 4) != 0) {
+            //padding
+            buffer[bufferPtr++] ='=';
+        }
+        if (bufferPtr > 0) {
+            out.write(buffer, 0, bufferPtr);
+        }
+    }
+
+    /**
+     * decode base 64 encoded string
+     * @param s String to decode
+     * @return byte array
+     * @throws IOException
+     */
+    public static byte[] base64Decode(String s) throws IOException {
+        char[] chrs = s.toCharArray();
+        ByteArrayOutputStream bout = new ByteArrayOutputStream();
+        BitOutputStream bos = new BitOutputStream(bout);
+        bos.setBitsToWrite(6);
+
+        boolean flush = true;
+
+        for (int i = 0; i < chrs.length; i++) {
+            char c = chrs[i];
+            //start of padding
+            if (c == '=') {
+                flush = false;
+                break;
+            }
+            int c0 = decodeTable[c];
+            if(c0 != -1) {
+                bos.write(c0);
+            }
+        }
+        if (flush) {
+            bos.flush();
+        }
+        return bout.toByteArray();
+    }
+
+
+    /**
+     * decode base64 encoded character stream and write it to given OutputStream
+     * @param in Reader character stream
+     * @param out OutputStream
+     * @throws IOException
+     */
+    public static void base64Decode(Reader in, OutputStream out) throws IOException {
+        BitOutputStream bos = new BitOutputStream(out);
+        bos.setBitsToWrite(6);
+
+        boolean flush = true;
+
+        while (true) {
+            int c = in.read();
+            if (c == -1) {
+                break;
+            }
+            //start of padding
+            if (c == '=') {
+                flush = false;
+                break;
+            }
+            final int b = decodeTable[c];
+            if(b != -1) {
+                bos.write(b);
+            }
+        }
+        if (flush) {
+            bos.flush();
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/io/ByteArray2DInputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/ByteArray2DInputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/ByteArray2DInputStream.java	(revision 0)
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.io;
+
+import java.io.ByteArrayInputStream;
+
+/**
+ * Like byteArrayInputStream but works with 2d byte array
+ * @author Andrey Kuznetsov
+ */
+public class ByteArray2DInputStream extends ByteArrayInputStream {
+
+    private byte[][] data;
+
+    int current;
+    boolean finished;
+
+    int markIndex;
+
+    public ByteArray2DInputStream(byte[][] data) {
+        super(data[0]);
+        this.data = data;
+    }
+
+    public int read() {
+        if (finished) {
+            return -1;
+        }
+        if (pos >= count) {
+            next();
+        }
+        return super.read();
+    }
+
+    private void next() {
+        if ((current + 1) < data.length) {
+            buf = data[++current];
+            pos = 0;
+            count = buf.length;
+        }
+        else {
+            finished = true;
+        }
+    }
+
+    public int read(byte[] buf, int off, int len) {
+        int read = 0;
+        while (read < len && !finished) {
+            if (pos >= count) {
+                next();
+            }
+            int rd = super.read(buf, off + read, len - read);
+            if(rd <= 0) {
+                break;
+            }
+            read += rd;
+        }
+        return read;
+    }
+
+    public long skip(long ns) {
+        long skipped = 0;
+        while (skipped < ns && !finished) {
+            if (pos >= count) {
+                next();
+            }
+            long skp = super.skip(ns - skipped);
+            skipped += skp;
+            if (skp == 0) {
+                break;
+            }
+        }
+        return skipped;
+    }
+
+    public void mark(int readAheadLimit) {
+        markIndex = current;
+        super.mark(readAheadLimit);
+    }
+
+    public void reset() {
+        buf = data[markIndex];
+        super.reset();
+        finished = false;
+    }
+}
Index: ocean/src/com/imagero/uio/io/LEDataOutputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/LEDataOutputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/LEDataOutputStream.java	(revision 0)
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.io;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutput;
+import java.io.DataOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * LEDataOutputStream.java
+ * <br>
+ * Little-endian writing.
+ * <br>
+ * @author Kouznetsov Andrei
+ *
+ */
+public class LEDataOutputStream extends FilterOutputStream implements DataOutput {
+
+	public LEDataOutputStream(OutputStream out) {
+		super(out);
+	}
+
+	public final void writeShort(int value) throws IOException {
+		write(value & 0xFF);
+		write((value >> 8) & 0xFF);
+	}
+
+	public final void writeChar(int value) throws IOException {
+		write(value & 0xFF);
+		write((value >> 8) & 0xFF);
+	}
+
+	public final void writeInt(int value) throws IOException {
+		write(value & 0xFF);
+		write((value >> 8) & 0xFF);
+		write((value >> 16) & 0xFF);
+		write((value >> 24) & 0xFF);
+	}
+
+	public final void writeLong(long value) throws IOException {
+		writeInt((int)(value & 0xFFFFFFFF));
+		writeInt((int)((value >> 32) & 0xFFFFFFFF));
+	}
+
+	public final void writeFloat(float value) throws IOException {
+		writeInt(Float.floatToIntBits(value));
+	}
+
+	public final void writeDouble(double value) throws IOException {
+		writeLong(Double.doubleToLongBits(value));
+	}
+
+	public void writeBoolean(boolean b) throws IOException {
+		out.write(b ? 1 : 0);
+	}
+
+	public void writeByte(int v) throws IOException {
+		write(v);
+	}
+
+	public void writeBytes(String s) throws IOException {
+		int len = s.length();
+		for (int i = 0 ; i < len ; i++) {
+			out.write((byte)s.charAt(i));
+		}
+	}
+
+	public void writeChars(String s) throws IOException {
+		int len = s.length();
+		byte [] b = new byte[len * 2];
+		int index = 0;
+		for(int i = 0; i < len; i++) {
+			int v = s.charAt(i);
+			b[index++] = (byte)((v >>> 0) & 0xFF);
+			b[index++] = (byte)((v >>> 8) & 0xFF);
+		}
+		write(b);
+	}
+
+	public void writeUTF(String str) throws IOException {
+		ByteArrayOutputStream out = new ByteArrayOutputStream(str.length());
+		DataOutputStream dataOut = new DataOutputStream(out);
+		dataOut.writeUTF(str);
+		dataOut.flush();
+		dataOut.close();
+		byte[] b = out.toByteArray();
+		write(b);
+	}
+}
+
+
Index: ocean/src/com/imagero/uio/io/JpegFilterInputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/JpegFilterInputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/JpegFilterInputStream.java	(revision 0)
@@ -0,0 +1,236 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+
+/**
+ * remove all App blocks from jpeg file
+ * <br>
+ * how to use:
+ * <br>
+ * <pre>
+ * 		//read data from file and save filtered data to another file
+ * 		File fs = new File("source.jpg");
+ * 		File fd = new File("dest.jpg");
+ * 		JpegFilterInputStream.filter(fs, fd);
+ *
+ *		or
+ *
+ * 		//filter data in byte array
+ * 		byte [] source = ...
+ * 		byte [] dest = JpegFilterInputStream.filter(source);
+ *
+ * 		or
+ *
+ * 		//filter data from InputStream
+ * 		InputStream in;
+ * 		OutputStream out;
+ * 		JpegFilterInputStream.filter(in, out);
+ *
+ *      or
+ *
+ *      //just wrap InputStream
+ *      JpegFilterInputStream jfis = new JpegFilterInputStream(in);
+ *
+ * </pre>
+ * @author Andrey Kuznetsov
+ */
+public class JpegFilterInputStream extends FilterInputStream {
+
+    public static final int APP_0 = 0xE0;
+    public static final int APP_1 = 0xE1;
+    public static final int APP_2 = 0xE2;
+    public static final int APP_3 = 0xE3;
+    public static final int APP_4 = 0xE4;
+    public static final int APP_5 = 0xE5;
+    public static final int APP_6 = 0xE6;
+    public static final int APP_7 = 0xE7;
+    public static final int APP_8 = 0xE8;
+    public static final int APP_9 = 0xE9;
+    public static final int APP_10 = 0xEA;
+    public static final int APP_11 = 0xEB;
+    public static final int APP_12 = 0xEC;
+    public static final int APP_13 = 0xED;
+    public static final int APP_14 = 0xEE;
+    public static final int APP_15 = 0xEF;
+
+    public static final int[] allMarkers = new int[]{
+        APP_0, APP_1, APP_2, APP_3, APP_4, APP_5, APP_6, APP_7,
+        APP_8, APP_9, APP_10, APP_11, APP_12, APP_13, APP_14, APP_15
+    };
+
+    public static final int[] defaultMarkers = new int[]{
+        APP_1, APP_2, APP_3, APP_4, APP_5, APP_6, APP_7,
+        APP_8, APP_9, APP_10, APP_11, APP_12, APP_13, APP_14, APP_15
+    };
+
+    public static void filterFile(File src, File dest) throws IOException {
+        FileInputStream in = new FileInputStream(src);
+        FileOutputStream out = new FileOutputStream(dest);
+
+        filter(in, out);
+        IOutils.closeStream(out);
+        IOutils.closeStream(in);
+    }
+
+    /**
+     * filter out all markers (except App0)
+     * @param in InputStream
+     * @param out
+     * @throws java.io.IOException
+     */
+    public static void filter(InputStream in, OutputStream out) throws IOException {
+        filter(in, out, defaultMarkers);
+    }
+
+    public static void filter(InputStream in0, OutputStream out, int[] markers) throws IOException {
+        JpegFilterInputStream in = new JpegFilterInputStream(in0, markers);
+        int a = 0;
+        while (a >= 0) {
+            a = in.read();
+            out.write(a);
+        }
+    }
+
+    /**
+     * filter out all markers (except App0)
+     * @param data
+     * @return byte array
+     * @throws IOException
+     */
+    public static byte[] filter(byte[] data) throws IOException {
+        return filter(data, defaultMarkers);
+    }
+
+    public static byte[] filter(byte[] data, int[] markers) throws IOException {
+        final ByteArrayInputStream in0 = new ByteArrayInputStream(data);
+        ByteArrayOutputStream bout = new ByteArrayOutputStream();
+        filter(in0, bout, markers);
+        return bout.toByteArray();
+    }
+
+    int[] markers;
+
+    /**
+     * create JpegFilterInputStream (filter out all markers except App0)
+     * @param in InputStream (with valid JPEG stream)
+     */
+    public JpegFilterInputStream(InputStream in) {
+        this(in, JpegFilterInputStream.defaultMarkers);
+    }
+
+    /**
+     * create JpegFilterInputStream
+     * @param in InputStream (with valid JPEG stream)
+     * @param markers markers to filter out
+     */
+    public JpegFilterInputStream(InputStream in, int[] markers) {
+        super(in);
+        this.markers = markers;
+    }
+
+    private boolean markerOn;
+
+    public int read() throws IOException {
+        int a = in.read();
+        if (!markerOn) {
+            if (a == 0xFF) {
+                markerOn = true;
+            }
+            return a;
+        }
+        else {
+            if (isAppMarker(a)) {
+                int length = (in.read() << 8) + in.read() - 2;
+//					Sys.out.println("length:" + length);
+                in.skip(length);
+                int b = in.read();
+                if (b != 0xFF) {
+                    throw new IOException("marker???");
+                }
+                return read();
+            }
+            else {
+                markerOn = false;
+                return a;
+            }
+        }
+    }
+
+    public int read(byte b[]) throws IOException {
+        return read(b, 0, b.length);
+    }
+
+    public int read(byte b[], int off, int len) throws IOException {
+        if (b == null) {
+            throw new NullPointerException();
+        }
+        if (off + len > b.length || off < 0) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        int read = 1;
+        int a = read();
+        if (a == -1) {
+            return -1;
+        }
+        b[off] = (byte) a;
+        for (int i = off + 1; i < len; i++) {
+            a = read();
+            if (a == -1) {
+                break;
+            }
+            read++;
+            b[i] = (byte) a;
+        }
+        return read;
+    }
+
+    public boolean isAppMarker(int a) {
+        for (int i = 0; i < markers.length; i++) {
+            if (a == markers[i]) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
Index: ocean/src/com/imagero/uio/io/IOutils.java
===================================================================
--- ocean/src/com/imagero/uio/io/IOutils.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/IOutils.java	(revision 0)
@@ -0,0 +1,792 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import com.imagero.uio.RandomAccessInput;
+import com.imagero.uio.RandomAccessOutput;
+import com.imagero.uio.ReadUtil;
+import com.imagero.uio.Sys;
+
+import java.io.*;
+
+/**
+ * IOutils.java
+ *
+ * @author Andrei Kouznetsov
+ */
+public class IOutils {
+    private static final int BIG_ENDIAN = 0x4D4D;
+    private static final int LITTLE_ENDIAN = 0x4949;
+
+    /**
+     * close silently stream<br>
+     * no exception it thrown
+     *
+     * @param bw
+     */
+    public static void closeStream(BufferedWriter bw) {
+        try {
+            if (bw != null) {
+                bw.close();
+            }
+        } catch (IOException ex) {
+        }
+    }
+
+    /**
+     * close silently stream<br>
+     * no exception it thrown
+     *
+     * @param br
+     */
+    public static void closeStream(BufferedReader br) {
+        try {
+            if (br != null) {
+                br.close();
+            }
+        } catch (IOException ex) {
+        }
+    }
+
+    /**
+     * close silently stream<br>
+     * no exception it thrown
+     *
+     * @param is
+     */
+    public static void closeStream(InputStream is) {
+        try {
+            if (is != null) {
+                is.close();
+            }
+        } catch (IOException ex) {
+        }
+    }
+
+    /**
+     * close silently stream<br>
+     * no exception it thrown
+     *
+     * @param os
+     */
+    public static void closeStream(OutputStream os) {
+        try {
+            if (os != null) {
+                os.close();
+            }
+        } catch (IOException ex) {
+        }
+    }
+
+    /**
+     * close silently stream<br>
+     * no exception it thrown
+     *
+     * @param raf
+     */
+    public static void closeStream(RandomAccessFile raf) {
+        try {
+            if (raf != null) {
+                raf.close();
+            }
+        } catch (IOException ex) {
+        }
+    }
+
+    /**
+     * close silently stream<br>
+     * no exception it thrown
+     *
+     * @param ro
+     */
+    public static void closeStream(RandomAccessInput ro) {
+        try {
+            if (ro != null) {
+                ro.close();
+            }
+        } catch (IOException ex) {
+        }
+    }
+
+    public static void closeStream(RandomAccessOutput ro) {
+        try {
+            if (ro != null) {
+                ro.close();
+            }
+        } catch (IOException ex) {
+        }
+    }
+
+    static final int[] mask = {1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768};
+    static byte b0 = (byte) '0';
+    static byte b1 = (byte) '1';
+
+    public static String toBinaryString(byte value) {
+        byte[] b = new byte[8];
+        int cnt = 0;
+        for (int i = 7; i > -1; i--) {
+            b[cnt++] = (value & mask[i]) == 0 ? b0 : b1;
+        }
+        return new String(b);
+    }
+
+    public static String toBinaryString(char value) {
+        byte[] b = new byte[16];
+        int cnt = 0;
+        for (int i = 15; i > -1; i--) {
+            b[cnt++] = (value & mask[i]) == 0 ? b0 : b1;
+        }
+        return new String(b);
+    }
+
+    public static String toBinaryString(int value, int length) {
+        byte[] b = new byte[length];
+        int cnt = 0;
+        for (int i = length - 1; i > -1; i--) {
+            if (((value >> i) & 1) == 1) {
+                b[cnt++] = b1;
+            } else {
+                b[cnt++] = b0;
+            }
+        }
+        return new String(b);
+    }
+
+    final static byte[] digits = {
+        (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5',
+        (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'a', (byte) 'b',
+        (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f'
+    };
+
+    public static String toHexString(byte value) {
+        return toUnsignedString(value & 0xFF, 4);
+    }
+
+    private static String toUnsignedString(int i, int shift) {
+        byte[] buf = new byte[]{(byte) '0', (byte) '0'};
+        int charPos = 2;
+        int radix = 1 << shift;
+        int mask = radix - 1;
+        do {
+            buf[--charPos] = digits[i & mask];
+            i >>>= shift;
+        } while (i != 0);
+
+        return new String(buf);
+    }
+
+    public static void printHexByte(int value) {
+        printHexImpl(value & 0xFFFF, 2);
+    }
+
+    public static void printlnHexByte(int value) {
+        printHexImpl(value & 0xFFFF, 2);
+        Sys.out.println("");
+    }
+
+    public static void printHexShort(int value) {
+        printHexImpl(value & 0xFFFF, 4);
+    }
+
+    public static void printlnHexShort(int value) {
+        printHexImpl(value & 0xFFFF, 4);
+        Sys.out.println("");
+    }
+
+    public static void printHexInt(int value) {
+        printHexImpl(value & 0xFFFFFFFF, 8);
+    }
+
+    public static void printlnHexInt(int value) {
+        printHexImpl(value & 0xFFFFFFFF, 8);
+        Sys.out.println("");
+    }
+
+    public static void printHexLong(long value) {
+        printHexImpl(value & 0xFFFFFFFFFFFFFFFFL, 16);
+    }
+
+    public static void printlnHexLong(long value) {
+        printHexImpl(value & 0xFFFFFFFFFFFFFFFFL, 16);
+        Sys.out.println("");
+    }
+
+    static void printHexImpl(long value, int length) {
+        String s = Long.toHexString(value);
+        //Sys.out.println("***********************" + s + " " + value);
+        for (int i = 0, size = length - s.length(); i < size; i++) {
+            Sys.out.print("0");
+        }
+        Sys.out.print(s);
+    }
+
+    static void printHexImpl(int value, int length) {
+        String s = Integer.toHexString(value);
+        if (s.length() > length) {
+            s = s.substring(s.length() - length);
+        }
+        //Sys.out.println("***********************" + s + " " + value);
+        for (int i = 0, size = length - s.length(); i < size; i++) {
+            Sys.out.print("0");
+        }
+        Sys.out.print(s);
+    }
+
+    public static String getExtension(File f) {
+        String s = f.getName();
+        return s.substring(s.lastIndexOf(".") + 1).toUpperCase();
+    }
+
+    /**
+     * read little-endian short
+     */
+    public static int readShort4D(InputStream in) throws IOException {
+        return ((in.read() & 0xFF) << 8) + ((in.read() & 0xFF) << 0);
+    }
+
+    /**
+     * read little-endian short
+     */
+    public static int readShort4D(DataInput in) throws IOException {
+        return ((in.readByte() & 0xFF) << 8) + ((in.readByte() & 0xFF) << 0);
+    }
+
+    /**
+     * read big-endian short
+     */
+    public static int readShort49(InputStream in) throws IOException {
+        return ((in.read() & 0xFF) << 0) + ((in.read() & 0xFF) << 8);
+    }
+
+    /**
+     * read big-endian short
+     */
+    public static int readShort49(DataInput in) throws IOException {
+        return ((in.readByte() & 0xFF) << 0) + ((in.readByte() & 0xFF) << 8);
+    }
+
+    /**
+     * read little-endian int
+     */
+    public static int readInt4D(InputStream in) throws IOException {
+        return (((in.read() & 0xFF) << 24) + ((in.read() & 0xFF) << 16) + ((in.read() & 0xFF) << 8) + ((in.read() & 0xFF) << 0));
+    }
+
+    /**
+     * read big-endian int
+     */
+    public static int readInt4D(DataInput in) throws IOException {
+        int b0 = (in.readByte() & 0xFF);
+        int b1 = (in.readByte() & 0xFF);
+        int b2 = (in.readByte() & 0xFF);
+        int b3 = (in.readByte() & 0xFF);
+        return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3;
+    }
+
+    /**
+     * read big-endian int
+     */
+    public static int readInt49(InputStream in) throws IOException {
+        return ((in.read() & 0xFF) << 0) + ((in.read() & 0xFF) << 8) + ((in.read() & 0xFF) << 16) + ((in.read() & 0xFF) << 24);
+    }
+
+    /**
+     * read big-endian int
+     */
+    public static int readInt49(DataInput in) throws IOException {
+        return ((in.readByte() & 0xFF) << 0) + ((in.readByte() & 0xFF) << 8) + ((in.readByte() & 0xFF) << 16) + ((in.readByte() & 0xFF) << 24);
+    }
+
+    /**
+     * read little-endian long
+     */
+    public static long readLong4D(InputStream in) throws IOException {
+        return ((long) (readInt4D(in)) << 32) + (readInt4D(in) & 0xFFFFFFFFL);
+    }
+
+    /**
+     * read little-endian long
+     */
+    public static long readLong4D(DataInput in) throws IOException {
+        return ((long) (readInt4D(in)) << 32) + (readInt4D(in) & 0xFFFFFFFFL);
+    }
+
+    /**
+     * read big-endian long
+     */
+    public static long readLong49(InputStream in) throws IOException {
+        return ((long) (readInt49(in)) & 0xFFFFFFFFL) + (readInt49(in) << 32);
+    }
+
+    /**
+     * read big-endian long
+     */
+    public static long readLong49(DataInput in) throws IOException {
+        return ((long) (readInt49(in)) & 0xFFFFFFFFL) + (readInt49(in) << 32);
+    }
+
+    public static byte readSByte(DataInput ro) throws IOException {
+        byte b = ro.readByte();
+        if (b < 0) {
+            b = (byte) -(~(b + 1));
+        }
+        return b;
+    }
+
+    public static short readSShort(DataInput ro) throws IOException {
+        short b = ro.readShort();
+        if (b < 0) {
+            b = (short) -(~(b + 1));
+        }
+        return b;
+    }
+
+    public static int readSInt(DataInput ro) throws IOException {
+        int b = ro.readInt();
+        if (b < 0) {
+            b = -(~(b + 1));
+        }
+        return b;
+    }
+
+    public static long readSLong(DataInput ro) throws IOException {
+        long b = ro.readLong();
+        if (b < 0) {
+            b = -(~(b + 1));
+        }
+        return b;
+    }
+
+    /**
+     * Read byte array and convert from 2's complement
+     * @param ro RandomAccessRO
+     * @param b0 byte array
+     * @throws IOException
+     */
+    public static void readFullyS(DataInput ro, byte[] b0) throws IOException {
+        ro.readFully(b0);
+        convertFrom2C(b0);
+    }
+
+    /**
+     * Convert byte array from 2's complement
+     */
+    public static final void convertFrom2C(byte[] b0) {
+        for (int i = 0; i < b0.length; i++) {
+            if (b0[i] < 0) {
+                b0[i] = (byte) -(~(b0[i] + 1));
+            }
+        }
+    }
+
+    /**
+     * Read short array and convert from 2's complement.
+     * @param ro RandomAccessRO
+     * @param b0 short array
+     * @throws IOException
+     */
+    public static void readFullyS(RandomAccessInput ro, short[] b0) throws IOException {
+        ReadUtil.readFully(ro, b0);
+        convertFrom2C(b0);
+    }
+
+    /**
+     * Convert short array from 2's complement
+     */
+    public static final void convertFrom2C(short[] b0) {
+        for (int i = 0; i < b0.length; i++) {
+            if (b0[i] < 0) {
+                b0[i] = (short) -(~(b0[i] + 1));
+            }
+        }
+    }
+
+    /**
+     * Read int array and convert from 2's complement
+     * @param ro RandomAccessRO
+     * @param b0 int array
+     * @throws IOException
+     */
+    public static void readFullyS(RandomAccessInput ro, int[] b0) throws IOException {
+        ReadUtil.readFully(ro, b0);
+        convertFrom2C(b0);
+    }
+
+    /**
+     * Convert int array from 2's complement
+     */
+    public static final void convertFrom2C(int[] b0) {
+        for (int i = 0; i < b0.length; i++) {
+            if (b0[i] < 0) {
+                b0[i] = -(~(b0[i] + 1));
+            }
+        }
+    }
+
+    /**
+     * Read short array and convert from 2's complement
+     * @param ro RandomAccessRO
+     * @param b0 long array
+     * @throws IOException
+     */
+    public static void readFullyS(RandomAccessInput ro, long[] b0) throws IOException {
+        ReadUtil.readFully(ro, b0);
+        convertFrom2C(b0);
+    }
+
+    /**
+     * Convert long array from 2's complement
+     */
+    public static final void convertFrom2C(long[] b0) {
+        for (int i = 0; i < b0.length; i++) {
+            if (b0[i] < 0) {
+                b0[i] = -(~(b0[i] + 1));
+            }
+        }
+    }
+
+    public static void readFully(InputStream in, byte b[]) throws UnexpectedEOFException, IOException {
+        readFully(in, b, 0, b.length);
+    }
+
+    public static void readFully(InputStream in, byte b[], int off, int len) throws UnexpectedEOFException, IOException {
+        int n = 0;
+        do {
+            int count = in.read(b, off + n, len - n);
+            if (count < 0) {
+                throw new UnexpectedEOFException(n > 0 ? n : 0);
+            }
+            n += count;
+        } while (n < len);
+    }
+
+    /**
+     * this method is like readFully, but instead of throwing <code>EOFException</code> it returns count of read bytes
+     *
+     * @param in InputStream to read
+     * @param b  byte array to fill
+     *
+     * @return number of bytes read into the buffer, or -1 if EOF was reached
+     *
+     * @throws IOException
+     *
+     */
+    public static int readFully2(InputStream in, byte b[]) throws IOException {
+        return readFully2(in, b, 0, b.length);
+    }
+
+    /**
+     * this method is like readFully, but instead of throwing <code>EOFException</code> it returns count of read bytes
+     *
+     * @param in  InputStream to read
+     * @param b   byte array to fill
+     * @param off start offset in byte array
+     * @param len number of bytes to read
+     *
+     * @return number of bytes read into the buffer, or -1 if EOF was reached
+     *
+     * @throws IOException
+     */
+    public static int readFully2(InputStream in, byte b[], int off, int len) throws IOException {
+        int n = 0;
+        int cnt0 = 0;
+        do {
+            int count = in.read(b, off + n, len - n);
+            if (count == 0) {
+                cnt0++;
+                if (cnt0 >= 3) {
+                    break;
+                }
+            } else {
+                cnt0 = 0;
+            }
+            if (count < 0) {
+                return n == 0 ? -1 : n;
+            }
+            n += count;
+        } while (n < len);
+        return n;
+    }
+
+    /**
+     * copy <code>length</code> bytes from <code>in</code> to <code>out</code>
+     * @param length amount of bytes to copy
+     * @param in source InputStream
+     * @param out destination OutputStream
+     * @throws IOException
+     */
+    public static long copy(long length, InputStream in, OutputStream out) throws IOException {
+        long copy = 0;
+        byte[] buffer = new byte[2048];
+        while (length > 0) {
+            int read = in.read(buffer, 0, (int) Math.min(buffer.length, length));
+            if (read <= 0) {
+                break;
+            }
+            copy += read;
+            length -= read;
+            out.write(buffer, 0, read);
+        }
+        return copy;
+    }
+
+    /**
+     * copy <code>length</code> bytes from source to destination stream
+     * @param length amount of bytes to copy
+     * @param in source stream
+     * @param out destination stream
+     * @throws IOException
+     */
+    public static long copy(long length, InputStream in, DataOutput out) throws IOException {
+        long copy = 0;
+        byte[] buffer = new byte[2048];
+        while (length > 0) {
+            int read = in.read(buffer, 0, (int) Math.min(buffer.length, length));
+            if (read <= 0) {
+                break;
+            }
+            copy += read;
+            length -= read;
+            out.write(buffer, 0, read);
+        }
+        return copy;
+    }
+
+    /**
+     * Copy file.
+     * @param src source file
+     * @param dest destination file
+     * @return how much bytes were copied.
+     * @throws IOException
+     */
+    public static long copy(File src, File dest) throws IOException {
+        InputStream in = new FileInputStream(src);
+        OutputStream out = new FileOutputStream(dest);
+        try {
+            return copy(in, out);
+        } finally {
+            IOutils.closeStream(in);
+            IOutils.closeStream(out);
+        }
+    }
+
+    /**
+     * copy data from <code>in</code> to <code>out</code>
+     * @param in source InputStream
+     * @param out destination OutputStream
+     * @return how much bytes were copied.
+     * @throws IOException
+     */
+    public static long copy(InputStream in, OutputStream out) throws IOException {
+        long copy = 0;
+        byte[] buffer = new byte[2048];
+        while (true) {
+            int read = in.read(buffer);
+            if (read <= 0) {
+                break;
+            }
+            copy += read;
+            out.write(buffer, 0, read);
+        }
+        return copy;
+    }
+
+    /**
+     * copy data from source to destination stream
+     * @param in source stream
+     * @param out destination stream
+     * @throws IOException
+     * @return amount of copied bytes
+     */
+    public static long copy(InputStream in, DataOutput out) throws IOException {
+        long copy = 0;
+        byte[] buffer = new byte[2048];
+        while (true) {
+            int read = in.read(buffer);
+            if (read <= 0) {
+                break;
+            }
+            copy += read;
+            out.write(buffer, 0, read);
+        }
+        return copy;
+    }
+
+    /**
+     * copy data from <code>in</code> to <code>out</code>
+     * @param in source RandomAccessRO
+     * @param offset offset in <code>in</code>
+     * @param out destination OutputStream
+     * @throws IOException
+     */
+    public static long copy(RandomAccessInput in, long offset, OutputStream out) throws IOException {
+        long copy = 0;
+        byte[] buffer = new byte[2048];
+        in.seek(offset);
+        while (true) {
+            int read = in.read(buffer);
+            if (read <= 0) {
+                break;
+            }
+            copy += read;
+            out.write(buffer, 0, read);
+        }
+        return copy;
+    }
+
+    /**
+     * copy data from source to destination stream
+     * @param in source stream
+     * @param offset offset in source stream
+     * @param out destination stream
+     * @throws IOException
+     * @return how much bytes was copied
+     */
+    public static long copy(RandomAccessInput in, long offset, DataOutput out) throws IOException {
+        long copy = 0;
+        byte[] buffer = new byte[2048];
+        in.seek(offset);
+        while (true) {
+            int read = in.read(buffer);
+            if (read <= 0) {
+                break;
+            }
+            copy += read;
+            out.write(buffer, 0, read);
+        }
+        return copy;
+    }
+
+    /**
+     * copy data from source to destination stream
+     * @param offset offset in source stream
+     * @param length amount of bytes to copy
+     * @param in source stream
+     * @param out destination stream
+     * @throws IOException
+     */
+    public static long copy(long offset, long length, RandomAccessInput in, OutputStream out) throws IOException {
+        long copy = 0;
+        byte[] buffer = new byte[2048];
+        in.seek(offset);
+        while (length > 0) {
+            int read = in.read(buffer, 0, (int) Math.min(buffer.length, length));
+            if (read <= 0) {
+                break;
+            }
+            copy += read;
+            length -= read;
+            out.write(buffer, 0, read);
+        }
+        return copy;
+    }
+
+    /**
+     * copy data from source to destination stream
+     * @param offset offset in source stream
+     * @param length amount of bytes to copy
+     * @param in source stream
+     * @param out destination stream
+     * @throws IOException
+     */
+    public static long copy(long offset, long length, RandomAccessInput in, DataOutput out) throws IOException {
+        long copy = 0;
+        byte[] buffer = new byte[2048];
+        in.seek(offset);
+        while (length > 0) {
+            int read = in.read(buffer, 0, (int) Math.min(buffer.length, length));
+            if (read <= 0) {
+                break;
+            }
+            copy += read;
+            length -= read;
+            out.write(buffer, 0, read);
+        }
+        return copy;
+    }
+
+    /**
+     * read big/little-endian short
+     */
+    public static int readShort(InputStream in, int byteOrder) throws IOException {
+        switch (byteOrder) {
+            case BIG_ENDIAN:
+                return readShort4D(in);
+            case LITTLE_ENDIAN:
+                return readShort49(in);
+            default:
+                throw new IllegalArgumentException("" + byteOrder);
+        }
+    }
+
+    public static int readShort(DataInput in, int byteOrder) throws IOException {
+        switch (byteOrder) {
+            case BIG_ENDIAN:
+                return readShort4D(in);
+            case LITTLE_ENDIAN:
+                return readShort49(in);
+            default:
+                throw new IllegalArgumentException("" + byteOrder);
+        }
+    }
+
+    /**
+     * read big/little-endian int
+     */
+    public static int readInt(InputStream in, int byteOrder) throws IOException {
+        switch (byteOrder) {
+            case BIG_ENDIAN:
+                return readInt4D(in);
+            case LITTLE_ENDIAN:
+                return readInt49(in);
+            default:
+                throw new IllegalArgumentException("" + byteOrder);
+        }
+    }
+
+    /**
+     * read big/little-endian int
+     */
+    public static int readInt(DataInput in, int byteOrder) throws IOException {
+        switch (byteOrder) {
+            case BIG_ENDIAN:
+                return readInt4D(in);
+            case LITTLE_ENDIAN:
+                return readInt49(in);
+            default:
+                throw new IllegalArgumentException("" + byteOrder);
+        }
+    }
+
+
+}
Index: ocean/src/com/imagero/uio/io/StringArrayReader.java
===================================================================
--- ocean/src/com/imagero/uio/io/StringArrayReader.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/StringArrayReader.java	(revision 0)
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://res.imagero.com
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.io;
+
+import java.io.IOException;
+import java.io.Reader;
+
+/**
+ * A character stream whose source is a string array.
+ * Very similar to java.io.StringReader.
+ * @author Andrey Kuznetsov
+ */
+public class StringArrayReader extends Reader {
+
+    private String[] str;
+    private int[] ends;
+    private int[] starts;
+    private int length;
+    private int next = 0;
+    private int mark = 0;
+
+    int current;
+
+    /**
+     * Create a new StringArrayReader.
+     *
+     * @param s  String array providing the character stream.
+     */
+    public StringArrayReader(String[] s) {
+        this.str = s;
+        for (int i = 0; i < s.length; i++) {
+            length += s[i].length();
+        }
+
+        starts = new int[s.length];
+        for (int i = 1; i < s.length; i++) {
+            starts[i] = starts[i - 1] + s[i - 1].length();
+        }
+
+        ends = new int[s.length];
+        ends[0] = s[0].length();
+        for (int i = 1; i < s.length; i++) {
+            ends[i] = ends[i - 1] + s[i].length();
+        }
+    }
+
+    /**
+     * Check to make sure that the stream has not been closed
+     */
+    private void ensureOpen() throws IOException {
+        if (str == null) {
+            throw new IOException("Stream closed");
+        }
+    }
+
+    /**
+     * Read a single character.
+     * @return The character read, or -1 if the end of the stream has been reached
+     * @exception IOException  If an I/O error occurs
+     */
+    public int read() throws IOException {
+        synchronized (lock) {
+            ensureOpen();
+            if (next >= length) {
+                return -1;
+            }
+            if (next >= ends[current]) {
+                current++;
+            }
+            return str[current].charAt(next++ - starts[current]);
+        }
+    }
+
+    /**
+     * Read characters into a portion of supplied char array.
+     *
+     * @param cbuf Destination char array
+     * @param off Where to start writing characters
+     * @param len Maximum number of characters to read
+     * @return The number of characters read, or -1 if the end of the stream has been reached
+     * @exception IOException If an I/O error occurs
+     */
+    public int read(char cbuf[], int off, int len) throws IOException {
+        synchronized (lock) {
+            ensureOpen();
+            if ((off < 0) || (off > cbuf.length) || (len < 0) || ((off + len) > cbuf.length) || ((off + len) < 0)) {
+                throw new IndexOutOfBoundsException();
+            }
+            else if (len == 0) {
+                return 0;
+            }
+            if (next >= length) {
+                return -1;
+            }
+            if (next >= ends[current]) {
+                current++;
+            }
+            int n = Math.min(ends[current] - next, len);
+            str[current].getChars(next - starts[current], next - starts[current] + n, cbuf, off);
+            next += n;
+            return n;
+        }
+    }
+
+    /**
+     * Skip characters.
+     * @exception  IOException  If an I/O error occurs
+     */
+    public long skip(long ns) throws IOException {
+        synchronized (lock) {
+            ensureOpen();
+            if (next >= length) {
+                return 0;
+            }
+            if (next >= ends[current]) {
+                current++;
+            }
+            long n = Math.min(ends[current] - next, ns);
+            next += n;
+            return n;
+        }
+    }
+
+    /**
+     * Tell whether this stream is ready to be read.
+     * @return True if the next read() is guaranteed not to block for input
+     * @exception IOException If the stream is closed
+     */
+    public boolean ready() throws IOException {
+        synchronized (lock) {
+            ensureOpen();
+            return true;
+        }
+    }
+
+    /**
+     * Tell whether this stream supports the mark() operation.
+     * @return true
+     */
+    public boolean markSupported() {
+        return true;
+    }
+
+    /**
+     * Mark the present position in the stream.
+     * Subsequent calls to reset() will reposition the stream to this point.
+     * @param  readAheadLimit Limit on the number of characters that may be
+     * read while still preserving the mark.
+     * @exception IllegalArgumentException If readAheadLimit is < 0
+     * @exception IOException If an I/O error occurs
+     */
+    public void mark(int readAheadLimit) throws IOException {
+        if (readAheadLimit < 0) {
+            throw new IllegalArgumentException("Read-ahead limit < 0");
+        }
+        synchronized (lock) {
+            ensureOpen();
+            mark = next;
+        }
+    }
+
+    /**
+     * Reset the stream to the most recent mark,
+     * or to the beginning of the string if it has never been marked.
+     * @exception IOException If an I/O error occurs
+     */
+    public void reset() throws IOException {
+        synchronized (lock) {
+            ensureOpen();
+            next = mark;
+            if (starts[current] > next) {
+                current++;
+            }
+        }
+    }
+
+    /**
+     * Close the stream.
+     */
+    public void close() {
+        str = null;
+    }
+}
Index: ocean/src/com/imagero/uio/io/PackBitsInputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/PackBitsInputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/PackBitsInputStream.java	(revision 0)
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * PackBits decoder
+ *
+ * @author Andrei Kouznetsov
+ */
+public class PackBitsInputStream extends FilterInputStream {
+
+    boolean finished;
+
+    int numSamples, value;
+    boolean copyLiter;
+
+    public PackBitsInputStream(InputStream in) {
+        super(in);
+    }
+
+    public int available() throws IOException {
+        if(finished) {
+            return 0;
+        }
+        return 1;
+    }
+
+    public void close() throws IOException {
+    }
+
+    public boolean markSupported() {
+        return false;
+    }
+
+    public synchronized void mark(int readlimit) {
+    }
+
+    public synchronized void reset() throws IOException {
+        throw new IOException("mark/reset not supported");
+    }
+
+    public int read(byte b[]) throws IOException {
+        return read(b, 0, b.length);
+    }
+
+    public int read(byte b[], int off, int len) throws IOException {
+        int i = off;
+
+        try {
+            for(; i < off + len; i++) {
+                int a = read();
+                if(a == -1) {
+                    i--;
+                    break;
+                }
+                b[i] = (byte) a;
+            }
+        }
+        catch(IOException ex) {
+            ex.printStackTrace();
+        }
+        return i - off;
+    }
+
+    public int read() throws IOException {
+        if(numSamples == 0) {
+            byte l = read128();
+            if(l < 0) {
+                numSamples = -l + 1;
+                copyLiter = false;
+                value = in.read();
+            }
+            else {
+                numSamples = l + 1;
+                copyLiter = true;
+            }
+        }
+        numSamples--;
+        if(copyLiter) {
+            return in.read();
+        }
+        else {
+            return value;
+        }
+    }
+
+    byte read128() throws IOException {
+        do {
+            byte a = (byte) in.read();
+            if(a != -128) {
+                return a;
+            }
+        }
+        while(true);
+    }
+}
Index: ocean/src/com/imagero/uio/io/PackBitsOutputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/PackBitsOutputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/PackBitsOutputStream.java	(revision 0)
@@ -0,0 +1,761 @@
+package com.imagero.uio.io;
+
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.ByteArrayInputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Date: 02.08.2007
+ *
+ * @author Andrey Kuznetsov
+ */
+public class PackBitsOutputStream extends FilterOutputStream {
+
+    private RunBuffer runs;
+    int width;
+    int cnt;
+
+    public PackBitsOutputStream(OutputStream out, int width) {
+        super(out);
+        runs = new RunBuffer(out);
+        this.width = width;
+    }
+
+    public void write(int b) throws IOException {
+        put(b);
+    }
+
+    private void put(int b) throws IOException {
+        runs.put(b);
+        if (cnt++ == width) {
+            flush();
+            cnt = 0;
+        }
+    }
+
+    public void write(byte b[]) throws IOException {
+        for (int i = 0; i < b.length; i++) {
+            put(b[i] & 0xFF);
+        }
+    }
+
+    public void write(byte b[], int off, int len) throws IOException {
+        for (int i = 0; i < len; i++) {
+            put(b[off + i] & 0xFF);
+        }
+    }
+
+    public void flush() throws IOException {
+        runs.flush();
+    }
+
+    public void close() throws IOException {
+        runs.flush();
+    }
+
+    static class RunOutputStream extends FilterOutputStream {
+        int maxRun;
+        int rep = -2;
+
+        int count;
+
+        public RunOutputStream(OutputStream out, int maxRun) {
+            super(out);
+            this.maxRun = maxRun;
+        }
+
+        public void write(int b) throws IOException {
+            if (b != rep) {
+                throw new IOException("Rep");
+            }
+            if (count++ == maxRun) {
+                out.write(count - 1);
+                out.write(rep);
+                count = 0;
+            }
+        }
+
+        public void set(int rep) {
+            this.rep = rep;
+            count = 2;
+        }
+
+        public void flush() throws IOException {
+            if (count != 0) {
+                out.write(count - 1);
+                out.write(rep);
+                count = 0;
+            }
+        }
+    }
+
+    static class RunBuffer {
+        int rep;
+        int count;
+
+        OutputStream out;
+        RunOutputStream ros;
+        private LiteralBuffer lbuf;
+
+        public RunBuffer(OutputStream out) {
+            this.out = out;
+            ros = new RunOutputStream(out, 128);
+            this.lbuf = new LiteralBuffer(out);
+        }
+
+        public void put(int k) throws IOException {
+            if (count == 0) {
+                rep = k;
+                count = 1;
+            } else {
+                if (rep == k) {
+                    if (count++ == 2) {
+                        ros.set(rep);
+                        lbuf.flush();
+                    } else if (count > 2) {
+                        ros.write(rep);
+                    }
+                } else {
+                    if (count > 2) {
+                        ros.flush();
+                    } else {
+                        for (int i = 0; i < count; i++) {
+                            lbuf.put(rep);
+                        }
+                    }
+                    rep = k;
+                    count = 1;
+                }
+            }
+        }
+
+        void flush() throws IOException {
+            if (count > 0) {
+                ros.flush();
+                for (int i = 0; i < count; i++) {
+                    lbuf.put(rep);
+                }
+                count = 0;
+            }
+            lbuf.flush();
+        }
+
+        void write() throws IOException {
+            lbuf.flush();
+            out.write(-(count - 1));
+            out.write(rep);
+            count = 0;
+        }
+    }
+
+    static class LiteralBuffer {
+        byte[] buffer = new byte[129];
+        int count;
+
+        OutputStream out;
+
+        public LiteralBuffer(OutputStream out) {
+            this.out = out;
+        }
+
+        public void put(int k) throws IOException {
+            buffer[count++] = (byte) k;
+            if (count == 128) {
+                write();
+            }
+        }
+
+        void write() throws IOException {
+            out.write(count - 1);
+            out.write(buffer, 0, count);
+            count = 0;
+        }
+
+        void flush() throws IOException {
+            if (count > 0) {
+                write();
+            }
+        }
+    }
+
+    static class PeekInputStream extends ByteArrayInputStream {
+        int offset;
+
+        public PeekInputStream(byte buf[]) {
+            super(buf);
+        }
+
+        public PeekInputStream(byte buf[], int offset, int length) {
+            super(buf, offset, length);
+            this.offset = offset;
+        }
+
+        public int peek(int offset) {
+            return buf[pos + offset];
+        }
+
+        public int getPosition() {
+            return pos - offset;
+        }
+
+        public void inc() {
+            pos++;
+        }
+    }
+
+    public static class PackBitsApache extends FilterOutputStream {
+
+        int bytesPerRow;
+        int inMax;
+        int inMaxMinus1;
+
+        PeekInputStream input;
+        byte[] tmp = new byte[128];
+
+        int wpos = 0;
+        byte[] row;
+
+        public PackBitsApache(OutputStream out, int bytesPerRow) {
+            super(out);
+            this.bytesPerRow = bytesPerRow;
+            inMax = bytesPerRow - 1;
+            inMaxMinus1 = inMax - 1;
+            this.row = new byte[bytesPerRow];
+        }
+
+        public void write(int b) throws IOException {
+            row[wpos++] = (byte) b;
+            if (wpos == bytesPerRow) {
+                wpos = 0;
+                input = new PeekInputStream(row);
+                packBits();
+            }
+        }
+
+        public void write(byte b[]) throws IOException {
+            write(b, 0, b.length);
+        }
+
+        public void write(byte b[], int off, int len) throws IOException {
+            for (int i = 0; i < len; i++) {
+                write(b[off + i] & 0xFF);
+            }
+        }
+
+        public void flush() throws IOException {
+            if (wpos > 0) {
+                input = new PeekInputStream(row, 0, wpos);
+                packBits();
+            }
+        }
+
+        private void packBits() throws IOException {
+            while (input.getPosition() <= inMax) {
+                doRunLoop();
+                doLiterLoop();
+            }
+        }
+
+        private void doLiterLoop() throws IOException {
+            int run = 0;
+            while (run < 128 &&
+                    ((input.getPosition() < inMax) && (input.peek(0) != input.peek(1))
+                    || ((input.getPosition() < inMaxMinus1) && (input.peek(0) != input.peek(2))))) {
+                tmp[run++] = (byte) input.read();
+            }
+
+            if (input.getPosition() == inMax && (run > 0 && run < 128)) {
+                tmp[run++] = (byte) input.read();
+            }
+
+            if (run > 0) {
+                out.write(run - 1);
+                out.write(tmp, 0, run);
+            } else if (input.getPosition() == inMax) {
+                out.write(0);
+                out.write(input.read() & 0xFF);
+            }
+        }
+
+        private void doRunLoop() throws IOException {
+            int run = 1;
+            byte replicate = (byte) input.peek(0);
+            while (run < 127 && input.getPosition() < inMax && input.peek(0) == input.peek(1)) {
+                run++;
+                input.inc();
+            }
+            if (run > 1) {
+                input.inc();
+                out.write(-(run - 1));
+                out.write(replicate);
+            }
+        }
+    }
+
+    static class PBApache {
+        static int compressPackBits(byte[] data, int numRows, int bytesPerRow, byte[] compData) {
+            int inOffset = 0;
+            int outOffset = 0;
+            byte[] tmp = new byte[128];
+            for (int i = 0; i < numRows; i++) {
+                outOffset = packBits(data, inOffset, bytesPerRow, compData, outOffset, tmp);
+                inOffset += bytesPerRow;
+            }
+            return outOffset;
+        }
+
+        private static int packBits(byte[] input, int inOffset, int inCount, byte[] output, int outOffset, byte[] tmp) {
+            int inMax = inOffset + inCount - 1;
+            int inMaxMinus1 = inMax - 1;
+            while (inOffset <= inMax) {
+                int run = 1;
+                byte replicate = input[inOffset];
+                while (run < 127 && inOffset < inMax && input[inOffset] == input[inOffset + 1]) {
+                    run++;
+                    inOffset++;
+                }
+                if (run > 1) {
+                    inOffset++;
+                    output[outOffset++] = (byte) (-(run - 1));
+                    output[outOffset++] = replicate;
+                }
+                run = 0;
+                while (run < 128 && ((inOffset < inMax && input[inOffset] != input[inOffset + 1]) || (inOffset < inMaxMinus1 && input[inOffset] != input[inOffset + 2]))) {
+                    tmp[run++] = input[inOffset++];
+                }
+                if (inOffset == inMax && (run > 0 && run < 128)) {
+                    tmp[run++] = input[inOffset++];
+                }
+                if (run > 0) {
+                    output[outOffset++] = (byte) (run - 1);
+                    for (int i = 0; i < run; i++) {
+                        output[outOffset++] = tmp[i];
+                    }
+                } else if (inOffset == inMax) {
+                    output[outOffset++] = (byte) 0;
+                    output[outOffset++] = input[inOffset++];
+                }
+            }
+            return outOffset;
+        }
+    }
+
+//    public static void main(String[] args) throws IOException {
+//        String s = "C:\\Support\\AndySwanson\\work\\IMG_0703_source_RC1.jpg";
+////        String s = "C:\\Support\\AndySwanson\\work\\IMG_0703_source_RC1_LZW.tif";
+////        String s = "C:\\Support\\AndySwanson\\work\\IMG_0703_source_RC1_RLE.tif";
+////        String d = "C:\\Support\\AndySwanson\\work\\IMG_0703_source_RC1.tif";
+//
+//
+//        ImageReader reader = ReaderFactory.createReader(s);
+//        int[] data = (int[]) reader.getAsArray(0, true);
+//        byte[] bdata = new byte[data.length * 3];
+//
+//        IntArrayInputStream iais = new IntArrayInputStream(data);
+//        SkipBytesInputStream sbis = new SkipBytesInputStream(iais, 2 + 4 + 8, 4);
+//
+//        ByteArrayOutputStream2 out2 = new ByteArrayOutputStream2(bdata);
+//        IOutils.copy(sbis, out2);
+//
+//        int w = reader.getWidth(0);
+//        int h = reader.getHeight(0);
+//        int w3 = w * 3;
+//
+//        ByteArrayOutputStream bout = new ByteArrayOutputStream();
+//        PackBitsApache packBitsApache = new PackBitsApache(bout, w3);
+//        packBitsApache.write(bdata);
+//        packBitsApache.close();
+//        byte[] res = bout.toByteArray();
+//
+//        byte[] compData = new byte[res.length];
+//        int length = PBApache.compressPackBits(bdata, h, w3, compData);
+//
+//        System.out.println(compData.length);
+//        System.out.println(res.length);
+//
+//        for (int i = 0; i < length; i++) {
+//            if (compData[i] != res[i]) {
+//                System.out.println(i);
+//                System.out.println(compData[i]);
+//                System.out.println(res[i]);
+//                return;
+//            }
+//        }
+//        System.out.println("identisch");
+//
+//
+//        ByteArrayOutputStream bout2 = new ByteArrayOutputStream();
+////        ParserOutputStream pos = new ParserOutputStream();
+////        MultiplexOutputStream mos = new MultiplexOutputStream(bout2, pos);
+////        mos.setSearchPosition(1930);
+////        mos.addActionListener(new ActionListener() {
+////            public void actionPerformed(ActionEvent e) {
+////                System.out.println("position");
+////            }
+////        });
+//
+////        PackBitsOutputStream pbos = new PackBitsOutputStream(mos, w3);
+//        Control pbos = new Control(bout2, w3);
+//        pbos.write(bdata);
+//        byte[] pbosData = bout2.toByteArray();
+//        System.out.println(pbosData.length + " " + length);
+//
+//        for (int i = 0; i < length; i++) {
+//            int k1 = pbosData[i] & 0xFF;
+//            int m1 = res[i] & 0xFF;
+//            if (k1 == m1) {
+////                System.out.println(Integer.toHexString(k) + " " + Integer.toHexString(m));
+//            } else {
+////                int k = pbosData[i - 2] & 0xFF;
+////                int k0 = pbosData[i - 1] & 0xFF;
+////                int k2 = pbosData[i + 1] & 0xFF;
+////                int k3 = pbosData[i + 2] & 0xFF;
+////                int k4 = pbosData[i + 3] & 0xFF;
+////                int m = res[i - 2] & 0xFF;
+////                int m0 = res[i - 1] & 0xFF;
+////                int m2 = res[i + 1] & 0xFF;
+////                int m3 = res[i + 2] & 0xFF;
+////                int m4 = res[i + 3] & 0xFF;
+////                System.out.println((i - 2) + " " + Integer.toHexString(k) + " " + Integer.toHexString(m) + " *");
+////                System.out.println((i - 1) + " " + Integer.toHexString(k0) + " " + Integer.toHexString(m0) + " *");
+//                System.out.println(i + " " + Integer.toHexString(k1) + " " + Integer.toHexString(m1) + " *");
+////                System.out.println((i + 1) + " " + Integer.toHexString(k2) + " " + Integer.toHexString(m2) + " *");
+////                System.out.println((i + 2) + " " + Integer.toHexString(k3) + " " + Integer.toHexString(m3) + " *");
+////                System.out.println((i + 3) + " " + Integer.toHexString(k4) + " " + Integer.toHexString(m4) + " *");
+////                break;
+////                System.out.println("*****************");
+//            }
+//        }
+//    }
+
+    static class MultiplexOutputStream extends OutputStream {
+
+        OutputStream out1, out2;
+        long p = 0;
+
+        long searchPosition = -1;
+
+        ActionListener listener;
+
+        public MultiplexOutputStream(OutputStream out1, OutputStream out2) {
+            this.out1 = out1;
+            this.out2 = out2;
+        }
+
+        public void write(int b) throws IOException {
+            inc();
+            out1.write(b);
+            out2.write(b);
+        }
+
+        public void addActionListener(ActionListener listener) {
+            this.listener = AWTEventMulticaster.add(this.listener, listener);
+        }
+
+        public void removeActionListener(ActionListener listener) {
+            this.listener = AWTEventMulticaster.remove(this.listener, listener);
+        }
+
+        public long getSearchPosition() {
+            return searchPosition;
+        }
+
+        public void setSearchPosition(long searchPosition) {
+            this.searchPosition = searchPosition;
+        }
+
+        private void inc() {
+            if (p++ == searchPosition) {
+                fireActionEvent();
+            }
+        }
+
+        private void fireActionEvent() {
+            if (listener != null) {
+                listener.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "Position"));
+            }
+        }
+
+        public void write(byte b[]) throws IOException {
+            inc(b.length);
+            out1.write(b);
+            out2.write(b);
+        }
+
+        private void inc(int length) {
+            p += length;
+            if (p - length <= searchPosition && p >= searchPosition) {
+                fireActionEvent();
+            }
+        }
+
+        public void write(byte b[], int off, int len) throws IOException {
+            inc(len);
+            out1.write(b, off, len);
+            out2.write(b, off, len);
+        }
+
+        public void flush() throws IOException {
+            out1.flush();
+            out2.flush();
+        }
+
+        public void close() throws IOException {
+            out1.close();
+            out2.close();
+        }
+    }
+
+//    static class ParserOutputStream extends OutputStream {
+//
+//        StreamParser sp;
+//        long p;
+//
+//        public ParserOutputStream() {
+//            sp = new StreamParser() {
+//                protected StreamParser.CharHandler createParameterHandler() {
+//                    return new ParameterHandler(this, new char[0], new char[0], new char[0]) {
+//                        public boolean nextChar(char c, long offset) {
+//                            ICharSequence[] prms = new ICharSequence[params.getCount()];
+//                            Entry entry = putParam(prms, offset);
+//                            parser.postParserEvent(this, entry);
+//                            setHandler(getNext());
+//                            return false;
+//                        }
+//                    };
+//                }
+//            };
+//            byte[] bstr = {0x53, 0x61, 0x0, 0x6f, 0x11, 0x7a};
+//            sp.register(new VKey(new String(bstr), VKey.KEY_TYPE_COMMENT));
+//
+//            sp.addParserListener(new ParserListener() {
+//                public boolean gotToken(ParserEvent e) {
+//                    Entry entry = e.getEntry();
+//                    long offset = entry.getOffset();
+//                    Object value = entry.getValue();
+//                    System.out.println(offset);
+//                    System.out.println(value);
+//                    return true;
+//                }
+//            });
+//        }
+//
+//        public void write(int b) throws IOException {
+//            sp.nextChar((char) b, p++);
+//        }
+//
+//        public void write(byte b[]) throws IOException {
+//            write(b, 0, b.length);
+//        }
+//
+//        public void write(byte b[], int off, int len) throws IOException {
+//            for (int i = 0; i < len; i++) {
+//                sp.nextChar((char) b[off + i], p++);
+//            }
+//        }
+//    }
+
+    static interface Writer {
+        boolean next(byte b) throws IOException;
+
+        byte get();
+
+        void flush() throws IOException;
+
+        Writer nextWriter();
+    }
+
+    static class Control extends FilterOutputStream {
+        Run run;
+        Liter liter;
+
+        Writer current;
+        Writer last;
+
+        int count;
+        int max;
+
+        public Control(OutputStream out, int max) {
+            super(out);
+            this.max = max;
+            run = new Run(out);
+            liter = new Liter(out);
+            run.nextWriter = liter;
+            liter.nextWriter = run;
+            current = run;
+        }
+
+        public void write(int b) throws IOException {
+            count++;
+            if (!current.next((byte) b)) {
+                last = current;
+                current = current.nextWriter();
+                byte n;
+                while ((n = last.get()) != -1) {
+                    current.next(n);
+                }
+            }
+            if (count == max) {
+                count = 0;
+                current.flush();
+            }
+        }
+
+        public void write(byte b[], int off, int len) throws IOException {
+            for (int i = 0; i < b.length; i++) {
+                write(b[i] & 0xFF);
+            }
+        }
+    }
+
+    static class Run implements Writer {
+        int run;
+        int count;
+        int w;
+
+        int rep;
+
+        byte last;
+
+        Writer nextWriter;
+
+        OutputStream out;
+
+        public Run(OutputStream out) {
+            this.out = out;
+        }
+
+        public Writer nextWriter() {
+            return nextWriter;
+        }
+
+        void init(byte rep, int count, int run) {
+            this.rep = rep;
+            this.run = run;
+            this.count = count;
+        }
+
+        public byte get() {
+            byte tmp = last;
+            last = -1;
+            return tmp;
+        }
+
+        public boolean next(byte b) throws IOException {
+            last = b;
+            if (run == 0) {
+                rep = b & 0xFF;
+                run++;
+                return true;
+            }
+            if (b == rep) {
+                run++;
+                count++;
+                if (run == 128) {
+                    write();
+                }
+                return true;
+            } else {
+                if (run > 1) {
+                    write();
+                }
+                return false;
+            }
+        }
+
+        private void write() throws IOException {
+            out.write(-(run - 1));
+            out.write(rep);
+            run = 0;
+        }
+
+        public void flush() throws IOException {
+            if (run > 0) {
+                write();
+            }
+        }
+    }
+
+    static class Liter implements Writer {
+        int b0 = -1;
+        int b1 = -1;
+        int b2 = -1;
+        int run;
+        int count;
+        int max;
+
+        int pos;
+        byte[] buffer = new byte[128];
+
+        Writer nextWriter;
+
+        OutputStream out;
+
+        public Liter(OutputStream out) {
+            this.out = out;
+        }
+
+        public Writer nextWriter() {
+            return nextWriter;
+        }
+
+        public boolean next(byte a) throws IOException {
+            b2 = a & 0xFF;
+            if (run > 0) {
+                if (b0 == b1 && b0 == b2) {
+                    write();
+                    return false;
+                }
+            }
+            shift();
+            if (run == 128) {
+                write();
+            }
+            return true;
+        }
+
+        void shift() {
+            if (b0 != -1) {
+                add(b0);
+            }
+            b0 = b1;
+            b1 = b2;
+            b2 = -1;
+        }
+
+        public byte get() {
+            byte b = (byte) b0;
+            b0 = b1;
+            b1 = -1;
+            return b;
+        }
+
+        private void add(int b) {
+            buffer[run++] = (byte) b;
+            count++;
+        }
+
+        public void flush() throws IOException {
+            if (run > 0 && run < 128) {
+                shift();
+                write();
+            } else if (run == 0) {
+                shift();
+                out.write(0);
+                out.write(buffer[0] & 0xFF);
+            }
+        }
+
+        private void write() throws IOException {
+            if (run > 0) {
+                out.write(run - 1);
+                out.write(buffer, 0, run);
+            }
+            run = 0;
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/io/TargaRLEInputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/TargaRLEInputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/TargaRLEInputStream.java	(revision 0)
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @author Andrey Kuznetsov
+ */
+public class TargaRLEInputStream extends RLEInputStream {
+
+    int numSamples;
+    byte [] value;
+    boolean rawPacket;
+    int pixelSize;
+    int vindex;
+
+    public TargaRLEInputStream(InputStream in, int pixelSize) {
+        super(in);
+        this.pixelSize = pixelSize;
+        value = new byte[pixelSize];
+    }
+
+    public int read() throws IOException {
+        if (numSamples == 0) {
+            int v = in.read();
+            if(v == -1) {
+                return -1;
+            }
+            if ((v >> 7) == 1) {
+                for (int i = 0; i < value.length; i++) {
+                    value[i] = (byte) in.read();
+                }
+                numSamples = ((v & 0x7F) + 1) * pixelSize;
+                rawPacket = false;
+            }
+            else {
+                numSamples = (v + 1) * pixelSize;
+                rawPacket = true;
+            }
+        }
+        numSamples--;
+        if (rawPacket) {
+            return in.read();
+        }
+        else {
+            int b = value[vindex++] & 0xFF;
+            if(vindex == pixelSize) {
+                vindex = 0;
+            }
+            return b;
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/io/RandomAccessInputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/RandomAccessInputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/RandomAccessInputStream.java	(revision 0)
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import com.imagero.uio.RandomAccessInput;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * same as FilterInputStream but with RandomAccessIO
+ * @author Kouznetsov Andrei
+ */
+public class RandomAccessInputStream extends InputStream {
+	protected static long MARK_UNDEFINED = -1L;
+
+	protected RandomAccessInput ro;
+	protected long pos;
+	protected long mark = MARK_UNDEFINED;
+	protected long startPos;
+
+
+	public RandomAccessInputStream(RandomAccessInput ro) {
+		this(ro, 0L);
+	}
+
+	public RandomAccessInputStream(RandomAccessInput ro, long startPos) {
+		this.ro = ro;
+		this.pos = startPos;
+		this.startPos = startPos;
+	}
+
+	synchronized public int read() throws IOException {
+		checkPos();
+		int a = ro.read();
+		pos++;
+		return a;
+	}
+
+	public int read(byte[] b) throws IOException {
+		return read(b, 0, b.length);
+	}
+
+	synchronized public int read(byte[] b, int off, int len) throws IOException {
+		checkPos();
+        if(ro == null) {
+            return -1;
+        }
+		int r = ro.read(b, off, len);
+		pos += r;
+		return r;
+	}
+
+	protected void checkPos() throws IOException {
+        moved = false;
+		if(ro != null && ro.getFilePointer() != pos) {
+			ro.seek(pos);
+		}
+	}
+
+    boolean moved;
+
+    /**
+     * releases reference to RandomAccessRO, but does not closes it
+     */
+	public void close() throws IOException {
+        if(moved) {
+            ro.seek(pos);
+        }
+		ro = null;
+	}
+
+	public int available() throws IOException {
+		return (int)(ro.length() - pos);
+	}
+
+	public boolean markSupported() {
+		return true;
+	}
+
+	public void mark(int i) {
+		mark = pos;
+	}
+
+	public void reset() throws IOException {
+		if(mark == MARK_UNDEFINED) {
+			throw new IOException("mark undefined");
+		}
+        moved = true;
+		pos = mark;
+	}
+
+	public long skip(long l) throws IOException {
+		long skip = Math.min(ro.length() - pos, l);
+		pos += skip;
+        moved = true;
+		return skip;
+	}
+}
Index: ocean/src/com/imagero/uio/io/App13InputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/App13InputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/App13InputStream.java	(revision 0)
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Read one or more App13 block(s)
+ * @author Andrey Kuznetsov
+ */
+public class App13InputStream extends FilterInputStream implements JpegConstants {
+
+    private static final byte[] ID8 = "8BIM".getBytes();
+    private static final byte[] PHOTOSHOP = "Photoshop3.0".getBytes();
+
+    boolean finished;
+
+    /**
+     * Create new App13InputStream.
+     * Note that <code>in</code> should support <code>mark()</code>
+     * @param in InputStream
+     * @throws IOException
+     */
+    public App13InputStream(InputStream in) throws IOException {
+        super(in);
+    }
+
+    void initBlock() throws IOException {
+        in.mark(3);
+        int marker = in.read();
+        int app13 = in.read();
+        if (marker != MARKER || app13 != APP_13) {
+            finished = true;
+            in.reset();
+            return;
+        }
+        length = (in.read() << 8) | (in.read() & 0xFF) - 2;
+
+        in.mark(4);
+        byte[] b = new byte[4];
+        for (int i = 0; i < 4; i++) {
+            b[i] = (byte) in.read();
+        }
+        in.reset();
+
+        boolean photoshop = true;
+
+        //some applications "forget" to write 'Photoshop 3.0' Identifier
+        for (int i = 0; i < b.length; i++) {
+            if (b[i] != PHOTOSHOP[i]) {
+                photoshop = false;
+                break;
+            }
+        }
+
+        if (photoshop) {
+            for (int i = 0; i < 14; i++) {
+                in.read();
+            }
+            length -= 14;
+        }
+        else {
+            for (int i = 0; i < b.length; i++) {
+                if (b[i] != ID8[i]) {
+                    throw new IOException("not App13 stream");
+                }
+            }
+        }
+    }
+
+    int length;
+
+    public int read() throws IOException {
+        if (finished) {
+            return -1;
+        }
+        if (length == 0) {
+            initBlock();
+            if (finished) {
+                return -1;
+            }
+        }
+        length--;
+        return super.read();
+    }
+
+    public int read(byte b[]) throws IOException {
+        return read(b, 0, b.length);
+    }
+
+    public int read(byte b[], int off, int len) throws IOException {
+        if (b == null) {
+            throw new NullPointerException();
+        }
+        if (off + len > b.length || off < 0) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        int read = 1;
+        int a = read();
+        if (a == -1) {
+            return -1;
+        }
+        b[off] = (byte) a;
+        for (int i = off + 1; i < len; i++) {
+            a = read();
+            if (a == -1) {
+                break;
+            }
+            read++;
+            b[i] = (byte) a;
+        }
+        return read;
+    }
+
+    public long skip(long n) throws IOException {
+        long remaining = n;
+        while (remaining > 0) {
+            int a = read();
+            if (a == -1) {
+                break;
+            }
+            remaining--;
+        }
+        return n - remaining;
+    }
+
+    public int available() throws IOException {
+        if (finished) {
+            return 0;
+        }
+        if (length == 0) {
+            initBlock();
+            if (finished) {
+                return 0;
+            }
+        }
+        return length;
+    }
+
+    public synchronized void mark(int readlimit) {
+
+    }
+
+    public synchronized void reset() throws IOException {
+
+    }
+
+    public boolean markSupported() {
+        return false;
+    }
+}
Index: ocean/src/com/imagero/uio/io/App13OutputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/App13OutputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/App13OutputStream.java	(revision 0)
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Utility to write data into App13 block(s).
+ * If data is longer as given App13 size then multiple App13 blocks are written.
+ * @author Andrey Kuznetsov
+ */
+public class App13OutputStream extends FilterOutputStream {
+
+    int count;
+    byte[] buffer;
+    private byte[] header = {(byte) 0xFF, (byte) 0xED, 0, 0, 'P', 'h', 'o', 't', 'o', 's', 'h', 'o', 'p', ' ', '3', '.', '0', 0};
+
+    /**
+     * create new App13OutputStream with default App13 size (32000)
+     * @param out OutputStream
+     */
+    public App13OutputStream(OutputStream out) {
+        this(out, 32000);
+    }
+
+    /**
+     * create App13OutputStream with user defined App13 size
+     * @param out OutputStream
+     * @param length length of App13
+     */
+    public App13OutputStream(OutputStream out, int length) {
+        super(out);
+        buffer = new byte[length];
+        for (int i = 0; i < header.length; i++) {
+            buffer[i] = header[i];
+        }
+        count = header.length;
+    }
+
+    public synchronized void write(int b) throws IOException {
+        if (count >= buffer.length) {
+            flushBuffer();
+        }
+        buffer[count++] = (byte) b;
+    }
+
+    protected void flushBuffer() throws IOException {
+        if (count > header.length) {
+            int cnt = count - 2;
+            buffer[2] = (byte) ((cnt >> 8) & 0xFF);
+            buffer[3] = (byte) (cnt & 0xFF);
+            out.write(buffer, 0, count);
+            count = header.length;
+        }
+    }
+
+    public void write(byte b[]) throws IOException {
+        write(b, 0, b.length);
+    }
+
+    public void write(byte b[], int off, int len) throws IOException {
+        for (int i = off; i < len; i++) {
+            write(b[i]);
+        }
+    }
+
+    public void flush() throws IOException {
+        flushBuffer();
+        out.flush();
+    }
+}
Index: ocean/src/com/imagero/uio/io/ASCII85InputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/ASCII85InputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/ASCII85InputStream.java	(revision 0)
@@ -0,0 +1,241 @@
+/*
+ * Copyright (c) 2003, www.pdfbox.org
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of pdfbox; nor the names of its
+ *    contributors may be used to endorse or promote products derived from this
+ *    software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://www.pdfbox.org
+ *
+ */
+package com.imagero.uio.io;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * This class represents an ASCII85 stream.
+ *
+ * @author Ben Litchfield
+ * @version $Revision: 1.2 $
+ */
+public class ASCII85InputStream extends FilterInputStream {
+    private int index;
+    private int n;
+    private boolean eof;
+
+    private byte[] ascii;
+    private byte[] b;
+
+    /**
+     * Constructor.
+     *
+     * @param is The input stream to actually read from.
+     */
+    public ASCII85InputStream(InputStream is) {
+        super(is);
+        ascii = new byte[5];
+        b = new byte[4];
+    }
+
+    /**
+     * This will read the next byte from the stream.
+     *
+     * @return The next byte read from the stream.
+     *
+     * @throws IOException If there is an error reading from the wrapped stream.
+     */
+    public final int read() throws IOException {
+        if (index >= n) {
+            if (eof) {
+                return -1;
+            }
+            index = 0;
+            int k;
+            byte z;
+            do {
+                int zz = (byte) in.read();
+                if (zz == -1) {
+                    eof = true;
+                    return -1;
+                }
+                z = (byte) zz;
+            }
+            while (z == '\n' || z == '\r' || z == ' ');
+
+            if (z == '~' || z == 'x') {
+                eof = true;
+                ascii = b = null;
+                n = 0;
+                return -1;
+            }
+            else if (z == 'z') {
+                b[0] = b[1] = b[2] = b[3] = 0;
+                n = 4;
+            }
+            else {
+                ascii[0] = z; // may be EOF here....
+                for (k = 1; k < 5; ++k) {
+                    do {
+                        int zz = (byte) in.read();
+                        if (zz == -1) {
+                            eof = true;
+                            return -1;
+                        }
+                        z = (byte) zz;
+                    }
+                    while (z == '\n' || z == '\r' || z == ' ');
+                    ascii[k] = z;
+                    if (z == '~' || z == 'x') {
+                        break;
+                    }
+                }
+                n = k - 1;
+                if (n == 0) {
+                    eof = true;
+                    ascii = null;
+                    b = null;
+                    return -1;
+                }
+                if (k < 5) {
+                    for (++k; k < 5; ++k) {
+                        ascii[k] = 0x21;
+                    }
+                    eof = true;
+                }
+                // decode stream
+                long t = 0;
+                for (k = 0; k < 5; ++k) {
+                    z = (byte) (ascii[k] - 0x21);
+                    if (z < 0 || z > 93) {
+                        n = 0;
+                        eof = true;
+                        ascii = null;
+                        b = null;
+                        throw new IOException("Invalid data in Ascii85 stream");
+                    }
+                    t = (t * 85L) + z;
+                }
+                for (k = 3; k >= 0; --k) {
+                    b[k] = (byte) (t & 0xFFL);
+                    t >>>= 8;
+                }
+            }
+        }
+        return b[index++] & 0xFF;
+    }
+
+    /**
+     * This will read a chunk of data.
+     *
+     * @param data The buffer to write data to.
+     * @param offset The offset into the data stream.
+     * @param len The number of byte to attempt to read.
+     *
+     * @return The number of bytes actually read.
+     *
+     * @throws IOException If there is an error reading data from the underlying stream.
+     */
+    public final int read(byte[] data, int offset, int len) throws IOException {
+        if (eof && index >= n) {
+            return -1;
+        }
+        for (int i = 0; i < len; i++) {
+            if (index < n) {
+                data[i + offset] = b[index++];
+            }
+            else {
+                int t = read();
+                if (t == -1) {
+                    return i;
+                }
+                data[i + offset] = (byte) t;
+            }
+        }
+        return len;
+    }
+
+    /**
+     * This will close the underlying stream and release any resources.
+     *
+     * @throws IOException If there is an error closing the underlying stream.
+     */
+    public void close() throws IOException {
+        ascii = null;
+        eof = true;
+        b = null;
+        super.close();
+    }
+
+    /**
+     * non supported interface methods.
+     *
+     * @return False always.
+     */
+    public boolean markSupported() {
+        return false;
+    }
+
+    /**
+     * Unsupported.
+     *
+     * @param nValue ignored.
+     *
+     * @return Always zero.
+     */
+    public long skip(long nValue) {
+        return 0;
+    }
+
+    /**
+     * Unsupported.
+     *
+     * @return Always zero.
+     */
+    public int available() {
+        if (eof) {
+            return 0;
+        }
+        else {
+            return 1;
+        }
+    }
+
+    /**
+     * Unsupported.
+     *
+     * @param readlimit ignored.
+     */
+    public void mark(int readlimit) {
+    }
+
+    /**
+     * Unsupported.
+     *
+     * @throws IOException telling that this is an unsupported action.
+     */
+    public void reset() throws IOException {
+        throw new IOException("Reset is not supported");
+    }
+}
Index: ocean/src/com/imagero/uio/io/Base64.java
===================================================================
--- ocean/src/com/imagero/uio/io/Base64.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/Base64.java	(revision 0)
@@ -0,0 +1,217 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+
+/**
+ * This class shows how simple and straightforward can be implementation
+ * of base 64 codec using BitInputStream and BitOutputStream
+ * @author Andrey Kuznetsov
+ */
+public class Base64 {
+
+    private static final char encodeTable[] = {
+        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+        'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+        'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+        'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
+    };
+
+    static int decodeTable [] = new int[0x100];
+    static int lineLength = 72;
+
+    public static int getLineLength() {
+        return lineLength;
+    }
+
+    public static void setLineLength(int lineLength) {
+        Base64.lineLength = lineLength;
+    }
+
+
+    static {
+        for (int i = 0; i < decodeTable.length; i++) {
+            decodeTable[i] = -1;
+        }
+        for (int j = 0; j < encodeTable.length; j++) {
+            decodeTable[encodeTable[j]] = j;
+        }
+    }
+
+    private static final char [] crlf = "\n".toCharArray();
+
+    /**
+     * base 64 encode
+     * @param b data to encode
+     * @return base 64 encoded String
+     * @throws IOException
+     */
+    public static String base64Encode(byte[] b) throws IOException {
+        BitInputStream bis = new BitInputStream(new ByteArrayInputStream(b));
+        bis.setBitsToRead(6);
+        StringBuffer sb = new StringBuffer();
+        int cnt = 0;
+        while (true) {
+            int c = bis.read();
+            //EOF reached
+            if (c == -1) {
+                break;
+            }
+            sb.append(encodeTable[c]);
+            if(++cnt == lineLength) {
+                sb.append("\n");
+                cnt = 0;
+            }
+        }
+        while ((sb.length() % 4) != 0) {
+            //padding
+            sb.append('=');
+        }
+        return sb.toString();
+    }
+
+    /**
+     * base64 encode data from InputStream and write ict to given character stream
+     * @param in InputStream
+     * @param out Writer
+     * @throws IOException
+     */
+    public static void base64Encode(InputStream in, Writer out) throws IOException {
+        BitInputStream bis = new BitInputStream(in);
+        char[] buffer = new char[4000];
+        bis.setBitsToRead(6);
+        int cnt = 0;
+        int bufferPtr = 0;
+        while (true) {
+            int c = bis.read();
+            //EOF reached
+            if (c == -1) {
+                break;
+            }
+            buffer[bufferPtr++] = encodeTable[c];
+            if(++cnt == lineLength) {
+                if(bufferPtr + crlf.length >= buffer.length) {
+                    out.write(buffer, 0, bufferPtr);
+                    bufferPtr = 0;
+                }
+                for (int i = 0; i < crlf.length; i++) {
+                   buffer[bufferPtr++] = crlf[i];
+                }
+                cnt = 0;
+            }
+            if (bufferPtr == buffer.length) {
+                out.write(buffer);
+                bufferPtr = 0;
+            }
+        }
+        while ((bufferPtr % 4) != 0) {
+            //padding
+            buffer[bufferPtr++] ='=';
+        }
+        if (bufferPtr > 0) {
+            out.write(buffer, 0, bufferPtr);
+        }
+    }
+
+    /**
+     * decode base 64 encoded string
+     * @param s String to decode
+     * @return byte array
+     * @throws IOException
+     */
+    public static byte[] base64Decode(String s) throws IOException {
+        char[] chrs = s.toCharArray();
+        ByteArrayOutputStream bout = new ByteArrayOutputStream();
+        BitOutputStream bos = new BitOutputStream(bout);
+        bos.setBitsToWrite(6);
+
+        boolean flush = true;
+
+        for (int i = 0; i < chrs.length; i++) {
+            char c = chrs[i];
+            //start of padding
+            if (c == '=') {
+                flush = false;
+                break;
+            }
+            int c0 = decodeTable[c];
+            if(c0 != -1) {
+                bos.write(c0);
+            }
+        }
+        if (flush) {
+            bos.flush();
+        }
+        return bout.toByteArray();
+    }
+
+
+    /**
+     * decode base64 encoded character stream and write it to given OutputStream
+     * @param in Reader character stream
+     * @param out OutputStream
+     * @throws IOException
+     */
+    public static void base64Decode(Reader in, OutputStream out) throws IOException {
+        BitOutputStream bos = new BitOutputStream(out);
+        bos.setBitsToWrite(6);
+
+        boolean flush = true;
+
+        while (true) {
+            int c = in.read();
+            if (c == -1) {
+                break;
+            }
+            //start of padding
+            if (c == '=') {
+                flush = false;
+                break;
+            }
+            final int b = decodeTable[c];
+            if(b != -1) {
+                bos.write(b);
+            }
+        }
+        if (flush) {
+            bos.flush();
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/io/Base64DInputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/Base64DInputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/Base64DInputStream.java	(revision 0)
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://res.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * InputStream for decoding data from base64 encoded String array
+ * @author Andrey Kuznetsov
+ */
+public class Base64DInputStream extends InputStream {
+
+    boolean finished;
+
+    protected byte[] buffer;
+    String[] s;
+
+    int current;
+
+    int p;
+
+    public Base64DInputStream(String[] s) {
+        this.s = s;
+    }
+
+    /**
+     * decode next byte
+     * @return int
+     * @throws IOException
+     */
+    public int read() throws IOException {
+        if (finished) {
+            return -1;
+        }
+        if (buffer == null || p >= buffer.length) {
+            next();
+        }
+        if (buffer == null) {
+            finished = true;
+            return -1;
+        }
+        try {
+            return buffer[p++] & 0xFF;
+        }
+        catch (ArrayIndexOutOfBoundsException ex) {
+            System.err.println(p + " " + buffer.length);
+            throw ex;
+        }
+    }
+
+    /**
+     * switch to next String
+     */
+    protected void next() throws IOException {
+        if (finished) {
+            buffer = null;
+            p = 0;
+            return;
+        }
+        if (current < s.length) {
+            String s = this.s[current++];
+            buffer = Base64.base64Decode(s);
+            p = 0;
+        }
+        else {
+            finished = true;
+            buffer = null;
+            p = 0;
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/io/BitDataOutputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/BitDataOutputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/BitDataOutputStream.java	(revision 0)
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrei Kouznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import com.imagero.uio.FilterDataOutput;
+
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * adds ability to write streams bitwise
+ * @author Andrey Kuznetsov
+ */
+public class BitDataOutputStream extends FilterDataOutput {
+
+    protected static final int[] mask = new int[64];
+
+    static {
+        for (int i = 0; i < mask.length; i++) {
+            mask[i] =  (1 << (i + 1)) - 1;
+        }
+    }
+
+    protected int bitbuf;
+    protected int vbits;
+
+    private int bitsToWrite = 8;
+
+    protected byte [] flipTable = BitInputStream.getFlipTable();
+
+    protected boolean invertBitOrder;
+
+    protected int fillByte = 0;
+
+    public BitDataOutputStream(DataOutput out) {
+        super(out);
+    }
+
+    public int getBitsToWrite() {
+        return bitsToWrite;
+    }
+
+    /**
+     * set how much bits should be written to stream every write() call
+     * @param bitsToWrite
+     */
+    public void setBitsToWrite(int bitsToWrite) {
+        this.bitsToWrite = bitsToWrite;
+    }
+
+    public boolean isInvertBitOrder() {
+        return invertBitOrder;
+    }
+
+    public void setInvertBitOrder(boolean invertBitOrder) {
+        this.invertBitOrder = invertBitOrder;
+    }
+
+    /**
+     * Writes some bits from the specified int to stream.
+     * @param b int which should be written
+     * @throws IOException if an I/O error occurs
+     * @see #setBitsToWrite
+     * @see #getBitsToWrite
+     */
+    public void write(int b) throws IOException {
+        write(b, bitsToWrite);
+    }
+
+    /**
+     * Writes some bits from the specified int to stream.
+     * @param b int which should be written
+     * @param nbits bit count to write
+     * @throws IOException if an I/O error occurs
+     */
+    public void write(int b, int nbits) throws IOException {
+        if (nbits == 0) {
+            return;
+        }
+        final int k = b & mask[nbits];
+        bitbuf = (bitbuf << nbits) | k;
+        vbits += nbits;
+
+        write8();
+    }
+
+    protected void write8() throws IOException {
+        while (vbits > 8) {
+            int c = (int) (bitbuf << (32 - vbits) >>> 24);
+            vbits -= 8;
+            if(invertBitOrder) {
+                c = flipTable[c] & 0xFF;
+            }
+            out.write(c);
+        }
+    }
+
+    /**
+     * get fill byte used to adjust stream to byte boundary.
+     * @return int
+     */
+    public int getFillByte() {
+        return fillByte;
+    }
+
+    /**
+     * set fill byte used to adjust stream to byte boundary
+     * @param fillByte int
+     */
+    public void setFillByte(int fillByte) {
+        this.fillByte = fillByte & 0xFF;
+    }
+
+    /**
+     * writes bits from buffer to output stream
+     * @throws IOException if I/O error occurs
+     */
+    public void flush() throws IOException {
+        write8();   //rather not needed, just to ensure
+        if(vbits > 0) {
+            write(fillByte, 8);
+        }
+        vbits = 0;
+        bitbuf = 0;
+    }
+}
Index: ocean/src/com/imagero/uio/io/BitInputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/BitInputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/BitInputStream.java	(revision 0)
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * adds ability to read streams bitewise and also to read predefined amount of bits every read() call
+ * @author Andrey Kuznetsov
+ */
+public class BitInputStream extends FilterInputStream {
+
+    int vbits = 0;
+    int bitbuf = 0;
+
+    int markBitbuf;
+    int markVbits;
+
+    private int bitsToRead = 8;
+    boolean invertBitOrder;
+
+    public BitInputStream(InputStream in) {
+        super(in);
+    }
+
+    /**
+     * how much bits is read every read() call (default - 8)
+     * @return
+     */
+    public int getBitsToRead() {
+        return bitsToRead;
+    }
+
+    /**
+     * set how much bits is read every read() call (max 8)
+     * @param bitsToRead
+     */
+    public void setBitsToRead(int bitsToRead) {
+        if(bitsToRead > 32) {
+            throw new IllegalArgumentException("" + bitsToRead);
+        }
+        this.bitsToRead = bitsToRead;
+    }
+
+    public boolean isInvertBitOrder() {
+        return invertBitOrder;
+    }
+
+    public void setInvertBitOrder(boolean invertBitOrder) {
+        this.invertBitOrder = invertBitOrder;
+        if(invertBitOrder) {
+            createFlipTable();
+        }
+    }
+
+    public int read() throws IOException {
+        return read(bitsToRead);
+    }
+
+    public int read(int nbits) throws IOException {
+        int ret;
+        //nothing to read
+        if (nbits == 0) {
+            return 0;
+        }
+        //too many bits requested
+        if(nbits > 32) {
+            throw new IllegalArgumentException("only 32 bit can be read at once");
+        }
+        if (nbits > 24) {
+            int nbits0 = nbits / 2;
+            int nbits1 = nbits - nbits0;
+            return (read(nbits0) << nbits1) | read(nbits1);
+        }
+        //not anough bits in buffer
+        if (nbits > vbits) {
+            fillBuffer(nbits);
+        }
+        //buffer still empty => we are reached EOF
+        if (vbits == 0) {
+            return -1;
+        }
+        ret = bitbuf << (32 - vbits) >>> (32 - nbits);
+        vbits -= nbits;
+
+        if(vbits < 0) {
+            vbits = 0;
+        }
+
+        return ret;
+    }
+
+    public int read(byte b[]) throws IOException {
+        return read(b, 0, b.length);
+    }
+
+    /**
+     * Reads data from input stream into an byte array.
+     *
+     * @param b the buffer into which the data is read.
+     * @param off the start offset of the data.
+     * @param len the maximum number of bytes read.
+     * @return the total number of bytes read into the buffer, or -1 if the EOF has been reached.
+     * @exception IOException if an I/O error occurs.
+     * @exception NullPointerException if supplied byte array is null
+     */
+    public int read(byte b[], int off, int len) throws IOException {
+        if (len <= 0) {
+            return 0;
+        }
+        int c = read();
+        if (c == -1) {
+            return -1;
+        }
+        b[off] = (byte) c;
+
+        int i = 1;
+        for (; i < len; ++i) {
+            c = read();
+            if (c == -1) {
+                break;
+            }
+            b[off + i] = (byte) c;
+        }
+        return i;
+    }
+
+    /**
+     * empties bit buffer.
+     */
+    public void resetBuffer() {
+        vbits = 0;
+        bitbuf = 0;
+    }
+
+    /**
+     * Skips some bytes from the input stream.
+     * If bit buffer is not empty, n - (vbits + 8) / 8 bytes skipped,
+     * then buffer is resetted and filled with same amount of bits as it has before skipping.
+     * @param n the number of bytes to be skipped.
+     * @return the actual number of bytes skipped.
+     * @exception IOException if an I/O error occurs.
+     */
+    public long skip(long n) throws IOException {
+        if (vbits == 0) {
+            return in.skip(n);
+        }
+        else {
+            int b = (vbits + 7) / 8;
+            in.skip(n - b);
+            int vbits = this.vbits;
+            resetBuffer();
+            fillBuffer(vbits);
+            return n;
+        }
+    }
+
+    /**
+     *
+     * @param n bits to skip
+     * @return number of bits skipped
+     */
+    public int skipBits(int n) throws IOException {
+        int k = n;
+        int nbits = k % 8;
+        read(nbits);
+        k -= nbits;
+        while(k > 0) {
+            try {
+                read(8);
+                k -= 8;
+            }
+            catch(IOException ex) {
+                break;
+            }
+        }
+        return n;
+    }
+
+    public int skipToByteBoundary() throws IOException {
+        int nbits = vbits % 8;
+        read(nbits);
+        return nbits;
+    }
+
+    private void fillBuffer(int nbits) throws IOException {
+        int c;
+        while (vbits < nbits) {
+            c = in.read();
+            if (c == -1) {
+                break;
+            }
+            if(invertBitOrder) {
+                c = flipTable[c] & 0xFF;
+            }
+            bitbuf = (bitbuf << 8) + (c & 0xFF);
+            vbits += 8;
+        }
+    }
+
+    public int getBitOffset() {
+        return 7 - (vbits % 8);
+    }
+
+    public synchronized void mark(int readlimit) {
+        in.mark(readlimit);
+        markBitbuf = bitbuf;
+        markVbits = vbits;
+    }
+
+    public synchronized void reset() throws IOException {
+        in.reset();
+        bitbuf = markBitbuf;
+        vbits = markVbits;
+    }
+
+    static private byte[] flipTable;
+
+    static byte [] getFlipTable() {
+        if(flipTable == null) {
+            createFlipTable();
+        }
+        return flipTable;
+    }
+
+    static private void createFlipTable() {
+        flipTable = new byte[256];
+        for (int i = 0; i < flipTable.length; i++) {
+            int b = 0;
+            for (int j = 0; j < 8; j++) {
+                int k = (i >> j) & 1;
+                b = (b << 1) | k;
+            }
+            flipTable[i] = (byte) b;
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/io/BitOutputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/BitOutputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/BitOutputStream.java	(revision 0)
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrei Kouznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * adds ability to write streams bitewise
+ * @author Andrey Kuznetsov
+ */
+public class BitOutputStream extends FilterOutputStream {
+
+    protected static final int[] mask = new int[32];
+
+    static {
+        for (int i = 0; i < mask.length; i++) {
+            mask[i] =  (1 << (i + 1)) - 1;
+        }
+    }
+
+    protected int bitbuf;
+    protected int vbits;
+
+    private int bitsToWrite = 8;
+
+    protected byte [] flipTable = BitInputStream.getFlipTable();
+
+    protected boolean invertBitOrder;
+
+    protected int fillByte = 0;
+
+    public BitOutputStream(OutputStream out) {
+        super(out);
+    }
+
+    public int getBitsToWrite() {
+        return bitsToWrite;
+    }
+
+    /**
+     * set how much bits should be written to stream every write() call
+     * @param bitsToWrite
+     */
+    public void setBitsToWrite(int bitsToWrite) {
+        this.bitsToWrite = bitsToWrite;
+    }
+
+    public boolean isInvertBitOrder() {
+        return invertBitOrder;
+    }
+
+    public void setInvertBitOrder(boolean invertBitOrder) {
+        this.invertBitOrder = invertBitOrder;
+    }
+
+    /**
+     * Writes some bits from the specified int to stream.
+     * @param b int which should be written
+     * @throws IOException if an I/O error occurs
+     * @see #setBitsToWrite
+     * @see #getBitsToWrite
+     */
+    public void write(int b) throws IOException {
+        write(b, bitsToWrite);
+    }
+
+    /**
+     * Writes some bits from the specified int to stream.
+     * @param b int which should be written
+     * @param nbits bit count to write
+     * @throws IOException if an I/O error occurs
+     */
+    public void write(int b, int nbits) throws IOException {
+        if (nbits == 0) {
+            return;
+        }
+        final int k = b & mask[nbits];
+        bitbuf = (bitbuf << nbits) | k;
+        vbits += nbits;
+
+        write8();
+    }
+
+    protected void write8() throws IOException {
+        while (vbits > 8) {
+            int c = (int) (bitbuf << (32 - vbits) >>> 24);
+            vbits -= 8;
+            if(invertBitOrder) {
+                c = flipTable[c] & 0xFF;
+            }
+            out.write(c);
+        }
+    }
+
+    /**
+     * get fill byte used to adjust stream to byte boundary.
+     * @return int
+     */
+    public int getFillByte() {
+        return fillByte;
+    }
+
+    /**
+     * set fill byte used to adjust stream to byte boundary
+     * @param fillByte int
+     */
+    public void setFillByte(int fillByte) {
+        this.fillByte = fillByte & 0xFF;
+    }
+
+    /**
+     * writes bits from buffer to output stream
+     * @throws IOException if I/O error occurs
+     */
+    public void flush() throws IOException {
+        write8();   //rather not needed, just to ensure
+        if(vbits > 0) {
+            write(fillByte, 8);
+        }
+        vbits = 0;
+        bitbuf = 0;
+        out.flush();
+    }
+}
Index: ocean/src/com/imagero/uio/io/ByteArray2DInputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/ByteArray2DInputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/ByteArray2DInputStream.java	(revision 0)
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.io;
+
+import java.io.ByteArrayInputStream;
+
+/**
+ * Like byteArrayInputStream but works with 2d byte array
+ * @author Andrey Kuznetsov
+ */
+public class ByteArray2DInputStream extends ByteArrayInputStream {
+
+    private byte[][] data;
+
+    int current;
+    boolean finished;
+
+    int markIndex;
+
+    public ByteArray2DInputStream(byte[][] data) {
+        super(data[0]);
+        this.data = data;
+    }
+
+    public int read() {
+        if (finished) {
+            return -1;
+        }
+        if (pos >= count) {
+            next();
+        }
+        return super.read();
+    }
+
+    private void next() {
+        if ((current + 1) < data.length) {
+            buf = data[++current];
+            pos = 0;
+            count = buf.length;
+        }
+        else {
+            finished = true;
+        }
+    }
+
+    public int read(byte[] buf, int off, int len) {
+        int read = 0;
+        while (read < len && !finished) {
+            if (pos >= count) {
+                next();
+            }
+            int rd = super.read(buf, off + read, len - read);
+            if(rd <= 0) {
+                break;
+            }
+            read += rd;
+        }
+        return read;
+    }
+
+    public long skip(long ns) {
+        long skipped = 0;
+        while (skipped < ns && !finished) {
+            if (pos >= count) {
+                next();
+            }
+            long skp = super.skip(ns - skipped);
+            skipped += skp;
+            if (skp == 0) {
+                break;
+            }
+        }
+        return skipped;
+    }
+
+    public void mark(int readAheadLimit) {
+        markIndex = current;
+        super.mark(readAheadLimit);
+    }
+
+    public void reset() {
+        buf = data[markIndex];
+        super.reset();
+        finished = false;
+    }
+}
Index: ocean/src/com/imagero/uio/io/ByteArrayOutputStream2.java
===================================================================
--- ocean/src/com/imagero/uio/io/ByteArrayOutputStream2.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/ByteArrayOutputStream2.java	(revision 0)
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import java.io.ByteArrayOutputStream;
+
+/**
+ * ByteArrayOutputStream which writes to external buffer.
+ * Length of this external buffer can't be changed.
+ *
+ * @author Andrey Kuznetsov
+ */
+public class ByteArrayOutputStream2 extends ByteArrayOutputStream {
+    public ByteArrayOutputStream2(byte[] buffer) {
+        super(0);
+        buf = buffer;
+    }
+
+    /**
+     * write given byte to buffer.
+     *
+     * @param b byte to write
+     * @throws ArrayIndexOutOfBoundsException if new byte count would exceed length of buffer after this operation
+     */
+    public synchronized void write(int b) {
+        int newcount = count + 1;
+        if (newcount > buf.length) {
+            throw new ArrayIndexOutOfBoundsException(newcount);
+        }
+        buf[count] = (byte) b;
+        count = newcount;
+    }
+
+    /**
+     * combine (OR) current value with given byte using supplied mask.
+     * resulting value is (b & mask) | (currentValue & ~mask)
+     * @param b byte to combine
+     * @param mask 8 bit mask
+     * @throws ArrayIndexOutOfBoundsException
+     */
+    public synchronized void write(int b, int mask) {
+        int newcount = count + 1;
+        if (newcount > buf.length) {
+            throw new ArrayIndexOutOfBoundsException(newcount);
+        }
+        buf[count] = (byte) ((b & mask) | (buf[count] & ~mask));
+        count = newcount;
+    }
+
+    /**
+     * Writes bytes from the specified byte array to buffer
+     * @param b byte array
+     * @param off start offset
+     * @param len number of bytes to write
+     * @throws ArrayIndexOutOfBoundsException if new byte count would exceed length of buffer after this operation (however the max possible byte count is written first)
+     */
+    public synchronized void write(byte b[], int off, int len) {
+        if (len == 0) {
+            return;
+        }
+        if ((off < 0) || (len < 0) || ((off + len) > b.length)) {
+            throw new IndexOutOfBoundsException();
+        }
+        int newcount = count + len;
+        if (newcount > buf.length) {
+            int max = buf.length - count;
+            System.arraycopy(b, off, buf, count, max);
+            throw new ArrayIndexOutOfBoundsException(newcount);
+        }
+        System.arraycopy(b, off, buf, count, len);
+        count = newcount;
+    }
+
+    public int getCount() {
+        return count;
+    }
+
+    /**
+     * Skip some bytes.
+     * Negative skip is possible.
+     * @param n byte count to skip
+     * @return how much bytes were skipped
+     */
+    public int skip(int n) {
+        int p = this.count;
+        seek(p + n);
+        return this.count - p;
+//        if (count + n > buf.length) {
+//            n = buf.length - count;
+//        }
+//        if (n < 0) {
+//            return 0;
+//        }
+//        count += n;
+//        return n;
+    }
+
+    public void seek(int pos) {
+        this.count = Math.min(Math.max(pos, 0), buf.length - 1);
+    }
+}
Index: ocean/src/com/imagero/uio/io/ByteArrayOutputStreamExt.java
===================================================================
--- ocean/src/com/imagero/uio/io/ByteArrayOutputStreamExt.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/ByteArrayOutputStreamExt.java	(revision 0)
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.io;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * ByteArrayOutputStreamExt extends ByteArrayOutputStream with posibility to drain off data into user specified buffer.
+ * ActionEvent is fired if buffer is full and going to grow.
+ * @author Andrey Kuznetsov
+ */
+public class ByteArrayOutputStreamExt extends ByteArrayOutputStream {
+
+    public static final String BUFFER_FULL = "buffer full";
+
+    ActionListener bufferListener;
+
+    public ByteArrayOutputStreamExt() {
+        this(1024);
+    }
+
+    public ByteArrayOutputStreamExt(int size) {
+        super(size);
+    }
+
+    public ByteArrayOutputStreamExt(ActionListener l) {
+        this.bufferListener = l;
+    }
+
+    public ByteArrayOutputStreamExt(int size, ActionListener l) {
+        super(size);
+        this.bufferListener = l;
+    }
+
+    protected void fireBufferFullEvent() {
+        drained = false;
+        if (bufferListener != null) {
+            ActionEvent e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, BUFFER_FULL);
+            bufferListener.actionPerformed(e);
+        }
+    }
+
+    /**
+     * Fill destination buffer with data which is removed from start of this buffer.
+     * @param dest destination buffer
+     * @return how much bytes was moved from this buffer into destination buffer.
+     */
+    public synchronized int drain(byte[] dest) {
+        int length = Math.min(dest.length, count);
+        if (length > 0) {
+            System.arraycopy(buf, 0, dest, 0, length);
+            int len = count - length;
+            if (len > 0) {
+                System.arraycopy(buf, length, buf, 0, len);
+            }
+            count -= length;
+        }
+        drained = true;
+        return length;
+    }
+
+    boolean drained;
+
+    public synchronized void write(int b) {
+        if (bufferListener != null) {
+            int newcount = count + 1;
+            if (newcount > buf.length) {
+                fireBufferFullEvent();
+            }
+        }
+        super.write(b);
+    }
+
+    /**
+     * Writes len bytes from given byte array starting at offset off to this buffer.
+     * If capacity of buffer is not enough for incoming data,
+     * then, at-first, buffer filled with data,
+     * then fired "buffer is full" event,
+     * thus giving the user possibility to "drain" buffer.
+     * If buffer was drained, then rest of data is written to buffer,
+     * otherwise buffer capacity is increased before writing.
+     * @param b
+     * @param off
+     * @param len
+     */
+    public synchronized void write(byte b[], int off, int len) {
+        int max = buf.length - count;
+        if (max > len || bufferListener == null) {
+            super.write(b, off, len);
+        } else {
+            super.write(b, off, max);
+            fireBufferFullEvent();
+            write2(b, off + max, len - max);
+        }
+    }
+
+    private void write2(byte b[], int off, int len) {
+        if (!drained) {
+            super.write(b, off, len);
+        } else {
+            drained = false;
+            int max = buf.length - count;
+            if (max > len) {
+                super.write(b, off, len);
+            } else {
+                super.write(b, off, max);
+                fireBufferFullEvent();
+                write2(b, off + max, len - max);
+            }
+        }
+    }
+
+    public void close() throws IOException {
+        fireBufferFullEvent();
+        super.close();
+    }
+
+    public void setBufferListener(ActionListener bufferListener) {
+        this.bufferListener = bufferListener;
+    }
+
+    /**
+     * Retrieve internal buffer.
+     * Recommended use - immediately after receiving "buffer full" event.
+     * Internal buffer is returned and replaced with new empty buffer.
+     */
+    public synchronized byte[] drain() {
+        byte[] tmp = buf;
+        buf = new byte[0];
+        count = 0;
+        drained = true;
+        return tmp;
+    }
+
+    public synchronized void writeTo(DataOutput out) throws IOException {
+        out.write(buf, 0, count);
+    }
+}
Index: ocean/src/com/imagero/uio/io/HexInputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/HexInputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/HexInputStream.java	(revision 0)
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @author Andrey Kuznetsov
+ */
+public class HexInputStream extends FilterInputStream {
+
+    private static final char encodeTable[] = {
+        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+    };
+
+    public static final int decodeTable [] = new int[0x100];
+
+    static {
+        for (int i = 0; i < decodeTable.length; i++) {
+            decodeTable[i] = -1;
+        }
+        for (int j = 0; j < encodeTable.length; j++) {
+            decodeTable[encodeTable[j]] = j;
+        }
+    }
+
+    boolean finished;
+
+    byte[] buffer = new byte[80];
+    int count;
+    int pos;
+
+    public HexInputStream(InputStream in) {
+        super(in);
+    }
+
+    public int read() throws IOException {
+        if (pos >= count) {
+            if (finished) {
+                return -1;
+            }
+            else {
+                fillBuffer();
+            }
+        }
+        if (pos < count) {
+            return buffer[pos++] & 0xFF;
+        }
+        return -1;
+    }
+
+    protected void fillBuffer() {
+        int k = 0;
+        try {
+            for (; k < buffer.length; k++) {
+                int b0 = in.read();
+                if (b0 == 13 || b0 == 10) {
+                    k--;
+                    continue;
+                }
+                if (b0 == '>') {
+                    k++;
+                    finished = true;
+                    break;
+                }
+                int b1 = in.read();
+                int d0 = decodeTable[b0];
+                int d1 = decodeTable[b1];
+                if (d0 == -1 || d1 == -1) {
+                    k--;
+                    continue;
+                }
+                buffer[k] = (byte) (d0 * 16 + d1);
+            }
+        }
+        catch (Throwable ex) {
+//            ex.printStackTrace();
+            System.err.println(ex.getMessage());
+        }
+        count = k;
+        pos = 0;
+    }
+
+    public long skip(long n) throws IOException {
+        long i = 0;
+        for (; i < n; i++) {
+            int a = read();
+            if (a == -1) {
+                break;
+            }
+        }
+        return i;
+    }
+
+    public int read(byte b[]) throws IOException {
+        return read(b, 0, b.length);
+    }
+
+    public int read(byte b[], int off, int len) throws IOException {
+        if (b == null) {
+            return (int) skip(len);
+        }
+        if (len <= 0) {
+            return 0;
+        }
+        int i = 0;
+        try {
+            for (; i < len; i++) {
+                int a = read();
+                if (a == -1) {
+                    break;
+                }
+                b[i] = (byte) a;
+            }
+        }
+        catch (IOException ex) {
+        }
+        return i == 0 ? -1 : i;
+    }
+}
Index: ocean/src/com/imagero/uio/io/IOutils.java
===================================================================
--- ocean/src/com/imagero/uio/io/IOutils.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/IOutils.java	(revision 0)
@@ -0,0 +1,792 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import com.imagero.uio.RandomAccessInput;
+import com.imagero.uio.RandomAccessOutput;
+import com.imagero.uio.ReadUtil;
+import com.imagero.uio.Sys;
+
+import java.io.*;
+
+/**
+ * IOutils.java
+ *
+ * @author Andrei Kouznetsov
+ */
+public class IOutils {
+    private static final int BIG_ENDIAN = 0x4D4D;
+    private static final int LITTLE_ENDIAN = 0x4949;
+
+    /**
+     * close silently stream<br>
+     * no exception it thrown
+     *
+     * @param bw
+     */
+    public static void closeStream(BufferedWriter bw) {
+        try {
+            if (bw != null) {
+                bw.close();
+            }
+        } catch (IOException ex) {
+        }
+    }
+
+    /**
+     * close silently stream<br>
+     * no exception it thrown
+     *
+     * @param br
+     */
+    public static void closeStream(BufferedReader br) {
+        try {
+            if (br != null) {
+                br.close();
+            }
+        } catch (IOException ex) {
+        }
+    }
+
+    /**
+     * close silently stream<br>
+     * no exception it thrown
+     *
+     * @param is
+     */
+    public static void closeStream(InputStream is) {
+        try {
+            if (is != null) {
+                is.close();
+            }
+        } catch (IOException ex) {
+        }
+    }
+
+    /**
+     * close silently stream<br>
+     * no exception it thrown
+     *
+     * @param os
+     */
+    public static void closeStream(OutputStream os) {
+        try {
+            if (os != null) {
+                os.close();
+            }
+        } catch (IOException ex) {
+        }
+    }
+
+    /**
+     * close silently stream<br>
+     * no exception it thrown
+     *
+     * @param raf
+     */
+    public static void closeStream(RandomAccessFile raf) {
+        try {
+            if (raf != null) {
+                raf.close();
+            }
+        } catch (IOException ex) {
+        }
+    }
+
+    /**
+     * close silently stream<br>
+     * no exception it thrown
+     *
+     * @param ro
+     */
+    public static void closeStream(RandomAccessInput ro) {
+        try {
+            if (ro != null) {
+                ro.close();
+            }
+        } catch (IOException ex) {
+        }
+    }
+
+    public static void closeStream(RandomAccessOutput ro) {
+        try {
+            if (ro != null) {
+                ro.close();
+            }
+        } catch (IOException ex) {
+        }
+    }
+
+    static final int[] mask = {1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768};
+    static byte b0 = (byte) '0';
+    static byte b1 = (byte) '1';
+
+    public static String toBinaryString(byte value) {
+        byte[] b = new byte[8];
+        int cnt = 0;
+        for (int i = 7; i > -1; i--) {
+            b[cnt++] = (value & mask[i]) == 0 ? b0 : b1;
+        }
+        return new String(b);
+    }
+
+    public static String toBinaryString(char value) {
+        byte[] b = new byte[16];
+        int cnt = 0;
+        for (int i = 15; i > -1; i--) {
+            b[cnt++] = (value & mask[i]) == 0 ? b0 : b1;
+        }
+        return new String(b);
+    }
+
+    public static String toBinaryString(int value, int length) {
+        byte[] b = new byte[length];
+        int cnt = 0;
+        for (int i = length - 1; i > -1; i--) {
+            if (((value >> i) & 1) == 1) {
+                b[cnt++] = b1;
+            } else {
+                b[cnt++] = b0;
+            }
+        }
+        return new String(b);
+    }
+
+    final static byte[] digits = {
+        (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5',
+        (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'a', (byte) 'b',
+        (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f'
+    };
+
+    public static String toHexString(byte value) {
+        return toUnsignedString(value & 0xFF, 4);
+    }
+
+    private static String toUnsignedString(int i, int shift) {
+        byte[] buf = new byte[]{(byte) '0', (byte) '0'};
+        int charPos = 2;
+        int radix = 1 << shift;
+        int mask = radix - 1;
+        do {
+            buf[--charPos] = digits[i & mask];
+            i >>>= shift;
+        } while (i != 0);
+
+        return new String(buf);
+    }
+
+    public static void printHexByte(int value) {
+        printHexImpl(value & 0xFFFF, 2);
+    }
+
+    public static void printlnHexByte(int value) {
+        printHexImpl(value & 0xFFFF, 2);
+        Sys.out.println("");
+    }
+
+    public static void printHexShort(int value) {
+        printHexImpl(value & 0xFFFF, 4);
+    }
+
+    public static void printlnHexShort(int value) {
+        printHexImpl(value & 0xFFFF, 4);
+        Sys.out.println("");
+    }
+
+    public static void printHexInt(int value) {
+        printHexImpl(value & 0xFFFFFFFF, 8);
+    }
+
+    public static void printlnHexInt(int value) {
+        printHexImpl(value & 0xFFFFFFFF, 8);
+        Sys.out.println("");
+    }
+
+    public static void printHexLong(long value) {
+        printHexImpl(value & 0xFFFFFFFFFFFFFFFFL, 16);
+    }
+
+    public static void printlnHexLong(long value) {
+        printHexImpl(value & 0xFFFFFFFFFFFFFFFFL, 16);
+        Sys.out.println("");
+    }
+
+    static void printHexImpl(long value, int length) {
+        String s = Long.toHexString(value);
+        //Sys.out.println("***********************" + s + " " + value);
+        for (int i = 0, size = length - s.length(); i < size; i++) {
+            Sys.out.print("0");
+        }
+        Sys.out.print(s);
+    }
+
+    static void printHexImpl(int value, int length) {
+        String s = Integer.toHexString(value);
+        if (s.length() > length) {
+            s = s.substring(s.length() - length);
+        }
+        //Sys.out.println("***********************" + s + " " + value);
+        for (int i = 0, size = length - s.length(); i < size; i++) {
+            Sys.out.print("0");
+        }
+        Sys.out.print(s);
+    }
+
+    public static String getExtension(File f) {
+        String s = f.getName();
+        return s.substring(s.lastIndexOf(".") + 1).toUpperCase();
+    }
+
+    /**
+     * read little-endian short
+     */
+    public static int readShort4D(InputStream in) throws IOException {
+        return ((in.read() & 0xFF) << 8) + ((in.read() & 0xFF) << 0);
+    }
+
+    /**
+     * read little-endian short
+     */
+    public static int readShort4D(DataInput in) throws IOException {
+        return ((in.readByte() & 0xFF) << 8) + ((in.readByte() & 0xFF) << 0);
+    }
+
+    /**
+     * read big-endian short
+     */
+    public static int readShort49(InputStream in) throws IOException {
+        return ((in.read() & 0xFF) << 0) + ((in.read() & 0xFF) << 8);
+    }
+
+    /**
+     * read big-endian short
+     */
+    public static int readShort49(DataInput in) throws IOException {
+        return ((in.readByte() & 0xFF) << 0) + ((in.readByte() & 0xFF) << 8);
+    }
+
+    /**
+     * read little-endian int
+     */
+    public static int readInt4D(InputStream in) throws IOException {
+        return (((in.read() & 0xFF) << 24) + ((in.read() & 0xFF) << 16) + ((in.read() & 0xFF) << 8) + ((in.read() & 0xFF) << 0));
+    }
+
+    /**
+     * read big-endian int
+     */
+    public static int readInt4D(DataInput in) throws IOException {
+        int b0 = (in.readByte() & 0xFF);
+        int b1 = (in.readByte() & 0xFF);
+        int b2 = (in.readByte() & 0xFF);
+        int b3 = (in.readByte() & 0xFF);
+        return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3;
+    }
+
+    /**
+     * read big-endian int
+     */
+    public static int readInt49(InputStream in) throws IOException {
+        return ((in.read() & 0xFF) << 0) + ((in.read() & 0xFF) << 8) + ((in.read() & 0xFF) << 16) + ((in.read() & 0xFF) << 24);
+    }
+
+    /**
+     * read big-endian int
+     */
+    public static int readInt49(DataInput in) throws IOException {
+        return ((in.readByte() & 0xFF) << 0) + ((in.readByte() & 0xFF) << 8) + ((in.readByte() & 0xFF) << 16) + ((in.readByte() & 0xFF) << 24);
+    }
+
+    /**
+     * read little-endian long
+     */
+    public static long readLong4D(InputStream in) throws IOException {
+        return ((long) (readInt4D(in)) << 32) + (readInt4D(in) & 0xFFFFFFFFL);
+    }
+
+    /**
+     * read little-endian long
+     */
+    public static long readLong4D(DataInput in) throws IOException {
+        return ((long) (readInt4D(in)) << 32) + (readInt4D(in) & 0xFFFFFFFFL);
+    }
+
+    /**
+     * read big-endian long
+     */
+    public static long readLong49(InputStream in) throws IOException {
+        return ((long) (readInt49(in)) & 0xFFFFFFFFL) + (readInt49(in) << 32);
+    }
+
+    /**
+     * read big-endian long
+     */
+    public static long readLong49(DataInput in) throws IOException {
+        return ((long) (readInt49(in)) & 0xFFFFFFFFL) + (readInt49(in) << 32);
+    }
+
+    public static byte readSByte(DataInput ro) throws IOException {
+        byte b = ro.readByte();
+        if (b < 0) {
+            b = (byte) -(~(b + 1));
+        }
+        return b;
+    }
+
+    public static short readSShort(DataInput ro) throws IOException {
+        short b = ro.readShort();
+        if (b < 0) {
+            b = (short) -(~(b + 1));
+        }
+        return b;
+    }
+
+    public static int readSInt(DataInput ro) throws IOException {
+        int b = ro.readInt();
+        if (b < 0) {
+            b = -(~(b + 1));
+        }
+        return b;
+    }
+
+    public static long readSLong(DataInput ro) throws IOException {
+        long b = ro.readLong();
+        if (b < 0) {
+            b = -(~(b + 1));
+        }
+        return b;
+    }
+
+    /**
+     * Read byte array and convert from 2's complement
+     * @param ro RandomAccessRO
+     * @param b0 byte array
+     * @throws IOException
+     */
+    public static void readFullyS(DataInput ro, byte[] b0) throws IOException {
+        ro.readFully(b0);
+        convertFrom2C(b0);
+    }
+
+    /**
+     * Convert byte array from 2's complement
+     */
+    public static final void convertFrom2C(byte[] b0) {
+        for (int i = 0; i < b0.length; i++) {
+            if (b0[i] < 0) {
+                b0[i] = (byte) -(~(b0[i] + 1));
+            }
+        }
+    }
+
+    /**
+     * Read short array and convert from 2's complement.
+     * @param ro RandomAccessRO
+     * @param b0 short array
+     * @throws IOException
+     */
+    public static void readFullyS(RandomAccessInput ro, short[] b0) throws IOException {
+        ReadUtil.readFully(ro, b0);
+        convertFrom2C(b0);
+    }
+
+    /**
+     * Convert short array from 2's complement
+     */
+    public static final void convertFrom2C(short[] b0) {
+        for (int i = 0; i < b0.length; i++) {
+            if (b0[i] < 0) {
+                b0[i] = (short) -(~(b0[i] + 1));
+            }
+        }
+    }
+
+    /**
+     * Read int array and convert from 2's complement
+     * @param ro RandomAccessRO
+     * @param b0 int array
+     * @throws IOException
+     */
+    public static void readFullyS(RandomAccessInput ro, int[] b0) throws IOException {
+        ReadUtil.readFully(ro, b0);
+        convertFrom2C(b0);
+    }
+
+    /**
+     * Convert int array from 2's complement
+     */
+    public static final void convertFrom2C(int[] b0) {
+        for (int i = 0; i < b0.length; i++) {
+            if (b0[i] < 0) {
+                b0[i] = -(~(b0[i] + 1));
+            }
+        }
+    }
+
+    /**
+     * Read short array and convert from 2's complement
+     * @param ro RandomAccessRO
+     * @param b0 long array
+     * @throws IOException
+     */
+    public static void readFullyS(RandomAccessInput ro, long[] b0) throws IOException {
+        ReadUtil.readFully(ro, b0);
+        convertFrom2C(b0);
+    }
+
+    /**
+     * Convert long array from 2's complement
+     */
+    public static final void convertFrom2C(long[] b0) {
+        for (int i = 0; i < b0.length; i++) {
+            if (b0[i] < 0) {
+                b0[i] = -(~(b0[i] + 1));
+            }
+        }
+    }
+
+    public static void readFully(InputStream in, byte b[]) throws UnexpectedEOFException, IOException {
+        readFully(in, b, 0, b.length);
+    }
+
+    public static void readFully(InputStream in, byte b[], int off, int len) throws UnexpectedEOFException, IOException {
+        int n = 0;
+        do {
+            int count = in.read(b, off + n, len - n);
+            if (count < 0) {
+                throw new UnexpectedEOFException(n > 0 ? n : 0);
+            }
+            n += count;
+        } while (n < len);
+    }
+
+    /**
+     * this method is like readFully, but instead of throwing <code>EOFException</code> it returns count of read bytes
+     *
+     * @param in InputStream to read
+     * @param b  byte array to fill
+     *
+     * @return number of bytes read into the buffer, or -1 if EOF was reached
+     *
+     * @throws IOException
+     *
+     */
+    public static int readFully2(InputStream in, byte b[]) throws IOException {
+        return readFully2(in, b, 0, b.length);
+    }
+
+    /**
+     * this method is like readFully, but instead of throwing <code>EOFException</code> it returns count of read bytes
+     *
+     * @param in  InputStream to read
+     * @param b   byte array to fill
+     * @param off start offset in byte array
+     * @param len number of bytes to read
+     *
+     * @return number of bytes read into the buffer, or -1 if EOF was reached
+     *
+     * @throws IOException
+     */
+    public static int readFully2(InputStream in, byte b[], int off, int len) throws IOException {
+        int n = 0;
+        int cnt0 = 0;
+        do {
+            int count = in.read(b, off + n, len - n);
+            if (count == 0) {
+                cnt0++;
+                if (cnt0 >= 3) {
+                    break;
+                }
+            } else {
+                cnt0 = 0;
+            }
+            if (count < 0) {
+                return n == 0 ? -1 : n;
+            }
+            n += count;
+        } while (n < len);
+        return n;
+    }
+
+    /**
+     * copy <code>length</code> bytes from <code>in</code> to <code>out</code>
+     * @param length amount of bytes to copy
+     * @param in source InputStream
+     * @param out destination OutputStream
+     * @throws IOException
+     */
+    public static long copy(long length, InputStream in, OutputStream out) throws IOException {
+        long copy = 0;
+        byte[] buffer = new byte[2048];
+        while (length > 0) {
+            int read = in.read(buffer, 0, (int) Math.min(buffer.length, length));
+            if (read <= 0) {
+                break;
+            }
+            copy += read;
+            length -= read;
+            out.write(buffer, 0, read);
+        }
+        return copy;
+    }
+
+    /**
+     * copy <code>length</code> bytes from source to destination stream
+     * @param length amount of bytes to copy
+     * @param in source stream
+     * @param out destination stream
+     * @throws IOException
+     */
+    public static long copy(long length, InputStream in, DataOutput out) throws IOException {
+        long copy = 0;
+        byte[] buffer = new byte[2048];
+        while (length > 0) {
+            int read = in.read(buffer, 0, (int) Math.min(buffer.length, length));
+            if (read <= 0) {
+                break;
+            }
+            copy += read;
+            length -= read;
+            out.write(buffer, 0, read);
+        }
+        return copy;
+    }
+
+    /**
+     * Copy file.
+     * @param src source file
+     * @param dest destination file
+     * @return how much bytes were copied.
+     * @throws IOException
+     */
+    public static long copy(File src, File dest) throws IOException {
+        InputStream in = new FileInputStream(src);
+        OutputStream out = new FileOutputStream(dest);
+        try {
+            return copy(in, out);
+        } finally {
+            IOutils.closeStream(in);
+            IOutils.closeStream(out);
+        }
+    }
+
+    /**
+     * copy data from <code>in</code> to <code>out</code>
+     * @param in source InputStream
+     * @param out destination OutputStream
+     * @return how much bytes were copied.
+     * @throws IOException
+     */
+    public static long copy(InputStream in, OutputStream out) throws IOException {
+        long copy = 0;
+        byte[] buffer = new byte[2048];
+        while (true) {
+            int read = in.read(buffer);
+            if (read <= 0) {
+                break;
+            }
+            copy += read;
+            out.write(buffer, 0, read);
+        }
+        return copy;
+    }
+
+    /**
+     * copy data from source to destination stream
+     * @param in source stream
+     * @param out destination stream
+     * @throws IOException
+     * @return amount of copied bytes
+     */
+    public static long copy(InputStream in, DataOutput out) throws IOException {
+        long copy = 0;
+        byte[] buffer = new byte[2048];
+        while (true) {
+            int read = in.read(buffer);
+            if (read <= 0) {
+                break;
+            }
+            copy += read;
+            out.write(buffer, 0, read);
+        }
+        return copy;
+    }
+
+    /**
+     * copy data from <code>in</code> to <code>out</code>
+     * @param in source RandomAccessRO
+     * @param offset offset in <code>in</code>
+     * @param out destination OutputStream
+     * @throws IOException
+     */
+    public static long copy(RandomAccessInput in, long offset, OutputStream out) throws IOException {
+        long copy = 0;
+        byte[] buffer = new byte[2048];
+        in.seek(offset);
+        while (true) {
+            int read = in.read(buffer);
+            if (read <= 0) {
+                break;
+            }
+            copy += read;
+            out.write(buffer, 0, read);
+        }
+        return copy;
+    }
+
+    /**
+     * copy data from source to destination stream
+     * @param in source stream
+     * @param offset offset in source stream
+     * @param out destination stream
+     * @throws IOException
+     * @return how much bytes was copied
+     */
+    public static long copy(RandomAccessInput in, long offset, DataOutput out) throws IOException {
+        long copy = 0;
+        byte[] buffer = new byte[2048];
+        in.seek(offset);
+        while (true) {
+            int read = in.read(buffer);
+            if (read <= 0) {
+                break;
+            }
+            copy += read;
+            out.write(buffer, 0, read);
+        }
+        return copy;
+    }
+
+    /**
+     * copy data from source to destination stream
+     * @param offset offset in source stream
+     * @param length amount of bytes to copy
+     * @param in source stream
+     * @param out destination stream
+     * @throws IOException
+     */
+    public static long copy(long offset, long length, RandomAccessInput in, OutputStream out) throws IOException {
+        long copy = 0;
+        byte[] buffer = new byte[2048];
+        in.seek(offset);
+        while (length > 0) {
+            int read = in.read(buffer, 0, (int) Math.min(buffer.length, length));
+            if (read <= 0) {
+                break;
+            }
+            copy += read;
+            length -= read;
+            out.write(buffer, 0, read);
+        }
+        return copy;
+    }
+
+    /**
+     * copy data from source to destination stream
+     * @param offset offset in source stream
+     * @param length amount of bytes to copy
+     * @param in source stream
+     * @param out destination stream
+     * @throws IOException
+     */
+    public static long copy(long offset, long length, RandomAccessInput in, DataOutput out) throws IOException {
+        long copy = 0;
+        byte[] buffer = new byte[2048];
+        in.seek(offset);
+        while (length > 0) {
+            int read = in.read(buffer, 0, (int) Math.min(buffer.length, length));
+            if (read <= 0) {
+                break;
+            }
+            copy += read;
+            length -= read;
+            out.write(buffer, 0, read);
+        }
+        return copy;
+    }
+
+    /**
+     * read big/little-endian short
+     */
+    public static int readShort(InputStream in, int byteOrder) throws IOException {
+        switch (byteOrder) {
+            case BIG_ENDIAN:
+                return readShort4D(in);
+            case LITTLE_ENDIAN:
+                return readShort49(in);
+            default:
+                throw new IllegalArgumentException("" + byteOrder);
+        }
+    }
+
+    public static int readShort(DataInput in, int byteOrder) throws IOException {
+        switch (byteOrder) {
+            case BIG_ENDIAN:
+                return readShort4D(in);
+            case LITTLE_ENDIAN:
+                return readShort49(in);
+            default:
+                throw new IllegalArgumentException("" + byteOrder);
+        }
+    }
+
+    /**
+     * read big/little-endian int
+     */
+    public static int readInt(InputStream in, int byteOrder) throws IOException {
+        switch (byteOrder) {
+            case BIG_ENDIAN:
+                return readInt4D(in);
+            case LITTLE_ENDIAN:
+                return readInt49(in);
+            default:
+                throw new IllegalArgumentException("" + byteOrder);
+        }
+    }
+
+    /**
+     * read big/little-endian int
+     */
+    public static int readInt(DataInput in, int byteOrder) throws IOException {
+        switch (byteOrder) {
+            case BIG_ENDIAN:
+                return readInt4D(in);
+            case LITTLE_ENDIAN:
+                return readInt49(in);
+            default:
+                throw new IllegalArgumentException("" + byteOrder);
+        }
+    }
+
+
+}
Index: ocean/src/com/imagero/uio/io/JpegFilterInputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/JpegFilterInputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/JpegFilterInputStream.java	(revision 0)
@@ -0,0 +1,236 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+
+/**
+ * remove all App blocks from jpeg file
+ * <br>
+ * how to use:
+ * <br>
+ * <pre>
+ * 		//read data from file and save filtered data to another file
+ * 		File fs = new File("source.jpg");
+ * 		File fd = new File("dest.jpg");
+ * 		JpegFilterInputStream.filter(fs, fd);
+ *
+ *		or
+ *
+ * 		//filter data in byte array
+ * 		byte [] source = ...
+ * 		byte [] dest = JpegFilterInputStream.filter(source);
+ *
+ * 		or
+ *
+ * 		//filter data from InputStream
+ * 		InputStream in;
+ * 		OutputStream out;
+ * 		JpegFilterInputStream.filter(in, out);
+ *
+ *      or
+ *
+ *      //just wrap InputStream
+ *      JpegFilterInputStream jfis = new JpegFilterInputStream(in);
+ *
+ * </pre>
+ * @author Andrey Kuznetsov
+ */
+public class JpegFilterInputStream extends FilterInputStream {
+
+    public static final int APP_0 = 0xE0;
+    public static final int APP_1 = 0xE1;
+    public static final int APP_2 = 0xE2;
+    public static final int APP_3 = 0xE3;
+    public static final int APP_4 = 0xE4;
+    public static final int APP_5 = 0xE5;
+    public static final int APP_6 = 0xE6;
+    public static final int APP_7 = 0xE7;
+    public static final int APP_8 = 0xE8;
+    public static final int APP_9 = 0xE9;
+    public static final int APP_10 = 0xEA;
+    public static final int APP_11 = 0xEB;
+    public static final int APP_12 = 0xEC;
+    public static final int APP_13 = 0xED;
+    public static final int APP_14 = 0xEE;
+    public static final int APP_15 = 0xEF;
+
+    public static final int[] allMarkers = new int[]{
+        APP_0, APP_1, APP_2, APP_3, APP_4, APP_5, APP_6, APP_7,
+        APP_8, APP_9, APP_10, APP_11, APP_12, APP_13, APP_14, APP_15
+    };
+
+    public static final int[] defaultMarkers = new int[]{
+        APP_1, APP_2, APP_3, APP_4, APP_5, APP_6, APP_7,
+        APP_8, APP_9, APP_10, APP_11, APP_12, APP_13, APP_14, APP_15
+    };
+
+    public static void filterFile(File src, File dest) throws IOException {
+        FileInputStream in = new FileInputStream(src);
+        FileOutputStream out = new FileOutputStream(dest);
+
+        filter(in, out);
+        IOutils.closeStream(out);
+        IOutils.closeStream(in);
+    }
+
+    /**
+     * filter out all markers (except App0)
+     * @param in InputStream
+     * @param out
+     * @throws java.io.IOException
+     */
+    public static void filter(InputStream in, OutputStream out) throws IOException {
+        filter(in, out, defaultMarkers);
+    }
+
+    public static void filter(InputStream in0, OutputStream out, int[] markers) throws IOException {
+        JpegFilterInputStream in = new JpegFilterInputStream(in0, markers);
+        int a = 0;
+        while (a >= 0) {
+            a = in.read();
+            out.write(a);
+        }
+    }
+
+    /**
+     * filter out all markers (except App0)
+     * @param data
+     * @return byte array
+     * @throws IOException
+     */
+    public static byte[] filter(byte[] data) throws IOException {
+        return filter(data, defaultMarkers);
+    }
+
+    public static byte[] filter(byte[] data, int[] markers) throws IOException {
+        final ByteArrayInputStream in0 = new ByteArrayInputStream(data);
+        ByteArrayOutputStream bout = new ByteArrayOutputStream();
+        filter(in0, bout, markers);
+        return bout.toByteArray();
+    }
+
+    int[] markers;
+
+    /**
+     * create JpegFilterInputStream (filter out all markers except App0)
+     * @param in InputStream (with valid JPEG stream)
+     */
+    public JpegFilterInputStream(InputStream in) {
+        this(in, JpegFilterInputStream.defaultMarkers);
+    }
+
+    /**
+     * create JpegFilterInputStream
+     * @param in InputStream (with valid JPEG stream)
+     * @param markers markers to filter out
+     */
+    public JpegFilterInputStream(InputStream in, int[] markers) {
+        super(in);
+        this.markers = markers;
+    }
+
+    private boolean markerOn;
+
+    public int read() throws IOException {
+        int a = in.read();
+        if (!markerOn) {
+            if (a == 0xFF) {
+                markerOn = true;
+            }
+            return a;
+        }
+        else {
+            if (isAppMarker(a)) {
+                int length = (in.read() << 8) + in.read() - 2;
+//					Sys.out.println("length:" + length);
+                in.skip(length);
+                int b = in.read();
+                if (b != 0xFF) {
+                    throw new IOException("marker???");
+                }
+                return read();
+            }
+            else {
+                markerOn = false;
+                return a;
+            }
+        }
+    }
+
+    public int read(byte b[]) throws IOException {
+        return read(b, 0, b.length);
+    }
+
+    public int read(byte b[], int off, int len) throws IOException {
+        if (b == null) {
+            throw new NullPointerException();
+        }
+        if (off + len > b.length || off < 0) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        int read = 1;
+        int a = read();
+        if (a == -1) {
+            return -1;
+        }
+        b[off] = (byte) a;
+        for (int i = off + 1; i < len; i++) {
+            a = read();
+            if (a == -1) {
+                break;
+            }
+            read++;
+            b[i] = (byte) a;
+        }
+        return read;
+    }
+
+    public boolean isAppMarker(int a) {
+        for (int i = 0; i < markers.length; i++) {
+            if (a == markers[i]) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
Index: ocean/src/com/imagero/uio/io/LEDataOutputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/LEDataOutputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/LEDataOutputStream.java	(revision 0)
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.io;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutput;
+import java.io.DataOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * LEDataOutputStream.java
+ * <br>
+ * Little-endian writing.
+ * <br>
+ * @author Kouznetsov Andrei
+ *
+ */
+public class LEDataOutputStream extends FilterOutputStream implements DataOutput {
+
+	public LEDataOutputStream(OutputStream out) {
+		super(out);
+	}
+
+	public final void writeShort(int value) throws IOException {
+		write(value & 0xFF);
+		write((value >> 8) & 0xFF);
+	}
+
+	public final void writeChar(int value) throws IOException {
+		write(value & 0xFF);
+		write((value >> 8) & 0xFF);
+	}
+
+	public final void writeInt(int value) throws IOException {
+		write(value & 0xFF);
+		write((value >> 8) & 0xFF);
+		write((value >> 16) & 0xFF);
+		write((value >> 24) & 0xFF);
+	}
+
+	public final void writeLong(long value) throws IOException {
+		writeInt((int)(value & 0xFFFFFFFF));
+		writeInt((int)((value >> 32) & 0xFFFFFFFF));
+	}
+
+	public final void writeFloat(float value) throws IOException {
+		writeInt(Float.floatToIntBits(value));
+	}
+
+	public final void writeDouble(double value) throws IOException {
+		writeLong(Double.doubleToLongBits(value));
+	}
+
+	public void writeBoolean(boolean b) throws IOException {
+		out.write(b ? 1 : 0);
+	}
+
+	public void writeByte(int v) throws IOException {
+		write(v);
+	}
+
+	public void writeBytes(String s) throws IOException {
+		int len = s.length();
+		for (int i = 0 ; i < len ; i++) {
+			out.write((byte)s.charAt(i));
+		}
+	}
+
+	public void writeChars(String s) throws IOException {
+		int len = s.length();
+		byte [] b = new byte[len * 2];
+		int index = 0;
+		for(int i = 0; i < len; i++) {
+			int v = s.charAt(i);
+			b[index++] = (byte)((v >>> 0) & 0xFF);
+			b[index++] = (byte)((v >>> 8) & 0xFF);
+		}
+		write(b);
+	}
+
+	public void writeUTF(String str) throws IOException {
+		ByteArrayOutputStream out = new ByteArrayOutputStream(str.length());
+		DataOutputStream dataOut = new DataOutputStream(out);
+		dataOut.writeUTF(str);
+		dataOut.flush();
+		dataOut.close();
+		byte[] b = out.toByteArray();
+		write(b);
+	}
+}
+
+
Index: ocean/src/com/imagero/uio/io/LimitedInputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/LimitedInputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/LimitedInputStream.java	(revision 0)
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * InputStream that reads specified number of bytes
+ *
+ * @author Kouznetsov Andrei
+ */
+public class LimitedInputStream extends FilterInputStream {
+    protected int limit;
+
+    /**
+     * create new LimitedInputStream
+     *
+     * @param in InputStream
+     * @param limit read limit
+     */
+    public LimitedInputStream(InputStream in, int limit) {
+        super(in);
+        this.limit = limit;
+    }
+
+    public int available() throws IOException {
+        return limit;
+    }
+
+    public int read() throws IOException {
+        if(limit-- <= 0) {
+            return -1;
+        }
+        return in.read();
+    }
+
+    public int read(byte b[]) throws IOException {
+        return in.read(b, 0, b.length);
+    }
+
+    public int read(byte b[], int off, int len) throws IOException {
+        if(limit > 0) {
+            int length = Math.min(len, limit);
+            int res = in.read(b, off, length);
+            if(res > 0) {
+                limit -= res;
+            }
+            return res;
+        }
+        return -1;
+    }
+
+    public long skip(long n) throws IOException {
+        if(limit > 0) {
+            long length = Math.min(n, limit);
+            long res = in.skip(length);
+            if(res > 0) {
+                limit -= res;
+            }
+            return res;
+        }
+        return -1;
+    }
+
+    int mark;
+
+    public synchronized void mark(int readlimit) {
+        in.mark(readlimit);
+        mark = limit;
+    }
+
+    public synchronized void reset() throws IOException {
+        in.reset();
+        limit = mark;
+    }
+}
\ No newline at end of file
Index: ocean/src/com/imagero/uio/io/MultiByteArrayOutputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/MultiByteArrayOutputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/MultiByteArrayOutputStream.java	(revision 0)
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+
+import com.imagero.uio.Sys;
+
+import java.io.OutputStream;
+import java.util.Vector;
+
+/**
+ * MultiByteArrayOutputStream.java
+ * <br>
+ * It's like ByteArrayOutputStream, but with multiple arrays <br>
+ * Array size is defined through <code>sizeX</code>;
+ *
+ * @author Kouznetsov Andrei
+ */
+public class MultiByteArrayOutputStream extends OutputStream {
+
+    Vector v;
+    int totalCount;
+
+    int sizeX = 1024;
+
+    protected byte buf[];
+
+    protected int pos;
+
+
+    public MultiByteArrayOutputStream() {
+        this(1024);
+    }
+
+    public MultiByteArrayOutputStream(int sizeX) {
+        this.sizeX = sizeX;
+        this.v = new Vector();
+        nextArray();
+    }
+
+    protected void nextArray() {
+        byte[] b = new byte[this.sizeX];
+        v.addElement(b);
+        this.buf = b;
+        this.pos = 0;
+    }
+
+    public synchronized void write(byte b[], int off, int len) {
+        for(int i = off; i < len; i++) {
+            write(b[off + i]);
+        }
+    }
+
+    public synchronized void write(int b) {
+        if(pos == sizeX) {
+            nextArray();
+        }
+        totalCount++;
+        buf[pos++] = (byte) (b & 0xFF);
+    }
+
+    public static void printHex(int value) {
+        value = value & 0xFF;
+        String s = Integer.toHexString(value);
+        if(s.length() == 1) {
+            Sys.out.print("0");
+        }
+        Sys.out.print(s);
+        Sys.out.print(" ");
+    }
+
+    public void reset() {
+        totalCount = 0;
+        v = new Vector();
+        nextArray();
+    }
+
+    public Vector getVector() {
+
+        int lastIndex = this.v.size() - 1;
+        byte[] b = (byte[]) this.v.elementAt(lastIndex);
+        byte[] b2 = new byte[pos];
+        System.arraycopy(b, 0, b2, 0, pos);
+        this.v.setElementAt(b2, lastIndex);
+
+        return this.v;
+    }
+
+    public void flush() {
+    }
+
+    public void close() {
+    }
+
+    public int length() {
+        return (this.v.size() - 1) * this.sizeX + this.pos;
+    }
+}
Index: ocean/src/com/imagero/uio/io/PackBitsInputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/PackBitsInputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/PackBitsInputStream.java	(revision 0)
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * PackBits decoder
+ *
+ * @author Andrei Kouznetsov
+ */
+public class PackBitsInputStream extends FilterInputStream {
+
+    boolean finished;
+
+    int numSamples, value;
+    boolean copyLiter;
+
+    public PackBitsInputStream(InputStream in) {
+        super(in);
+    }
+
+    public int available() throws IOException {
+        if(finished) {
+            return 0;
+        }
+        return 1;
+    }
+
+    public void close() throws IOException {
+    }
+
+    public boolean markSupported() {
+        return false;
+    }
+
+    public synchronized void mark(int readlimit) {
+    }
+
+    public synchronized void reset() throws IOException {
+        throw new IOException("mark/reset not supported");
+    }
+
+    public int read(byte b[]) throws IOException {
+        return read(b, 0, b.length);
+    }
+
+    public int read(byte b[], int off, int len) throws IOException {
+        int i = off;
+
+        try {
+            for(; i < off + len; i++) {
+                int a = read();
+                if(a == -1) {
+                    i--;
+                    break;
+                }
+                b[i] = (byte) a;
+            }
+        }
+        catch(IOException ex) {
+            ex.printStackTrace();
+        }
+        return i - off;
+    }
+
+    public int read() throws IOException {
+        if(numSamples == 0) {
+            byte l = read128();
+            if(l < 0) {
+                numSamples = -l + 1;
+                copyLiter = false;
+                value = in.read();
+            }
+            else {
+                numSamples = l + 1;
+                copyLiter = true;
+            }
+        }
+        numSamples--;
+        if(copyLiter) {
+            return in.read();
+        }
+        else {
+            return value;
+        }
+    }
+
+    byte read128() throws IOException {
+        do {
+            byte a = (byte) in.read();
+            if(a != -128) {
+                return a;
+            }
+        }
+        while(true);
+    }
+}
Index: ocean/src/com/imagero/uio/io/PackBitsOutputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/PackBitsOutputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/PackBitsOutputStream.java	(revision 0)
@@ -0,0 +1,761 @@
+package com.imagero.uio.io;
+
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.ByteArrayInputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Date: 02.08.2007
+ *
+ * @author Andrey Kuznetsov
+ */
+public class PackBitsOutputStream extends FilterOutputStream {
+
+    private RunBuffer runs;
+    int width;
+    int cnt;
+
+    public PackBitsOutputStream(OutputStream out, int width) {
+        super(out);
+        runs = new RunBuffer(out);
+        this.width = width;
+    }
+
+    public void write(int b) throws IOException {
+        put(b);
+    }
+
+    private void put(int b) throws IOException {
+        runs.put(b);
+        if (cnt++ == width) {
+            flush();
+            cnt = 0;
+        }
+    }
+
+    public void write(byte b[]) throws IOException {
+        for (int i = 0; i < b.length; i++) {
+            put(b[i] & 0xFF);
+        }
+    }
+
+    public void write(byte b[], int off, int len) throws IOException {
+        for (int i = 0; i < len; i++) {
+            put(b[off + i] & 0xFF);
+        }
+    }
+
+    public void flush() throws IOException {
+        runs.flush();
+    }
+
+    public void close() throws IOException {
+        runs.flush();
+    }
+
+    static class RunOutputStream extends FilterOutputStream {
+        int maxRun;
+        int rep = -2;
+
+        int count;
+
+        public RunOutputStream(OutputStream out, int maxRun) {
+            super(out);
+            this.maxRun = maxRun;
+        }
+
+        public void write(int b) throws IOException {
+            if (b != rep) {
+                throw new IOException("Rep");
+            }
+            if (count++ == maxRun) {
+                out.write(count - 1);
+                out.write(rep);
+                count = 0;
+            }
+        }
+
+        public void set(int rep) {
+            this.rep = rep;
+            count = 2;
+        }
+
+        public void flush() throws IOException {
+            if (count != 0) {
+                out.write(count - 1);
+                out.write(rep);
+                count = 0;
+            }
+        }
+    }
+
+    static class RunBuffer {
+        int rep;
+        int count;
+
+        OutputStream out;
+        RunOutputStream ros;
+        private LiteralBuffer lbuf;
+
+        public RunBuffer(OutputStream out) {
+            this.out = out;
+            ros = new RunOutputStream(out, 128);
+            this.lbuf = new LiteralBuffer(out);
+        }
+
+        public void put(int k) throws IOException {
+            if (count == 0) {
+                rep = k;
+                count = 1;
+            } else {
+                if (rep == k) {
+                    if (count++ == 2) {
+                        ros.set(rep);
+                        lbuf.flush();
+                    } else if (count > 2) {
+                        ros.write(rep);
+                    }
+                } else {
+                    if (count > 2) {
+                        ros.flush();
+                    } else {
+                        for (int i = 0; i < count; i++) {
+                            lbuf.put(rep);
+                        }
+                    }
+                    rep = k;
+                    count = 1;
+                }
+            }
+        }
+
+        void flush() throws IOException {
+            if (count > 0) {
+                ros.flush();
+                for (int i = 0; i < count; i++) {
+                    lbuf.put(rep);
+                }
+                count = 0;
+            }
+            lbuf.flush();
+        }
+
+        void write() throws IOException {
+            lbuf.flush();
+            out.write(-(count - 1));
+            out.write(rep);
+            count = 0;
+        }
+    }
+
+    static class LiteralBuffer {
+        byte[] buffer = new byte[129];
+        int count;
+
+        OutputStream out;
+
+        public LiteralBuffer(OutputStream out) {
+            this.out = out;
+        }
+
+        public void put(int k) throws IOException {
+            buffer[count++] = (byte) k;
+            if (count == 128) {
+                write();
+            }
+        }
+
+        void write() throws IOException {
+            out.write(count - 1);
+            out.write(buffer, 0, count);
+            count = 0;
+        }
+
+        void flush() throws IOException {
+            if (count > 0) {
+                write();
+            }
+        }
+    }
+
+    static class PeekInputStream extends ByteArrayInputStream {
+        int offset;
+
+        public PeekInputStream(byte buf[]) {
+            super(buf);
+        }
+
+        public PeekInputStream(byte buf[], int offset, int length) {
+            super(buf, offset, length);
+            this.offset = offset;
+        }
+
+        public int peek(int offset) {
+            return buf[pos + offset];
+        }
+
+        public int getPosition() {
+            return pos - offset;
+        }
+
+        public void inc() {
+            pos++;
+        }
+    }
+
+    public static class PackBitsApache extends FilterOutputStream {
+
+        int bytesPerRow;
+        int inMax;
+        int inMaxMinus1;
+
+        PeekInputStream input;
+        byte[] tmp = new byte[128];
+
+        int wpos = 0;
+        byte[] row;
+
+        public PackBitsApache(OutputStream out, int bytesPerRow) {
+            super(out);
+            this.bytesPerRow = bytesPerRow;
+            inMax = bytesPerRow - 1;
+            inMaxMinus1 = inMax - 1;
+            this.row = new byte[bytesPerRow];
+        }
+
+        public void write(int b) throws IOException {
+            row[wpos++] = (byte) b;
+            if (wpos == bytesPerRow) {
+                wpos = 0;
+                input = new PeekInputStream(row);
+                packBits();
+            }
+        }
+
+        public void write(byte b[]) throws IOException {
+            write(b, 0, b.length);
+        }
+
+        public void write(byte b[], int off, int len) throws IOException {
+            for (int i = 0; i < len; i++) {
+                write(b[off + i] & 0xFF);
+            }
+        }
+
+        public void flush() throws IOException {
+            if (wpos > 0) {
+                input = new PeekInputStream(row, 0, wpos);
+                packBits();
+            }
+        }
+
+        private void packBits() throws IOException {
+            while (input.getPosition() <= inMax) {
+                doRunLoop();
+                doLiterLoop();
+            }
+        }
+
+        private void doLiterLoop() throws IOException {
+            int run = 0;
+            while (run < 128 &&
+                    ((input.getPosition() < inMax) && (input.peek(0) != input.peek(1))
+                    || ((input.getPosition() < inMaxMinus1) && (input.peek(0) != input.peek(2))))) {
+                tmp[run++] = (byte) input.read();
+            }
+
+            if (input.getPosition() == inMax && (run > 0 && run < 128)) {
+                tmp[run++] = (byte) input.read();
+            }
+
+            if (run > 0) {
+                out.write(run - 1);
+                out.write(tmp, 0, run);
+            } else if (input.getPosition() == inMax) {
+                out.write(0);
+                out.write(input.read() & 0xFF);
+            }
+        }
+
+        private void doRunLoop() throws IOException {
+            int run = 1;
+            byte replicate = (byte) input.peek(0);
+            while (run < 127 && input.getPosition() < inMax && input.peek(0) == input.peek(1)) {
+                run++;
+                input.inc();
+            }
+            if (run > 1) {
+                input.inc();
+                out.write(-(run - 1));
+                out.write(replicate);
+            }
+        }
+    }
+
+    static class PBApache {
+        static int compressPackBits(byte[] data, int numRows, int bytesPerRow, byte[] compData) {
+            int inOffset = 0;
+            int outOffset = 0;
+            byte[] tmp = new byte[128];
+            for (int i = 0; i < numRows; i++) {
+                outOffset = packBits(data, inOffset, bytesPerRow, compData, outOffset, tmp);
+                inOffset += bytesPerRow;
+            }
+            return outOffset;
+        }
+
+        private static int packBits(byte[] input, int inOffset, int inCount, byte[] output, int outOffset, byte[] tmp) {
+            int inMax = inOffset + inCount - 1;
+            int inMaxMinus1 = inMax - 1;
+            while (inOffset <= inMax) {
+                int run = 1;
+                byte replicate = input[inOffset];
+                while (run < 127 && inOffset < inMax && input[inOffset] == input[inOffset + 1]) {
+                    run++;
+                    inOffset++;
+                }
+                if (run > 1) {
+                    inOffset++;
+                    output[outOffset++] = (byte) (-(run - 1));
+                    output[outOffset++] = replicate;
+                }
+                run = 0;
+                while (run < 128 && ((inOffset < inMax && input[inOffset] != input[inOffset + 1]) || (inOffset < inMaxMinus1 && input[inOffset] != input[inOffset + 2]))) {
+                    tmp[run++] = input[inOffset++];
+                }
+                if (inOffset == inMax && (run > 0 && run < 128)) {
+                    tmp[run++] = input[inOffset++];
+                }
+                if (run > 0) {
+                    output[outOffset++] = (byte) (run - 1);
+                    for (int i = 0; i < run; i++) {
+                        output[outOffset++] = tmp[i];
+                    }
+                } else if (inOffset == inMax) {
+                    output[outOffset++] = (byte) 0;
+                    output[outOffset++] = input[inOffset++];
+                }
+            }
+            return outOffset;
+        }
+    }
+
+//    public static void main(String[] args) throws IOException {
+//        String s = "C:\\Support\\AndySwanson\\work\\IMG_0703_source_RC1.jpg";
+////        String s = "C:\\Support\\AndySwanson\\work\\IMG_0703_source_RC1_LZW.tif";
+////        String s = "C:\\Support\\AndySwanson\\work\\IMG_0703_source_RC1_RLE.tif";
+////        String d = "C:\\Support\\AndySwanson\\work\\IMG_0703_source_RC1.tif";
+//
+//
+//        ImageReader reader = ReaderFactory.createReader(s);
+//        int[] data = (int[]) reader.getAsArray(0, true);
+//        byte[] bdata = new byte[data.length * 3];
+//
+//        IntArrayInputStream iais = new IntArrayInputStream(data);
+//        SkipBytesInputStream sbis = new SkipBytesInputStream(iais, 2 + 4 + 8, 4);
+//
+//        ByteArrayOutputStream2 out2 = new ByteArrayOutputStream2(bdata);
+//        IOutils.copy(sbis, out2);
+//
+//        int w = reader.getWidth(0);
+//        int h = reader.getHeight(0);
+//        int w3 = w * 3;
+//
+//        ByteArrayOutputStream bout = new ByteArrayOutputStream();
+//        PackBitsApache packBitsApache = new PackBitsApache(bout, w3);
+//        packBitsApache.write(bdata);
+//        packBitsApache.close();
+//        byte[] res = bout.toByteArray();
+//
+//        byte[] compData = new byte[res.length];
+//        int length = PBApache.compressPackBits(bdata, h, w3, compData);
+//
+//        System.out.println(compData.length);
+//        System.out.println(res.length);
+//
+//        for (int i = 0; i < length; i++) {
+//            if (compData[i] != res[i]) {
+//                System.out.println(i);
+//                System.out.println(compData[i]);
+//                System.out.println(res[i]);
+//                return;
+//            }
+//        }
+//        System.out.println("identisch");
+//
+//
+//        ByteArrayOutputStream bout2 = new ByteArrayOutputStream();
+////        ParserOutputStream pos = new ParserOutputStream();
+////        MultiplexOutputStream mos = new MultiplexOutputStream(bout2, pos);
+////        mos.setSearchPosition(1930);
+////        mos.addActionListener(new ActionListener() {
+////            public void actionPerformed(ActionEvent e) {
+////                System.out.println("position");
+////            }
+////        });
+//
+////        PackBitsOutputStream pbos = new PackBitsOutputStream(mos, w3);
+//        Control pbos = new Control(bout2, w3);
+//        pbos.write(bdata);
+//        byte[] pbosData = bout2.toByteArray();
+//        System.out.println(pbosData.length + " " + length);
+//
+//        for (int i = 0; i < length; i++) {
+//            int k1 = pbosData[i] & 0xFF;
+//            int m1 = res[i] & 0xFF;
+//            if (k1 == m1) {
+////                System.out.println(Integer.toHexString(k) + " " + Integer.toHexString(m));
+//            } else {
+////                int k = pbosData[i - 2] & 0xFF;
+////                int k0 = pbosData[i - 1] & 0xFF;
+////                int k2 = pbosData[i + 1] & 0xFF;
+////                int k3 = pbosData[i + 2] & 0xFF;
+////                int k4 = pbosData[i + 3] & 0xFF;
+////                int m = res[i - 2] & 0xFF;
+////                int m0 = res[i - 1] & 0xFF;
+////                int m2 = res[i + 1] & 0xFF;
+////                int m3 = res[i + 2] & 0xFF;
+////                int m4 = res[i + 3] & 0xFF;
+////                System.out.println((i - 2) + " " + Integer.toHexString(k) + " " + Integer.toHexString(m) + " *");
+////                System.out.println((i - 1) + " " + Integer.toHexString(k0) + " " + Integer.toHexString(m0) + " *");
+//                System.out.println(i + " " + Integer.toHexString(k1) + " " + Integer.toHexString(m1) + " *");
+////                System.out.println((i + 1) + " " + Integer.toHexString(k2) + " " + Integer.toHexString(m2) + " *");
+////                System.out.println((i + 2) + " " + Integer.toHexString(k3) + " " + Integer.toHexString(m3) + " *");
+////                System.out.println((i + 3) + " " + Integer.toHexString(k4) + " " + Integer.toHexString(m4) + " *");
+////                break;
+////                System.out.println("*****************");
+//            }
+//        }
+//    }
+
+    static class MultiplexOutputStream extends OutputStream {
+
+        OutputStream out1, out2;
+        long p = 0;
+
+        long searchPosition = -1;
+
+        ActionListener listener;
+
+        public MultiplexOutputStream(OutputStream out1, OutputStream out2) {
+            this.out1 = out1;
+            this.out2 = out2;
+        }
+
+        public void write(int b) throws IOException {
+            inc();
+            out1.write(b);
+            out2.write(b);
+        }
+
+        public void addActionListener(ActionListener listener) {
+            this.listener = AWTEventMulticaster.add(this.listener, listener);
+        }
+
+        public void removeActionListener(ActionListener listener) {
+            this.listener = AWTEventMulticaster.remove(this.listener, listener);
+        }
+
+        public long getSearchPosition() {
+            return searchPosition;
+        }
+
+        public void setSearchPosition(long searchPosition) {
+            this.searchPosition = searchPosition;
+        }
+
+        private void inc() {
+            if (p++ == searchPosition) {
+                fireActionEvent();
+            }
+        }
+
+        private void fireActionEvent() {
+            if (listener != null) {
+                listener.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "Position"));
+            }
+        }
+
+        public void write(byte b[]) throws IOException {
+            inc(b.length);
+            out1.write(b);
+            out2.write(b);
+        }
+
+        private void inc(int length) {
+            p += length;
+            if (p - length <= searchPosition && p >= searchPosition) {
+                fireActionEvent();
+            }
+        }
+
+        public void write(byte b[], int off, int len) throws IOException {
+            inc(len);
+            out1.write(b, off, len);
+            out2.write(b, off, len);
+        }
+
+        public void flush() throws IOException {
+            out1.flush();
+            out2.flush();
+        }
+
+        public void close() throws IOException {
+            out1.close();
+            out2.close();
+        }
+    }
+
+//    static class ParserOutputStream extends OutputStream {
+//
+//        StreamParser sp;
+//        long p;
+//
+//        public ParserOutputStream() {
+//            sp = new StreamParser() {
+//                protected StreamParser.CharHandler createParameterHandler() {
+//                    return new ParameterHandler(this, new char[0], new char[0], new char[0]) {
+//                        public boolean nextChar(char c, long offset) {
+//                            ICharSequence[] prms = new ICharSequence[params.getCount()];
+//                            Entry entry = putParam(prms, offset);
+//                            parser.postParserEvent(this, entry);
+//                            setHandler(getNext());
+//                            return false;
+//                        }
+//                    };
+//                }
+//            };
+//            byte[] bstr = {0x53, 0x61, 0x0, 0x6f, 0x11, 0x7a};
+//            sp.register(new VKey(new String(bstr), VKey.KEY_TYPE_COMMENT));
+//
+//            sp.addParserListener(new ParserListener() {
+//                public boolean gotToken(ParserEvent e) {
+//                    Entry entry = e.getEntry();
+//                    long offset = entry.getOffset();
+//                    Object value = entry.getValue();
+//                    System.out.println(offset);
+//                    System.out.println(value);
+//                    return true;
+//                }
+//            });
+//        }
+//
+//        public void write(int b) throws IOException {
+//            sp.nextChar((char) b, p++);
+//        }
+//
+//        public void write(byte b[]) throws IOException {
+//            write(b, 0, b.length);
+//        }
+//
+//        public void write(byte b[], int off, int len) throws IOException {
+//            for (int i = 0; i < len; i++) {
+//                sp.nextChar((char) b[off + i], p++);
+//            }
+//        }
+//    }
+
+    static interface Writer {
+        boolean next(byte b) throws IOException;
+
+        byte get();
+
+        void flush() throws IOException;
+
+        Writer nextWriter();
+    }
+
+    static class Control extends FilterOutputStream {
+        Run run;
+        Liter liter;
+
+        Writer current;
+        Writer last;
+
+        int count;
+        int max;
+
+        public Control(OutputStream out, int max) {
+            super(out);
+            this.max = max;
+            run = new Run(out);
+            liter = new Liter(out);
+            run.nextWriter = liter;
+            liter.nextWriter = run;
+            current = run;
+        }
+
+        public void write(int b) throws IOException {
+            count++;
+            if (!current.next((byte) b)) {
+                last = current;
+                current = current.nextWriter();
+                byte n;
+                while ((n = last.get()) != -1) {
+                    current.next(n);
+                }
+            }
+            if (count == max) {
+                count = 0;
+                current.flush();
+            }
+        }
+
+        public void write(byte b[], int off, int len) throws IOException {
+            for (int i = 0; i < b.length; i++) {
+                write(b[i] & 0xFF);
+            }
+        }
+    }
+
+    static class Run implements Writer {
+        int run;
+        int count;
+        int w;
+
+        int rep;
+
+        byte last;
+
+        Writer nextWriter;
+
+        OutputStream out;
+
+        public Run(OutputStream out) {
+            this.out = out;
+        }
+
+        public Writer nextWriter() {
+            return nextWriter;
+        }
+
+        void init(byte rep, int count, int run) {
+            this.rep = rep;
+            this.run = run;
+            this.count = count;
+        }
+
+        public byte get() {
+            byte tmp = last;
+            last = -1;
+            return tmp;
+        }
+
+        public boolean next(byte b) throws IOException {
+            last = b;
+            if (run == 0) {
+                rep = b & 0xFF;
+                run++;
+                return true;
+            }
+            if (b == rep) {
+                run++;
+                count++;
+                if (run == 128) {
+                    write();
+                }
+                return true;
+            } else {
+                if (run > 1) {
+                    write();
+                }
+                return false;
+            }
+        }
+
+        private void write() throws IOException {
+            out.write(-(run - 1));
+            out.write(rep);
+            run = 0;
+        }
+
+        public void flush() throws IOException {
+            if (run > 0) {
+                write();
+            }
+        }
+    }
+
+    static class Liter implements Writer {
+        int b0 = -1;
+        int b1 = -1;
+        int b2 = -1;
+        int run;
+        int count;
+        int max;
+
+        int pos;
+        byte[] buffer = new byte[128];
+
+        Writer nextWriter;
+
+        OutputStream out;
+
+        public Liter(OutputStream out) {
+            this.out = out;
+        }
+
+        public Writer nextWriter() {
+            return nextWriter;
+        }
+
+        public boolean next(byte a) throws IOException {
+            b2 = a & 0xFF;
+            if (run > 0) {
+                if (b0 == b1 && b0 == b2) {
+                    write();
+                    return false;
+                }
+            }
+            shift();
+            if (run == 128) {
+                write();
+            }
+            return true;
+        }
+
+        void shift() {
+            if (b0 != -1) {
+                add(b0);
+            }
+            b0 = b1;
+            b1 = b2;
+            b2 = -1;
+        }
+
+        public byte get() {
+            byte b = (byte) b0;
+            b0 = b1;
+            b1 = -1;
+            return b;
+        }
+
+        private void add(int b) {
+            buffer[run++] = (byte) b;
+            count++;
+        }
+
+        public void flush() throws IOException {
+            if (run > 0 && run < 128) {
+                shift();
+                write();
+            } else if (run == 0) {
+                shift();
+                out.write(0);
+                out.write(buffer[0] & 0xFF);
+            }
+        }
+
+        private void write() throws IOException {
+            if (run > 0) {
+                out.write(run - 1);
+                out.write(buffer, 0, run);
+            }
+            run = 0;
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/io/RandomAccessInputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/RandomAccessInputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/RandomAccessInputStream.java	(revision 0)
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import com.imagero.uio.RandomAccessInput;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * same as FilterInputStream but with RandomAccessIO
+ * @author Kouznetsov Andrei
+ */
+public class RandomAccessInputStream extends InputStream {
+	protected static long MARK_UNDEFINED = -1L;
+
+	protected RandomAccessInput ro;
+	protected long pos;
+	protected long mark = MARK_UNDEFINED;
+	protected long startPos;
+
+
+	public RandomAccessInputStream(RandomAccessInput ro) {
+		this(ro, 0L);
+	}
+
+	public RandomAccessInputStream(RandomAccessInput ro, long startPos) {
+		this.ro = ro;
+		this.pos = startPos;
+		this.startPos = startPos;
+	}
+
+	synchronized public int read() throws IOException {
+		checkPos();
+		int a = ro.read();
+		pos++;
+		return a;
+	}
+
+	public int read(byte[] b) throws IOException {
+		return read(b, 0, b.length);
+	}
+
+	synchronized public int read(byte[] b, int off, int len) throws IOException {
+		checkPos();
+        if(ro == null) {
+            return -1;
+        }
+		int r = ro.read(b, off, len);
+		pos += r;
+		return r;
+	}
+
+	protected void checkPos() throws IOException {
+        moved = false;
+		if(ro != null && ro.getFilePointer() != pos) {
+			ro.seek(pos);
+		}
+	}
+
+    boolean moved;
+
+    /**
+     * releases reference to RandomAccessRO, but does not closes it
+     */
+	public void close() throws IOException {
+        if(moved) {
+            ro.seek(pos);
+        }
+		ro = null;
+	}
+
+	public int available() throws IOException {
+		return (int)(ro.length() - pos);
+	}
+
+	public boolean markSupported() {
+		return true;
+	}
+
+	public void mark(int i) {
+		mark = pos;
+	}
+
+	public void reset() throws IOException {
+		if(mark == MARK_UNDEFINED) {
+			throw new IOException("mark undefined");
+		}
+        moved = true;
+		pos = mark;
+	}
+
+	public long skip(long l) throws IOException {
+		long skip = Math.min(ro.length() - pos, l);
+		pos += skip;
+        moved = true;
+		return skip;
+	}
+}
Index: ocean/src/com/imagero/uio/io/RandomAccessOutputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/RandomAccessOutputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/RandomAccessOutputStream.java	(revision 0)
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import com.imagero.uio.RandomAccessIO;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * RandomAccessOutputStream.java
+ * Can be used as bridge between RandomAccessFile and OutputStream.
+ * @author Andrey Kuznetsov
+ */
+public class RandomAccessOutputStream extends OutputStream {
+
+    protected RandomAccessIO ra;
+    protected long pos;
+
+    public RandomAccessOutputStream(RandomAccessIO ra) {
+        this(ra, 0L);
+    }
+
+    public RandomAccessOutputStream(RandomAccessIO ra, long startPos) {
+        this.ra = ra;
+        this.pos = startPos;
+    }
+
+    protected void checkPos() throws IOException {
+        long fp = ra.getFilePointer();
+        if (fp != pos) {
+            ra.seek(pos);
+        }
+    }
+
+    public void write(int b) throws IOException {
+        checkPos();
+        writeImpl(b);
+        pos++;
+    }
+
+    private void writeImpl(int b) throws IOException {
+        ra.write(b);
+    }
+
+    public void write(byte b[]) throws IOException {
+        write(b, 0, b.length);
+    }
+
+    public void write(byte b[], int off, int len) throws IOException {
+        checkPos();
+        ra.write(b, off, len);
+        pos += len;
+    }
+
+    public void close() throws IOException {
+        ra = null;
+    }
+}
Index: ocean/src/com/imagero/uio/io/RLE4InputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/RLE4InputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/RLE4InputStream.java	(revision 0)
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import java.io.IOException;
+
+/**
+ * PackBits decoder
+ *
+ * @author Andrei Kouznetsov
+ */
+public class RLE4InputStream extends RLEInputStream {
+
+    byte[] value = new byte[2];
+
+    BitInputStream bin;
+
+    byte[] buffer = new byte[128];
+    int bufferStart;
+    int bufferLength;
+
+    ByteArrayOutputStreamExt bout;
+    BitOutputStream bitOut;
+
+    public RLE4InputStream(BitInputStream in) {
+        super(in);
+        bin = in;
+        bin.setBitsToRead(4);
+        bout = new ByteArrayOutputStreamExt();
+        bitOut = new BitOutputStream(bout);
+    }
+
+    public int read() throws IOException {
+        if (bufferStart >= bufferLength) {
+            fillBuffer();
+        }
+        if (bufferStart >= bufferLength) {
+            return -1;
+        }
+        return buffer[bufferStart++] & 0xFF;
+    }
+
+    private void fillBuffer() throws IOException {
+        if (bout.size() == 0) {
+            fillBufferImpl();
+        }
+        bufferStart = 0;
+        bufferLength = bout.drain(buffer);
+    }
+
+    private void fillBufferImpl() throws IOException {
+        int len = bin.read(8);
+        if (len == 0) {
+            int value = bin.read(8);
+            switch (value) {
+                case 0:
+                    throw new EndOfLineException();
+                case 1:
+                    finished = true;
+                    throw new EndOfBitmapException();
+                case 2:
+                    int x = bin.read(8);
+                    int y = bin.read(8);
+                    throw new DeltaRecordException(x, y);
+                default:
+                    int skipCount = 0;
+                    if ((value & 3) != 0) {
+                        skipCount = (value + 3) / 4 * 4 - value;
+                    }
+                    for (int i = 0; i < value; i++) {
+                        bitOut.write(bin.read(4), 4);
+                    }
+                    for (int i = 0; i < skipCount; i++) {
+                        /*int ignored = */bin.read(4);
+                    }
+            }
+        }
+        else {
+            value[0] = (byte) bin.read(4);
+            value[1] = (byte) bin.read(4);
+
+            for (int i = 0; i < len; i++) {
+                bitOut.write(value[i & 1], 4);
+            }
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/io/RLE8InputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/RLE8InputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/RLE8InputStream.java	(revision 0)
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * PackBits decoder
+ *
+ * @author Andrei Kouznetsov
+ */
+public class RLE8InputStream extends RLEInputStream {
+
+    int numSamples, value;
+    boolean copyLiter;
+    boolean ignoreByte;
+
+    public RLE8InputStream(InputStream in) {
+        super(in);
+    }
+
+    public int read() throws IOException {
+        if (numSamples == 0) {
+            if (ignoreByte) {
+                ignoreByte = false;
+                /*int ignored = */in.read();
+            }
+            int len = in.read();
+            if (len == 0) {
+                value = in.read();
+                switch (value) {
+                    case 0:
+                        throw new EndOfLineException();
+                    case 1:
+                        finished = true;
+                        throw new EndOfBitmapException();
+                    case 2:
+                        int x = in.read();
+                        int y = in.read();
+                        throw new DeltaRecordException(x, y);
+                    default:
+                        copyLiter = true;
+                        numSamples = value;
+                        if ((numSamples & 1) != 0) {
+                            ignoreByte = true;
+                        }
+                }
+            }
+            else {
+                numSamples = len;
+                copyLiter = false;
+                value = in.read();
+            }
+        }
+        numSamples--;
+        if (copyLiter) {
+            return in.read();
+        }
+        else {
+            return value;
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/io/RLEInputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/RLEInputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/RLEInputStream.java	(revision 0)
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @author Andrey Kuznetsov
+ */
+public abstract class RLEInputStream extends FilterInputStream {
+    boolean finished;
+
+    public RLEInputStream(InputStream in) {
+        super(in);
+    }
+
+    public int available() throws IOException {
+        if (finished) {
+            return 0;
+        }
+        return 1;
+    }
+
+    public void close() throws IOException {
+    }
+
+    public boolean markSupported() {
+        return false;
+    }
+
+    public synchronized void mark(int readlimit) {
+    }
+
+    public synchronized void reset() throws IOException {
+        throw new IOException("mark/reset not supported");
+    }
+
+    public int read(byte b[]) throws IOException {
+        return read(b, 0, b.length);
+    }
+
+    public int read(byte b[], int off, int len) throws IOException {
+        int i = off;
+
+        try {
+            for (; i < off + len; i++) {
+                int a = read();
+                if (a == -1) {
+                    i--;
+                    break;
+                }
+                b[i] = (byte) a;
+            }
+        }
+        catch (EndOfLineException ex) {
+            //ignore
+        }
+        catch(EndOfBitmapException ex) {
+            //ignore
+        }
+        catch(IOException ex) {
+            ex.printStackTrace();
+        }
+        return i - off;
+    }
+
+    public abstract int read() throws IOException;
+
+    public static class EndOfLineException extends IOException {
+        public EndOfLineException() {
+            super("EndOfLineException");
+        }
+    }
+
+    public static class EndOfBitmapException extends IOException {
+        public EndOfBitmapException() {
+            super("EndOfBitmapException");
+        }
+    }
+
+    public static class DeltaRecordException extends IOException {
+        public final int dx;
+        public final int dy;
+
+        public DeltaRecordException(int dx, int dy) {
+            this.dx = dx;
+            this.dy = dy;
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/io/SkipBytesInputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/SkipBytesInputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/SkipBytesInputStream.java	(revision 0)
@@ -0,0 +1,68 @@
+package com.imagero.uio.io;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Date: 30.07.2007
+ *
+ * @author Andrey Kuznetsov
+ */
+public class SkipBytesInputStream extends FilterInputStream {
+
+    static long[] msk = createMask();
+
+    private static long[] createMask() {
+        long[] m = new long[64];
+        m[0] = 1;
+        for (int i = 1; i < m.length; i++) {
+            m[i] = m[i - 1] << 1;
+        }
+        return m;
+    }
+
+    public SkipBytesInputStream(InputStream in, long mask, int mod) {
+        super(in);
+        this.mask = mask;
+        this.mod = mod;
+    }
+
+    int fp;
+    long mask;
+    int mod;
+
+    public int read() throws IOException {
+        if (mask == 0) {
+            return super.read();
+        } else {
+            int a = in.read();
+            long k = msk[(fp++ % mod)] & mask;
+            if (k != 0) {
+                return a;
+            } else {
+                return read();
+            }
+        }
+    }
+
+    public int read(byte b[], int off, int len) throws IOException {
+        int k = in.read(b, off, len);
+        return read0(b, off, k);
+    }
+
+    private int read0(byte[] b, int off, int len) {
+        if (mask == 0) {
+            return len;
+        }
+        int len0 = 0;
+        int p = off;
+        for (int i = 0; i < len; i++) {
+            if ((msk[(fp++ % mod)] & mask) != 0) {
+                b[p++] = b[i];
+                len0++;
+            }
+        }
+        return len0;
+    }
+}
Index: ocean/src/com/imagero/uio/io/StringArrayReader.java
===================================================================
--- ocean/src/com/imagero/uio/io/StringArrayReader.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/StringArrayReader.java	(revision 0)
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://res.imagero.com
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.io;
+
+import java.io.IOException;
+import java.io.Reader;
+
+/**
+ * A character stream whose source is a string array.
+ * Very similar to java.io.StringReader.
+ * @author Andrey Kuznetsov
+ */
+public class StringArrayReader extends Reader {
+
+    private String[] str;
+    private int[] ends;
+    private int[] starts;
+    private int length;
+    private int next = 0;
+    private int mark = 0;
+
+    int current;
+
+    /**
+     * Create a new StringArrayReader.
+     *
+     * @param s  String array providing the character stream.
+     */
+    public StringArrayReader(String[] s) {
+        this.str = s;
+        for (int i = 0; i < s.length; i++) {
+            length += s[i].length();
+        }
+
+        starts = new int[s.length];
+        for (int i = 1; i < s.length; i++) {
+            starts[i] = starts[i - 1] + s[i - 1].length();
+        }
+
+        ends = new int[s.length];
+        ends[0] = s[0].length();
+        for (int i = 1; i < s.length; i++) {
+            ends[i] = ends[i - 1] + s[i].length();
+        }
+    }
+
+    /**
+     * Check to make sure that the stream has not been closed
+     */
+    private void ensureOpen() throws IOException {
+        if (str == null) {
+            throw new IOException("Stream closed");
+        }
+    }
+
+    /**
+     * Read a single character.
+     * @return The character read, or -1 if the end of the stream has been reached
+     * @exception IOException  If an I/O error occurs
+     */
+    public int read() throws IOException {
+        synchronized (lock) {
+            ensureOpen();
+            if (next >= length) {
+                return -1;
+            }
+            if (next >= ends[current]) {
+                current++;
+            }
+            return str[current].charAt(next++ - starts[current]);
+        }
+    }
+
+    /**
+     * Read characters into a portion of supplied char array.
+     *
+     * @param cbuf Destination char array
+     * @param off Where to start writing characters
+     * @param len Maximum number of characters to read
+     * @return The number of characters read, or -1 if the end of the stream has been reached
+     * @exception IOException If an I/O error occurs
+     */
+    public int read(char cbuf[], int off, int len) throws IOException {
+        synchronized (lock) {
+            ensureOpen();
+            if ((off < 0) || (off > cbuf.length) || (len < 0) || ((off + len) > cbuf.length) || ((off + len) < 0)) {
+                throw new IndexOutOfBoundsException();
+            }
+            else if (len == 0) {
+                return 0;
+            }
+            if (next >= length) {
+                return -1;
+            }
+            if (next >= ends[current]) {
+                current++;
+            }
+            int n = Math.min(ends[current] - next, len);
+            str[current].getChars(next - starts[current], next - starts[current] + n, cbuf, off);
+            next += n;
+            return n;
+        }
+    }
+
+    /**
+     * Skip characters.
+     * @exception  IOException  If an I/O error occurs
+     */
+    public long skip(long ns) throws IOException {
+        synchronized (lock) {
+            ensureOpen();
+            if (next >= length) {
+                return 0;
+            }
+            if (next >= ends[current]) {
+                current++;
+            }
+            long n = Math.min(ends[current] - next, ns);
+            next += n;
+            return n;
+        }
+    }
+
+    /**
+     * Tell whether this stream is ready to be read.
+     * @return True if the next read() is guaranteed not to block for input
+     * @exception IOException If the stream is closed
+     */
+    public boolean ready() throws IOException {
+        synchronized (lock) {
+            ensureOpen();
+            return true;
+        }
+    }
+
+    /**
+     * Tell whether this stream supports the mark() operation.
+     * @return true
+     */
+    public boolean markSupported() {
+        return true;
+    }
+
+    /**
+     * Mark the present position in the stream.
+     * Subsequent calls to reset() will reposition the stream to this point.
+     * @param  readAheadLimit Limit on the number of characters that may be
+     * read while still preserving the mark.
+     * @exception IllegalArgumentException If readAheadLimit is < 0
+     * @exception IOException If an I/O error occurs
+     */
+    public void mark(int readAheadLimit) throws IOException {
+        if (readAheadLimit < 0) {
+            throw new IllegalArgumentException("Read-ahead limit < 0");
+        }
+        synchronized (lock) {
+            ensureOpen();
+            mark = next;
+        }
+    }
+
+    /**
+     * Reset the stream to the most recent mark,
+     * or to the beginning of the string if it has never been marked.
+     * @exception IOException If an I/O error occurs
+     */
+    public void reset() throws IOException {
+        synchronized (lock) {
+            ensureOpen();
+            next = mark;
+            if (starts[current] > next) {
+                current++;
+            }
+        }
+    }
+
+    /**
+     * Close the stream.
+     */
+    public void close() {
+        str = null;
+    }
+}
Index: ocean/src/com/imagero/uio/io/TargaRLEInputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/TargaRLEInputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/TargaRLEInputStream.java	(revision 0)
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @author Andrey Kuznetsov
+ */
+public class TargaRLEInputStream extends RLEInputStream {
+
+    int numSamples;
+    byte [] value;
+    boolean rawPacket;
+    int pixelSize;
+    int vindex;
+
+    public TargaRLEInputStream(InputStream in, int pixelSize) {
+        super(in);
+        this.pixelSize = pixelSize;
+        value = new byte[pixelSize];
+    }
+
+    public int read() throws IOException {
+        if (numSamples == 0) {
+            int v = in.read();
+            if(v == -1) {
+                return -1;
+            }
+            if ((v >> 7) == 1) {
+                for (int i = 0; i < value.length; i++) {
+                    value[i] = (byte) in.read();
+                }
+                numSamples = ((v & 0x7F) + 1) * pixelSize;
+                rawPacket = false;
+            }
+            else {
+                numSamples = (v + 1) * pixelSize;
+                rawPacket = true;
+            }
+        }
+        numSamples--;
+        if (rawPacket) {
+            return in.read();
+        }
+        else {
+            int b = value[vindex++] & 0xFF;
+            if(vindex == pixelSize) {
+                vindex = 0;
+            }
+            return b;
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/io/TIFFStripInputStream.java
===================================================================
--- ocean/src/com/imagero/uio/io/TIFFStripInputStream.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/TIFFStripInputStream.java	(revision 0)
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio.io;
+
+import com.imagero.uio.RandomAccessIO;
+
+import java.io.IOException;
+
+/**
+ * InputStream over all strips
+ * @author Andrei Kouznetsov
+ */
+public class TIFFStripInputStream extends RandomAccessInputStream {
+
+	int[] stripOffsets, stripByteCount;
+
+	int currentStrip = -1;
+	long stripLimit;
+	int markStrip;
+
+	public TIFFStripInputStream(RandomAccessIO ra, int[] stripByteCount, int[] stripOffsets) {
+		super(ra);
+		this.stripByteCount = stripByteCount;
+		this.stripOffsets = stripOffsets;
+	}
+
+	public TIFFStripInputStream(RandomAccessIO ra, long startPos, int[] stripByteCount, int[] stripOffsets) {
+		super(ra, startPos);
+		this.stripByteCount = stripByteCount;
+		this.stripOffsets = stripOffsets;
+	}
+
+	protected void checkPos() throws IOException {
+		if(pos > stripLimit || currentStrip == -1) {
+//			Sys.out.println("currentStrip:" + currentStrip);
+			if(currentStrip >= stripOffsets.length) {
+				throw new IOException();
+			}
+			currentStrip++;
+			stripLimit = stripOffsets[currentStrip] + stripByteCount[currentStrip];
+			pos = stripOffsets[currentStrip];
+
+//			File ft = new File(TiffReader.workDir + "tables" + currentStrip + ".jpg");
+//			FileOutputStream fout = new FileOutputStream(ft);
+//			in.seek(pos);
+//			byte [] b = new byte[256];
+//			for(int i = 0; i < stripByteCount[currentStrip];) {
+//				int r = in.read(b);
+//				if(r < 0) {
+//					break;
+//				}
+//				fout.write(b, 0, r);
+//				i =+ r;
+//			}
+//			fout.close();
+		}
+		super.checkPos();
+	}
+
+	synchronized public int read(byte[] b, int off, int len) throws IOException {
+		return super.read(b, off, Math.min(len, (int)(stripLimit - pos)));
+	}
+
+	public void mark(int i) {
+		super.mark(i);
+		markStrip = currentStrip;
+	}
+
+	public void reset() throws IOException {
+		super.reset();
+		currentStrip = markStrip;
+	}
+
+	public long skip(long l) throws IOException {
+		int lsi = stripOffsets.length - 1;
+		long limit = stripOffsets[lsi] + stripByteCount[lsi];
+		long remaining = l;
+		while(remaining > 0) {
+			checkPos();
+			long cs = Math.min(remaining, stripLimit - pos);
+			if(cs > limit - pos) {
+				cs = limit - pos;
+				remaining -= cs;
+				pos += cs;
+				break;
+			}
+			remaining -= cs;
+			pos += cs;
+		}
+		return l - remaining;
+	}
+
+	public int available() {
+		try {
+			return super.available();
+		}
+		catch(IOException e) {
+			e.printStackTrace();
+		}
+		return 0;
+	}
+
+	public long getPos() {
+		long res = 0;
+		for(int i = 0; i < currentStrip; i++) {
+			res += stripByteCount[i];
+		}
+		res += stripByteCount[currentStrip]
+			- (stripByteCount[currentStrip] + stripOffsets[currentStrip] - pos);
+		return res;
+	}
+}
Index: ocean/src/com/imagero/uio/io/UnexpectedEOFException.java
===================================================================
--- ocean/src/com/imagero/uio/io/UnexpectedEOFException.java	(revision 0)
+++ ocean/src/com/imagero/uio/io/UnexpectedEOFException.java	(revision 0)
@@ -0,0 +1,26 @@
+package com.imagero.uio.io;
+
+import java.io.EOFException;
+
+/**
+ * If EOFException is thrown during readFully() it is still useful to know how much bytes were read.
+ * Date: 22.12.2007
+ *
+ * @author Andrey Kuznetsov
+ */
+public class UnexpectedEOFException extends EOFException {
+    private long count;
+
+    public UnexpectedEOFException(long count) {
+        this("EOF", count);
+    }
+
+    public UnexpectedEOFException(String s, long count) {
+        super(s);
+        this.count = count;
+    }
+
+    public long getCount() {
+        return count;
+    }
+}
Index: ocean/src/com/imagero/uio/RandomAccessInput.java
===================================================================
--- ocean/src/com/imagero/uio/RandomAccessInput.java	(revision 0)
+++ ocean/src/com/imagero/uio/RandomAccessInput.java	(revision 0)
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.DataInput;
+
+/**
+ * @author Andrey Kuznetsov
+ */
+public interface RandomAccessInput extends Input, DataInput, Endian, Seekable {
+
+    /**
+     * Create RandomAccessInput child from given offset
+     * @param offset offset in parent stream
+     * @param byteOrder byte order for new stream
+     * @param syncPointer if true then streams will share same stream position
+     * @return RandomAccessInput
+     * @throws IOException
+     */
+    RandomAccessInput createInputChild(long offset, long length, int byteOrder, boolean syncPointer) throws IOException;
+
+    /**
+     * Create InputStream starting from given offset.
+     * @param offset offset in parent (for created InputStream) stream
+     * @return InputStream
+     */
+    InputStream createInputStream(long offset);
+
+    /**
+     * get stream position of child InputStream (created with createInputStream());
+     * @param child child InputStream
+     * @return Stream position or -1 if supplied InputStream is not a child of this stream
+     */
+    long getChildPosition(InputStream child);
+
+    /**
+     * set stream position of child InputStream.
+     * @param child child InputStream
+     * @param position new stream position
+     */ 
+    void setChildPosition(InputStream child, long position);
+
+    /**
+     * Same as readLine, but returns byte array instead of String
+     * @return byte array
+     * @throws IOException
+     */
+    byte [] readByteLine() throws IOException;
+
+    /**
+     * Same as readByteLine(), but read in given byte array and returns how much was read.
+     * @param dest byte array
+     * @return how much bytes read
+     * @throws IOException
+     */
+    int readByteLine(byte [] dest) throws IOException;
+
+    /**
+     * Same as readShort() from DataInput, but uses given byte order instead of streams byte order
+     * @param byteOrder byte order
+     * @return short
+     * @throws IOException
+     */
+    short readShort(int byteOrder) throws IOException;
+
+    /**
+     * Same as readUnsignedShort() from DataInput, but uses given byte order instead of streams byte order
+     * @param byteOrder byte order
+     * @return int
+     * @throws IOException
+     */
+    int readUnsignedShort(int byteOrder) throws IOException;
+
+    /**
+     * Same as readChar() from DataInput, but uses given byte order instead of streams byte order
+     * @param byteOrder byte order
+     * @return char
+     * @throws IOException
+     */
+    char readChar(int byteOrder) throws IOException;
+
+    /**
+     * Same as readInt() from DataInput, but uses given byte order instead of streams byte order
+     * @param byteOrder byte order
+     * @return int
+     * @throws IOException
+     */
+    int readInt(int byteOrder) throws IOException;
+
+    /**
+     * Same as readLong() from DataInput, but uses given byte order instead of streams byte order
+     * @param byteOrder byte order
+     * @return long
+     * @throws IOException
+     */
+    long readLong(int byteOrder) throws IOException;
+
+    /**
+     * Same as readFloat() from DataInput, but uses given byte order instead of streams byte order
+     * @param byteOrder byte order
+     * @return float
+     * @throws IOException
+     */
+    float readFloat(int byteOrder) throws IOException;
+
+    /**
+     * Same as readDouble() from DataInput, but uses given byte order instead of streams byte order
+     * @param byteOrder byte order
+     * @return double
+     * @throws IOException
+     */
+    double readDouble(int byteOrder) throws IOException;
+
+    /**
+     * Reads four input bytes and returns an long value in the range 0 through 0xFFFFFFFF.
+     * @return long
+     * @throws IOException
+     */
+    long readUnsignedInt() throws IOException;
+
+    /**
+     * Same as readUnsignedInt(), but uses given byte order instead of streams byte order
+     * @param byteOrder byte order
+     * @return long
+     * @throws IOException
+     */
+    long readUnsignedInt(int byteOrder) throws IOException;
+
+    boolean isBuffered();
+}
Index: ocean/src/com/imagero/uio/RandomAccessIO.java
===================================================================
--- ocean/src/com/imagero/uio/RandomAccessIO.java	(revision 0)
+++ ocean/src/com/imagero/uio/RandomAccessIO.java	(revision 0)
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio;
+
+import java.io.IOException;
+
+
+/**
+ * @author Andrey Kuznetsov
+ */
+public interface RandomAccessIO extends RandomAccessInput, RandomAccessOutput {
+    RandomAccessIO createIOChild(long offset, long length, int byteOrder, boolean syncPointer) throws IOException;
+}
Index: ocean/src/com/imagero/uio/RandomAccessOutput.java
===================================================================
--- ocean/src/com/imagero/uio/RandomAccessOutput.java	(revision 0)
+++ ocean/src/com/imagero/uio/RandomAccessOutput.java	(revision 0)
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.DataOutput;
+
+
+/**
+ * @author Andrey Kuznetsov
+ */
+public interface RandomAccessOutput extends DataOutput, Endian, Seekable {
+
+    RandomAccessOutput createOutputChild(long offset, int byteOrder, boolean syncPointer) throws IOException;
+    OutputStream createOutputStream(long offset);
+    void flush() throws IOException;
+
+    void writeShort(int v, int byteOrder) throws IOException;
+
+    void writeChar(int v, int byteOrder) throws IOException;
+
+    void writeInt(int v, int byteOrder) throws IOException;
+
+    void writeLong(long v, int byteOrder) throws IOException;
+
+    void writeFloat(float v, int byteOrder) throws IOException;
+
+    void writeDouble(double v, int byteOrder) throws IOException;
+
+    /**
+     * Set length of stream.
+     * @param newLength new stream length
+     * @throws IOException
+     */
+    void setLength(long newLength) throws IOException;
+}
Index: ocean/src/com/imagero/uio/ReadUtil.java
===================================================================
--- ocean/src/com/imagero/uio/ReadUtil.java	(revision 0)
+++ ocean/src/com/imagero/uio/ReadUtil.java	(revision 0)
@@ -0,0 +1,350 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+ package com.imagero.uio;
+
+import com.imagero.uio.io.UnexpectedEOFException;
+
+import java.io.IOException;
+
+/**
+ * Methods to fill data in primitive arrays
+ *
+ * Date: 01.03.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class ReadUtil {
+    public static int read(RandomAccessInput in, short[] dest) throws IOException {
+        return read(in, dest, 0, dest.length, in.getByteOrder());
+    }
+
+    public static int read(RandomAccessInput in, short[] dest, int byteOrder) throws IOException {
+        return read(in, dest, 0, dest.length, byteOrder);
+    }
+
+    public static int read(RandomAccessInput in, short[] dest, int offset, int length) throws IOException {
+        return read(in, dest, offset, length, in.getByteOrder());
+    }
+
+    public static int read(RandomAccessInput in, char[] dest) throws IOException {
+        return read(in, dest, 0, dest.length, in.getByteOrder());
+    }
+
+    public static int read(RandomAccessInput in, char[] dest, int byteOrder) throws IOException {
+        return read(in, dest, 0, dest.length, byteOrder);
+    }
+
+    public static int read(RandomAccessInput in, char[] dest, int offset, int length) throws IOException {
+        return read(in, dest, offset, length, in.getByteOrder());
+    }
+
+    public static int read(RandomAccessInput in, int[] dest) throws IOException {
+        return read(in, dest, 0, dest.length, in.getByteOrder());
+    }
+
+    public static int read(RandomAccessInput in, int[] dest, int byteOrder) throws IOException {
+        return read(in, dest, 0, dest.length, byteOrder);
+    }
+
+    public static int read(RandomAccessInput in, int[] dest, int offset, int length) throws IOException {
+        return read(in, dest, offset, length, in.getByteOrder());
+    }
+
+    public static int read(RandomAccessInput in, long[] dest) throws IOException {
+        return read(in, dest, 0, dest.length, in.getByteOrder());
+    }
+
+    public static int read(RandomAccessInput in, long[] dest, int byteOrder) throws IOException {
+        return read(in, dest, 0, dest.length, byteOrder);
+    }
+
+    public static int read(RandomAccessInput in, long[] dest, int offset, int length) throws IOException {
+        return read(in, dest, offset, length, in.getByteOrder());
+    }
+
+    public static int read(RandomAccessInput in, float[] dest) throws IOException {
+        return read(in, dest, 0, dest.length, in.getByteOrder());
+    }
+
+    public static int read(RandomAccessInput in, float[] dest, int byteOrder) throws IOException {
+        return read(in, dest, 0, dest.length, byteOrder);
+    }
+
+    public static int read(RandomAccessInput in, float[] dest, int offset, int length) throws IOException {
+        return read(in, dest, offset, length, in.getByteOrder());
+    }
+
+    public static int read(RandomAccessInput in, double[] dest) throws IOException {
+        return read(in, dest, 0, dest.length, in.getByteOrder());
+    }
+
+    public static int read(RandomAccessInput in, double[] dest, int byteOrder) throws IOException {
+        return read(in, dest, 0, dest.length, byteOrder);
+    }
+
+    public static int read(RandomAccessInput in, double[] dest, int offset, int length) throws IOException {
+        return read(in, dest, offset, length, in.getByteOrder());
+    }
+
+    public static void readFully(RandomAccessInput in, short[] dest) throws IOException {
+        readFully(in, dest, 0, dest.length, in.getByteOrder());
+    }
+
+    public static void readFully(RandomAccessInput in, short[] dest, int byteOrder) throws IOException {
+        readFully(in, dest, 0, dest.length, byteOrder);
+    }
+
+    public static void readFully(RandomAccessInput in, short[] dest, int destOffset, int len) throws IOException {
+        readFully(in, dest, destOffset, len, in.getByteOrder());
+    }
+
+    public static void readFully(RandomAccessInput in, char[] dest) throws IOException {
+        readFully(in, dest, 0, dest.length, in.getByteOrder());
+    }
+
+    public static void readFully(RandomAccessInput in, char[] dest, int byteOrder) throws IOException {
+        readFully(in, dest, 0, dest.length, byteOrder);
+    }
+
+    public static void readFully(RandomAccessInput in, char[] dest, int destOffset, int len) throws IOException {
+        readFully(in, dest, destOffset, len, in.getByteOrder());
+    }
+
+    public static void readFully(RandomAccessInput in, int[] dest) throws IOException {
+        readFully(in, dest, 0, dest.length, in.getByteOrder());
+    }
+
+    public static void readFully(RandomAccessInput in, int[] dest, int byteOrder) throws IOException {
+        readFully(in, dest, 0, dest.length, byteOrder);
+    }
+
+    public static void readFully(RandomAccessInput in, int[] dest, int destOffset, int len) throws IOException {
+        readFully(in, dest, destOffset, len, in.getByteOrder());
+    }
+
+    public static void readFully(RandomAccessInput in, long[] dest) throws IOException {
+        readFully(in, dest, 0, dest.length, in.getByteOrder());
+    }
+
+    public static void readFully(RandomAccessInput in, long[] dest, int byteOrder) throws IOException {
+        readFully(in, dest, 0, dest.length, byteOrder);
+    }
+
+    public static void readFully(RandomAccessInput in, long[] dest, int destOffset, int len) throws IOException {
+        readFully(in, dest, destOffset, len, in.getByteOrder());
+    }
+
+    public static void readFully(RandomAccessInput in, float[] dest) throws IOException {
+        readFully(in, dest, 0, dest.length, in.getByteOrder());
+    }
+
+    public static void readFully(RandomAccessInput in, float[] dest, int byteOrder) throws IOException {
+        readFully(in, dest, 0, dest.length, byteOrder);
+    }
+
+    public static void readFully(RandomAccessInput in, float[] dest, int destOffset, int len) throws IOException {
+        readFully(in, dest, destOffset, len, in.getByteOrder());
+    }
+
+    public static void readFully(RandomAccessInput in, double[] dest) throws IOException {
+        readFully(in, dest, 0, dest.length, in.getByteOrder());
+    }
+
+    public static void readFully(RandomAccessInput in, double[] dest, int byteOrder) throws IOException {
+        readFully(in, dest, 0, dest.length, byteOrder);
+    }
+
+    public static void readFully(RandomAccessInput in, double[] dest, int destOffset, int len) throws IOException {
+        readFully(in, dest, destOffset, len, in.getByteOrder());
+    }
+
+    public static void readFully(RandomAccessInput in, short[] dest, int offset, int length, int byteOrder) throws IOException {
+        int sum = 0;
+        while (length > 0) {
+            int read = read(in, dest, offset, length, byteOrder);
+            if (read <= 0) {
+                throw new UnexpectedEOFException(sum);
+            }
+            sum += read;
+            length -= read;
+            offset += read;
+        }
+    }
+
+    public static void readFully(RandomAccessInput in, char[] dest, int offset, int length, int byteOrder) throws IOException {
+        int sum = 0;
+        while (length > 0) {
+            int read = read(in, dest, offset, length, byteOrder);
+            if (read <= 0) {
+                throw new UnexpectedEOFException(sum);
+            }
+            sum += read;
+            length -= read;
+            offset += read;
+        }
+    }
+
+    public static void readFully(RandomAccessInput in, int[] dest, int offset, int length, int byteOrder) throws IOException {
+        int sum = 0;
+        while (length > 0) {
+            int read = read(in, dest, offset, length, byteOrder);
+            if (read <= 0) {
+                throw new UnexpectedEOFException(sum);
+            }
+            sum += read;
+            length -= read;
+            offset += read;
+        }
+    }
+
+    public static void readFully(RandomAccessInput in, long[] dest, int offset, int length, int byteOrder) throws IOException {
+        int sum = 0;
+        while (length > 0) {
+            int read = read(in, dest, offset, length, byteOrder);
+            if (read <= 0) {
+                throw new UnexpectedEOFException(sum);
+            }
+            sum += read;
+            length -= read;
+            offset += read;
+        }
+    }
+
+    public static void readFully(RandomAccessInput in, float[] dest, int offset, int length, int byteOrder) throws IOException {
+        int sum = 0;
+        while (length > 0) {
+            int read = read(in, dest, offset, length, byteOrder);
+            if (read <= 0) {
+                throw new UnexpectedEOFException(sum);
+            }
+            sum += read;
+            length -= read;
+            offset += read;
+        }
+    }
+
+    public static void readFully(RandomAccessInput in, double[] dest, int offset, int length, int byteOrder) throws IOException {
+        int sum = 0;
+        while (length > 0) {
+            int read = read(in, dest, offset, length, byteOrder);
+            if (read <= 0) {
+                throw new UnexpectedEOFException(sum);
+            }
+            sum += read;
+            length -= read;
+            offset += read;
+        }
+    }
+
+    public static int read(RandomAccessInput in, short[] dest, int destOffset, int len, int byteOrder) throws IOException {
+        byte[] b = new byte[len * 2];
+        int cnt = in.read(b);
+
+        if ((cnt & 1) != 0) {
+            in.seek(in.getFilePointer() - 1);
+        }
+        final int count = cnt >> 1;
+        Transformer.byteToShort(b, 0, count, dest, destOffset, byteOrder == RandomAccessInput.BIG_ENDIAN);
+        return count;
+    }
+
+    public static int read(RandomAccessInput in, char[] dest, int destOffset, int len, int byteOrder) throws IOException {
+        byte[] b = new byte[len * 2];
+
+        int cnt = in.read(b);
+
+        if ((cnt & 1) != 0) {
+            in.seek(in.getFilePointer() - 1);
+        }
+
+        final int count = cnt >> 1;
+        Transformer.byteToChar(b, 0, count, dest, destOffset, byteOrder == RandomAccessInput.BIG_ENDIAN);
+        return count;
+    }
+
+    public static int read(RandomAccessInput in, int[] dest, int destOffset, int len, int byteOrder) throws IOException {
+        byte[] b = new byte[len * 4];
+
+        int cnt = in.read(b);
+
+        int r3 = cnt & 3;
+        if (r3 != 0) {
+            in.seek(in.getFilePointer() - r3);
+        }
+
+        final int count = cnt >> 2;
+        Transformer.byteToInt(b, 0, count, dest, destOffset, byteOrder == RandomAccessInput.BIG_ENDIAN);
+        return count;
+    }
+
+    public static int read(RandomAccessInput in, float[] dest, int destOffset, int len, int byteOrder) throws IOException {
+        byte[] b = new byte[len * 4];
+        int cnt = in.read(b);
+
+        int r3 = cnt & 3;
+        if (r3 != 0) {
+            in.seek(in.getFilePointer() - r3);
+        }
+
+        final int count = cnt >> 2;
+        Transformer.byteToFloat(b, 0, count, dest, destOffset, byteOrder == RandomAccessInput.BIG_ENDIAN);
+        return count;
+    }
+
+    public static int read(RandomAccessInput in, long[] dest, int destOffset, int len, int byteOrder) throws IOException {
+        byte[] b = new byte[len * 8];
+        int cnt = in.read(b);
+
+        int r7 = cnt & 7;
+        if (r7 != 0) {
+            in.seek(in.getFilePointer() - r7);
+        }
+
+        final int count = cnt >> 3;
+        Transformer.byteToLong(b, 0, count, dest, destOffset, byteOrder == RandomAccessInput.BIG_ENDIAN);
+        return count;
+    }
+
+    public static int read(RandomAccessInput in, double[] dest, int destOffset, int len, int byteOrder) throws IOException {
+        byte[] b = new byte[len * 8];
+        int cnt = in.read(b);
+
+        int r7 = cnt & 7;
+        if (r7 != 0) {
+            in.seek(in.getFilePointer() - r7);
+        }
+
+        final int count = cnt >> 3;
+        Transformer.byteToDouble(b, 0, count, dest, destOffset, byteOrder == RandomAccessInput.BIG_ENDIAN);
+        return count;
+    }
+}
Index: ocean/src/com/imagero/uio/Seekable.java
===================================================================
--- ocean/src/com/imagero/uio/Seekable.java	(revision 0)
+++ ocean/src/com/imagero/uio/Seekable.java	(revision 0)
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+ package com.imagero.uio;
+
+import java.io.IOException;
+
+/**
+ * Date: 01.03.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public interface Seekable {
+    long getFilePointer() throws IOException;
+
+    long length() throws IOException;
+
+    void seek(long offset) throws IOException;
+
+    void close() throws IOException;
+}
Index: ocean/src/com/imagero/uio/Sys.java
===================================================================
--- ocean/src/com/imagero/uio/Sys.java	(revision 0)
+++ ocean/src/com/imagero/uio/Sys.java	(revision 0)
@@ -0,0 +1,335 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio;
+
+import java.io.IOException;
+import java.io.PrintStream;
+
+/**
+ * @author Andrei Kouznetsov
+ *         Date: 05.07.2004
+ *         Time: 15:02:01
+ */
+public class Sys {
+    public static final PrintStreamFilter out = new PrintStreamFilter(java.lang.System.out);
+    public static final PrintStreamFilter err = new PrintStreamFilter(java.lang.System.err);
+
+    public static PrintStreamFilter getErr() {
+        return err;
+    }
+
+    /**
+     * Set PrintStream used for output.
+     * Set to null to disable output.
+     * @param err PrintStream or null
+     */
+    public static void setErr(PrintStream err) {
+        Sys.err.setPs(err);
+    }
+
+    public static PrintStreamFilter getOut() {
+        return out;
+    }
+
+    /**
+     * Set PrintStream used for output.
+     * Set to null to disable output.
+     * @param out PrintStream or null
+     */
+    public static void setOut(PrintStream out) {
+        Sys.out.setPs(out);
+    }
+
+    public static class PrintStreamFilter {
+        PrintStream ps;
+
+        public PrintStreamFilter(PrintStream ps) {
+            this.ps = ps;
+        }
+
+        public PrintStream getPrintStream() {
+            return ps;
+        }
+
+        void setPs(PrintStream ps) {
+            this.ps = ps;
+        }
+
+        public void print(String x) {
+            if (ps == null) {
+                return;
+            }
+            if (x == null) {
+                ps.print(x);
+                return;
+            }
+            char[] chars = new char[x.length()];
+            x.getChars(0, x.length(), chars, 0);
+            int p = chars.length;
+            for (int j = 0; j < p; j++) {
+                char c = chars[j];
+                if (Character.isLetterOrDigit(c)) {
+                    chars[j] = c;
+                } else if (Character.isWhitespace(c)) {
+                    chars[j] = c;
+                } else if (Character.isISOControl(c)) {
+                    chars[j] = '.';
+                } else {
+                    chars[j] = c;
+                }
+            }
+            ps.print(chars);
+        }
+
+        public void println(String x) {
+            print(x);
+            println();
+        }
+
+        public void println() {
+            if (ps == null) {
+                return;
+            }
+            ps.println();
+        }
+
+        public void flush() {
+            if (ps == null) {
+                return;
+            }
+            ps.flush();
+        }
+
+        public void close() {
+            if (ps == null) {
+                return;
+            }
+            ps.close();
+        }
+
+        public boolean checkError() {
+            if (ps == null) {
+                return false;
+            }
+            return ps.checkError();
+        }
+
+        public void write(int b) {
+            if (ps == null) {
+                return;
+            }
+            ps.write(b);
+        }
+
+        public void write(byte buf[], int off, int len) {
+            if (ps == null) {
+                return;
+            }
+            ps.write(buf, off, len);
+        }
+
+        public void print(boolean b) {
+            if (ps == null) {
+                return;
+            }
+            ps.print(b);
+        }
+
+        public void print(char c) {
+            if (ps == null) {
+                return;
+            }
+            ps.print(c);
+        }
+
+        public void print(int i) {
+            if (ps == null) {
+                return;
+            }
+            ps.print(i);
+        }
+
+        public void print(long l) {
+            if (ps == null) {
+                return;
+            }
+            ps.print(l);
+        }
+
+        public void print(float f) {
+            if (ps == null) {
+                return;
+            }
+            ps.print(f);
+        }
+
+        public void print(double d) {
+            if (ps == null) {
+                return;
+            }
+            ps.print(d);
+        }
+
+        public void print(char s[]) {
+            if (ps == null) {
+                return;
+            }
+            ps.print(s);
+        }
+
+        public void print(Object obj) {
+            if (ps == null) {
+                return;
+            }
+            ps.print(obj);
+        }
+
+        public void println(boolean x) {
+            if (ps == null) {
+                return;
+            }
+            ps.println(x);
+        }
+
+        public void println(char x) {
+            if (ps == null) {
+                return;
+            }
+            ps.println(x);
+        }
+
+        public void println(int x) {
+            if (ps == null) {
+                return;
+            }
+            ps.println(x);
+        }
+
+        public void println(long x) {
+            if (ps == null) {
+                return;
+            }
+            ps.println(x);
+        }
+
+        public void println(float x) {
+            if (ps == null) {
+                return;
+            }
+            ps.println(x);
+        }
+
+        public void println(double x) {
+            if (ps == null) {
+                return;
+            }
+            ps.println(x);
+        }
+
+        public void println(char x[]) {
+            if (ps == null) {
+                return;
+            }
+            ps.println(x);
+        }
+
+        public void println(Object x) {
+            if (ps == null) {
+                return;
+            }
+            ps.println(x);
+        }
+
+        public void write(byte b[]) throws IOException {
+            if (ps == null) {
+                return;
+            }
+            ps.write(b);
+        }
+
+        public void println(Object[] objects) {
+            for (int i = 0; i < objects.length; i++) {
+                print(objects[i]);
+            }
+            println();
+        }
+
+        public void println(Object[] objects, String delimiter) {
+            for (int i = 0; i < objects.length; i++) {
+                print(objects[i]);
+                print(delimiter);
+            }
+            println();
+        }
+
+        public void println(long[] longs, String delimiter) {
+            for (int i = 0; i < longs.length; i++) {
+                print(longs[i]);
+                print(delimiter);
+            }
+            println();
+        }
+
+        public void println(int[] numbers, String delimiter) {
+            for (int i = 0; i < numbers.length; i++) {
+                print(numbers[i]);
+                print(delimiter);
+            }
+            println();
+        }
+
+        public void println(char[] chars, String delimiter) {
+            for (int i = 0; i < chars.length; i++) {
+                print(chars[i]);
+                print(delimiter);
+            }
+            println();
+        }
+
+        public void println(short[] shorts, String delimiter) {
+            for (int i = 0; i < shorts.length; i++) {
+                print(shorts[i]);
+                print(delimiter);
+            }
+            println();
+        }
+
+        public void println(byte[] bytes, String delimiter) {
+            for (int i = 0; i < bytes.length; i++) {
+                print(bytes[i]);
+                print(delimiter);
+            }
+            println();
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/test/ComparingRandomAccessInput.java
===================================================================
--- ocean/src/com/imagero/uio/test/ComparingRandomAccessInput.java	(revision 0)
+++ ocean/src/com/imagero/uio/test/ComparingRandomAccessInput.java	(revision 0)
@@ -0,0 +1,354 @@
+package com.imagero.uio.test;
+
+import com.imagero.uio.RandomAccessInput;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ *
+ * Trace method calls and their results.
+ *
+ * Date: 29.02.2008
+ * @author Andrey Kuznetsov
+ */
+public class ComparingRandomAccessInput implements RandomAccessInput {
+
+    RandomAccessInput r0;
+    RandomAccessInput r1;
+
+    public ComparingRandomAccessInput(RandomAccessInput ra1, RandomAccessInput ra2) {
+        this.r0 = ra1;
+        this.r1 = ra2;
+    }
+
+    public RandomAccessInput createInputChild(long offset, long length, int byteOrder, boolean syncPointer) throws IOException {
+        RandomAccessInput c0 = r0.createInputChild(offset, length, byteOrder, syncPointer);
+        RandomAccessInput c1 = r1.createInputChild(offset, length, byteOrder, syncPointer);
+        return new ComparingRandomAccessInput(c0, c1);
+    }
+
+    public InputStream createInputStream(long offset) {
+        InputStream in = r0.createInputStream(offset);
+        return in;
+    }
+
+    public long getChildPosition(InputStream child) {
+        return r0.getChildPosition(child);
+    }
+
+    public void setChildPosition(InputStream child, long position) {
+        r0.setChildPosition(child, position);
+    }
+
+    public int read() throws IOException {
+        int a0 = r0.read();
+        int a1 = r1.read();
+        Assertions.assertEquals(a0, a1);
+        return a0;
+    }
+
+    public long skip(long n) throws IOException {
+        long s0 = r0.skip(n);
+        long s1 = r1.skip(n);
+        Assertions.assertEquals(s0, s1);
+        return s0;
+    }
+
+    public int read(byte[] b0) throws IOException {
+        byte [] b1 = new byte[b0.length];
+        int k0 = r0.read(b0);
+        int k1 = r1.read(b1);
+        Assertions.assertEquals(k0, k1);
+        Assertions.assertEquals(b0, b1);
+        return k0;
+    }
+
+    public int read(byte[] b0, int off, int len) throws IOException {
+        byte[] b1 = new byte[b0.length];
+        int k0 = r0.read(b0, off, len);
+        int k1 = r1.read(b1, off, len);
+        Assertions.assertEquals(k0, k1);
+        Assertions.assertEquals(off, b0, b1);
+        return k0;
+    }
+
+    public void seek(long offset) throws IOException {
+        r0.seek(offset);
+        r1.seek(offset);
+    }
+
+    public void close() throws IOException {
+        r0.close();
+        r1.close();
+    }
+
+    public int getByteOrder() {
+        int b0 = r0.getByteOrder();
+        int b1 = r1.getByteOrder();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public void setByteOrder(int byteOrder) {
+        r0.setByteOrder(byteOrder);
+        r1.setByteOrder(byteOrder);
+    }
+
+    public long getFilePointer() throws IOException {
+        long fp0 = r0.getFilePointer();
+        long fp1 = r1.getFilePointer();
+        Assertions.assertEquals(fp0, fp1);
+        return fp0;
+    }
+
+    public long length() throws IOException {
+        long e0 = r0.length();
+        long e1 = r1.length();
+        Assertions.assertEquals(e0, e1);
+        return e0;
+    }
+
+    public boolean isBuffered() {
+        boolean b0 = r0.isBuffered();
+        boolean b1 = r1.isBuffered();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public void readFully(byte b0[]) throws IOException {
+        getFilePointer();
+        r0.readFully(b0);
+        byte [] b1 = new byte[b0.length];
+        r1.readFully(b1);
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+    }
+
+    public void readFully(byte b0[], int off, int len) throws IOException {
+        getFilePointer();
+        r0.readFully(b0, off, len);
+        byte[] b1 = new byte[b0.length];
+        r1.readFully(b1, off, len);
+        getFilePointer();
+        Assertions.assertEquals(off, len, b0, b1);
+    }
+
+    public int skipBytes(int n) throws IOException {
+        getFilePointer();
+        int s0 = r0.skipBytes(n);
+        int s1 = r1.skipBytes(n);
+        getFilePointer();
+        Assertions.assertEquals(s0, s1);
+        return s0;
+    }
+
+    public boolean readBoolean() throws IOException {
+        getFilePointer();
+        boolean b0 = r0.readBoolean();
+        boolean b1 = r1.readBoolean();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public byte readByte() throws IOException {
+        getFilePointer();
+        byte b0 = r0.readByte();
+        byte b1 = r1.readByte();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public int readUnsignedByte() throws IOException {
+        getFilePointer();
+        int b0 = r0.readUnsignedByte();
+        int b1 = r1.readUnsignedByte();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public short readShort() throws IOException {
+        getFilePointer();
+        short b0 = r0.readShort();
+        short b1 = r1.readShort();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public int readUnsignedShort() throws IOException {
+        getFilePointer();
+        int b0 = r0.readUnsignedShort();
+        int b1 = r1.readUnsignedShort();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public char readChar() throws IOException {
+        getFilePointer();
+        char b0 = r0.readChar();
+        char b1 = r1.readChar();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public int readInt() throws IOException {
+        getFilePointer();
+        int b0 = r0.readInt();
+        int b1 = r1.readInt();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public long readLong() throws IOException {
+        getFilePointer();
+        long b0 = r0.readLong();
+        long b1 = r1.readLong();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public float readFloat() throws IOException {
+        getFilePointer();
+        float b0 = r0.readFloat();
+        float b1 = r1.readFloat();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public double readDouble() throws IOException {
+        getFilePointer();
+        double b0 = r0.readDouble();
+        double b1 = r1.readDouble();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public String readLine() throws IOException {
+        getFilePointer();
+        String s0 = r0.readLine();
+        String s1 = r1.readLine();
+        getFilePointer();
+        Assertions.assertEquals(s0, s1);
+        return s0;
+    }
+
+    public String readUTF() throws IOException {
+        getFilePointer();
+        String s0 = r0.readUTF();
+        String s1 = r1.readUTF();
+        getFilePointer();
+        Assertions.assertEquals(s0, s1);
+        return s0;
+    }
+
+    public byte[] readByteLine() throws IOException {
+        getFilePointer();
+        byte [] s0 = r0.readByteLine();
+        byte [] s1 = r1.readByteLine();
+        getFilePointer();
+        Assertions.assertEquals(s0, s1);
+        return s0;
+    }
+
+    public int readByteLine(byte[] d0) throws IOException {
+        getFilePointer();
+        int s0 = r0.readByteLine(d0);
+        byte [] d1 = new byte[d0.length];
+        int s1 = r1.readByteLine(d1);
+        getFilePointer();
+        Assertions.assertEquals(s0, s1);
+        Assertions.assertEquals(0, s0, d0, d1);
+        return s0;
+    }
+
+    public short readShort(int byteOrder) throws IOException {
+        getFilePointer();
+        short b0 = r0.readShort(byteOrder);
+        short b1 = r1.readShort(byteOrder);
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public int readUnsignedShort(int byteOrder) throws IOException {
+        getFilePointer();
+        int b0 = r0.readUnsignedShort();
+        int b1 = r1.readUnsignedShort();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public char readChar(int byteOrder) throws IOException {
+        getFilePointer();
+        char b0 = r0.readChar(byteOrder);
+        char b1 = r1.readChar(byteOrder);
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public int readInt(int byteOrder) throws IOException {
+        getFilePointer();
+        int b0 = r0.readInt(byteOrder);
+        int b1 = r1.readInt(byteOrder);
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public long readLong(int byteOrder) throws IOException {
+        getFilePointer();
+        long b0 = r0.readLong(byteOrder);
+        long b1 = r1.readLong(byteOrder);
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public float readFloat(int byteOrder) throws IOException {
+        getFilePointer();
+        float b0 = r0.readFloat(byteOrder);
+        float b1 = r1.readFloat(byteOrder);
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public double readDouble(int byteOrder) throws IOException {
+        getFilePointer();
+        double b0 = r0.readDouble(byteOrder);
+        double b1 = r1.readDouble(byteOrder);
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public long readUnsignedInt() throws IOException {
+        getFilePointer();
+        long b0 = r0.readUnsignedInt();
+        long b1 = r1.readUnsignedInt();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public long readUnsignedInt(int byteOrder) throws IOException {
+        getFilePointer();
+        long b0 = r0.readUnsignedInt(byteOrder);
+        long b1 = r1.readUnsignedInt(byteOrder);
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+}
Index: ocean/src/com/imagero/uio/test/ComparingRandomAccessOutput.java
===================================================================
--- ocean/src/com/imagero/uio/test/ComparingRandomAccessOutput.java	(revision 0)
+++ ocean/src/com/imagero/uio/test/ComparingRandomAccessOutput.java	(revision 0)
@@ -0,0 +1,181 @@
+package com.imagero.uio.test;
+
+import com.imagero.uio.RandomAccessOutput;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ *
+ * Trace method calls and their results.
+ *
+ * Date: 29.02.2008
+ * @author Andrey Kuznetsov
+ */
+public class ComparingRandomAccessOutput implements RandomAccessOutput {
+
+    RandomAccessOutput r0;
+    RandomAccessOutput r1;
+
+    public ComparingRandomAccessOutput(RandomAccessOutput ra1, RandomAccessOutput ra2) {
+        this.r0 = ra1;
+        this.r1 = ra2;
+    }
+
+    public RandomAccessOutput createOutputChild(long offset, int byteOrder, boolean syncPointer) throws IOException {
+        RandomAccessOutput c0 = r0.createOutputChild(offset, byteOrder, syncPointer);
+        RandomAccessOutput c1 = r1.createOutputChild(offset, byteOrder, syncPointer);
+        return new ComparingRandomAccessOutput(c0, c1);
+    }
+
+    public OutputStream createOutputStream(long offset) {
+        OutputStream out = r0.createOutputStream(offset);
+        return out;
+    }
+
+    public void flush() throws IOException {
+        r0.flush();
+        r1.flush();
+    }
+
+    public void write(int b) throws IOException {
+        r0.write(b);
+        r1.write(b);
+    }
+
+    public void write(byte b[]) throws IOException {
+        r0.write(b);
+        r1.write(b);
+    }
+
+    public void write(byte b[], int off, int len) throws IOException {
+        r0.write(b, off, len);
+        r1.write(b, off, len);
+    }
+
+    public void writeBoolean(boolean v) throws IOException {
+        r0.writeBoolean(v);
+        r1.writeBoolean(v);
+    }
+
+    public void writeByte(int v) throws IOException {
+        r0.writeByte(v);
+        r1.writeByte(v);
+    }
+
+    public void writeShort(int v) throws IOException {
+        r0.writeShort(v);
+        r1.writeShort(v);
+    }
+
+    public void writeChar(int v) throws IOException {
+        r0.writeChar(v);
+        r1.writeChar(v);
+    }
+
+    public void writeInt(int v) throws IOException {
+        r0.writeInt(v);
+        r1.writeInt(v);
+    }
+
+    public void writeLong(long v) throws IOException {
+        r0.writeLong(v);
+        r1.writeLong(v);
+    }
+
+    public void writeFloat(float v) throws IOException {
+        r0.writeFloat(v);
+        r1.writeFloat(v);
+    }
+
+    public void writeDouble(double v) throws IOException {
+        r0.writeDouble(v);
+        r1.writeDouble(v);
+    }
+
+    public void writeBytes(String s) throws IOException {
+        r0.writeBytes(s);
+        r1.writeBytes(s);
+    }
+
+    public void writeChars(String s) throws IOException {
+        r0.writeChars(s);
+        r1.writeChars(s);
+   }
+
+    public void writeUTF(String s) throws IOException {
+        r0.writeUTF(s);
+        r1.writeUTF(s);
+    }
+
+    public void writeShort(int v, int byteOrder) throws IOException {
+        r0.writeShort(v, byteOrder);
+        r1.writeShort(v, byteOrder);
+    }
+
+    public void writeChar(int v, int byteOrder) throws IOException {
+        r0.writeChar(v, byteOrder);
+        r1.writeChar(v, byteOrder);
+   }
+
+    public void writeInt(int v, int byteOrder) throws IOException {
+        r0.writeInt(v, byteOrder);
+        r1.writeInt(v, byteOrder);
+    }
+
+    public void writeLong(long v, int byteOrder) throws IOException {
+        r0.writeLong(v, byteOrder);
+        r1.writeLong(v, byteOrder);
+    }
+
+    public void writeFloat(float v, int byteOrder) throws IOException {
+        r0.writeFloat(v, byteOrder);
+        r1.writeFloat(v, byteOrder);
+    }
+
+    public void writeDouble(double v, int byteOrder) throws IOException {
+        r0.writeDouble(v, byteOrder);
+        r1.writeDouble(v, byteOrder);
+   }
+
+    public void setLength(long newLength) throws IOException {
+        r0.setLength(newLength);
+        r1.setLength(newLength);
+    }
+
+    public int getByteOrder() {
+        int b0 = r0.getByteOrder();
+        int b1 = r1.getByteOrder();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public void close() throws IOException {
+        r0.close();
+        r1.close();
+    }
+
+    public void seek(long offset) throws IOException {
+        r0.seek(offset);
+        r1.seek(offset);
+    }
+
+    public long getFilePointer() throws IOException {
+        long p0 = r0.getFilePointer();
+        long p1 = r1.getFilePointer();
+        Assertions.assertEquals(p0, p1);
+        return p0;
+    }
+
+    public void setByteOrder(int byteOrder) {
+        r0.setByteOrder(byteOrder);
+        r1.setByteOrder(byteOrder);
+    }
+
+    public long length() throws IOException {
+        long k0 = r0.length();
+        long k1 = r1.length();
+        Assertions.assertEquals(k0, k1);
+        return k0;
+    }
+}
Index: ocean/src/com/imagero/uio/test/ComparingRandomAccessInput2.java
===================================================================
--- ocean/src/com/imagero/uio/test/ComparingRandomAccessInput2.java	(revision 0)
+++ ocean/src/com/imagero/uio/test/ComparingRandomAccessInput2.java	(revision 0)
@@ -0,0 +1,363 @@
+package com.imagero.uio.test;
+
+import com.imagero.uio.RandomAccessInput;
+import com.imagero.uio.impl.OffsetRandomAccessFile;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+
+/**
+ * Compare input from RandomAccessFile and RandomAccessInput.
+ *
+ * Date: 29.02.2008
+ * @author Andrey Kuznetsov
+ */
+public class ComparingRandomAccessInput2 implements RandomAccessInput {
+
+    File f;
+    RandomAccessFile r0;
+    RandomAccessInput r1;
+
+    public ComparingRandomAccessInput2(File f, RandomAccessInput ra2) throws IOException {
+        this.f = f;
+        this.r0 = new RandomAccessFile(f, "r");
+        this.r1 = ra2;
+    }
+
+    protected ComparingRandomAccessInput2(File f, RandomAccessFile ra1,  RandomAccessInput ra2) throws IOException {
+        this.f = f;
+        this.r0 = ra1;
+        this.r1 = ra2;
+    }
+
+    public RandomAccessInput createInputChild(long offset, long length, int byteOrder, boolean syncPointer) throws IOException {
+        RandomAccessFile c0 = new OffsetRandomAccessFile(f, "r", offset);
+        RandomAccessInput c1 = r1.createInputChild(offset, 0, byteOrder, syncPointer);
+        return new ComparingRandomAccessInput2(f, c0, c1);
+    }
+
+    public InputStream createInputStream(long offset) {
+        InputStream in = r1.createInputStream(offset);
+        return in;
+    }
+
+    public long getChildPosition(InputStream child) {
+        return r1.getChildPosition(child);
+    }
+
+    public void setChildPosition(InputStream child, long position) {
+        r1.setChildPosition(child, position);
+    }
+
+    public int read() throws IOException {
+        int a0 = r0.read();
+        int a1 = r1.read();
+        Assertions.assertEquals(a0, a1);
+        return a0;
+    }
+
+    public long skip(long n) throws IOException {
+        r0.seek(r0.getFilePointer() + n);
+        long s0 = r0.getFilePointer();
+        long s1 = r1.skip(n);
+        Assertions.assertEquals(s0, s1);
+        return s0;
+    }
+
+    public int read(byte[] b0) throws IOException {
+        byte [] b1 = new byte[b0.length];
+        int k0 = r0.read(b0);
+        int k1 = r1.read(b1);
+        Assertions.assertEquals(k0, k1);
+        Assertions.assertEquals(b0, b1);
+        return k0;
+    }
+
+    public int read(byte[] b0, int off, int len) throws IOException {
+        byte[] b1 = new byte[b0.length];
+        int k0 = r0.read(b0, off, len);
+        int k1 = r1.read(b1, off, len);
+        Assertions.assertEquals(k0, k1);
+        Assertions.assertEquals(off, b0, b1);
+        return k0;
+    }
+
+    public void seek(long offset) throws IOException {
+        r0.seek(offset);
+        r1.seek(offset);
+    }
+
+    public void close() throws IOException {
+        r0.close();
+        r1.close();
+    }
+
+    public int getByteOrder() {
+        int b1 = r1.getByteOrder();
+        Assertions.assertEquals(BIG_ENDIAN, b1);//we need BIG_ENDIAN
+        return b1;
+    }
+
+    public void setByteOrder(int byteOrder) {
+        r1.setByteOrder(byteOrder);
+    }
+
+    public long getFilePointer() throws IOException {
+        long fp0 = r0.getFilePointer();
+        long fp1 = r1.getFilePointer();
+        Assertions.assertEquals(fp0, fp1);
+        return fp0;
+    }
+
+    public long length() throws IOException {
+        long e0 = r0.length();
+        long e1 = r1.length();
+        Assertions.assertEquals(e0, e1);
+        return e0;
+    }
+
+    public boolean isBuffered() {
+        boolean b1 = r1.isBuffered();
+        return b1;
+    }
+
+    public void readFully(byte b0[]) throws IOException {
+        getFilePointer();
+        r0.readFully(b0);
+        byte [] b1 = new byte[b0.length];
+        r1.readFully(b1);
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+    }
+
+    public void readFully(byte b0[], int off, int len) throws IOException {
+        getFilePointer();
+        r0.readFully(b0, off, len);
+        byte[] b1 = new byte[b0.length];
+        r1.readFully(b1, off, len);
+        getFilePointer();
+        Assertions.assertEquals(off, len, b0, b1);
+    }
+
+    public int skipBytes(int n) throws IOException {
+        getFilePointer();
+        int s0 = r0.skipBytes(n);
+        int s1 = r1.skipBytes(n);
+        getFilePointer();
+        Assertions.assertEquals(s0, s1);
+        return s0;
+    }
+
+    public boolean readBoolean() throws IOException {
+        getFilePointer();
+        boolean b0 = r0.readBoolean();
+        boolean b1 = r1.readBoolean();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public byte readByte() throws IOException {
+        getFilePointer();
+        byte b0 = r0.readByte();
+        byte b1 = r1.readByte();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public int readUnsignedByte() throws IOException {
+        getFilePointer();
+        int b0 = r0.readUnsignedByte();
+        int b1 = r1.readUnsignedByte();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public short readShort() throws IOException {
+        getFilePointer();
+        short b0 = r0.readShort();
+        short b1 = r1.readShort();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public int readUnsignedShort() throws IOException {
+        getFilePointer();
+        int b0 = r0.readUnsignedShort();
+        int b1 = r1.readUnsignedShort();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public char readChar() throws IOException {
+        getFilePointer();
+        char b0 = r0.readChar();
+        char b1 = r1.readChar();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public int readInt() throws IOException {
+        getFilePointer();
+        int b0 = r0.readInt();
+        int b1 = r1.readInt();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public long readLong() throws IOException {
+        getFilePointer();
+        long b0 = r0.readLong();
+        long b1 = r1.readLong();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public float readFloat() throws IOException {
+        getFilePointer();
+        float b0 = r0.readFloat();
+        float b1 = r1.readFloat();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public double readDouble() throws IOException {
+        getFilePointer();
+        double b0 = r0.readDouble();
+        double b1 = r1.readDouble();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public String readLine() throws IOException {
+        getFilePointer();
+        String s0 = r0.readLine();
+        String s1 = r1.readLine();
+        getFilePointer();
+        Assertions.assertEquals(s0, s1);
+        return s0;
+    }
+
+    public String readUTF() throws IOException {
+        getFilePointer();
+        String s0 = r0.readUTF();
+        String s1 = r1.readUTF();
+        getFilePointer();
+        Assertions.assertEquals(s0, s1);
+        return s0;
+    }
+
+    public byte[] readByteLine() throws IOException {
+        getFilePointer();
+//        byte [] s0 = r0.readByteLine();
+        byte [] s1 = r1.readByteLine();
+        r0.seek(r1.getFilePointer());//can't compare - ergo don't read, just move file pointer
+        getFilePointer();
+//        Assertions.assertEquals(s0, s1);
+        return s1;
+    }
+
+    public int readByteLine(byte[] d0) throws IOException {
+        getFilePointer();
+//        int s0 = r0.readByteLine(d0);
+        byte [] d1 = new byte[d0.length];
+        int s1 = r1.readByteLine(d1);
+        r0.seek(r1.getFilePointer());//can't compare - ergo don't read, just move file pointer
+        getFilePointer();
+//        Assertions.assertEquals(s0, s1);
+//        Assertions.assertEquals(0, s0, d0, d1);
+        return s1;
+    }
+
+    public short readShort(int byteOrder) throws IOException {
+        getFilePointer();
+        short b0 = r0.readShort();
+        short b1 = r1.readShort(byteOrder);
+        getFilePointer();
+//        Assertions.assertEquals(b0, b1); //don't compare
+        return b0;
+    }
+
+    public int readUnsignedShort(int byteOrder) throws IOException {
+        getFilePointer();
+        int b0 = r0.readUnsignedShort();
+        int b1 = r1.readUnsignedShort(byteOrder);
+        getFilePointer();
+//        Assertions.assertEquals(b0, b1); //don't compare
+        return b0;
+    }
+
+    public char readChar(int byteOrder) throws IOException {
+        getFilePointer();
+        char b0 = r0.readChar();
+        char b1 = r1.readChar(byteOrder);
+        getFilePointer();
+//        Assertions.assertEquals(b0, b1); //don't compare
+        return b0;
+    }
+
+    public int readInt(int byteOrder) throws IOException {
+        getFilePointer();
+        int b0 = r0.readInt();
+        int b1 = r1.readInt(byteOrder);
+        getFilePointer();
+//        Assertions.assertEquals(b0, b1); //don't compare
+        return b0;
+    }
+
+    public long readLong(int byteOrder) throws IOException {
+        getFilePointer();
+        long b0 = r0.readLong();
+        long b1 = r1.readLong(byteOrder);
+        getFilePointer();
+//        Assertions.assertEquals(b0, b1); //don't compare
+        return b0;
+    }
+
+    public float readFloat(int byteOrder) throws IOException {
+        getFilePointer();
+        float b0 = r0.readFloat();
+        float b1 = r1.readFloat(byteOrder);
+        getFilePointer();
+//        Assertions.assertEquals(b0, b1); //don't compare
+        return b0;
+    }
+
+    public double readDouble(int byteOrder) throws IOException {
+        getFilePointer();
+        double b0 = r0.readDouble();
+        double b1 = r1.readDouble(byteOrder);
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public long readUnsignedInt() throws IOException {
+        getFilePointer();
+        long b0 = ((long) r0.readInt()) & 0xFFFFFFFFL;
+        long b1 = r1.readUnsignedInt();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public long readUnsignedInt(int byteOrder) throws IOException {
+        getFilePointer();
+        long b0 = r0.readInt();
+        long b1 = r1.readUnsignedInt(byteOrder);
+        getFilePointer();
+//        Assertions.assertEquals(b0, b1); //don't compare
+        return b0;
+    }
+}
Index: ocean/src/com/imagero/uio/test/Assertions.java
===================================================================
--- ocean/src/com/imagero/uio/test/Assertions.java	(revision 0)
+++ ocean/src/com/imagero/uio/test/Assertions.java	(revision 0)
@@ -0,0 +1,68 @@
+package com.imagero.uio.test;
+
+/**
+ * Date: 01.03.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class Assertions {
+    protected static void assertEquals(String a, String b) {
+        if(!a.equals(b)) {
+            throw new RuntimeException(a + " " + b);
+        }
+    }
+
+    protected static void assertEquals(boolean a, boolean b) {
+        if(a != b) {
+            throw new RuntimeException(a + " " + b);
+        }
+    }
+
+    protected static void assertEquals(int a, int b) {
+        if(a != b) {
+            throw new RuntimeException(a + " " + b);
+        }
+    }
+
+    protected static void assertEquals(long a, long b) {
+        if(a != b) {
+            throw new RuntimeException(a + " " + b);
+        }
+    }
+
+    protected static void assertEquals(float a, float b) {
+        if(a != b) {
+            throw new RuntimeException(a + " " + b);
+        }
+    }
+
+    protected static void assertEquals(double a, double b) {
+        if(a != b) {
+            throw new RuntimeException(a + " " + b);
+        }
+    }
+
+    protected static void assertEquals(byte [] a, byte [] b) {
+        for (int i = 0; i < a.length; i++) {
+            if(a[i] != b[i]) {
+                throw new RuntimeException(a[i] + " " + b[i] + " at " + i);
+            }
+        }
+    }
+
+    protected static void assertEquals(int offset, byte [] a, byte [] b) {
+        for (int i = offset; i < a.length; i++) {
+            if(a[i] != b[i]) {
+                throw new RuntimeException(a[i] + " " + b[i] + " at " + i);
+            }
+        }
+    }
+
+    protected static void assertEquals(int offset, int length, byte [] a, byte [] b) {
+        for (int i = 0; i < length; i++) {
+            if(a[offset + i] != b[offset + i]) {
+                throw new RuntimeException(a[i] + " " + b[i] + " at " + i);
+            }
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/test/ComparingRandomAccessIO.java
===================================================================
--- ocean/src/com/imagero/uio/test/ComparingRandomAccessIO.java	(revision 0)
+++ ocean/src/com/imagero/uio/test/ComparingRandomAccessIO.java	(revision 0)
@@ -0,0 +1,154 @@
+package com.imagero.uio.test;
+
+import com.imagero.uio.RandomAccessIO;
+import com.imagero.uio.RandomAccessOutput;
+import com.imagero.uio.test.ComparingRandomAccessInput;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ *
+ * Trace method calls and their results.
+ *
+ * Date: 29.02.2008
+ * @author Andrey Kuznetsov
+ */
+public class ComparingRandomAccessIO extends ComparingRandomAccessInput implements RandomAccessIO {
+
+    RandomAccessIO r0;
+    RandomAccessIO r1;
+
+    public ComparingRandomAccessIO(RandomAccessIO ra1, RandomAccessIO ra2) {
+        super(ra1, ra2);
+        this.r0 = ra1;
+        this.r1 = ra2;
+    }
+
+    public RandomAccessIO createIOChild(long offset, long length, int byteOrder, boolean syncPointer) throws IOException {
+        RandomAccessIO c0 = r0.createIOChild(offset, 0, byteOrder, syncPointer);
+        RandomAccessIO c1 = r1.createIOChild(offset, 0, byteOrder, syncPointer);
+        return new ComparingRandomAccessIO(c0, c1);
+    }
+
+    public RandomAccessOutput createOutputChild(long offset, int byteOrder, boolean syncPointer) throws IOException {
+        RandomAccessOutput c0 = r0.createOutputChild(offset, byteOrder, syncPointer);
+        RandomAccessOutput c1 = r1.createOutputChild(offset, byteOrder, syncPointer);
+        return new ComparingRandomAccessOutput(c0, c1);
+    }
+
+    public OutputStream createOutputStream(long offset) {
+        OutputStream out = r0.createOutputStream(offset);
+        return out;
+    }
+
+    public void flush() throws IOException {
+        r0.flush();
+        r1.flush();
+    }
+
+    public void write(int b) throws IOException {
+        r0.write(b);
+        r1.write(b);
+    }
+
+    public void write(byte b[]) throws IOException {
+        r0.write(b);
+        r1.write(b);
+    }
+
+    public void write(byte b[], int off, int len) throws IOException {
+        r0.write(b, off, len);
+        r1.write(b, off, len);
+    }
+
+    public void writeBoolean(boolean v) throws IOException {
+        r0.writeBoolean(v);
+        r1.writeBoolean(v);
+    }
+
+    public void writeByte(int v) throws IOException {
+        r0.writeByte(v);
+        r1.writeByte(v);
+    }
+
+    public void writeShort(int v) throws IOException {
+        r0.writeShort(v);
+        r1.writeShort(v);
+    }
+
+    public void writeChar(int v) throws IOException {
+        r0.writeChar(v);
+        r1.writeChar(v);
+    }
+
+    public void writeInt(int v) throws IOException {
+        r0.writeInt(v);
+        r1.writeInt(v);
+    }
+
+    public void writeLong(long v) throws IOException {
+        r0.writeLong(v);
+        r1.writeLong(v);
+    }
+
+    public void writeFloat(float v) throws IOException {
+        r0.writeFloat(v);
+        r1.writeFloat(v);
+    }
+
+    public void writeDouble(double v) throws IOException {
+        r0.writeDouble(v);
+        r1.writeDouble(v);
+    }
+
+    public void writeBytes(String s) throws IOException {
+        r0.writeBytes(s);
+        r1.writeBytes(s);
+    }
+
+    public void writeChars(String s) throws IOException {
+        r0.writeChars(s);
+        r1.writeChars(s);
+   }
+
+    public void writeUTF(String s) throws IOException {
+        r0.writeUTF(s);
+        r1.writeUTF(s);
+    }
+
+    public void writeShort(int v, int byteOrder) throws IOException {
+        r0.writeShort(v, byteOrder);
+        r1.writeShort(v, byteOrder);
+    }
+
+    public void writeChar(int v, int byteOrder) throws IOException {
+        r0.writeChar(v, byteOrder);
+        r1.writeChar(v, byteOrder);
+   }
+
+    public void writeInt(int v, int byteOrder) throws IOException {
+        r0.writeInt(v, byteOrder);
+        r1.writeInt(v, byteOrder);
+    }
+
+    public void writeLong(long v, int byteOrder) throws IOException {
+        r0.writeLong(v, byteOrder);
+        r1.writeLong(v, byteOrder);
+    }
+
+    public void writeFloat(float v, int byteOrder) throws IOException {
+        r0.writeFloat(v, byteOrder);
+        r1.writeFloat(v, byteOrder);
+    }
+
+    public void writeDouble(double v, int byteOrder) throws IOException {
+        r0.writeDouble(v, byteOrder);
+        r1.writeDouble(v, byteOrder);
+   }
+
+    public void setLength(long newLength) throws IOException {
+        r0.setLength(newLength);
+        r1.setLength(newLength);
+    }
+}
Index: ocean/src/com/imagero/uio/test/Assertions.java
===================================================================
--- ocean/src/com/imagero/uio/test/Assertions.java	(revision 0)
+++ ocean/src/com/imagero/uio/test/Assertions.java	(revision 0)
@@ -0,0 +1,68 @@
+package com.imagero.uio.test;
+
+/**
+ * Date: 01.03.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class Assertions {
+    protected static void assertEquals(String a, String b) {
+        if(!a.equals(b)) {
+            throw new RuntimeException(a + " " + b);
+        }
+    }
+
+    protected static void assertEquals(boolean a, boolean b) {
+        if(a != b) {
+            throw new RuntimeException(a + " " + b);
+        }
+    }
+
+    protected static void assertEquals(int a, int b) {
+        if(a != b) {
+            throw new RuntimeException(a + " " + b);
+        }
+    }
+
+    protected static void assertEquals(long a, long b) {
+        if(a != b) {
+            throw new RuntimeException(a + " " + b);
+        }
+    }
+
+    protected static void assertEquals(float a, float b) {
+        if(a != b) {
+            throw new RuntimeException(a + " " + b);
+        }
+    }
+
+    protected static void assertEquals(double a, double b) {
+        if(a != b) {
+            throw new RuntimeException(a + " " + b);
+        }
+    }
+
+    protected static void assertEquals(byte [] a, byte [] b) {
+        for (int i = 0; i < a.length; i++) {
+            if(a[i] != b[i]) {
+                throw new RuntimeException(a[i] + " " + b[i] + " at " + i);
+            }
+        }
+    }
+
+    protected static void assertEquals(int offset, byte [] a, byte [] b) {
+        for (int i = offset; i < a.length; i++) {
+            if(a[i] != b[i]) {
+                throw new RuntimeException(a[i] + " " + b[i] + " at " + i);
+            }
+        }
+    }
+
+    protected static void assertEquals(int offset, int length, byte [] a, byte [] b) {
+        for (int i = 0; i < length; i++) {
+            if(a[offset + i] != b[offset + i]) {
+                throw new RuntimeException(a[i] + " " + b[i] + " at " + i);
+            }
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/test/ComparingRandomAccessInput.java
===================================================================
--- ocean/src/com/imagero/uio/test/ComparingRandomAccessInput.java	(revision 0)
+++ ocean/src/com/imagero/uio/test/ComparingRandomAccessInput.java	(revision 0)
@@ -0,0 +1,354 @@
+package com.imagero.uio.test;
+
+import com.imagero.uio.RandomAccessInput;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ *
+ * Trace method calls and their results.
+ *
+ * Date: 29.02.2008
+ * @author Andrey Kuznetsov
+ */
+public class ComparingRandomAccessInput implements RandomAccessInput {
+
+    RandomAccessInput r0;
+    RandomAccessInput r1;
+
+    public ComparingRandomAccessInput(RandomAccessInput ra1, RandomAccessInput ra2) {
+        this.r0 = ra1;
+        this.r1 = ra2;
+    }
+
+    public RandomAccessInput createInputChild(long offset, long length, int byteOrder, boolean syncPointer) throws IOException {
+        RandomAccessInput c0 = r0.createInputChild(offset, length, byteOrder, syncPointer);
+        RandomAccessInput c1 = r1.createInputChild(offset, length, byteOrder, syncPointer);
+        return new ComparingRandomAccessInput(c0, c1);
+    }
+
+    public InputStream createInputStream(long offset) {
+        InputStream in = r0.createInputStream(offset);
+        return in;
+    }
+
+    public long getChildPosition(InputStream child) {
+        return r0.getChildPosition(child);
+    }
+
+    public void setChildPosition(InputStream child, long position) {
+        r0.setChildPosition(child, position);
+    }
+
+    public int read() throws IOException {
+        int a0 = r0.read();
+        int a1 = r1.read();
+        Assertions.assertEquals(a0, a1);
+        return a0;
+    }
+
+    public long skip(long n) throws IOException {
+        long s0 = r0.skip(n);
+        long s1 = r1.skip(n);
+        Assertions.assertEquals(s0, s1);
+        return s0;
+    }
+
+    public int read(byte[] b0) throws IOException {
+        byte [] b1 = new byte[b0.length];
+        int k0 = r0.read(b0);
+        int k1 = r1.read(b1);
+        Assertions.assertEquals(k0, k1);
+        Assertions.assertEquals(b0, b1);
+        return k0;
+    }
+
+    public int read(byte[] b0, int off, int len) throws IOException {
+        byte[] b1 = new byte[b0.length];
+        int k0 = r0.read(b0, off, len);
+        int k1 = r1.read(b1, off, len);
+        Assertions.assertEquals(k0, k1);
+        Assertions.assertEquals(off, b0, b1);
+        return k0;
+    }
+
+    public void seek(long offset) throws IOException {
+        r0.seek(offset);
+        r1.seek(offset);
+    }
+
+    public void close() throws IOException {
+        r0.close();
+        r1.close();
+    }
+
+    public int getByteOrder() {
+        int b0 = r0.getByteOrder();
+        int b1 = r1.getByteOrder();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public void setByteOrder(int byteOrder) {
+        r0.setByteOrder(byteOrder);
+        r1.setByteOrder(byteOrder);
+    }
+
+    public long getFilePointer() throws IOException {
+        long fp0 = r0.getFilePointer();
+        long fp1 = r1.getFilePointer();
+        Assertions.assertEquals(fp0, fp1);
+        return fp0;
+    }
+
+    public long length() throws IOException {
+        long e0 = r0.length();
+        long e1 = r1.length();
+        Assertions.assertEquals(e0, e1);
+        return e0;
+    }
+
+    public boolean isBuffered() {
+        boolean b0 = r0.isBuffered();
+        boolean b1 = r1.isBuffered();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public void readFully(byte b0[]) throws IOException {
+        getFilePointer();
+        r0.readFully(b0);
+        byte [] b1 = new byte[b0.length];
+        r1.readFully(b1);
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+    }
+
+    public void readFully(byte b0[], int off, int len) throws IOException {
+        getFilePointer();
+        r0.readFully(b0, off, len);
+        byte[] b1 = new byte[b0.length];
+        r1.readFully(b1, off, len);
+        getFilePointer();
+        Assertions.assertEquals(off, len, b0, b1);
+    }
+
+    public int skipBytes(int n) throws IOException {
+        getFilePointer();
+        int s0 = r0.skipBytes(n);
+        int s1 = r1.skipBytes(n);
+        getFilePointer();
+        Assertions.assertEquals(s0, s1);
+        return s0;
+    }
+
+    public boolean readBoolean() throws IOException {
+        getFilePointer();
+        boolean b0 = r0.readBoolean();
+        boolean b1 = r1.readBoolean();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public byte readByte() throws IOException {
+        getFilePointer();
+        byte b0 = r0.readByte();
+        byte b1 = r1.readByte();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public int readUnsignedByte() throws IOException {
+        getFilePointer();
+        int b0 = r0.readUnsignedByte();
+        int b1 = r1.readUnsignedByte();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public short readShort() throws IOException {
+        getFilePointer();
+        short b0 = r0.readShort();
+        short b1 = r1.readShort();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public int readUnsignedShort() throws IOException {
+        getFilePointer();
+        int b0 = r0.readUnsignedShort();
+        int b1 = r1.readUnsignedShort();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public char readChar() throws IOException {
+        getFilePointer();
+        char b0 = r0.readChar();
+        char b1 = r1.readChar();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public int readInt() throws IOException {
+        getFilePointer();
+        int b0 = r0.readInt();
+        int b1 = r1.readInt();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public long readLong() throws IOException {
+        getFilePointer();
+        long b0 = r0.readLong();
+        long b1 = r1.readLong();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public float readFloat() throws IOException {
+        getFilePointer();
+        float b0 = r0.readFloat();
+        float b1 = r1.readFloat();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public double readDouble() throws IOException {
+        getFilePointer();
+        double b0 = r0.readDouble();
+        double b1 = r1.readDouble();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public String readLine() throws IOException {
+        getFilePointer();
+        String s0 = r0.readLine();
+        String s1 = r1.readLine();
+        getFilePointer();
+        Assertions.assertEquals(s0, s1);
+        return s0;
+    }
+
+    public String readUTF() throws IOException {
+        getFilePointer();
+        String s0 = r0.readUTF();
+        String s1 = r1.readUTF();
+        getFilePointer();
+        Assertions.assertEquals(s0, s1);
+        return s0;
+    }
+
+    public byte[] readByteLine() throws IOException {
+        getFilePointer();
+        byte [] s0 = r0.readByteLine();
+        byte [] s1 = r1.readByteLine();
+        getFilePointer();
+        Assertions.assertEquals(s0, s1);
+        return s0;
+    }
+
+    public int readByteLine(byte[] d0) throws IOException {
+        getFilePointer();
+        int s0 = r0.readByteLine(d0);
+        byte [] d1 = new byte[d0.length];
+        int s1 = r1.readByteLine(d1);
+        getFilePointer();
+        Assertions.assertEquals(s0, s1);
+        Assertions.assertEquals(0, s0, d0, d1);
+        return s0;
+    }
+
+    public short readShort(int byteOrder) throws IOException {
+        getFilePointer();
+        short b0 = r0.readShort(byteOrder);
+        short b1 = r1.readShort(byteOrder);
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public int readUnsignedShort(int byteOrder) throws IOException {
+        getFilePointer();
+        int b0 = r0.readUnsignedShort();
+        int b1 = r1.readUnsignedShort();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public char readChar(int byteOrder) throws IOException {
+        getFilePointer();
+        char b0 = r0.readChar(byteOrder);
+        char b1 = r1.readChar(byteOrder);
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public int readInt(int byteOrder) throws IOException {
+        getFilePointer();
+        int b0 = r0.readInt(byteOrder);
+        int b1 = r1.readInt(byteOrder);
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public long readLong(int byteOrder) throws IOException {
+        getFilePointer();
+        long b0 = r0.readLong(byteOrder);
+        long b1 = r1.readLong(byteOrder);
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public float readFloat(int byteOrder) throws IOException {
+        getFilePointer();
+        float b0 = r0.readFloat(byteOrder);
+        float b1 = r1.readFloat(byteOrder);
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public double readDouble(int byteOrder) throws IOException {
+        getFilePointer();
+        double b0 = r0.readDouble(byteOrder);
+        double b1 = r1.readDouble(byteOrder);
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public long readUnsignedInt() throws IOException {
+        getFilePointer();
+        long b0 = r0.readUnsignedInt();
+        long b1 = r1.readUnsignedInt();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public long readUnsignedInt(int byteOrder) throws IOException {
+        getFilePointer();
+        long b0 = r0.readUnsignedInt(byteOrder);
+        long b1 = r1.readUnsignedInt(byteOrder);
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+}
Index: ocean/src/com/imagero/uio/test/ComparingRandomAccessInput2.java
===================================================================
--- ocean/src/com/imagero/uio/test/ComparingRandomAccessInput2.java	(revision 0)
+++ ocean/src/com/imagero/uio/test/ComparingRandomAccessInput2.java	(revision 0)
@@ -0,0 +1,363 @@
+package com.imagero.uio.test;
+
+import com.imagero.uio.RandomAccessInput;
+import com.imagero.uio.impl.OffsetRandomAccessFile;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+
+/**
+ * Compare input from RandomAccessFile and RandomAccessInput.
+ *
+ * Date: 29.02.2008
+ * @author Andrey Kuznetsov
+ */
+public class ComparingRandomAccessInput2 implements RandomAccessInput {
+
+    File f;
+    RandomAccessFile r0;
+    RandomAccessInput r1;
+
+    public ComparingRandomAccessInput2(File f, RandomAccessInput ra2) throws IOException {
+        this.f = f;
+        this.r0 = new RandomAccessFile(f, "r");
+        this.r1 = ra2;
+    }
+
+    protected ComparingRandomAccessInput2(File f, RandomAccessFile ra1,  RandomAccessInput ra2) throws IOException {
+        this.f = f;
+        this.r0 = ra1;
+        this.r1 = ra2;
+    }
+
+    public RandomAccessInput createInputChild(long offset, long length, int byteOrder, boolean syncPointer) throws IOException {
+        RandomAccessFile c0 = new OffsetRandomAccessFile(f, "r", offset);
+        RandomAccessInput c1 = r1.createInputChild(offset, 0, byteOrder, syncPointer);
+        return new ComparingRandomAccessInput2(f, c0, c1);
+    }
+
+    public InputStream createInputStream(long offset) {
+        InputStream in = r1.createInputStream(offset);
+        return in;
+    }
+
+    public long getChildPosition(InputStream child) {
+        return r1.getChildPosition(child);
+    }
+
+    public void setChildPosition(InputStream child, long position) {
+        r1.setChildPosition(child, position);
+    }
+
+    public int read() throws IOException {
+        int a0 = r0.read();
+        int a1 = r1.read();
+        Assertions.assertEquals(a0, a1);
+        return a0;
+    }
+
+    public long skip(long n) throws IOException {
+        r0.seek(r0.getFilePointer() + n);
+        long s0 = r0.getFilePointer();
+        long s1 = r1.skip(n);
+        Assertions.assertEquals(s0, s1);
+        return s0;
+    }
+
+    public int read(byte[] b0) throws IOException {
+        byte [] b1 = new byte[b0.length];
+        int k0 = r0.read(b0);
+        int k1 = r1.read(b1);
+        Assertions.assertEquals(k0, k1);
+        Assertions.assertEquals(b0, b1);
+        return k0;
+    }
+
+    public int read(byte[] b0, int off, int len) throws IOException {
+        byte[] b1 = new byte[b0.length];
+        int k0 = r0.read(b0, off, len);
+        int k1 = r1.read(b1, off, len);
+        Assertions.assertEquals(k0, k1);
+        Assertions.assertEquals(off, b0, b1);
+        return k0;
+    }
+
+    public void seek(long offset) throws IOException {
+        r0.seek(offset);
+        r1.seek(offset);
+    }
+
+    public void close() throws IOException {
+        r0.close();
+        r1.close();
+    }
+
+    public int getByteOrder() {
+        int b1 = r1.getByteOrder();
+        Assertions.assertEquals(BIG_ENDIAN, b1);//we need BIG_ENDIAN
+        return b1;
+    }
+
+    public void setByteOrder(int byteOrder) {
+        r1.setByteOrder(byteOrder);
+    }
+
+    public long getFilePointer() throws IOException {
+        long fp0 = r0.getFilePointer();
+        long fp1 = r1.getFilePointer();
+        Assertions.assertEquals(fp0, fp1);
+        return fp0;
+    }
+
+    public long length() throws IOException {
+        long e0 = r0.length();
+        long e1 = r1.length();
+        Assertions.assertEquals(e0, e1);
+        return e0;
+    }
+
+    public boolean isBuffered() {
+        boolean b1 = r1.isBuffered();
+        return b1;
+    }
+
+    public void readFully(byte b0[]) throws IOException {
+        getFilePointer();
+        r0.readFully(b0);
+        byte [] b1 = new byte[b0.length];
+        r1.readFully(b1);
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+    }
+
+    public void readFully(byte b0[], int off, int len) throws IOException {
+        getFilePointer();
+        r0.readFully(b0, off, len);
+        byte[] b1 = new byte[b0.length];
+        r1.readFully(b1, off, len);
+        getFilePointer();
+        Assertions.assertEquals(off, len, b0, b1);
+    }
+
+    public int skipBytes(int n) throws IOException {
+        getFilePointer();
+        int s0 = r0.skipBytes(n);
+        int s1 = r1.skipBytes(n);
+        getFilePointer();
+        Assertions.assertEquals(s0, s1);
+        return s0;
+    }
+
+    public boolean readBoolean() throws IOException {
+        getFilePointer();
+        boolean b0 = r0.readBoolean();
+        boolean b1 = r1.readBoolean();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public byte readByte() throws IOException {
+        getFilePointer();
+        byte b0 = r0.readByte();
+        byte b1 = r1.readByte();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public int readUnsignedByte() throws IOException {
+        getFilePointer();
+        int b0 = r0.readUnsignedByte();
+        int b1 = r1.readUnsignedByte();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public short readShort() throws IOException {
+        getFilePointer();
+        short b0 = r0.readShort();
+        short b1 = r1.readShort();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public int readUnsignedShort() throws IOException {
+        getFilePointer();
+        int b0 = r0.readUnsignedShort();
+        int b1 = r1.readUnsignedShort();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public char readChar() throws IOException {
+        getFilePointer();
+        char b0 = r0.readChar();
+        char b1 = r1.readChar();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public int readInt() throws IOException {
+        getFilePointer();
+        int b0 = r0.readInt();
+        int b1 = r1.readInt();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public long readLong() throws IOException {
+        getFilePointer();
+        long b0 = r0.readLong();
+        long b1 = r1.readLong();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public float readFloat() throws IOException {
+        getFilePointer();
+        float b0 = r0.readFloat();
+        float b1 = r1.readFloat();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public double readDouble() throws IOException {
+        getFilePointer();
+        double b0 = r0.readDouble();
+        double b1 = r1.readDouble();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public String readLine() throws IOException {
+        getFilePointer();
+        String s0 = r0.readLine();
+        String s1 = r1.readLine();
+        getFilePointer();
+        Assertions.assertEquals(s0, s1);
+        return s0;
+    }
+
+    public String readUTF() throws IOException {
+        getFilePointer();
+        String s0 = r0.readUTF();
+        String s1 = r1.readUTF();
+        getFilePointer();
+        Assertions.assertEquals(s0, s1);
+        return s0;
+    }
+
+    public byte[] readByteLine() throws IOException {
+        getFilePointer();
+//        byte [] s0 = r0.readByteLine();
+        byte [] s1 = r1.readByteLine();
+        r0.seek(r1.getFilePointer());//can't compare - ergo don't read, just move file pointer
+        getFilePointer();
+//        Assertions.assertEquals(s0, s1);
+        return s1;
+    }
+
+    public int readByteLine(byte[] d0) throws IOException {
+        getFilePointer();
+//        int s0 = r0.readByteLine(d0);
+        byte [] d1 = new byte[d0.length];
+        int s1 = r1.readByteLine(d1);
+        r0.seek(r1.getFilePointer());//can't compare - ergo don't read, just move file pointer
+        getFilePointer();
+//        Assertions.assertEquals(s0, s1);
+//        Assertions.assertEquals(0, s0, d0, d1);
+        return s1;
+    }
+
+    public short readShort(int byteOrder) throws IOException {
+        getFilePointer();
+        short b0 = r0.readShort();
+        short b1 = r1.readShort(byteOrder);
+        getFilePointer();
+//        Assertions.assertEquals(b0, b1); //don't compare
+        return b0;
+    }
+
+    public int readUnsignedShort(int byteOrder) throws IOException {
+        getFilePointer();
+        int b0 = r0.readUnsignedShort();
+        int b1 = r1.readUnsignedShort(byteOrder);
+        getFilePointer();
+//        Assertions.assertEquals(b0, b1); //don't compare
+        return b0;
+    }
+
+    public char readChar(int byteOrder) throws IOException {
+        getFilePointer();
+        char b0 = r0.readChar();
+        char b1 = r1.readChar(byteOrder);
+        getFilePointer();
+//        Assertions.assertEquals(b0, b1); //don't compare
+        return b0;
+    }
+
+    public int readInt(int byteOrder) throws IOException {
+        getFilePointer();
+        int b0 = r0.readInt();
+        int b1 = r1.readInt(byteOrder);
+        getFilePointer();
+//        Assertions.assertEquals(b0, b1); //don't compare
+        return b0;
+    }
+
+    public long readLong(int byteOrder) throws IOException {
+        getFilePointer();
+        long b0 = r0.readLong();
+        long b1 = r1.readLong(byteOrder);
+        getFilePointer();
+//        Assertions.assertEquals(b0, b1); //don't compare
+        return b0;
+    }
+
+    public float readFloat(int byteOrder) throws IOException {
+        getFilePointer();
+        float b0 = r0.readFloat();
+        float b1 = r1.readFloat(byteOrder);
+        getFilePointer();
+//        Assertions.assertEquals(b0, b1); //don't compare
+        return b0;
+    }
+
+    public double readDouble(int byteOrder) throws IOException {
+        getFilePointer();
+        double b0 = r0.readDouble();
+        double b1 = r1.readDouble(byteOrder);
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public long readUnsignedInt() throws IOException {
+        getFilePointer();
+        long b0 = ((long) r0.readInt()) & 0xFFFFFFFFL;
+        long b1 = r1.readUnsignedInt();
+        getFilePointer();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public long readUnsignedInt(int byteOrder) throws IOException {
+        getFilePointer();
+        long b0 = r0.readInt();
+        long b1 = r1.readUnsignedInt(byteOrder);
+        getFilePointer();
+//        Assertions.assertEquals(b0, b1); //don't compare
+        return b0;
+    }
+}
Index: ocean/src/com/imagero/uio/test/ComparingRandomAccessIO.java
===================================================================
--- ocean/src/com/imagero/uio/test/ComparingRandomAccessIO.java	(revision 0)
+++ ocean/src/com/imagero/uio/test/ComparingRandomAccessIO.java	(revision 0)
@@ -0,0 +1,154 @@
+package com.imagero.uio.test;
+
+import com.imagero.uio.RandomAccessIO;
+import com.imagero.uio.RandomAccessOutput;
+import com.imagero.uio.test.ComparingRandomAccessInput;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ *
+ * Trace method calls and their results.
+ *
+ * Date: 29.02.2008
+ * @author Andrey Kuznetsov
+ */
+public class ComparingRandomAccessIO extends ComparingRandomAccessInput implements RandomAccessIO {
+
+    RandomAccessIO r0;
+    RandomAccessIO r1;
+
+    public ComparingRandomAccessIO(RandomAccessIO ra1, RandomAccessIO ra2) {
+        super(ra1, ra2);
+        this.r0 = ra1;
+        this.r1 = ra2;
+    }
+
+    public RandomAccessIO createIOChild(long offset, long length, int byteOrder, boolean syncPointer) throws IOException {
+        RandomAccessIO c0 = r0.createIOChild(offset, 0, byteOrder, syncPointer);
+        RandomAccessIO c1 = r1.createIOChild(offset, 0, byteOrder, syncPointer);
+        return new ComparingRandomAccessIO(c0, c1);
+    }
+
+    public RandomAccessOutput createOutputChild(long offset, int byteOrder, boolean syncPointer) throws IOException {
+        RandomAccessOutput c0 = r0.createOutputChild(offset, byteOrder, syncPointer);
+        RandomAccessOutput c1 = r1.createOutputChild(offset, byteOrder, syncPointer);
+        return new ComparingRandomAccessOutput(c0, c1);
+    }
+
+    public OutputStream createOutputStream(long offset) {
+        OutputStream out = r0.createOutputStream(offset);
+        return out;
+    }
+
+    public void flush() throws IOException {
+        r0.flush();
+        r1.flush();
+    }
+
+    public void write(int b) throws IOException {
+        r0.write(b);
+        r1.write(b);
+    }
+
+    public void write(byte b[]) throws IOException {
+        r0.write(b);
+        r1.write(b);
+    }
+
+    public void write(byte b[], int off, int len) throws IOException {
+        r0.write(b, off, len);
+        r1.write(b, off, len);
+    }
+
+    public void writeBoolean(boolean v) throws IOException {
+        r0.writeBoolean(v);
+        r1.writeBoolean(v);
+    }
+
+    public void writeByte(int v) throws IOException {
+        r0.writeByte(v);
+        r1.writeByte(v);
+    }
+
+    public void writeShort(int v) throws IOException {
+        r0.writeShort(v);
+        r1.writeShort(v);
+    }
+
+    public void writeChar(int v) throws IOException {
+        r0.writeChar(v);
+        r1.writeChar(v);
+    }
+
+    public void writeInt(int v) throws IOException {
+        r0.writeInt(v);
+        r1.writeInt(v);
+    }
+
+    public void writeLong(long v) throws IOException {
+        r0.writeLong(v);
+        r1.writeLong(v);
+    }
+
+    public void writeFloat(float v) throws IOException {
+        r0.writeFloat(v);
+        r1.writeFloat(v);
+    }
+
+    public void writeDouble(double v) throws IOException {
+        r0.writeDouble(v);
+        r1.writeDouble(v);
+    }
+
+    public void writeBytes(String s) throws IOException {
+        r0.writeBytes(s);
+        r1.writeBytes(s);
+    }
+
+    public void writeChars(String s) throws IOException {
+        r0.writeChars(s);
+        r1.writeChars(s);
+   }
+
+    public void writeUTF(String s) throws IOException {
+        r0.writeUTF(s);
+        r1.writeUTF(s);
+    }
+
+    public void writeShort(int v, int byteOrder) throws IOException {
+        r0.writeShort(v, byteOrder);
+        r1.writeShort(v, byteOrder);
+    }
+
+    public void writeChar(int v, int byteOrder) throws IOException {
+        r0.writeChar(v, byteOrder);
+        r1.writeChar(v, byteOrder);
+   }
+
+    public void writeInt(int v, int byteOrder) throws IOException {
+        r0.writeInt(v, byteOrder);
+        r1.writeInt(v, byteOrder);
+    }
+
+    public void writeLong(long v, int byteOrder) throws IOException {
+        r0.writeLong(v, byteOrder);
+        r1.writeLong(v, byteOrder);
+    }
+
+    public void writeFloat(float v, int byteOrder) throws IOException {
+        r0.writeFloat(v, byteOrder);
+        r1.writeFloat(v, byteOrder);
+    }
+
+    public void writeDouble(double v, int byteOrder) throws IOException {
+        r0.writeDouble(v, byteOrder);
+        r1.writeDouble(v, byteOrder);
+   }
+
+    public void setLength(long newLength) throws IOException {
+        r0.setLength(newLength);
+        r1.setLength(newLength);
+    }
+}
Index: ocean/src/com/imagero/uio/test/ComparingRandomAccessOutput.java
===================================================================
--- ocean/src/com/imagero/uio/test/ComparingRandomAccessOutput.java	(revision 0)
+++ ocean/src/com/imagero/uio/test/ComparingRandomAccessOutput.java	(revision 0)
@@ -0,0 +1,181 @@
+package com.imagero.uio.test;
+
+import com.imagero.uio.RandomAccessOutput;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ *
+ * Trace method calls and their results.
+ *
+ * Date: 29.02.2008
+ * @author Andrey Kuznetsov
+ */
+public class ComparingRandomAccessOutput implements RandomAccessOutput {
+
+    RandomAccessOutput r0;
+    RandomAccessOutput r1;
+
+    public ComparingRandomAccessOutput(RandomAccessOutput ra1, RandomAccessOutput ra2) {
+        this.r0 = ra1;
+        this.r1 = ra2;
+    }
+
+    public RandomAccessOutput createOutputChild(long offset, int byteOrder, boolean syncPointer) throws IOException {
+        RandomAccessOutput c0 = r0.createOutputChild(offset, byteOrder, syncPointer);
+        RandomAccessOutput c1 = r1.createOutputChild(offset, byteOrder, syncPointer);
+        return new ComparingRandomAccessOutput(c0, c1);
+    }
+
+    public OutputStream createOutputStream(long offset) {
+        OutputStream out = r0.createOutputStream(offset);
+        return out;
+    }
+
+    public void flush() throws IOException {
+        r0.flush();
+        r1.flush();
+    }
+
+    public void write(int b) throws IOException {
+        r0.write(b);
+        r1.write(b);
+    }
+
+    public void write(byte b[]) throws IOException {
+        r0.write(b);
+        r1.write(b);
+    }
+
+    public void write(byte b[], int off, int len) throws IOException {
+        r0.write(b, off, len);
+        r1.write(b, off, len);
+    }
+
+    public void writeBoolean(boolean v) throws IOException {
+        r0.writeBoolean(v);
+        r1.writeBoolean(v);
+    }
+
+    public void writeByte(int v) throws IOException {
+        r0.writeByte(v);
+        r1.writeByte(v);
+    }
+
+    public void writeShort(int v) throws IOException {
+        r0.writeShort(v);
+        r1.writeShort(v);
+    }
+
+    public void writeChar(int v) throws IOException {
+        r0.writeChar(v);
+        r1.writeChar(v);
+    }
+
+    public void writeInt(int v) throws IOException {
+        r0.writeInt(v);
+        r1.writeInt(v);
+    }
+
+    public void writeLong(long v) throws IOException {
+        r0.writeLong(v);
+        r1.writeLong(v);
+    }
+
+    public void writeFloat(float v) throws IOException {
+        r0.writeFloat(v);
+        r1.writeFloat(v);
+    }
+
+    public void writeDouble(double v) throws IOException {
+        r0.writeDouble(v);
+        r1.writeDouble(v);
+    }
+
+    public void writeBytes(String s) throws IOException {
+        r0.writeBytes(s);
+        r1.writeBytes(s);
+    }
+
+    public void writeChars(String s) throws IOException {
+        r0.writeChars(s);
+        r1.writeChars(s);
+   }
+
+    public void writeUTF(String s) throws IOException {
+        r0.writeUTF(s);
+        r1.writeUTF(s);
+    }
+
+    public void writeShort(int v, int byteOrder) throws IOException {
+        r0.writeShort(v, byteOrder);
+        r1.writeShort(v, byteOrder);
+    }
+
+    public void writeChar(int v, int byteOrder) throws IOException {
+        r0.writeChar(v, byteOrder);
+        r1.writeChar(v, byteOrder);
+   }
+
+    public void writeInt(int v, int byteOrder) throws IOException {
+        r0.writeInt(v, byteOrder);
+        r1.writeInt(v, byteOrder);
+    }
+
+    public void writeLong(long v, int byteOrder) throws IOException {
+        r0.writeLong(v, byteOrder);
+        r1.writeLong(v, byteOrder);
+    }
+
+    public void writeFloat(float v, int byteOrder) throws IOException {
+        r0.writeFloat(v, byteOrder);
+        r1.writeFloat(v, byteOrder);
+    }
+
+    public void writeDouble(double v, int byteOrder) throws IOException {
+        r0.writeDouble(v, byteOrder);
+        r1.writeDouble(v, byteOrder);
+   }
+
+    public void setLength(long newLength) throws IOException {
+        r0.setLength(newLength);
+        r1.setLength(newLength);
+    }
+
+    public int getByteOrder() {
+        int b0 = r0.getByteOrder();
+        int b1 = r1.getByteOrder();
+        Assertions.assertEquals(b0, b1);
+        return b0;
+    }
+
+    public void close() throws IOException {
+        r0.close();
+        r1.close();
+    }
+
+    public void seek(long offset) throws IOException {
+        r0.seek(offset);
+        r1.seek(offset);
+    }
+
+    public long getFilePointer() throws IOException {
+        long p0 = r0.getFilePointer();
+        long p1 = r1.getFilePointer();
+        Assertions.assertEquals(p0, p1);
+        return p0;
+    }
+
+    public void setByteOrder(int byteOrder) {
+        r0.setByteOrder(byteOrder);
+        r1.setByteOrder(byteOrder);
+    }
+
+    public long length() throws IOException {
+        long k0 = r0.length();
+        long k1 = r1.length();
+        Assertions.assertEquals(k0, k1);
+        return k0;
+    }
+}
Index: ocean/src/com/imagero/uio/Transformer.java
===================================================================
--- ocean/src/com/imagero/uio/Transformer.java	(revision 0)
+++ ocean/src/com/imagero/uio/Transformer.java	(revision 0)
@@ -0,0 +1,495 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio;
+
+import com.imagero.uio.io.BitInputStream;
+import com.imagero.uio.io.BitOutputStream;
+import com.imagero.uio.io.ByteArrayOutputStreamExt;
+import com.imagero.uio.RandomAccessInput;
+import com.imagero.uio.xform.XTransformer;
+import com.imagero.uio.xform.ByteToXBE;
+import com.imagero.uio.xform.ByteToXLE;
+import com.imagero.uio.xform.XtoByteBE;
+import com.imagero.uio.xform.XtoByteLE;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+/**
+ * Primitive type conversion, array copying, etc.
+ *
+ * @author Andrey Kuznetsov
+ */
+public class Transformer extends XTransformer {
+
+
+
+    public static final int byteToInt(byte[] source, int sourceOffset, int[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return ByteToXBE.byteToInt(source, sourceOffset, dest, destOffset);
+        } else {
+            return ByteToXLE.byteToInt(source, sourceOffset, dest, destOffset);
+        }
+    }
+
+    public static final void byteToInt(byte[] source, int sourceOffset, int count, int[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            ByteToXBE.byteToInt(source, sourceOffset, count, dest, destOffset);
+        } else {
+            ByteToXLE.byteToInt(source, sourceOffset, count, dest, destOffset);
+        }
+    }
+
+    public static final int intToByte(int[] source, int sourceOffset, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return XtoByteBE.intToByte(source, sourceOffset, dest, destOffset);
+        } else {
+            return XtoByteLE.intToByte(source, sourceOffset, dest, destOffset);
+        }
+    }
+
+    public static final void intToByte(int[] source, int srcOffset, int count, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            XtoByteBE.intToByte(source, srcOffset, count, dest, destOffset);
+        } else {
+            XtoByteLE.intToByte(source, srcOffset, count, dest, destOffset);
+        }
+    }
+
+    public static int intToByte(int v, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return XtoByteBE.intToByte(v, dest, destOffset);
+        } else {
+            return XtoByteLE.intToByte(v, dest, destOffset);
+        }
+    }
+
+    public static final int byteToInt(byte[] source, int sourceOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return ByteToXBE.byteToInt(source, sourceOffset);
+        } else {
+            return ByteToXLE.byteToInt(source, sourceOffset);
+        }
+    }
+
+
+/* **********************************************************************/
+
+    public static int byteToChar(byte[] source, int sourceOffset, char[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return ByteToXBE.byteToChar(source, sourceOffset, dest, destOffset);
+        } else {
+            return ByteToXLE.byteToChar(source, sourceOffset, dest, destOffset);
+        }
+    }
+
+    public static void byteToChar(byte[] source, int sourceOffset, int count, char[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            ByteToXBE.byteToChar(source, sourceOffset, count, dest, destOffset);
+        } else {
+            ByteToXLE.byteToChar(source, sourceOffset, count, dest, destOffset);
+        }
+    }
+
+    public static int byteToChar(byte[] source, int sourceOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return ByteToXBE.byteToChar(source, sourceOffset);
+        } else {
+            return ByteToXLE.byteToChar(source, sourceOffset);
+        }
+    }
+
+/* **********************************************************************/
+    public static int charToByte(char[] source, int srcOffset, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return XtoByteBE.charToByte(source, srcOffset, dest, destOffset);
+        } else {
+            return XtoByteLE.charToByte(source, srcOffset, dest, destOffset);
+        }
+    }
+
+    public static int charToByte(char v, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return XtoByteBE.charToByte(v, dest, destOffset);
+        } else {
+            return XtoByteLE.charToByte(v, dest, destOffset);
+        }
+    }
+
+    public static void charToByte(char[] source, int srcOffset, int count, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            XtoByteBE.charToByte(source, srcOffset, count, dest, destOffset);
+        } else {
+            XtoByteLE.charToByte(source, srcOffset, count, dest, destOffset);
+        }
+    }
+
+/* **********************************************************************/
+
+    public static int byteToDouble(byte[] source, int sourceOffset, double[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return ByteToXBE.byteToDoubleBE(source, sourceOffset, dest, destOffset);
+        } else {
+            return ByteToXLE.byteToDoubleLE(source, sourceOffset, dest, destOffset);
+        }
+    }
+
+    public static void byteToDouble(byte[] source, int sourceOffset, int count, double[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            ByteToXBE.byteToDoubleBE(source, sourceOffset, count, dest, destOffset);
+        } else {
+            ByteToXLE.byteToDoubleLE(source, sourceOffset, count, dest, destOffset);
+        }
+    }
+
+    public static double byteToDouble(byte[] source, int sourceOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return ByteToXBE.byteToDoubleBE(source, sourceOffset);
+        } else {
+            return ByteToXLE.byteToDoubleLE(source, sourceOffset);
+        }
+    }
+
+/* **********************************************************************/
+
+    public static int doubleToByte(double[] source, int srcOffset, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return XtoByteBE.doubleToByteBE(source, srcOffset, dest, destOffset);
+        } else {
+            return XtoByteLE.doubleToByteLE(source, srcOffset, dest, destOffset);
+        }
+    }
+
+    public static void doubleToByte(double[] source, int srcOffset, int count, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            XtoByteBE.doubleToByteBE(source, srcOffset, count, dest, destOffset);
+        } else {
+            XtoByteLE.doubleToByteLE(source, srcOffset, count, dest, destOffset);
+        }
+    }
+
+    public static int doubleToByte(double d, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return XtoByteBE.doubleToByteBE(d, dest, destOffset);
+        } else {
+            return XtoByteLE.doubleToByteLE(d, dest, destOffset);
+        }
+    }
+
+/* **********************************************************************/
+
+    public static int byteToFloat(byte[] source, int sourceOffset, float[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return ByteToXBE.byteToFloatBE(source, sourceOffset, dest, destOffset);
+        } else {
+            return ByteToXLE.byteToFloatLE(source, sourceOffset, dest, destOffset);
+        }
+    }
+
+    public static void byteToFloat(byte[] source, int sourceOffset, int count, float[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            ByteToXBE.byteToFloatBE(source, sourceOffset, count, dest, destOffset);
+        } else {
+            ByteToXLE.byteToFloatLE(source, sourceOffset, count, dest, destOffset);
+        }
+    }
+
+    public static float byteToFloat(byte[] source, int sourceOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return ByteToXBE.byteToFloatBE(source, sourceOffset);
+        } else {
+            return ByteToXLE.byteToFloatLE(source, sourceOffset);
+        }
+    }
+
+/* **********************************************************************/
+
+    public static int floatToByte(float[] source, int offset, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return XtoByteBE.floatToByteBE(source, offset, dest, destOffset);
+        } else {
+            return XtoByteLE.floatToByteLE(source, offset, dest, destOffset);
+        }
+    }
+
+    public static void floatToByte(float[] source, int offset, int count, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            XtoByteBE.floatToByteBE(source, offset, count, dest, destOffset);
+        } else {
+            XtoByteLE.floatToByteLE(source, offset, count, dest, destOffset);
+        }
+    }
+
+    public static int floatToByte(float f, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return XtoByteBE.floatToByteBE(f, dest, destOffset);
+        } else {
+            return XtoByteLE.floatToByteLE(f, dest, destOffset);
+        }
+    }
+
+/* **********************************************************************/
+
+    public static int byteToLong(byte[] source, int sourceOffset, long[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return ByteToXBE.byteToLongBE(source, sourceOffset, dest, destOffset);
+        } else {
+            return ByteToXLE.byteToLongLE(source, sourceOffset, dest, destOffset);
+        }
+    }
+
+    public static void byteToLong(byte[] source, int sourceOffset, int count, long[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            ByteToXBE.byteToLongBE(source, sourceOffset, count, dest, destOffset);
+        } else {
+            ByteToXLE.byteToLongLE(source, sourceOffset, count, dest, destOffset);
+        }
+    }
+
+    public static long byteToLong(byte[] source, int sourceOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return ByteToXBE.byteToLongBE(source, sourceOffset);
+        } else {
+            return ByteToXLE.byteToLongLE(source, sourceOffset);
+        }
+    }
+
+/* **********************************************************************/
+
+    public static int longToByte(long[] source, int offset, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return XtoByteBE.longToByteBE(source, offset, dest, destOffset);
+        } else {
+            return XtoByteLE.longToByteLE(source, offset, dest, destOffset);
+        }
+    }
+
+    public static void longToByte(long[] source, int offset, int count, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            XtoByteBE.longToByteBE(source, offset, count, dest, destOffset);
+        } else {
+            XtoByteLE.longToByteLE(source, offset, count, dest, destOffset);
+        }
+    }
+
+    public static int longToByte(long v, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return XtoByteBE.longToByteBE(v, dest, destOffset);
+        } else {
+            return XtoByteLE.longToByteLE(v, dest, destOffset);
+        }
+    }
+
+/* **********************************************************************/
+
+
+    public static int byteToShort(byte[] source, int sourceOffset, short[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return ByteToXBE.byteToShortBE(source, sourceOffset, dest, destOffset);
+        } else {
+            return ByteToXLE.byteToShortLE(source, sourceOffset, dest, destOffset);
+        }
+    }
+
+    public static void byteToShort(byte[] source, int sourceOffset, int count, short[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            ByteToXBE.byteToShortBE(source, sourceOffset, count, dest, destOffset);
+        } else {
+            ByteToXLE.byteToShortLE(source, sourceOffset, count, dest, destOffset);
+        }
+    }
+
+    public static int byteToShort(byte[] source, int sourceOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return ByteToXBE.byteToShortBE(source, sourceOffset);
+        } else {
+            return ByteToXLE.byteToShortLE(source, sourceOffset);
+        }
+    }
+
+/* **********************************************************************/
+
+    public static int shortToByte(short[] source, int offset, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return XtoByteBE.shortToByteBE(source, offset, dest, destOffset);
+        } else {
+            return XtoByteBE.shortToByteBE(source, offset, dest, destOffset);
+        }
+    }
+
+    public static void shortToByte(short[] source, int offset, int count, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            XtoByteBE.shortToByteBE(source, offset, count, dest, destOffset);
+        } else {
+            XtoByteLE.shortToByteLE(source, offset, count, dest, destOffset);
+        }
+    }
+
+    public static int shortToByte(short v, byte[] dest, int destOffset, boolean bigEndian) {
+        if (bigEndian) {
+            return XtoByteBE.shortToByteBE(v, dest, destOffset);
+        } else {
+            return XtoByteLE.shortToByteLE(v, dest, destOffset);
+        }
+    }
+
+    public static final byte[] readByteLine(RandomAccessInput in) throws IOException {
+        long start = in.getFilePointer();
+        long end = start;
+        boolean finished = false;
+        boolean eof = false;
+        int length = 0;
+        while (!finished) {
+            int k = in.read();
+            switch (k) {
+                case -1:
+                    eof = true;
+                    finished = true;
+                    break;
+                case '\n':
+                    finished = true;
+                    end = in.getFilePointer();
+                    length = (int) (end - start);
+                    break;
+                case '\r':
+                    finished = true;
+                    end = in.getFilePointer();
+                    length = (int) (end - start);
+                    if ((in.read()) == '\n') {
+                        end = in.getFilePointer();
+                    }
+                    break;
+                default:
+                    k = 0;
+                    break;
+            }
+        }
+        if (eof && length == 0) {
+            return null;
+        }
+        byte[] b = new byte[length];
+        in.seek(start);
+        in.readFully(b);
+        in.seek(end);
+        return b;
+    }
+
+    public static final int readByteLine(RandomAccessInput in, byte[] dest) throws IOException {
+        long start = in.getFilePointer();
+        long end = start;
+        boolean finished = false;
+        boolean eof = false;
+        int length = 0;
+        int cnt = 0;
+        while (!finished) {
+            if (cnt++ >= dest.length) {
+                finished = true;
+                break;
+            }
+            switch (in.read()) {
+                case -1:
+                    eof = true;
+                    finished = true;
+                    break;
+                case '\n':
+                    finished = true;
+                    end = in.getFilePointer();
+                    length = (int) (end - start);
+                    break;
+                case '\r':
+                    finished = true;
+                    end = in.getFilePointer();
+                    length = (int) (end - start);
+                    if ((in.read()) == '\n') {
+                        end = in.getFilePointer();
+                    }
+                    break;
+            }
+        }
+
+        if (eof && length == 0) {
+            return 0;
+        }
+        if (length == 0) {
+            end = in.getFilePointer();
+            length = Math.min(dest.length, (int) (end - start));
+        }
+        in.seek(start);
+        in.readFully(dest, 0, length);
+        in.seek(end);
+        return length;
+    }
+
+    /**
+     * Make right shift for all bytes of given array
+     * @param src byte array
+     * @param first value used to fill empty bits of first byte in array
+     * @param shift shift amount (from 1 to 7)
+     * @throws java.io.IOException
+     */
+    public static void shiftRight(byte[] src, int first, int shift) throws IOException {
+        ByteArrayOutputStreamExt out = new ByteArrayOutputStreamExt(src.length + 1);
+        BitOutputStream bos = new BitOutputStream(out);
+
+        bos.write(first, shift);
+        for (int i = 0; i < src.length; i++) {
+            bos.write(src[i] & 0xFF);
+        }
+        bos.close();
+
+        byte[] dst = out.drain();
+        System.arraycopy(dst, 0, src, 0, src.length);
+    }
+
+    /**
+     * Make left shift for all bytes in given array
+     * @param src byte array
+     * @param shift shift amount (from 1 to 7)
+     * @throws java.io.IOException
+     */
+    public static void shiftLeft(byte[] src, int shift) throws IOException {
+        ByteArrayInputStream in = new ByteArrayInputStream(src);
+        BitInputStream bis = new BitInputStream(in);
+        bis.read(shift);
+        bis.setBitsToRead(8);
+        int a = bis.read();
+        int p = 0;
+        while (a != -1 && p < src.length) {
+            int b = bis.read();
+            src[p++] = (byte) a;
+            if (b == -1) {
+                break;
+            }
+            a = b;
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/UIOStreamBuilder.java
===================================================================
--- ocean/src/com/imagero/uio/UIOStreamBuilder.java	(revision 0)
+++ ocean/src/com/imagero/uio/UIOStreamBuilder.java	(revision 0)
@@ -0,0 +1,782 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.uio;
+
+import com.imagero.uio.bio.BIOFactory;
+import com.imagero.uio.bio.BufferedRandomAccessIO;
+import com.imagero.uio.bio.ByteArrayRandomAccessIO;
+import com.imagero.uio.bio.IOController;
+import com.imagero.uio.bio.VariableSizeByteBuffer;
+import com.imagero.uio.bio.content.ByteArrayContent;
+import com.imagero.uio.bio.content.CharArrayContent;
+import com.imagero.uio.bio.content.Content;
+import com.imagero.uio.bio.content.DoubleArrayContent;
+import com.imagero.uio.bio.content.FloatArrayContent;
+import com.imagero.uio.bio.content.IntArrayContent;
+import com.imagero.uio.bio.content.LongArrayContent;
+import com.imagero.uio.bio.content.RandomAccessFileContent;
+import com.imagero.uio.bio.content.ShortArrayContent;
+import com.imagero.uio.bio.content.Span;
+import com.imagero.uio.bio.content.SpannedRandomAccessInputContent;
+import com.imagero.uio.bio.content.SpannedRandomAccessIOContent;
+import com.imagero.uio.impl.RandomAccessFileWrapper;
+import com.imagero.uio.impl.RandomAccessFileX;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.net.URL;
+
+/**
+ * <pre>
+ * UIOStreamBuilder is a builder pattern implementation and replacement for RandomAccessFactory.
+ * Usual process looks like
+ * File f = ...;
+ * RandomAccessIO ro = new UIOStreamBuilder(f).setByteOrder(RandomAccessIO.LITTLE_ENDIAN).setBuffered(true).create();
+ * or
+ * RandomAccessIO ra = (RandomAccessIO)new UIOStreamBuilder(f).setMode(UIOStreamBuilder.READ_WRITE).create();
+ *
+ * Defaul values are:
+ * mode - UIOStreamBuilder.READ_ONLY
+ * byte order - RandomAccessIO.BIG_ENDIAN
+ * buffered - false (however some streams are always buffered)
+ *
+ * </pre>
+ * @see #create
+ * @see #setBuffered
+ * @see #setByteOrder
+ * @see #setBufferSize
+ * @see #setCacheFile
+ * @see #setMaxBufferCount
+ * @see #setMode
+ * @see #setStart
+ * @see #setLength
+ *
+ * @author Andrey Kuznetsov
+ */
+public class UIOStreamBuilder {
+
+    public static final String READ_ONLY = "r";
+    public static final String READ_WRITE = "rw";
+
+    String mode = READ_ONLY;
+
+    int byteOrder = RandomAccessInput.BIG_ENDIAN;
+
+    Long start;
+    Long length;
+
+    private boolean buffered = true;
+
+    private Integer maxBufferCount;
+    private Integer bufferSize;
+
+    Creator creator;
+
+    File cache;
+
+    public static int DEFAULT_CHUNK_SIZE = 256 * 1024;
+    public static int DEFAULT_CHUNK_COUNT = 8;
+
+    public boolean isReadOnly() {
+        if (READ_WRITE.equals(mode)) {
+            return false;
+        }
+        return true;
+    }
+
+    public UIOStreamBuilder(String filename) {
+        this(new File(filename));
+    }
+
+    public UIOStreamBuilder(File file) {
+        this.creator = new FileCreator(file);
+    }
+
+    public UIOStreamBuilder(RandomAccessFile rafSource) {
+        this.creator = new RAFCreator(rafSource);
+    }
+
+    public UIOStreamBuilder(RandomAccessIO ra) {
+        this.creator = new RAIOCreator(ra);
+    }
+
+    public UIOStreamBuilder(RandomAccessInput ro) {
+        this.creator = new RAICreator(ro);
+    }
+
+    public UIOStreamBuilder(RandomAccessInput ro, Span [] spans) {
+        this.creator = new SpannedCreator(ro, spans);
+    }
+
+    public UIOStreamBuilder(byte[] byteSource) {
+        this.creator = new ByteCreator(byteSource);
+    }
+
+    public UIOStreamBuilder(byte[][] byteSource) {
+        if (byteSource.length == 1) {
+            this.creator = new ByteCreator(byteSource[0]);
+        } else {
+            this.creator = new Byte2DCreator(byteSource);
+        }
+    }
+
+    public UIOStreamBuilder(short[] shortSource) {
+        creator = new ShortCreator(shortSource);
+    }
+
+    public UIOStreamBuilder(short[][] shortSource) {
+        creator = new ShortCreator(shortSource);
+    }
+
+    public UIOStreamBuilder(char[] charSource) {
+        creator = new CharCreator(charSource);
+    }
+
+    public UIOStreamBuilder(char[][] charSource) {
+        creator = new CharCreator(charSource);
+    }
+
+    public UIOStreamBuilder(int[] intSource) {
+        creator = new IntCreator(intSource);
+    }
+
+    public UIOStreamBuilder(int[][] intSource) {
+        creator = new IntCreator(intSource);
+    }
+
+    public UIOStreamBuilder(long[] longSource) {
+        creator = new LongCreator(longSource);
+    }
+
+    public UIOStreamBuilder(long[][] longSource) {
+        creator = new LongCreator(longSource);
+    }
+
+    public UIOStreamBuilder(float[] floatSource) {
+        creator = new FloatCreator(floatSource);
+    }
+
+    public UIOStreamBuilder(float[][] floatSource) {
+        creator = new FloatCreator(floatSource);
+    }
+
+    public UIOStreamBuilder(double[] doubleSource) {
+        creator = new DoubleCreator(doubleSource);
+    }
+
+    public UIOStreamBuilder(double[][] doubleSource) {
+        creator = new DoubleCreator(doubleSource);
+    }
+
+    static private File getTmpDir() {
+        String name = System.getProperty("uio.temp.dir");
+        if (name != null && name.length() > 0) {
+            File f = new File(name);
+            if (!f.exists()) {
+                f.mkdirs();
+            }
+            if (f.isDirectory()) {
+                return f;
+            }
+        }
+        return null;
+    }
+
+    static File createTempFile(String prefix) {
+        File dir = getTmpDir();
+        if (dir != null) {
+            return new File(dir, prefix + Integer.toHexString(dir.hashCode()));
+        }
+        return null;
+    }
+
+    /**
+     * always buffered
+     * @param url
+     */
+    public UIOStreamBuilder(URL url) {
+        creator = new URLCreator(url);
+    }
+
+    /**
+     * always buffered
+     * @param in
+     */
+    public UIOStreamBuilder(InputStream in) {
+        creator = new ISCreator(in);
+    }
+
+    /**
+     * Always buffered.
+     * Two things are very important:
+     * 1. closing RandomAccessOutput created by this method does not close OutputStream
+     * 2. To write data to OutputStream RandomAccessOutput must be closed or flushed.
+     *
+     * @param out OutputStream
+     */
+    public UIOStreamBuilder(OutputStream out) {
+        creator = new OSCreator(out);
+    }
+
+    /**
+     * set mode (writeable or read only)
+     * @param mode READ_ONLY or READ_WRITE
+     * @return UIOStreamBuilder
+     */
+    public UIOStreamBuilder setMode(String mode) {
+        if (READ_ONLY.equals(mode) || READ_WRITE.equals(mode)) {
+            this.mode = mode;
+            return this;
+        } else {
+            throw new IllegalArgumentException(mode);
+        }
+    }
+
+    /**
+     * set byte order (big endian or little endian)
+     * @param byteOrder LITTLE_ENDIAN or BIG_ENDIAN (default value - BIG_ENDIAN)
+     * @return UIOStreamBuilder
+     */
+    public UIOStreamBuilder setByteOrder(int byteOrder) {
+        switch (byteOrder) {
+            case RandomAccessInput.LITTLE_ENDIAN:
+            case RandomAccessInput.BIG_ENDIAN:
+                this.byteOrder = byteOrder;
+                return this;
+            default:
+                throw new IllegalArgumentException("" + byteOrder);
+        }
+    }
+
+    /**
+     * set start offset
+     * @param start start offset of stream (default value - 0L)
+     * @return UIOStreamBuilder
+     */
+    public UIOStreamBuilder setStart(long start) {
+        if (start < 0) {
+            throw new IllegalArgumentException(" " + start);
+        }
+        this.start = new Long(start);
+        return this;
+    }
+
+    /**
+     * set stream length
+     * @param length stream length
+     * @return UIOStreamBuilder
+     */
+    public UIOStreamBuilder setLength(long length) {
+        if (length < 0) {
+            throw new IllegalArgumentException(" " + length);
+        }
+        this.length = new Long(length);
+        if (start == null) {
+            start = new Long(0L);
+        }
+        return this;
+    }
+
+    /**
+     * set if stream should be buffered or not (rather a hint because some streams are always buffered)
+     * @param buffered true or false (default value - false)
+     * @return UIOStreamBuilder
+     */
+    public UIOStreamBuilder setBuffered(boolean buffered) {
+        this.buffered = buffered;
+        return this;
+    }
+
+    /**
+     * set maxBufferCount for MemoryAccessManager - for unbuffered streams this parameter is ignored.
+     * @param max
+     * @return UIOStreamBuilder
+     */
+    public UIOStreamBuilder setMaxBufferCount(int max) {
+        this.maxBufferCount = new Integer(max);
+        return this;
+    }
+
+    /**
+     * set size for memory chunks used by MemoryAccessManager - for unbuffered streams this parameter is ignored.
+     * @param bufferSize
+     * @return UIOStreamBuilder
+     */
+    public UIOStreamBuilder setBufferSize(int bufferSize) {
+        this.bufferSize = new Integer(bufferSize);
+        return this;
+    }
+
+    /**
+     * Set file which can be used to cache data (only for Streams)
+     * @param f File
+     * @return UIOStreamBuilder
+     */
+    public UIOStreamBuilder setCacheFile(File f) {
+        this.cache = f;
+        return this;
+    }
+
+    /**
+     * finally create desired stream
+     * @return RandomAccessIinput
+     * @throws java.io.IOException
+     */
+    public RandomAccessInput create() throws IOException {
+        if (length != null && length.longValue() == 0) {
+            Sys.err.println("Warning: stream length is 0");
+        }
+        if (buffered) {
+            return creator.createBuffered();
+        } else {
+            return creator.create();
+        }
+    }
+
+    abstract class Creator {
+        abstract RandomAccessInput create() throws IOException;
+
+        abstract RandomAccessInput createBuffered() throws IOException;
+
+        protected int getByteOrder() {
+            return byteOrder != 0 ? byteOrder : RandomAccessInput.BIG_ENDIAN;
+        }
+
+        protected int getBufferCount() {
+            if (maxBufferCount != null) {
+                return maxBufferCount.intValue();
+            }
+            return DEFAULT_CHUNK_COUNT;
+        }
+
+        protected int getBufferSize() {
+            if (bufferSize != null) {
+                return bufferSize.intValue();
+            }
+            return DEFAULT_CHUNK_SIZE;
+        }
+    }
+
+    class FileCreator extends Creator {
+
+        File fileSource;
+
+        public FileCreator(File fileSource) {
+            this.fileSource = fileSource;
+        }
+
+        public RandomAccessInput create() throws IOException {
+            final RandomAccessFileX rafx = new RandomAccessFileX(fileSource, mode);
+            if (start == null && length == null) {
+                return new RandomAccessFileWrapper(rafx, getByteOrder());
+            } else if (length == null) {
+                return new RandomAccessFileWrapper(rafx, start.longValue(), getByteOrder());
+            } else {
+                return new RandomAccessFileWrapper(rafx, start.longValue(), length.longValue(), getByteOrder());
+            }
+        }
+
+        RandomAccessInput createBuffered() throws IOException {
+            Content bc = new RandomAccessFileContent(fileSource, mode);
+            IOController controller = new IOController(DEFAULT_CHUNK_SIZE, bc);
+            BufferedRandomAccessIO bio = new BufferedRandomAccessIO(controller);
+            bio.setByteOrder(byteOrder);
+            return bio;
+        }
+    }
+
+    class RAFCreator extends Creator {
+        RandomAccessFile rafSource;
+
+        public RAFCreator(RandomAccessFile rafSource) {
+            this.rafSource = rafSource;
+        }
+
+        public RandomAccessInput create() throws IOException {
+            if (start == null && length == null) {
+                return new RandomAccessFileWrapper(rafSource, getByteOrder());
+            } else if (length == null) {
+                return new RandomAccessFileWrapper(rafSource, start.longValue(), getByteOrder());
+            } else {
+                return new RandomAccessFileWrapper(rafSource, start.longValue(), length.longValue(), getByteOrder());
+            }
+        }
+
+        RandomAccessInput createBuffered() throws IOException {
+            Content bc = new RandomAccessFileContent(rafSource);
+            IOController controller = new IOController(DEFAULT_CHUNK_SIZE, bc);
+            return new BufferedRandomAccessIO(controller);
+        }
+    }
+
+    class RAICreator extends Creator {
+        RandomAccessInput roSource;
+
+        public RAICreator(RandomAccessInput roSource) {
+            this.roSource = roSource;
+        }
+
+        protected int getByteOrder() {
+            return byteOrder != 0 ? byteOrder : roSource.getByteOrder();
+        }
+
+        public RandomAccessInput create() throws IOException {
+            return roSource.createInputChild(start != null ? start.longValue() : 0L, 0, byteOrder, true);
+        }
+
+        RandomAccessInput createBuffered() throws IOException {
+            return create();
+        }
+    }
+
+    class RAIOCreator extends Creator {
+        RandomAccessIO raSource;
+
+        public RAIOCreator(RandomAccessIO raSource) {
+            this.raSource = raSource;
+        }
+
+        protected int getByteOrder() {
+            return byteOrder != 0 ? byteOrder : raSource.getByteOrder();
+        }
+
+        public RandomAccessInput create() throws IOException {
+            return raSource.createIOChild(start != null ? start.longValue() : 0L, 0, byteOrder, true);
+        }
+
+        RandomAccessInput createBuffered() throws IOException {
+            return create();
+        }
+    }
+
+    class Byte2DCreator extends Creator {
+
+        byte[][] byteSource;
+
+        public Byte2DCreator(byte[][] byteSource) {
+            this.byteSource = byteSource;
+        }
+
+        RandomAccessInput create() throws IOException {
+            return createBuffered();
+        }
+
+        RandomAccessInput createBuffered() throws IOException {
+            Content content = new ByteArrayContent(byteSource);
+            IOController controller = new IOController(DEFAULT_CHUNK_SIZE, content);
+            return new BufferedRandomAccessIO(controller, start != null ? start.longValue() : 0L);
+        }
+    }
+
+    class ByteCreator extends Creator {
+
+        byte[] byteSource;
+
+        public ByteCreator(byte[] byteSource) {
+            this.byteSource = byteSource;
+        }
+
+        RandomAccessInput create() throws IOException {
+            if (start != null) {
+                VariableSizeByteBuffer vsb;
+                vsb = new VariableSizeByteBuffer(byteSource);
+                return new ByteArrayRandomAccessIO(start.intValue(), length != null? length.intValue(): 0, vsb);
+            }
+            return new ByteArrayRandomAccessIO(byteSource);
+        }
+
+        RandomAccessInput createBuffered() throws IOException {
+            return create();
+        }
+    }
+
+    class ShortCreator extends Creator {
+        short[][] shortSource;
+
+        public ShortCreator(short[] shortSource) {
+            this(new short[][]{shortSource});
+        }
+
+        public ShortCreator(short[][] shortSource) {
+            this.shortSource = shortSource;
+        }
+
+        RandomAccessInput create() throws IOException {
+            Content content = new ShortArrayContent(shortSource);
+            IOController controller = new IOController(DEFAULT_CHUNK_SIZE, content);
+            return new BufferedRandomAccessIO(controller, start != null ? start.longValue() : 0L);
+        }
+
+        RandomAccessInput createBuffered() throws IOException {
+            return create();
+        }
+    }
+
+    class CharCreator extends Creator {
+        char[][] charSource;
+
+        public CharCreator(char[] charSource) {
+            this(new char[][]{charSource});
+        }
+
+        public CharCreator(char[][] charSource) {
+            this.charSource = charSource;
+        }
+
+        RandomAccessInput create() throws IOException {
+            Content content = new CharArrayContent(charSource);
+            IOController controller = new IOController(DEFAULT_CHUNK_SIZE, content);
+            return new BufferedRandomAccessIO(controller, start != null ? start.longValue() : 0L);
+        }
+
+        RandomAccessInput createBuffered() throws IOException {
+            return create();
+        }
+    }
+
+    class IntCreator extends Creator {
+        int[][] intSource;
+
+        public IntCreator(int[] intSource) {
+            this(new int[][]{intSource});
+        }
+
+        public IntCreator(int[][] intSource) {
+            this.intSource = intSource;
+        }
+
+        RandomAccessInput create() throws IOException {
+            Content content = new IntArrayContent(intSource);
+            IOController controller = new IOController(DEFAULT_CHUNK_SIZE, content);
+            return new BufferedRandomAccessIO(controller, start != null ? start.longValue() : 0L);
+        }
+
+        RandomAccessInput createBuffered() throws IOException {
+            return create();
+        }
+    }
+
+    class SpannedCreator extends Creator {
+        Span [] spans;
+        RandomAccessInput raiSource;
+
+        public SpannedCreator(RandomAccessInput raiSource, Span[] spans) {
+            this.raiSource = raiSource;
+            this.spans = spans;
+        }
+
+        RandomAccessInput create() throws IOException {
+            Content content;
+            if(mode == READ_ONLY) {
+                RandomAccessInput inputChild = raiSource.createInputChild(0, 0, getByteOrder(), false);
+                content = new SpannedRandomAccessInputContent(inputChild, spans);
+            }
+            else {
+                if(raiSource instanceof RandomAccessIO) {
+                    RandomAccessIO inputChild = ((RandomAccessIO)raiSource).createIOChild(0, 0, getByteOrder(), false);
+                    content = new SpannedRandomAccessIOContent(inputChild, spans);
+                }
+                else {
+                    RandomAccessInput inputChild = raiSource.createInputChild(0, 0, getByteOrder(), false);
+                    content = new SpannedRandomAccessInputContent(inputChild, spans);
+                }
+            }
+            IOController controller = new IOController(DEFAULT_CHUNK_SIZE, content);
+            return new BufferedRandomAccessIO(controller, 0L);
+        }
+
+        protected int getByteOrder() {
+            return byteOrder != 0 ? byteOrder : raiSource.getByteOrder();
+        }
+
+
+        RandomAccessInput createBuffered() throws IOException {
+            return create();
+        }
+    }
+
+    class LongCreator extends Creator {
+        long[][] longSource;
+
+        public LongCreator(long[] longSource) {
+            this(new long[][]{longSource});
+        }
+
+        public LongCreator(long[][] longSource) {
+            this.longSource = longSource;
+        }
+
+        RandomAccessInput create() throws IOException {
+            Content content = new LongArrayContent(longSource);
+            IOController controller = new IOController(DEFAULT_CHUNK_SIZE, content);
+            return new BufferedRandomAccessIO(controller, start != null ? start.longValue() : 0L);
+        }
+
+        RandomAccessInput createBuffered() throws IOException {
+            return create();
+        }
+    }
+
+    class FloatCreator extends Creator {
+        float[][] floatSource;
+
+        public FloatCreator(float[] floatSource) {
+            this(new float[][]{floatSource});
+        }
+
+        public FloatCreator(float[][] floatSource) {
+            this.floatSource = floatSource;
+        }
+
+        RandomAccessInput create() throws IOException {
+            Content content = new FloatArrayContent(floatSource);
+            IOController controller = new IOController(DEFAULT_CHUNK_SIZE, content);
+            return new BufferedRandomAccessIO(controller, start != null ? start.longValue() : 0L);
+        }
+
+        RandomAccessInput createBuffered() throws IOException {
+            return create();
+        }
+    }
+
+    class DoubleCreator extends Creator {
+        double[][] doubleSource;
+
+        public DoubleCreator(double[] doubleSource) {
+            this(new double[][]{doubleSource});
+        }
+
+        public DoubleCreator(double[][] doubleSource) {
+            this.doubleSource = doubleSource;
+        }
+
+        RandomAccessInput create() throws IOException {
+            Content content = new DoubleArrayContent(doubleSource);
+            IOController controller = new IOController(DEFAULT_CHUNK_SIZE, content);
+            return new BufferedRandomAccessIO(controller, start != null ? start.longValue() : 0L);
+        }
+
+        RandomAccessInput createBuffered() throws IOException {
+            return create();
+        }
+    }
+
+    class URLCreator extends Creator {
+        URL url;
+        FileCreator fileCreator;
+
+        public URLCreator(URL url) {
+            this.url = url;
+            final String protocol = url.getProtocol();
+            if ("file".equalsIgnoreCase(protocol)) {
+                File f = new File(url.getFile());
+                fileCreator = new FileCreator(f);
+            }
+        }
+
+        RandomAccessInput create() throws IOException {
+            if (fileCreator == null) {
+                return create0();
+            } else {
+                return fileCreator.create();
+            }
+        }
+
+        RandomAccessInput createBuffered() throws IOException {
+            if (fileCreator == null) {
+                return create0();
+            } else {
+                return fileCreator.createBuffered();
+            }
+        }
+
+        private RandomAccessInput create0() {
+            if (cache == null) {
+                cache = createTempFile("urc");
+            }
+            IOController controller = BIOFactory.createIOController(url, cache, getBufferSize());
+            BufferedRandomAccessIO rio = new BufferedRandomAccessIO(controller);
+            rio.setByteOrder(byteOrder);
+            return rio;
+        }
+    }
+
+    class ISCreator extends Creator {
+        InputStream inputStreamSource;
+
+        public ISCreator(InputStream inputStreamSource) {
+            this.inputStreamSource = inputStreamSource;
+        }
+
+        RandomAccessInput create() throws IOException {
+            if (inputStreamSource instanceof ByteArrayInputStream) {
+                return new BaisWrapper((ByteArrayInputStream) inputStreamSource);
+            } else {
+                if (cache == null) {
+                    cache = createTempFile("isc");
+                }
+                IOController controller = BIOFactory.createIOController(inputStreamSource, cache, getBufferSize());
+                BufferedRandomAccessIO bio = new BufferedRandomAccessIO(controller);
+                bio.setByteOrder(byteOrder);
+                return bio;
+            }
+        }
+
+        RandomAccessInput createBuffered() throws IOException {
+            return create();
+        }
+    }
+
+    class OSCreator extends Creator {
+        OutputStream outputStreamSource;
+
+        public OSCreator(OutputStream outputStreamSource) {
+            this.outputStreamSource = outputStreamSource;
+            setMode(READ_WRITE);
+        }
+
+        RandomAccessInput create() throws IOException {
+            final BufferedRandomAccessIO bio = BIOFactory.create(outputStreamSource);
+            bio.setByteOrder(byteOrder);
+            return bio;
+        }
+
+        RandomAccessInput createBuffered() throws IOException {
+            return create();
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/WriteUtil.java
===================================================================
--- ocean/src/com/imagero/uio/WriteUtil.java	(revision 0)
+++ ocean/src/com/imagero/uio/WriteUtil.java	(revision 0)
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+ package com.imagero.uio;
+
+import com.imagero.uio.xform.XtoByteBE;
+import com.imagero.uio.xform.XtoByteLE;
+
+import java.io.IOException;
+
+/**
+ * Methods to write data from primitive arrays.
+ *
+ * Date: 01.03.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class WriteUtil {
+    public static void write(RandomAccessOutput io, short[] data) throws IOException {
+        write(io, data, 0, data.length, io.getByteOrder());
+    }
+
+    public static void write(RandomAccessOutput io, short[] data, int byteOrder) throws IOException {
+        write(io, data, 0, data.length, byteOrder);
+    }
+
+    public static void write(RandomAccessOutput io, short[] data, int offset, int length) throws IOException {
+        write(io, data, offset, length, io.getByteOrder());
+    }
+
+    public static void write(RandomAccessOutput io, char[] data) throws IOException {
+        write(io, data, 0, data.length, io.getByteOrder());
+    }
+
+    public static void write(RandomAccessOutput io, char[] data, int byteOrder) throws IOException {
+        write(io, data, 0, data.length, byteOrder);
+    }
+
+    public static void write(RandomAccessOutput io, char[] data, int offset, int length) throws IOException {
+        write(io, data, offset, length, io.getByteOrder());
+    }
+
+    public static void write(RandomAccessOutput io, int[] data) throws IOException {
+        write(io, data, 0, data.length, io.getByteOrder());
+    }
+
+    public static void write(RandomAccessOutput io, int[] data, int byteOrder) throws IOException {
+        write(io, data, 0, data.length, byteOrder);
+    }
+
+    public static void write(RandomAccessOutput io, int[] data, int offset, int length) throws IOException {
+        write(io, data, offset, length, io.getByteOrder());
+    }
+
+    public static void write(RandomAccessOutput io, float[] data) throws IOException {
+        write(io, data, 0, data.length, io.getByteOrder());
+    }
+
+    public static void write(RandomAccessOutput io, float[] data, int byteOrder) throws IOException {
+        write(io, data, 0, data.length, byteOrder);
+    }
+
+    public static void write(RandomAccessOutput io, float[] data, int offset, int length) throws IOException {
+        write(io, data, offset, length, io.getByteOrder());
+    }
+
+    public static void write(RandomAccessOutput io, long[] data) throws IOException {
+        write(io, data, 0, data.length, io.getByteOrder());
+    }
+
+    public static void write(RandomAccessOutput io, long[] data, int byteOrder) throws IOException {
+        write(io, data, 0, data.length, byteOrder);
+    }
+
+    public static void write(RandomAccessOutput io, long[] data, int offset, int length) throws IOException {
+        write(io, data, offset, length, io.getByteOrder());
+    }
+
+    public static void write(RandomAccessOutput io, double[] data) throws IOException {
+        write(io, data, 0, data.length, io.getByteOrder());
+    }
+
+    public static void write(RandomAccessOutput io, double[] data, int byteOrder) throws IOException {
+        write(io, data, 0, data.length, byteOrder);
+    }
+
+    public static void write(RandomAccessOutput io, double[] data, int offset, int length) throws IOException {
+        write(io, data, offset, length, io.getByteOrder());
+    }
+
+    public static void write(RandomAccessOutput io, short[] sh, int offset, int length, int byteOrder) throws IOException {
+        byte[] b = new byte[length << 1];
+        if(byteOrder == RandomAccessInput.BIG_ENDIAN) {
+            XtoByteBE.shortToByteBE(sh, offset, length, b, 0);
+        }
+        else {
+            XtoByteLE.shortToByteLE(sh, offset, length, b, 0);
+        }
+        io.write(b);
+    }
+
+    public static void write(RandomAccessOutput io, char[] sh, int offset, int length, int byteOrder) throws IOException {
+        byte[] b = transform(sh, offset, length, byteOrder);
+        io.write(b);
+    }
+
+    protected static byte[] transform(char[] sh, int offset, int length, int byteOrder) {
+        byte[] b = new byte[length << 1];
+        if(byteOrder == RandomAccessInput.BIG_ENDIAN) {
+            XtoByteBE.charToByte(sh, offset, length, b, 0);
+        }
+        else {
+            XtoByteLE.charToByte(sh, offset, length, b, 0);
+        }
+        return b;
+    }
+
+    public static void write(RandomAccessOutput io, int[] source, int offset, int length, int byteOrder) throws IOException {
+        byte[] b = transform(source, offset, length, byteOrder);
+        io.write(b);
+    }
+
+    protected static byte[] transform(int[] source, int offset, int length, int byteOrder) {
+        byte[] b = new byte[length << 2];
+        if(byteOrder == RandomAccessInput.BIG_ENDIAN) {
+            XtoByteBE.intToByte(source, offset, length, b, 0);
+        }
+        else {
+            XtoByteLE.intToByte(source, offset, length, b, 0);
+        }
+        return b;
+    }
+
+    public static void write(RandomAccessOutput io, float[] source, int offset, int length, int byteOrder) throws IOException {
+        byte[] b = transform(source, offset, length, byteOrder);
+        io.write(b);
+    }
+
+    protected static byte[] transform(float[] source, int offset, int length, int byteOrder) {
+        byte[] b = new byte[length << 2];
+        if(byteOrder == RandomAccessInput.BIG_ENDIAN) {
+            XtoByteBE.floatToByteBE(source, offset, length, b, 0);
+        }
+        else {
+            XtoByteLE.floatToByteLE(source, offset, length, b, 0);
+        }
+        return b;
+    }
+
+    public static void write(RandomAccessOutput io, long[] source, int offset, int length, int byteOrder) throws IOException {
+        byte[] b = transform(source, offset, length, byteOrder);
+        io.write(b);
+    }
+
+    protected static byte[] transform(long[] source, int offset, int length, int byteOrder) {
+        byte[] b = new byte[length << 3];
+        if(byteOrder == RandomAccessInput.BIG_ENDIAN) {
+            XtoByteBE.longToByteBE(source, offset, length, b, 0);
+        }
+        else {
+            XtoByteLE.longToByteLE(source, offset, length, b, 0);
+        }
+        return b;
+    }
+
+    public static void write(RandomAccessOutput io, double[] source, int offset, int length, int byteOrder) throws IOException {
+        byte[] b = transform(source, offset, length, byteOrder);
+        io.write(b);
+    }
+
+    protected static byte[] transform(double[] source, int offset, int length, int byteOrder) {
+        byte[] b = new byte[length << 3];
+        if(byteOrder == RandomAccessInput.BIG_ENDIAN) {
+            XtoByteBE.doubleToByteBE(source, offset, length, b, 0);
+        }
+        else {
+            XtoByteLE.doubleToByteLE(source, offset, length, b, 0);
+        }
+        return b;
+    }
+}
Index: ocean/src/com/imagero/uio/xform/ByteToXLE.java
===================================================================
--- ocean/src/com/imagero/uio/xform/ByteToXLE.java	(revision 0)
+++ ocean/src/com/imagero/uio/xform/ByteToXLE.java	(revision 0)
@@ -0,0 +1,249 @@
+package com.imagero.uio.xform;
+
+/**
+ * Date: 10.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class ByteToXLE {
+    /**
+     * write int in LITTLE_ENDIAN order
+     *
+     * @param source       source byte array
+     * @param sourceOffset offset in source array
+     * @param destOffset   offset in destination array
+     *
+     * @return new offset in source array (for next writeUnitXX)
+     */
+    public static final int byteToInt(byte[] source, int sourceOffset, int[] dest, int destOffset) {
+        int v = ((source[sourceOffset++] & 0xFF))
+                | (((source[sourceOffset++] & 0xFF)) << 8)
+                | (((source[sourceOffset++] & 0xFF)) << 16)
+                | (((source[sourceOffset++] & 0xFF)) << 24);
+        dest[destOffset] = v;
+        return sourceOffset;
+    }
+
+    public static final int byteToInt(byte[] source, int sourceOffset) {
+        return ((source[sourceOffset++] & 0xFF))
+                | (((source[sourceOffset++] & 0xFF)) << 8)
+                | (((source[sourceOffset++] & 0xFF)) << 16)
+                | (((source[sourceOffset++] & 0xFF)) << 24);
+    }
+
+    public static final void byteToInt(byte[] source, int sourceOffset, int count, int[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = ((source[sourceOffset++] & 0xFF))
+                    | (((source[sourceOffset++] & 0xFF)) << 8)
+                    | (((source[sourceOffset++] & 0xFF)) << 16)
+                    | (((source[sourceOffset++] & 0xFF)) << 24);
+            dest[destOffset + i] = v;
+        }
+    }
+
+    public static int byteToChar(byte[] source, int sourceOffset, char[] dest, int destOffset) {
+        int v = ((source[sourceOffset++] & 0xFF)) | (((source[sourceOffset++] & 0xFF)) << 8);
+        dest[destOffset] = (char) v;
+        return sourceOffset;
+    }
+
+    public static int byteToChar(byte[] source, int sourceOffset) {
+        return ((source[sourceOffset++] & 0xFF)) | (((source[sourceOffset++] & 0xFF)) << 8);
+    }
+
+    public static void byteToChar(byte[] source, int sourceOffset, int count, char[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = ((source[sourceOffset++] & 0xFF)) | (((source[sourceOffset++] & 0xFF)) << 8);
+            dest[destOffset + i] = (char) v;
+        }
+    }
+
+    public static int byteToDoubleLE(byte[] source, int sourceOffset, double[] dest, int destOffset) {
+        long v = (source[sourceOffset++] & 0xFF)
+                | ((long) (source[sourceOffset++] & 0xFF) << 8)
+                | ((long) (source[sourceOffset++] & 0xFF) << 16)
+                | ((long) (source[sourceOffset++] & 0xFF) << 24)
+                | ((long) (source[sourceOffset++] & 0xFF) << 32)
+                | ((long) (source[sourceOffset++] & 0xFF) << 40)
+                | ((long) (source[sourceOffset++] & 0xFF) << 48)
+                | ((long) (source[sourceOffset++] & 0xFF) << 56);
+        dest[destOffset] = Double.longBitsToDouble(v);
+        return sourceOffset;
+    }
+
+    public static double byteToDoubleLE(byte[] source, int sourceOffset) {
+        long v = (source[sourceOffset++] & 0xFF)
+                | ((long) (source[sourceOffset++] & 0xFF) << 8)
+                | ((long) (source[sourceOffset++] & 0xFF) << 16)
+                | ((long) (source[sourceOffset++] & 0xFF) << 24)
+                | ((long) (source[sourceOffset++] & 0xFF) << 32)
+                | ((long) (source[sourceOffset++] & 0xFF) << 40)
+                | ((long) (source[sourceOffset++] & 0xFF) << 48)
+                | ((long) (source[sourceOffset++] & 0xFF) << 56);
+        return Double.longBitsToDouble(v);
+    }
+
+    public static void byteToDoubleLE(byte[] source, int sourceOffset, int count, double[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            long v = (source[sourceOffset++] & 0xFF)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 8)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 16)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 24)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 32)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 40)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 48)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 56);
+            dest[destOffset + i] = Double.longBitsToDouble(v);
+        }
+    }
+
+    public static int byteToFloatLE(byte[] source, int sourceOffset, float[] dest, int destOffset) {
+        int v = ((source[sourceOffset++] & 0xFF))
+                | (((source[sourceOffset++] & 0xFF)) << 8)
+                | (((source[sourceOffset++] & 0xFF)) << 16)
+                | (((source[sourceOffset++] & 0xFF)) << 24);
+        dest[destOffset] = Float.intBitsToFloat(v);
+        return sourceOffset;
+    }
+
+    public static float byteToFloatLE(byte[] source, int sourceOffset) {
+        int v = ((source[sourceOffset++] & 0xFF))
+                | (((source[sourceOffset++] & 0xFF)) << 8)
+                | (((source[sourceOffset++] & 0xFF)) << 16)
+                | (((source[sourceOffset++] & 0xFF)) << 24);
+        return Float.intBitsToFloat(v);
+    }
+
+    public static void byteToFloatLE(byte[] source, int sourceOffset, int count, float[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = ((source[sourceOffset++] & 0xFF))
+                    | (((source[sourceOffset++] & 0xFF)) << 8)
+                    | (((source[sourceOffset++] & 0xFF)) << 16)
+                    | (((source[sourceOffset++] & 0xFF)) << 24);
+            dest[destOffset + i] = Float.intBitsToFloat(v);
+        }
+    }
+
+    public static int byteToLongLE(byte[] source, int sourceOffset, long[] dest, int destOffset) {
+        long v = (source[sourceOffset++] & 0xFF)
+                | ((long) (source[sourceOffset++] & 0xFF) << 8)
+                | ((long) (source[sourceOffset++] & 0xFF) << 16)
+                | ((long) (source[sourceOffset++] & 0xFF) << 24)
+                | ((long) (source[sourceOffset++] & 0xFF) << 32)
+                | ((long) (source[sourceOffset++] & 0xFF) << 40)
+                | ((long) (source[sourceOffset++] & 0xFF) << 48)
+                | ((long) (source[sourceOffset++] & 0xFF) << 56);
+        dest[destOffset] = v;
+        return sourceOffset;
+    }
+
+    public static long byteToLongLE(byte[] source, int sourceOffset) {
+        return (source[sourceOffset++] & 0xFF)
+                | ((long) (source[sourceOffset++] & 0xFF) << 8)
+                | ((long) (source[sourceOffset++] & 0xFF) << 16)
+                | ((long) (source[sourceOffset++] & 0xFF) << 24)
+                | ((long) (source[sourceOffset++] & 0xFF) << 32)
+                | ((long) (source[sourceOffset++] & 0xFF) << 40)
+                | ((long) (source[sourceOffset++] & 0xFF) << 48)
+                | ((long) (source[sourceOffset++] & 0xFF) << 56);
+    }
+
+    public static void byteToLongLE(byte[] source, int sourceOffset, int count, long[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            long v = (source[sourceOffset++] & 0xFF)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 8)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 16)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 24)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 32)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 40)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 48)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 56);
+            dest[destOffset + i] = v;
+        }
+    }
+
+    public static int byteToShortLE(byte[] source, int sourceOffset, short[] dest, int destOffset) {
+        int v = ((source[sourceOffset++] & 0xFF))
+                | (((source[sourceOffset++] & 0xFF)) << 8);
+        dest[destOffset] = (short) v;
+        return sourceOffset;
+    }
+
+    public static int byteToShortLE(byte[] source, int sourceOffset) {
+        return ((source[sourceOffset++] & 0xFF)) | (((source[sourceOffset++] & 0xFF)) << 8);
+    }
+
+    public static void byteToShortLE(byte[] source, int sourceOffset, int count, short[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = ((source[sourceOffset++] & 0xFF))
+                    | (((source[sourceOffset++] & 0xFF)) << 8);
+            dest[destOffset + i] = (short) v;
+        }
+    }
+
+    public static final void byteToIntLE(final int bytesInInt, byte[] source, int sourceOffset, final int count, int[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = 0;
+            for (int j = 0, shift = 0; j < bytesInInt; j++) {
+                v = v | ((source[sourceOffset + j] & 0xFF) << shift);
+                shift += 8;
+            }
+            dest[destOffset++] = v & 0xFFFFFFFF;
+            sourceOffset += bytesInInt;
+        }
+    }
+
+    public static final void byteToShortLE(final int bytesInShort, byte[] source, int sourceOffset, final int count, short[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = 0;
+            for (int j = 0, shift = 0; j < bytesInShort; j++) {
+                v = v | ((source[sourceOffset + j] & 0xFF) << shift);
+                shift += 8;
+            }
+            dest[destOffset++] = (short) v;
+            sourceOffset += bytesInShort;
+        }
+    }
+
+    public static final void byteToLongLE(final int bytesInLong, byte[] source, int sourceOffset, final int count, long[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            long v = 0;
+            for (int j = 0, shift = 0; j < bytesInLong; j++) {
+                v = v | ((source[sourceOffset + j] & 0xFF) << shift);
+                shift += 8;
+            }
+            dest[destOffset++] = v;
+            sourceOffset += bytesInLong;
+        }
+    }
+
+    public static final int byteToIntLE(int bytesInInt, byte[] source, int sourceOffset, int[] dest, int destOffset) {
+        int v = 0;
+        for (int i = 0, shift = 0; i < bytesInInt; i++) {
+            v = v | ((source[sourceOffset++] & 0xFF) << shift);
+            shift += 8;
+        }
+        dest[destOffset] = v & 0xFFFFFFFF;
+        return sourceOffset;
+    }
+
+    public static final int byteToShortLE(int bytesInShort, byte[] source, int sourceOffset, short[] dest, int destOffset) {
+        int v = 0;
+        for (int i = 0, shift = 0; i < bytesInShort; i++) {
+            v = v | ((source[sourceOffset++] & 0xFF) << shift);
+            shift += 8;
+        }
+        dest[destOffset] = (short) v;
+        return sourceOffset;
+    }
+
+    public static final int byteToLongLE(int bytesInLong, byte[] source, int sourceOffset, long[] dest, int destOffset) {
+        long v = 0;
+        for (int i = 0, shift = 0; i < bytesInLong; i++) {
+            v = v | ((source[sourceOffset++] & 0xFF) << shift);
+            shift += 8;
+        }
+        dest[destOffset] = v;
+        return sourceOffset;
+    }
+}
Index: ocean/src/com/imagero/uio/xform/XtoByteLE.java
===================================================================
--- ocean/src/com/imagero/uio/xform/XtoByteLE.java	(revision 0)
+++ ocean/src/com/imagero/uio/xform/XtoByteLE.java	(revision 0)
@@ -0,0 +1,299 @@
+package com.imagero.uio.xform;
+
+import com.imagero.uio.Transformer;
+
+/**
+ * Date: 10.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class XtoByteLE {
+    static final int[] SHIFTS_INT_LE = new int[]{0, 8, 16, 24};
+
+    /**
+     * read int in LITTLE_ENDIAN order
+     *
+     * @param sourceOffset     offset in source array
+     * @param dest       byte array (destination)
+     * @param destOffset offset in destination array
+     *
+     * @return offset in destination array (updated)
+     */
+    public static final int intToByte(int[] source, int sourceOffset, byte[] dest, int destOffset) {
+        int v = source[sourceOffset];
+        dest[destOffset++] = (byte) (v & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+        return destOffset;
+    }
+
+    public static final int intToByte(int v, byte[] dest, int destOffset) {
+        dest[destOffset++] = (byte) (v & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+        return destOffset;
+    }
+
+    /**
+     * convert <code>count</code> ints to bytes (Little Endian)
+     * @param source int array
+     * @param srcOffset start offset in <code>source</code> array
+     * @param count how much ints to process
+     * @param dest destination byte array
+     * @param destOffset start offset in <code>dest</code> array
+     */
+    public static final void intToByte(int[] source, int srcOffset, int count, byte[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = source[srcOffset + i];
+            dest[destOffset++] = (byte) (v & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+        }
+    }
+
+    /**
+     * convert <code>count</code> ints to bytes (Little Endian)
+     * @param source int array
+     * @param srcOffset start offset in <code>source</code> array
+     * @param count how much ints to process
+     * @param dest destination byte array
+     * @param destOffset start offset in <code>dest</code> array
+     * @param skip how much bytes should be thrown away before start writing to destination (1 to 3)
+     */
+    public static final void intToByteLE(int[] source, int srcOffset, int count, byte[] dest, int destOffset, int skip) {
+        if (skip > 0) {
+            int v = source[srcOffset++];
+            for (int i = skip; i < 4; i++) {
+                dest[destOffset++] = (byte) ((v >>> SHIFTS_INT_LE[i]) & 0xFF);
+            }
+        }
+        intToByte(source, srcOffset, count, dest, destOffset);
+    }
+
+    public static final int charToByte(char[] source, int srcOffset, byte[] dest, int destOffset) {
+        int v = source[srcOffset];
+        dest[destOffset++] = (byte) (v & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        return destOffset;
+    }
+
+    public static final int charToByte(char v, byte[] dest, int destOffset) {
+        dest[destOffset++] = (byte) (v & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        return destOffset;
+    }
+
+    public static final void charToByte(char[] source, int srcOffset, int count, byte[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = source[srcOffset + i];
+            dest[destOffset++] = (byte) (v & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        }
+    }
+
+    public static int doubleToByteLE(double[] source, int srcOffset, byte[] dest, int destOffset) {
+        double d = source[srcOffset];
+        long v = Double.doubleToLongBits(d);
+        dest[destOffset++] = (byte) (v & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 32) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 40) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 48) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 56) & 0xFF);
+        return destOffset;
+    }
+
+    public static int doubleToByteLE(double d, byte[] dest, int destOffset) {
+        long v = Double.doubleToLongBits(d);
+        dest[destOffset++] = (byte) (v & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 32) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 40) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 48) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 56) & 0xFF);
+        return destOffset;
+    }
+
+    public static void doubleToByteLE(double[] source, int srcOffset, int count, byte[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            double d = source[srcOffset + i];
+            long v = Double.doubleToLongBits(d);
+            dest[destOffset++] = (byte) (v & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 32) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 40) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 48) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 56) & 0xFF);
+        }
+    }
+
+    public static int floatToByteLE(float[] source, int offset, byte[] dest, int destOffset) {
+        float f = source[offset];
+        int v = Float.floatToIntBits(f);
+        dest[destOffset++] = (byte) (v & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+        return destOffset;
+    }
+
+    public static int floatToByteLE(float f, byte[] dest, int destOffset) {
+        int v = Float.floatToIntBits(f);
+        dest[destOffset++] = (byte) (v & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+        return destOffset;
+    }
+
+    public static void floatToByteLE(float[] source, int offset, int count, byte[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            float f = source[offset + i];
+            int v = Float.floatToIntBits(f);
+            dest[destOffset++] = (byte) (v & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+        }
+    }
+
+    public static int longToByteLE(long[] source, int offset, byte[] dest, int destOffset) {
+        long v = source[offset];
+        dest[destOffset++] = (byte) (v & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 32) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 40) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 48) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 56) & 0xFF);
+
+        return destOffset;
+    }
+
+    public static int longToByteLE(long v, byte[] dest, int destOffset) {
+        dest[destOffset++] = (byte) (v & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 32) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 40) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 48) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 56) & 0xFF);
+
+        return destOffset;
+    }
+
+    public static void longToByteLE(long[] source, int offset, int count, byte[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            long v = source[offset + i];
+            dest[destOffset++] = (byte) (v & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 32) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 40) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 48) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 56) & 0xFF);
+        }
+    }
+
+    public static int shortToByteLE(short[] source, int offset, byte[] dest, int destOffset) {
+        int v = source[offset];
+        dest[destOffset++] = (byte) (v & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        return destOffset;
+    }
+
+    public static int shortToByteLE(short v, byte[] dest, int destOffset) {
+        dest[destOffset++] = (byte) (v & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        return destOffset;
+    }
+
+    public static void shortToByteLE(short[] source, int offset, int count, byte[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = source[offset + i];
+            dest[destOffset++] = (byte) (v & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        }
+    }
+
+    public static int shortToByteLE(final byte mask, short[] source, int offset, byte[] dest, int destOffset) {
+        int v = source[offset];
+        final int bytesInShort = 2;
+        for (int j = 0; j < bytesInShort; j++) {
+            if ((mask & XtoByteBE.byte_mask[j]) != 0) {
+                dest[destOffset++] = (byte) ((v >>> XtoByteBE.shift_mask[j]) & 0xFF);
+            }
+        }
+        return destOffset;
+    }
+
+    public static void shortToByteLE(final byte mask, short[] source, int offset, int count, byte[] dest, int destOffset) {
+        final int bytesInShort = 2;
+        for (int i = 0; i < count; i++) {
+            int v = source[offset + i];
+            for (int j = 0; j < bytesInShort; j++) {
+                if ((mask & XtoByteBE.byte_mask[j]) != 0) {
+                    dest[destOffset++] = (byte) ((v >>> XtoByteBE.shift_mask[j]) & 0xFF);
+                }
+            }
+        }
+    }
+
+    public static int intToByteLE(final byte mask, int[] source, int offset, byte[] dest, int destOffset) {
+        int v = source[offset];
+        final int bytesInInt = 4;
+        for (int j = 0; j < bytesInInt; j++) {
+            if ((mask & XtoByteBE.byte_mask[j]) != 0) {
+                dest[destOffset++] = (byte) ((v >>> XtoByteBE.shift_mask[j]) & 0xFF);
+            }
+        }
+        return destOffset;
+    }
+
+    public static void intToByteLE(final byte mask, int[] source, int offset, int count, byte[] dest, int destOffset) {
+        final int bytesInInt = 4;
+        for (int i = 0; i < count; i++) {
+            int v = source[offset + i];
+            for (int j = 0; j < bytesInInt; j++) {
+                if ((mask & XtoByteBE.byte_mask[j]) != 0) {
+                    dest[destOffset++] = (byte) ((v >>> XtoByteBE.shift_mask[j]) & 0xFF);
+                }
+            }
+        }
+    }
+
+    public static int longToByteLE(final byte mask, long[] source, int offset, byte[] dest, int destOffset) {
+        long v = source[offset];
+        final int bytesInLong = 8;
+        for (int j = 0; j < bytesInLong; j++) {
+            if ((mask & XtoByteBE.byte_mask[j]) != 0) {
+                dest[destOffset++] = (byte) ((v >>> XtoByteBE.shift_mask[j]) & 0xFF);
+            }
+        }
+        return destOffset;
+    }
+
+    public static void longToByteLE(final byte mask, long[] source, int offset, int count, byte[] dest, int destOffset) {
+        final int bytesInLong = 8;
+        for (int i = 0; i < count; i++) {
+            long v = source[offset + i];
+            for (int j = 0; j < bytesInLong; j++) {
+                if ((mask & XtoByteBE.byte_mask[j]) != 0) {
+                    dest[destOffset++] = (byte) ((v >>> XtoByteBE.shift_mask[j]) & 0xFF);
+                }
+            }
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/xform/ByteToXBE.java
===================================================================
--- ocean/src/com/imagero/uio/xform/ByteToXBE.java	(revision 0)
+++ ocean/src/com/imagero/uio/xform/ByteToXBE.java	(revision 0)
@@ -0,0 +1,248 @@
+package com.imagero.uio.xform;
+
+/**
+ * Big endian transform from bytes to other primitives.
+ * Date: 10.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class ByteToXBE {
+    /**
+     * write int in BIG_ENDIAN order
+     *
+     * @param source       source byte array
+     * @param sourceOffset offset in source array
+     * @param destOffset   offset in destination array
+     *
+     * @return new offset in source array (for next writeUnitXX)
+     */
+    public static final int byteToInt(byte[] source, int sourceOffset, int[] dest, int destOffset) {
+        int v = ((source[sourceOffset++] & 0xFF) << 24)
+                | ((source[sourceOffset++] & 0xFF) << 16)
+                | ((source[sourceOffset++] & 0xFF) << 8)
+                | (source[sourceOffset++] & 0xFF);
+        dest[destOffset] = v;
+        return sourceOffset;
+    }
+
+    public static final int byteToInt(byte[] source, int sourceOffset) {
+        return ((source[sourceOffset++] & 0xFF) << 24)
+                | ((source[sourceOffset++] & 0xFF) << 16)
+                | ((source[sourceOffset++] & 0xFF) << 8)
+                | (source[sourceOffset++] & 0xFF);
+    }
+
+    public static final void byteToInt(byte[] source, int sourceOffset, int count, int[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = ((source[sourceOffset++] & 0xFF) << 24)
+                    | ((source[sourceOffset++] & 0xFF) << 16)
+                    | ((source[sourceOffset++] & 0xFF) << 8)
+                    | (source[sourceOffset++] & 0xFF);
+            dest[destOffset + i] = v;
+        }
+    }
+
+    public static int byteToChar(byte[] source, int sourceOffset, char[] dest, int destOffset) {
+        int v = ((source[sourceOffset++] & 0xFF) << 8)
+                | (source[sourceOffset++] & 0xFF);
+        dest[destOffset] = (char) v;
+        return sourceOffset;
+    }
+
+    public static int byteToChar(byte[] source, int sourceOffset) {
+        return ((source[sourceOffset++] & 0xFF) << 8) | (source[sourceOffset++] & 0xFF);
+    }
+
+    public static void byteToChar(byte[] source, int sourceOffset, int count, char[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = ((source[sourceOffset++] & 0xFF) << 8)
+                    | (source[sourceOffset++] & 0xFF);
+            dest[destOffset + i] = (char) v;
+        }
+    }
+
+    public static int byteToDoubleBE(byte[] source, int sourceOffset, double[] dest, int destOffset) {
+        long v = ((long) (source[sourceOffset++] & 0xFF) << 56)
+                | ((long) (source[sourceOffset++] & 0xFF) << 48)
+                | ((long) (source[sourceOffset++] & 0xFF) << 40)
+                | ((long) (source[sourceOffset++] & 0xFF) << 32)
+                | ((long) (source[sourceOffset++] & 0xFF) << 24)
+                | ((long) (source[sourceOffset++] & 0xFF) << 16)
+                | ((long) (source[sourceOffset++] & 0xFF) << 8)
+                | (source[sourceOffset++] & 0xFF);
+        dest[destOffset] = Double.longBitsToDouble(v);
+        return sourceOffset;
+    }
+
+    public static double byteToDoubleBE(byte[] source, int sourceOffset) {
+        long v = ((long) (source[sourceOffset++] & 0xFF) << 56)
+                | ((long) (source[sourceOffset++] & 0xFF) << 48)
+                | ((long) (source[sourceOffset++] & 0xFF) << 40)
+                | ((long) (source[sourceOffset++] & 0xFF) << 32)
+                | ((long) (source[sourceOffset++] & 0xFF) << 24)
+                | ((long) (source[sourceOffset++] & 0xFF) << 16)
+                | ((long) (source[sourceOffset++] & 0xFF) << 8)
+                | (source[sourceOffset++] & 0xFF);
+        return Double.longBitsToDouble(v);
+    }
+
+    public static void byteToDoubleBE(byte[] source, int sourceOffset, int count, double[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            long v = ((long) (source[sourceOffset++] & 0xFF) << 56)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 48)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 40)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 32)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 24)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 16)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 8)
+                    | (source[sourceOffset++] & 0xFF);
+            dest[destOffset + i] = Double.longBitsToDouble(v);
+        }
+    }
+
+    public static int byteToFloatBE(byte[] source, int sourceOffset, float[] dest, int destOffset) {
+        int v = ((source[sourceOffset++] & 0xFF) << 24)
+                | ((source[sourceOffset++] & 0xFF) << 16)
+                | ((source[sourceOffset++] & 0xFF) << 8)
+                | (source[sourceOffset++] & 0xFF);
+        dest[destOffset] = Float.intBitsToFloat(v);
+        return sourceOffset;
+    }
+
+    public static float byteToFloatBE(byte[] source, int sourceOffset) {
+        int v = ((source[sourceOffset++] & 0xFF) << 24)
+                | ((source[sourceOffset++] & 0xFF) << 16)
+                | ((source[sourceOffset++] & 0xFF) << 8)
+                | (source[sourceOffset++] & 0xFF);
+        return Float.intBitsToFloat(v);
+    }
+
+    public static void byteToFloatBE(byte[] source, int sourceOffset, int count, float[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = ((source[sourceOffset++] & 0xFF) << 24)
+                    | ((source[sourceOffset++] & 0xFF) << 16)
+                    | ((source[sourceOffset++] & 0xFF) << 8)
+                    | (source[sourceOffset++] & 0xFF);
+            dest[destOffset + i] = Float.intBitsToFloat(v);
+        }
+    }
+
+    public static int byteToLongBE(byte[] source, int sourceOffset, long[] dest, int destOffset) {
+        long v = ((long) (source[sourceOffset++] & 0xFF) << 56)
+                | ((long) (source[sourceOffset++] & 0xFF) << 48)
+                | ((long) (source[sourceOffset++] & 0xFF) << 40)
+                | ((long) (source[sourceOffset++] & 0xFF) << 32)
+                | ((long) (source[sourceOffset++] & 0xFF) << 24)
+                | ((long) (source[sourceOffset++] & 0xFF) << 16)
+                | ((long) (source[sourceOffset++] & 0xFF) << 8)
+                | (source[sourceOffset++] & 0xFF);
+
+        dest[destOffset] = v;
+        return sourceOffset;
+    }
+
+    public static long byteToLongBE(byte[] source, int sourceOffset) {
+        return ((long) (source[sourceOffset++] & 0xFF) << 56)
+                | ((long) (source[sourceOffset++] & 0xFF) << 48)
+                | ((long) (source[sourceOffset++] & 0xFF) << 40)
+                | ((long) (source[sourceOffset++] & 0xFF) << 32)
+                | ((long) (source[sourceOffset++] & 0xFF) << 24)
+                | ((long) (source[sourceOffset++] & 0xFF) << 16)
+                | ((long) (source[sourceOffset++] & 0xFF) << 8)
+                | (source[sourceOffset++] & 0xFF);
+    }
+
+    public static void byteToLongBE(byte[] source, int sourceOffset, int count, long[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            long v = ((long) (source[sourceOffset++] & 0xFF) << 56)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 48)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 40)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 32)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 24)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 16)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 8)
+                    | (source[sourceOffset++] & 0xFF);
+
+            dest[destOffset + i] = v;
+        }
+    }
+
+    public static final void byteToIntBE(final int bytesInInt, byte[] source, int sourceOffset, final int count, int[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = 0;
+            for (int j = 0; j < bytesInInt; j++) {
+                v = (v << 8) | (source[sourceOffset + j] & 0xFF);
+            }
+            dest[destOffset++] = v & 0xFFFFFFFF;
+            sourceOffset += bytesInInt;
+        }
+    }
+
+    public static final void byteToShortBE(final int bytesInShort, byte[] source, int sourceOffset, final int count, short[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = 0;
+            for (int j = 0; j < bytesInShort; j++) {
+                v = (v << 8) | (source[sourceOffset + j] & 0xFF);
+            }
+            dest[destOffset++] = (short) v;
+            sourceOffset += bytesInShort;
+        }
+    }
+
+    public static final void byteToLongBE(final int bytesInLong, byte[] source, int sourceOffset, final int count, long[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            long v = 0;
+            for (int j = 0; j < bytesInLong; j++) {
+                v = (v << 8) | (source[sourceOffset + j] & 0xFF);
+            }
+            dest[destOffset++] = v;
+            sourceOffset += bytesInLong;
+        }
+    }
+
+    public static final int byteToIntBE(int bytesInInt, byte[] source, int sourceOffset, int[] dest, int destOffset) {
+        int v = 0;
+        for (int i = 0; i < bytesInInt; i++) {
+            v = (v << 8) | (source[sourceOffset++] & 0xFF);
+        }
+        dest[destOffset] = v & 0xFFFFFFFF;
+        return sourceOffset;
+    }
+
+    public static final int byteToShortBE(int bytesInShort, byte[] source, int sourceOffset, short[] dest, int destOffset) {
+        int v = 0;
+        for (int i = 0; i < bytesInShort; i++) {
+            v = (v << 8) | (source[sourceOffset++] & 0xFF);
+        }
+        dest[destOffset] = (short) v;
+        return sourceOffset;
+    }
+
+    public static final int byteToLongBE(int bytesInLong, byte[] source, int sourceOffset, long[] dest, int destOffset) {
+        long v = 0;
+        for (int i = 0; i < bytesInLong; i++) {
+            v = (v << 8) | (source[sourceOffset++] & 0xFF);
+        }
+        dest[destOffset] = v;
+        return sourceOffset;
+    }
+
+    public static int byteToShortBE(byte[] source, int sourceOffset, short[] dest, int destOffset) {
+        int v = ((source[sourceOffset++] & 0xFF) << 8)
+                | (source[sourceOffset++] & 0xFF);
+        dest[destOffset] = (short) v;
+        return sourceOffset;
+    }
+
+    public static int byteToShortBE(byte[] source, int sourceOffset) {
+        return ((source[sourceOffset++] & 0xFF) << 8) | (source[sourceOffset++] & 0xFF);
+    }
+
+    public static void byteToShortBE(byte[] source, int sourceOffset, int count, short[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = ((source[sourceOffset++] & 0xFF) << 8)
+                    | (source[sourceOffset++] & 0xFF);
+            dest[destOffset + i] = (short) v;
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/xform/XtoByteBE.java
===================================================================
--- ocean/src/com/imagero/uio/xform/XtoByteBE.java	(revision 0)
+++ ocean/src/com/imagero/uio/xform/XtoByteBE.java	(revision 0)
@@ -0,0 +1,312 @@
+package com.imagero.uio.xform;
+
+
+
+/**
+ * Date: 10.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class XtoByteBE {
+    //private static final int[] SHIFTS_LONG_BE = new int[]{56, 48, 40, 32, 24, 16, 8, 0};
+    static final int[] SHIFTS_INT_BE = new int[]{24, 16, 8, 0};
+    static final int[] byte_mask = {1, 2, 4, 8, 16, 32, 64, 128, 256};
+    static final int[] shift_mask = {0, 8, 16, 24, 32, 40, 48, 56};
+
+    /**
+     * read int in BIG_ENDIAN order
+     *
+     * @param sourceOffset     offset in source array
+     * @param dest       byte array (destination)
+     * @param destOffset offset in destination array
+     *
+     * @return offset in destination array (updated)
+     */
+    public static final int intToByte(int[] source, int sourceOffset, byte[] dest, int destOffset) {
+        int v = source[sourceOffset];
+        dest[destOffset++] = (byte) ((v >> 24) & 0xFF);
+        dest[destOffset++] = (byte) ((v >> 16) & 0xFF);
+        dest[destOffset++] = (byte) ((v >> 8) & 0xFF);
+        dest[destOffset++] = (byte) (v & 0xFF);
+        return destOffset;
+    }
+
+    public static final int intToByte(int v, byte[] dest, int destOffset) {
+        dest[destOffset++] = (byte) ((v >> 24) & 0xFF);
+        dest[destOffset++] = (byte) ((v >> 16) & 0xFF);
+        dest[destOffset++] = (byte) ((v >> 8) & 0xFF);
+        dest[destOffset++] = (byte) (v & 0xFF);
+        return destOffset;
+    }
+
+    /**
+     * convert <code>count</code> ints to bytes (Big Endian)
+     * @param source int array
+     * @param srcOffset start offset in <code>source</code> array
+     * @param count how much ints to process
+     * @param dest destination byte array
+     * @param destOffset
+     */
+    public static final void intToByte(int[] source, int srcOffset, int count, byte[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = source[srcOffset + i];
+            dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+            dest[destOffset++] = (byte) (v & 0xFF);
+        }
+    }
+
+    /**
+     *
+     * convert <code>count</code> ints to bytes (Big Endian)
+     * @param source int array
+     * @param srcOffset start offset in <code>source</code> array
+     * @param count how much ints to process
+     * @param dest destination byte array
+     * @param skip how much bytes should be thrown away before start writing to destination (1 to 3)
+     */
+    public static final void intToByteBE(int[] source, int srcOffset, int count, byte[] dest, int destOffset, int skip) {
+        if (skip > 0) {
+            int v = source[srcOffset++];
+            for (int i = skip; i < 4; i++) {
+                dest[destOffset++] = (byte) ((v >>> SHIFTS_INT_BE[i]) & 0xFF);
+            }
+        }
+        intToByte(source, srcOffset, count, dest, destOffset);
+    }
+
+    public static int charToByte(char[] source, int srcOffset, byte[] dest, int destOffset) {
+        int v = source[srcOffset];
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) (v & 0xFF);
+        return destOffset;
+    }
+
+    public static int charToByte(char v, byte[] dest, int destOffset) {
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) (v & 0xFF);
+        return destOffset;
+    }
+
+    public static void charToByte(char[] source, int srcOffset, int count, byte[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = source[srcOffset + i];
+            dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+            dest[destOffset++] = (byte) (v & 0xFF);
+        }
+    }
+
+    public static int doubleToByteBE(double[] source, int srcOffset, byte[] dest, int destOffset) {
+        double d = source[srcOffset];
+        long v = Double.doubleToLongBits(d);
+        dest[destOffset++] = (byte) ((v >>> 56) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 48) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 40) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 32) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) (v & 0xFF);
+        return destOffset;
+    }
+
+    public static int doubleToByteBE(double d, byte[] dest, int destOffset) {
+        long v = Double.doubleToLongBits(d);
+        dest[destOffset++] = (byte) ((v >>> 56) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 48) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 40) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 32) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) (v & 0xFF);
+        return destOffset;
+    }
+
+    public static void doubleToByteBE(double[] source, int srcOffset, int count, byte[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            double d = source[srcOffset + i];
+            long v = Double.doubleToLongBits(d);
+            dest[destOffset++] = (byte) ((v >>> 56) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 48) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 40) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 32) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+            dest[destOffset++] = (byte) (v & 0xFF);
+        }
+    }
+
+    public static int floatToByteBE(float[] source, int offset, byte[] dest, int destOffset) {
+        float f = source[offset];
+        int v = Float.floatToIntBits(f);
+        dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) (v & 0xFF);
+        return destOffset;
+    }
+
+    public static int floatToByteBE(float f, byte[] dest, int destOffset) {
+        int v = Float.floatToIntBits(f);
+        dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) (v & 0xFF);
+        return destOffset;
+    }
+
+    public static void floatToByteBE(float[] source, int offset, int count, byte[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            float f = source[offset + i];
+            int v = Float.floatToIntBits(f);
+            dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+            dest[destOffset++] = (byte) (v & 0xFF);
+        }
+    }
+
+    public static int longToByteBE(long[] source, int offset, byte[] dest, int destOffset) {
+        long v = source[offset];
+        dest[destOffset++] = (byte) ((v >>> 56) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 48) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 40) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 32) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) (v & 0xFF);
+
+        return destOffset;
+    }
+
+    public static int longToByteBE(long v, byte[] dest, int destOffset) {
+        dest[destOffset++] = (byte) ((v >>> 56) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 48) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 40) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 32) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) (v & 0xFF);
+
+        return destOffset;
+    }
+
+    public static void longToByteBE(long[] source, int offset, int count, byte[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            long v = source[offset + i];
+            dest[destOffset++] = (byte) ((v >>> 56) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 48) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 40) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 32) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+            dest[destOffset++] = (byte) (v & 0xFF);
+        }
+    }
+
+    public static int shortToByteBE(short[] source, int offset, byte[] dest, int destOffset) {
+        int v = source[offset];
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) (v & 0xFF);
+        return destOffset;
+    }
+
+    public static int shortToByteBE(short v, byte[] dest, int destOffset) {
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) (v & 0xFF);
+        return destOffset;
+    }
+
+    public static void shortToByteBE(short[] source, int offset, int count, byte[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = source[offset + i];
+            dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+            dest[destOffset++] = (byte) (v & 0xFF);
+        }
+    }
+
+    public static int shortToByteBE(final byte mask, short[] source, int offset, byte[] dest, int destOffset) {
+        final int bytesInShort = 2;
+        int v = source[offset];
+        for (int j = 0; j < bytesInShort; j++) {
+            if ((mask & byte_mask[j]) != 0) {
+                dest[destOffset++] = (byte) ((v >>> shift_mask[bytesInShort - 1 - j]) & 0xFF);
+            }
+        }
+        return destOffset;
+    }
+
+    public static void shortToByteBE(final byte mask, short[] source, int offset, int count, byte[] dest, int destOffset) {
+        final int bytesInShort = 2;
+        for (int i = 0; i < count; i++) {
+            int v = source[offset + i];
+            for (int j = 0; j < bytesInShort; j++) {
+                if ((mask & byte_mask[j]) != 0) {
+                    dest[destOffset++] = (byte) ((v >>> shift_mask[bytesInShort - 1 - j]) & 0xFF);
+                }
+            }
+        }
+    }
+
+    public static int intToByteBE(final byte mask, int[] source, int offset, byte[] dest, int destOffset) {
+        final int bytesInInt = 4;
+        int v = source[offset];
+        for (int j = 0; j < bytesInInt; j++) {
+            if ((mask & byte_mask[j]) != 0) {
+                dest[destOffset++] = (byte) ((v >>> shift_mask[bytesInInt - 1 - j]) & 0xFF);
+            }
+        }
+        return destOffset;
+    }
+
+    public static void intToByteBE(final byte mask, int[] source, int offset, int count, byte[] dest, int destOffset) {
+        final int bytesInInt = 4;
+        for (int i = 0; i < count; i++) {
+            int v = source[offset + i];
+            for (int j = 0; j < bytesInInt; j++) {
+                if ((mask & byte_mask[j]) != 0) {
+                    dest[destOffset++] = (byte) ((v >>> shift_mask[bytesInInt - 1 - j]) & 0xFF);
+                }
+            }
+        }
+    }
+
+    public static int longToByteBE(final byte mask, long[] source, int offset, byte[] dest, int destOffset) {
+        final int bytesInLong = 8;
+        long v = source[offset];
+        for (int j = 0; j < bytesInLong; j++) {
+            if ((mask & byte_mask[j]) != 0) {
+                dest[destOffset++] = (byte) ((v >>> shift_mask[bytesInLong - 1 - j]) & 0xFF);
+            }
+        }
+        return destOffset;
+    }
+
+    public static void longToByteBE(final byte mask, long[] source, int offset, int count, byte[] dest, int destOffset) {
+        final int bytesInLong = 8;
+        for (int i = 0; i < count; i++) {
+            long v = source[offset + i];
+            for (int j = 0; j < bytesInLong; j++) {
+                if ((mask & byte_mask[j]) != 0) {
+                    dest[destOffset++] = (byte) ((v >>> shift_mask[bytesInLong - 1 - j]) & 0xFF);
+                }
+            }
+        }
+    }
+
+    public static final int getBytesPerNumber(long mask) {
+        int bpi = 0;
+        for (int i = 0; i < byte_mask.length; i++) {
+            if ((mask & byte_mask[i]) != 0) {
+                bpi++;
+            }
+        }
+        return bpi;
+    }
+}
Index: ocean/src/com/imagero/uio/xform/XTransformer.java
===================================================================
--- ocean/src/com/imagero/uio/xform/XTransformer.java	(revision 0)
+++ ocean/src/com/imagero/uio/xform/XTransformer.java	(revision 0)
@@ -0,0 +1,567 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.xform;
+
+
+/**
+ * Primitive type conversion, array copying, etc.
+ * The difference to Transformer is that XTransformer does not restricted to big and little endiannes.
+ * User may read bytes in any order.
+ *
+ * @author Andrey Kuznetsov
+ */
+public class XTransformer {
+
+    protected static final int[] shifts = new int[]{0, 8, 16, 24, 32, 40, 48, 56};
+    protected static final int[] POSITIONS_BE = {0, 1, 2, 3, 4, 5, 6, 7};
+    protected static final int[] POSITIONS_LE_SHORT = {1, 0};
+    protected static final int[] POSITIONS_LE_INT = {3, 2, 1, 0};
+    protected static final int[] POSITIONS_LE_LONG = {7, 6, 5, 4, 3, 2, 1, 0};
+    protected static final int[][] POSITIONS_LE = {POSITIONS_BE, POSITIONS_LE_SHORT, POSITIONS_LE_INT, POSITIONS_LE_LONG};
+
+    public static final int TYPE_BE = 0;
+    public static final int TYPE_LE_SHORT = 1;
+    public static final int TYPE_LE_INT = 2;
+    public static final int TYPE_LE_LONG = 3;
+
+    public static final int[] get(int type) {
+        return POSITIONS_LE[type];
+    }
+
+    /**
+     * write int in BIG_ENDIAN order
+     *
+     * @param source       source byte array
+     * @param sourceOffset offset in source array
+     * @param destOffset   offset in destination array
+     *
+     * @return new offset in source array (for next writeUnitXX)
+     */
+    public static final int byteToInt(byte[] source, int sourceOffset, int[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        int v = ((source[sourceOffset + positions[p++]] & 0xFF) << 24)
+                | ((source[sourceOffset + positions[p++]] & 0xFF) << 16)
+                | ((source[sourceOffset + positions[p++]] & 0xFF) << 8)
+                | (source[sourceOffset + positions[p++]] & 0xFF);
+        dest[destOffset] = v;
+        return sourceOffset + p;
+    }
+
+    public static final int byteToInt(byte[] source, int sourceOffset, int[] positions) {
+        int p = 0;
+        return ((source[sourceOffset + positions[p++]] & 0xFF) << 24)
+                | ((source[sourceOffset + positions[p++]] & 0xFF) << 16)
+                | ((source[sourceOffset + positions[p++]] & 0xFF) << 8)
+                | (source[sourceOffset + positions[p++]] & 0xFF);
+    }
+
+    /**
+     * read int in BIG_ENDIAN order
+     *
+     * @param sourceOffset     offset in source array
+     * @param dest       byte array (destination)
+     * @param destOffset offset in destination array
+     *
+     * @return offset in destination array (updated)
+     */
+    public static final int intToByte(int[] source, int sourceOffset, byte[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        int v = source[sourceOffset];
+        dest[destOffset + positions[p++]] = (byte) ((v >> 24) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >> 16) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >> 8) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+        return destOffset + 4;
+    }
+
+    public static final int intToByte(int v, byte[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        dest[destOffset + positions[p++]] = (byte) ((v >> 24) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >> 16) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >> 8) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+        return destOffset + 4;
+    }
+
+    /**
+     * convert <code>count</code> ints to bytes (Big Endian)
+     * @param source int array
+     * @param srcOffset start offset in <code>source</code> array
+     * @param count how much ints to process
+     * @param dest destination byte array
+     * @param destOffset
+     */
+    public static final void intToByte(int[] source, int srcOffset, int count, byte[] dest, int destOffset, int[] positions) {
+        for (int i = 0; i < count; i++) {
+            int p = 0;
+            int v = source[srcOffset + i];
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 24) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 16) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 8) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+            destOffset += 4;
+        }
+    }
+
+
+    public static final void byteToInt(byte[] source, int sourceOffset, int count, int[] dest, int destOffset, int[] positions) {
+        for (int i = 0; i < count; i++) {
+            int p = 0;
+            int v = ((source[sourceOffset + positions[p++]] & 0xFF) << 24)
+                    | ((source[sourceOffset + positions[p++]] & 0xFF) << 16)
+                    | ((source[sourceOffset + positions[p++]] & 0xFF) << 8)
+                    | (source[sourceOffset + positions[p++]] & 0xFF);
+            dest[destOffset + i] = v;
+            sourceOffset += 4;
+        }
+    }
+
+
+/* **********************************************************************/
+
+    public static int byteToChar(byte[] source, int sourceOffset, char[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        int v = ((source[sourceOffset + positions[p++]] & 0xFF) << 8)
+                | (source[sourceOffset + positions[p++]] & 0xFF);
+        dest[destOffset] = (char) v;
+        return sourceOffset + 2;
+    }
+
+    public static int byteToChar(byte[] source, int sourceOffset, int[] positions) {
+        int p = 0;
+        return ((source[sourceOffset + positions[p++]] & 0xFF) << 8) | (source[sourceOffset + positions[p++]] & 0xFF);
+    }
+
+    public static void byteToChar(byte[] source, int sourceOffset, int count, char[] dest, int destOffset, int[] positions) {
+        for (int i = 0; i < count; i++) {
+            int p = 0;
+            int v = ((source[sourceOffset + positions[p++]] & 0xFF) << 8)
+                    | (source[sourceOffset + positions[p++]] & 0xFF);
+            dest[destOffset + i] = (char) v;
+        }
+    }
+
+/* **********************************************************************/
+
+    public static int charToByte(char[] source, int srcOffset, byte[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        int v = source[srcOffset];
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+        return destOffset + 2;
+    }
+
+    public static int charToByte(char v, byte[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+        return destOffset + 2;
+    }
+
+    public static void charToByte(char[] source, int srcOffset, int count, byte[] dest, int destOffset, int[] positions) {
+        for (int i = 0; i < count; i++) {
+            int p = 0;
+            int v = source[srcOffset + i];
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 8) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+            destOffset += 2;
+        }
+    }
+
+/* **********************************************************************/
+
+    public static int byteToDouble(byte[] source, int sourceOffset, double[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        long v = ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 56)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 48)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 40)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 32)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 24)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 16)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 8)
+                | (source[sourceOffset + positions[p++]] & 0xFF);
+        dest[destOffset] = Double.longBitsToDouble(v);
+        return sourceOffset + 8;
+    }
+
+    public static double byteToDouble(byte[] source, int sourceOffset, int[] positions) {
+        int p = 0;
+        long v = ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 56)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 48)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 40)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 32)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 24)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 16)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 8)
+                | (source[sourceOffset + positions[p++]] & 0xFF);
+        return Double.longBitsToDouble(v);
+    }
+
+    public static void byteToDouble(byte[] source, int sourceOffset, int count, double[] dest, int destOffset, int[] positions) {
+        for (int i = 0; i < count; i++) {
+            int p = 0;
+            long v = ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 56)
+                    | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 48)
+                    | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 40)
+                    | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 32)
+                    | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 24)
+                    | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 16)
+                    | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 8)
+                    | (source[sourceOffset + positions[p++]] & 0xFF);
+            dest[destOffset + i] = Double.longBitsToDouble(v);
+        }
+    }
+
+/* **********************************************************************/
+
+    public static int doubleToByte(double[] source, int srcOffset, byte[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        double d = source[srcOffset];
+        long v = Double.doubleToLongBits(d);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 56) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 48) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 40) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 32) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 24) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+        return destOffset + 8;
+    }
+
+    public static int doubleToByte(double d, byte[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        long v = Double.doubleToLongBits(d);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 56) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 48) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 40) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 32) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 24) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+        return destOffset + 8;
+    }
+
+    public static void doubleToByte(double[] source, int srcOffset, int count, byte[] dest, int destOffset, int[] positions) {
+        for (int i = 0; i < count; i++) {
+            int p = 0;
+            double d = source[srcOffset + i];
+            long v = Double.doubleToLongBits(d);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 56) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 48) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 40) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 32) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 24) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 16) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 8) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+            destOffset += 8;
+        }
+    }
+
+/* **********************************************************************/
+
+    public static int byteToFloat(byte[] source, int sourceOffset, float[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        int v = ((source[sourceOffset + positions[p++]] & 0xFF) << 24)
+                | ((source[sourceOffset + positions[p++]] & 0xFF) << 16)
+                | ((source[sourceOffset + positions[p++]] & 0xFF) << 8)
+                | (source[sourceOffset + positions[p++]] & 0xFF);
+        dest[destOffset] = Float.intBitsToFloat(v);
+        return sourceOffset;
+    }
+
+    public static float byteToFloat(byte[] source, int sourceOffset, int[] positions) {
+        int p = 0;
+        int v = ((source[sourceOffset + positions[p++]] & 0xFF) << 24)
+                | ((source[sourceOffset + positions[p++]] & 0xFF) << 16)
+                | ((source[sourceOffset + positions[p++]] & 0xFF) << 8)
+                | (source[sourceOffset + positions[p++]] & 0xFF);
+        return Float.intBitsToFloat(v);
+    }
+
+    public static void byteToFloat(byte[] source, int sourceOffset, int count, float[] dest, int destOffset, int[] positions) {
+        for (int i = 0; i < count; i++) {
+            int p = 0;
+            int v = ((source[sourceOffset + positions[p++]] & 0xFF) << 24)
+                    | ((source[sourceOffset + positions[p++]] & 0xFF) << 16)
+                    | ((source[sourceOffset + positions[p++]] & 0xFF) << 8)
+                    | (source[sourceOffset + positions[p++]] & 0xFF);
+            dest[destOffset + i] = Float.intBitsToFloat(v);
+        }
+    }
+
+/* **********************************************************************/
+
+    public static int floatToByte(float[] source, int offset, byte[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        float f = source[offset];
+        int v = Float.floatToIntBits(f);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 24) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+        return destOffset + 4;
+    }
+
+    public static int floatToByte(float f, byte[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        int v = Float.floatToIntBits(f);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 24) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+        return destOffset + 4;
+    }
+
+    public static void floatToByte(float[] source, int offset, int count, byte[] dest, int destOffset, int[] positions) {
+        for (int i = 0; i < count; i++) {
+            int p = 0;
+            float f = source[offset + i];
+            int v = Float.floatToIntBits(f);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 24) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 16) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 8) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+            destOffset += 4;
+        }
+    }
+
+/* **********************************************************************/
+
+    public static int byteToLong(byte[] source, int sourceOffset, long[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        long v = ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 56)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 48)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 40)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 32)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 24)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 16)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 8)
+                | (source[sourceOffset + positions[p++]] & 0xFF);
+
+        dest[destOffset] = v;
+        return sourceOffset + 8;
+    }
+
+    public static long byteToLong(byte[] source, int sourceOffset, int[] positions) {
+        int p = 0;
+        return ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 56)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 48)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 40)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 32)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 24)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 16)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 8)
+                | (source[sourceOffset + positions[p++]] & 0xFF);
+    }
+
+    public static void byteToLong(byte[] source, int sourceOffset, int count, long[] dest, int destOffset, int[] positions) {
+        for (int i = 0; i < count; i++) {
+            int p = 0;
+            long v = ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 56)
+                    | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 48)
+                    | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 40)
+                    | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 32)
+                    | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 24)
+                    | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 16)
+                    | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 8)
+                    | (source[sourceOffset + positions[p++]] & 0xFF);
+
+            dest[destOffset + i] = v;
+        }
+    }
+
+/* **********************************************************************/
+
+    public static int longToByte(long[] source, int offset, byte[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        long v = source[offset];
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 56) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 48) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 40) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 32) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 24) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+
+        return destOffset + 8;
+    }
+
+    public static int longToByte(long v, byte[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 56) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 48) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 40) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 32) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 24) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+
+        return destOffset;
+    }
+
+    public static void longToByte(long[] source, int offset, int count, byte[] dest, int destOffset, int[] positions) {
+        for (int i = 0; i < count; i++) {
+            int p = 0;
+            long v = source[offset + i];
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 56) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 48) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 40) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 32) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 24) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 16) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 8) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+            destOffset += 8;
+        }
+    }
+
+/* **********************************************************************/
+
+    public static int byteToShort(byte[] source, int sourceOffset, short[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        int v = ((source[sourceOffset + positions[p++]] & 0xFF) << 8)
+                | (source[sourceOffset + positions[p++]] & 0xFF);
+        dest[destOffset] = (short) v;
+        return sourceOffset + 2;
+    }
+
+    public static int byteToShort(byte[] source, int sourceOffset, int[] positions) {
+        int p = 0;
+        return ((source[sourceOffset + positions[p++]] & 0xFF) << 8) | (source[sourceOffset + positions[p++]] & 0xFF);
+    }
+
+    public static void byteToShort(byte[] source, int sourceOffset, int count, short[] dest, int destOffset, int[] positions) {
+        for (int i = 0; i < count; i++) {
+            int p = 0;
+            int v = ((source[sourceOffset + positions[p++]] & 0xFF) << 8)
+                    | (source[sourceOffset + positions[p++]] & 0xFF);
+            dest[destOffset + i] = (short) v;
+        }
+    }
+
+/* **********************************************************************/
+
+    public static int shortToByte(short[] source, int offset, byte[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        int v = source[offset];
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+        return destOffset + 2;
+    }
+
+    public static int shortToByte(short v, byte[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+        return destOffset + 2;
+    }
+
+    public static void shortToByte(short[] source, int offset, int count, byte[] dest, int destOffset, int[] positions) {
+        for (int i = 0; i < count; i++) {
+            int p = 0;
+            int v = source[offset + i];
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 8) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+            destOffset += 2;
+        }
+    }
+
+    /* **********************************************************************/
+
+    public static final void byteToInt(final int bytesInInt, byte[] source, int sourceOffset, final int count, int[] dest, int destOffset, int[] positions) {
+        for (int i = 0; i < count; i++) {
+            int v = 0;
+            for (int j = 0; j < bytesInInt; j++) {
+                v = (v << 8) | (source[sourceOffset + positions[j]] & 0xFF);
+            }
+            dest[destOffset++] = v & 0xFFFFFFFF;
+            sourceOffset += bytesInInt;
+        }
+    }
+
+    public static final void byteToShort(final int bytesInShort, byte[] source, int sourceOffset, final int count, short[] dest, int destOffset, int[] positions) {
+        for (int i = 0; i < count; i++) {
+            int v = 0;
+            for (int j = 0; j < bytesInShort; j++) {
+                v = (v << 8) | (source[sourceOffset + positions[j]] & 0xFF);
+            }
+            dest[destOffset++] = (short) v;
+            sourceOffset += bytesInShort;
+        }
+    }
+
+    public static final void byteToLong(final int bytesInLong, byte[] source, int sourceOffset, final int count, long[] dest, int destOffset, int[] positions) {
+        for (int i = 0; i < count; i++) {
+            long v = 0;
+            for (int j = 0; j < bytesInLong; j++) {
+                v = (v << 8) | (source[sourceOffset + positions[j]] & 0xFF);
+            }
+            dest[destOffset++] = v;
+            sourceOffset += bytesInLong;
+        }
+    }
+
+/* **********************************************************************/
+
+
+    public static final int byteToInt(int bytesInInt, byte[] source, int sourceOffset, int[] dest, int destOffset, int[] positions) {
+        int v = 0;
+        for (int i = 0; i < bytesInInt; i++) {
+            v = (v << 8) | (source[sourceOffset + positions[i]] & 0xFF);
+        }
+        dest[destOffset] = v & 0xFFFFFFFF;
+        return sourceOffset + bytesInInt;
+    }
+
+    public static final int byteToShort(int bytesInShort, byte[] source, int sourceOffset, short[] dest, int destOffset, int[] positions) {
+        int v = 0;
+        for (int i = 0; i < bytesInShort; i++) {
+            v = (v << 8) | (source[sourceOffset + positions[i]] & 0xFF);
+        }
+        dest[destOffset] = (short) v;
+        return sourceOffset + bytesInShort;
+    }
+
+    public static final int byteToLong(int bytesInLong, byte[] source, int sourceOffset, long[] dest, int destOffset, int[] positions) {
+        long v = 0;
+        for (int i = 0; i < bytesInLong; i++) {
+            v = (v << 8) | (source[sourceOffset + positions[i]] & 0xFF);
+        }
+        dest[destOffset] = v;
+        return sourceOffset + bytesInLong;
+    }
+}
\ No newline at end of file
Index: ocean/src/com/imagero/uio/xform/ByteToXBE.java
===================================================================
--- ocean/src/com/imagero/uio/xform/ByteToXBE.java	(revision 0)
+++ ocean/src/com/imagero/uio/xform/ByteToXBE.java	(revision 0)
@@ -0,0 +1,248 @@
+package com.imagero.uio.xform;
+
+/**
+ * Big endian transform from bytes to other primitives.
+ * Date: 10.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class ByteToXBE {
+    /**
+     * write int in BIG_ENDIAN order
+     *
+     * @param source       source byte array
+     * @param sourceOffset offset in source array
+     * @param destOffset   offset in destination array
+     *
+     * @return new offset in source array (for next writeUnitXX)
+     */
+    public static final int byteToInt(byte[] source, int sourceOffset, int[] dest, int destOffset) {
+        int v = ((source[sourceOffset++] & 0xFF) << 24)
+                | ((source[sourceOffset++] & 0xFF) << 16)
+                | ((source[sourceOffset++] & 0xFF) << 8)
+                | (source[sourceOffset++] & 0xFF);
+        dest[destOffset] = v;
+        return sourceOffset;
+    }
+
+    public static final int byteToInt(byte[] source, int sourceOffset) {
+        return ((source[sourceOffset++] & 0xFF) << 24)
+                | ((source[sourceOffset++] & 0xFF) << 16)
+                | ((source[sourceOffset++] & 0xFF) << 8)
+                | (source[sourceOffset++] & 0xFF);
+    }
+
+    public static final void byteToInt(byte[] source, int sourceOffset, int count, int[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = ((source[sourceOffset++] & 0xFF) << 24)
+                    | ((source[sourceOffset++] & 0xFF) << 16)
+                    | ((source[sourceOffset++] & 0xFF) << 8)
+                    | (source[sourceOffset++] & 0xFF);
+            dest[destOffset + i] = v;
+        }
+    }
+
+    public static int byteToChar(byte[] source, int sourceOffset, char[] dest, int destOffset) {
+        int v = ((source[sourceOffset++] & 0xFF) << 8)
+                | (source[sourceOffset++] & 0xFF);
+        dest[destOffset] = (char) v;
+        return sourceOffset;
+    }
+
+    public static int byteToChar(byte[] source, int sourceOffset) {
+        return ((source[sourceOffset++] & 0xFF) << 8) | (source[sourceOffset++] & 0xFF);
+    }
+
+    public static void byteToChar(byte[] source, int sourceOffset, int count, char[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = ((source[sourceOffset++] & 0xFF) << 8)
+                    | (source[sourceOffset++] & 0xFF);
+            dest[destOffset + i] = (char) v;
+        }
+    }
+
+    public static int byteToDoubleBE(byte[] source, int sourceOffset, double[] dest, int destOffset) {
+        long v = ((long) (source[sourceOffset++] & 0xFF) << 56)
+                | ((long) (source[sourceOffset++] & 0xFF) << 48)
+                | ((long) (source[sourceOffset++] & 0xFF) << 40)
+                | ((long) (source[sourceOffset++] & 0xFF) << 32)
+                | ((long) (source[sourceOffset++] & 0xFF) << 24)
+                | ((long) (source[sourceOffset++] & 0xFF) << 16)
+                | ((long) (source[sourceOffset++] & 0xFF) << 8)
+                | (source[sourceOffset++] & 0xFF);
+        dest[destOffset] = Double.longBitsToDouble(v);
+        return sourceOffset;
+    }
+
+    public static double byteToDoubleBE(byte[] source, int sourceOffset) {
+        long v = ((long) (source[sourceOffset++] & 0xFF) << 56)
+                | ((long) (source[sourceOffset++] & 0xFF) << 48)
+                | ((long) (source[sourceOffset++] & 0xFF) << 40)
+                | ((long) (source[sourceOffset++] & 0xFF) << 32)
+                | ((long) (source[sourceOffset++] & 0xFF) << 24)
+                | ((long) (source[sourceOffset++] & 0xFF) << 16)
+                | ((long) (source[sourceOffset++] & 0xFF) << 8)
+                | (source[sourceOffset++] & 0xFF);
+        return Double.longBitsToDouble(v);
+    }
+
+    public static void byteToDoubleBE(byte[] source, int sourceOffset, int count, double[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            long v = ((long) (source[sourceOffset++] & 0xFF) << 56)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 48)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 40)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 32)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 24)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 16)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 8)
+                    | (source[sourceOffset++] & 0xFF);
+            dest[destOffset + i] = Double.longBitsToDouble(v);
+        }
+    }
+
+    public static int byteToFloatBE(byte[] source, int sourceOffset, float[] dest, int destOffset) {
+        int v = ((source[sourceOffset++] & 0xFF) << 24)
+                | ((source[sourceOffset++] & 0xFF) << 16)
+                | ((source[sourceOffset++] & 0xFF) << 8)
+                | (source[sourceOffset++] & 0xFF);
+        dest[destOffset] = Float.intBitsToFloat(v);
+        return sourceOffset;
+    }
+
+    public static float byteToFloatBE(byte[] source, int sourceOffset) {
+        int v = ((source[sourceOffset++] & 0xFF) << 24)
+                | ((source[sourceOffset++] & 0xFF) << 16)
+                | ((source[sourceOffset++] & 0xFF) << 8)
+                | (source[sourceOffset++] & 0xFF);
+        return Float.intBitsToFloat(v);
+    }
+
+    public static void byteToFloatBE(byte[] source, int sourceOffset, int count, float[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = ((source[sourceOffset++] & 0xFF) << 24)
+                    | ((source[sourceOffset++] & 0xFF) << 16)
+                    | ((source[sourceOffset++] & 0xFF) << 8)
+                    | (source[sourceOffset++] & 0xFF);
+            dest[destOffset + i] = Float.intBitsToFloat(v);
+        }
+    }
+
+    public static int byteToLongBE(byte[] source, int sourceOffset, long[] dest, int destOffset) {
+        long v = ((long) (source[sourceOffset++] & 0xFF) << 56)
+                | ((long) (source[sourceOffset++] & 0xFF) << 48)
+                | ((long) (source[sourceOffset++] & 0xFF) << 40)
+                | ((long) (source[sourceOffset++] & 0xFF) << 32)
+                | ((long) (source[sourceOffset++] & 0xFF) << 24)
+                | ((long) (source[sourceOffset++] & 0xFF) << 16)
+                | ((long) (source[sourceOffset++] & 0xFF) << 8)
+                | (source[sourceOffset++] & 0xFF);
+
+        dest[destOffset] = v;
+        return sourceOffset;
+    }
+
+    public static long byteToLongBE(byte[] source, int sourceOffset) {
+        return ((long) (source[sourceOffset++] & 0xFF) << 56)
+                | ((long) (source[sourceOffset++] & 0xFF) << 48)
+                | ((long) (source[sourceOffset++] & 0xFF) << 40)
+                | ((long) (source[sourceOffset++] & 0xFF) << 32)
+                | ((long) (source[sourceOffset++] & 0xFF) << 24)
+                | ((long) (source[sourceOffset++] & 0xFF) << 16)
+                | ((long) (source[sourceOffset++] & 0xFF) << 8)
+                | (source[sourceOffset++] & 0xFF);
+    }
+
+    public static void byteToLongBE(byte[] source, int sourceOffset, int count, long[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            long v = ((long) (source[sourceOffset++] & 0xFF) << 56)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 48)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 40)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 32)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 24)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 16)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 8)
+                    | (source[sourceOffset++] & 0xFF);
+
+            dest[destOffset + i] = v;
+        }
+    }
+
+    public static final void byteToIntBE(final int bytesInInt, byte[] source, int sourceOffset, final int count, int[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = 0;
+            for (int j = 0; j < bytesInInt; j++) {
+                v = (v << 8) | (source[sourceOffset + j] & 0xFF);
+            }
+            dest[destOffset++] = v & 0xFFFFFFFF;
+            sourceOffset += bytesInInt;
+        }
+    }
+
+    public static final void byteToShortBE(final int bytesInShort, byte[] source, int sourceOffset, final int count, short[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = 0;
+            for (int j = 0; j < bytesInShort; j++) {
+                v = (v << 8) | (source[sourceOffset + j] & 0xFF);
+            }
+            dest[destOffset++] = (short) v;
+            sourceOffset += bytesInShort;
+        }
+    }
+
+    public static final void byteToLongBE(final int bytesInLong, byte[] source, int sourceOffset, final int count, long[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            long v = 0;
+            for (int j = 0; j < bytesInLong; j++) {
+                v = (v << 8) | (source[sourceOffset + j] & 0xFF);
+            }
+            dest[destOffset++] = v;
+            sourceOffset += bytesInLong;
+        }
+    }
+
+    public static final int byteToIntBE(int bytesInInt, byte[] source, int sourceOffset, int[] dest, int destOffset) {
+        int v = 0;
+        for (int i = 0; i < bytesInInt; i++) {
+            v = (v << 8) | (source[sourceOffset++] & 0xFF);
+        }
+        dest[destOffset] = v & 0xFFFFFFFF;
+        return sourceOffset;
+    }
+
+    public static final int byteToShortBE(int bytesInShort, byte[] source, int sourceOffset, short[] dest, int destOffset) {
+        int v = 0;
+        for (int i = 0; i < bytesInShort; i++) {
+            v = (v << 8) | (source[sourceOffset++] & 0xFF);
+        }
+        dest[destOffset] = (short) v;
+        return sourceOffset;
+    }
+
+    public static final int byteToLongBE(int bytesInLong, byte[] source, int sourceOffset, long[] dest, int destOffset) {
+        long v = 0;
+        for (int i = 0; i < bytesInLong; i++) {
+            v = (v << 8) | (source[sourceOffset++] & 0xFF);
+        }
+        dest[destOffset] = v;
+        return sourceOffset;
+    }
+
+    public static int byteToShortBE(byte[] source, int sourceOffset, short[] dest, int destOffset) {
+        int v = ((source[sourceOffset++] & 0xFF) << 8)
+                | (source[sourceOffset++] & 0xFF);
+        dest[destOffset] = (short) v;
+        return sourceOffset;
+    }
+
+    public static int byteToShortBE(byte[] source, int sourceOffset) {
+        return ((source[sourceOffset++] & 0xFF) << 8) | (source[sourceOffset++] & 0xFF);
+    }
+
+    public static void byteToShortBE(byte[] source, int sourceOffset, int count, short[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = ((source[sourceOffset++] & 0xFF) << 8)
+                    | (source[sourceOffset++] & 0xFF);
+            dest[destOffset + i] = (short) v;
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/xform/ByteToXLE.java
===================================================================
--- ocean/src/com/imagero/uio/xform/ByteToXLE.java	(revision 0)
+++ ocean/src/com/imagero/uio/xform/ByteToXLE.java	(revision 0)
@@ -0,0 +1,249 @@
+package com.imagero.uio.xform;
+
+/**
+ * Date: 10.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class ByteToXLE {
+    /**
+     * write int in LITTLE_ENDIAN order
+     *
+     * @param source       source byte array
+     * @param sourceOffset offset in source array
+     * @param destOffset   offset in destination array
+     *
+     * @return new offset in source array (for next writeUnitXX)
+     */
+    public static final int byteToInt(byte[] source, int sourceOffset, int[] dest, int destOffset) {
+        int v = ((source[sourceOffset++] & 0xFF))
+                | (((source[sourceOffset++] & 0xFF)) << 8)
+                | (((source[sourceOffset++] & 0xFF)) << 16)
+                | (((source[sourceOffset++] & 0xFF)) << 24);
+        dest[destOffset] = v;
+        return sourceOffset;
+    }
+
+    public static final int byteToInt(byte[] source, int sourceOffset) {
+        return ((source[sourceOffset++] & 0xFF))
+                | (((source[sourceOffset++] & 0xFF)) << 8)
+                | (((source[sourceOffset++] & 0xFF)) << 16)
+                | (((source[sourceOffset++] & 0xFF)) << 24);
+    }
+
+    public static final void byteToInt(byte[] source, int sourceOffset, int count, int[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = ((source[sourceOffset++] & 0xFF))
+                    | (((source[sourceOffset++] & 0xFF)) << 8)
+                    | (((source[sourceOffset++] & 0xFF)) << 16)
+                    | (((source[sourceOffset++] & 0xFF)) << 24);
+            dest[destOffset + i] = v;
+        }
+    }
+
+    public static int byteToChar(byte[] source, int sourceOffset, char[] dest, int destOffset) {
+        int v = ((source[sourceOffset++] & 0xFF)) | (((source[sourceOffset++] & 0xFF)) << 8);
+        dest[destOffset] = (char) v;
+        return sourceOffset;
+    }
+
+    public static int byteToChar(byte[] source, int sourceOffset) {
+        return ((source[sourceOffset++] & 0xFF)) | (((source[sourceOffset++] & 0xFF)) << 8);
+    }
+
+    public static void byteToChar(byte[] source, int sourceOffset, int count, char[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = ((source[sourceOffset++] & 0xFF)) | (((source[sourceOffset++] & 0xFF)) << 8);
+            dest[destOffset + i] = (char) v;
+        }
+    }
+
+    public static int byteToDoubleLE(byte[] source, int sourceOffset, double[] dest, int destOffset) {
+        long v = (source[sourceOffset++] & 0xFF)
+                | ((long) (source[sourceOffset++] & 0xFF) << 8)
+                | ((long) (source[sourceOffset++] & 0xFF) << 16)
+                | ((long) (source[sourceOffset++] & 0xFF) << 24)
+                | ((long) (source[sourceOffset++] & 0xFF) << 32)
+                | ((long) (source[sourceOffset++] & 0xFF) << 40)
+                | ((long) (source[sourceOffset++] & 0xFF) << 48)
+                | ((long) (source[sourceOffset++] & 0xFF) << 56);
+        dest[destOffset] = Double.longBitsToDouble(v);
+        return sourceOffset;
+    }
+
+    public static double byteToDoubleLE(byte[] source, int sourceOffset) {
+        long v = (source[sourceOffset++] & 0xFF)
+                | ((long) (source[sourceOffset++] & 0xFF) << 8)
+                | ((long) (source[sourceOffset++] & 0xFF) << 16)
+                | ((long) (source[sourceOffset++] & 0xFF) << 24)
+                | ((long) (source[sourceOffset++] & 0xFF) << 32)
+                | ((long) (source[sourceOffset++] & 0xFF) << 40)
+                | ((long) (source[sourceOffset++] & 0xFF) << 48)
+                | ((long) (source[sourceOffset++] & 0xFF) << 56);
+        return Double.longBitsToDouble(v);
+    }
+
+    public static void byteToDoubleLE(byte[] source, int sourceOffset, int count, double[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            long v = (source[sourceOffset++] & 0xFF)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 8)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 16)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 24)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 32)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 40)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 48)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 56);
+            dest[destOffset + i] = Double.longBitsToDouble(v);
+        }
+    }
+
+    public static int byteToFloatLE(byte[] source, int sourceOffset, float[] dest, int destOffset) {
+        int v = ((source[sourceOffset++] & 0xFF))
+                | (((source[sourceOffset++] & 0xFF)) << 8)
+                | (((source[sourceOffset++] & 0xFF)) << 16)
+                | (((source[sourceOffset++] & 0xFF)) << 24);
+        dest[destOffset] = Float.intBitsToFloat(v);
+        return sourceOffset;
+    }
+
+    public static float byteToFloatLE(byte[] source, int sourceOffset) {
+        int v = ((source[sourceOffset++] & 0xFF))
+                | (((source[sourceOffset++] & 0xFF)) << 8)
+                | (((source[sourceOffset++] & 0xFF)) << 16)
+                | (((source[sourceOffset++] & 0xFF)) << 24);
+        return Float.intBitsToFloat(v);
+    }
+
+    public static void byteToFloatLE(byte[] source, int sourceOffset, int count, float[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = ((source[sourceOffset++] & 0xFF))
+                    | (((source[sourceOffset++] & 0xFF)) << 8)
+                    | (((source[sourceOffset++] & 0xFF)) << 16)
+                    | (((source[sourceOffset++] & 0xFF)) << 24);
+            dest[destOffset + i] = Float.intBitsToFloat(v);
+        }
+    }
+
+    public static int byteToLongLE(byte[] source, int sourceOffset, long[] dest, int destOffset) {
+        long v = (source[sourceOffset++] & 0xFF)
+                | ((long) (source[sourceOffset++] & 0xFF) << 8)
+                | ((long) (source[sourceOffset++] & 0xFF) << 16)
+                | ((long) (source[sourceOffset++] & 0xFF) << 24)
+                | ((long) (source[sourceOffset++] & 0xFF) << 32)
+                | ((long) (source[sourceOffset++] & 0xFF) << 40)
+                | ((long) (source[sourceOffset++] & 0xFF) << 48)
+                | ((long) (source[sourceOffset++] & 0xFF) << 56);
+        dest[destOffset] = v;
+        return sourceOffset;
+    }
+
+    public static long byteToLongLE(byte[] source, int sourceOffset) {
+        return (source[sourceOffset++] & 0xFF)
+                | ((long) (source[sourceOffset++] & 0xFF) << 8)
+                | ((long) (source[sourceOffset++] & 0xFF) << 16)
+                | ((long) (source[sourceOffset++] & 0xFF) << 24)
+                | ((long) (source[sourceOffset++] & 0xFF) << 32)
+                | ((long) (source[sourceOffset++] & 0xFF) << 40)
+                | ((long) (source[sourceOffset++] & 0xFF) << 48)
+                | ((long) (source[sourceOffset++] & 0xFF) << 56);
+    }
+
+    public static void byteToLongLE(byte[] source, int sourceOffset, int count, long[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            long v = (source[sourceOffset++] & 0xFF)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 8)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 16)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 24)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 32)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 40)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 48)
+                    | ((long) (source[sourceOffset++] & 0xFF) << 56);
+            dest[destOffset + i] = v;
+        }
+    }
+
+    public static int byteToShortLE(byte[] source, int sourceOffset, short[] dest, int destOffset) {
+        int v = ((source[sourceOffset++] & 0xFF))
+                | (((source[sourceOffset++] & 0xFF)) << 8);
+        dest[destOffset] = (short) v;
+        return sourceOffset;
+    }
+
+    public static int byteToShortLE(byte[] source, int sourceOffset) {
+        return ((source[sourceOffset++] & 0xFF)) | (((source[sourceOffset++] & 0xFF)) << 8);
+    }
+
+    public static void byteToShortLE(byte[] source, int sourceOffset, int count, short[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = ((source[sourceOffset++] & 0xFF))
+                    | (((source[sourceOffset++] & 0xFF)) << 8);
+            dest[destOffset + i] = (short) v;
+        }
+    }
+
+    public static final void byteToIntLE(final int bytesInInt, byte[] source, int sourceOffset, final int count, int[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = 0;
+            for (int j = 0, shift = 0; j < bytesInInt; j++) {
+                v = v | ((source[sourceOffset + j] & 0xFF) << shift);
+                shift += 8;
+            }
+            dest[destOffset++] = v & 0xFFFFFFFF;
+            sourceOffset += bytesInInt;
+        }
+    }
+
+    public static final void byteToShortLE(final int bytesInShort, byte[] source, int sourceOffset, final int count, short[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = 0;
+            for (int j = 0, shift = 0; j < bytesInShort; j++) {
+                v = v | ((source[sourceOffset + j] & 0xFF) << shift);
+                shift += 8;
+            }
+            dest[destOffset++] = (short) v;
+            sourceOffset += bytesInShort;
+        }
+    }
+
+    public static final void byteToLongLE(final int bytesInLong, byte[] source, int sourceOffset, final int count, long[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            long v = 0;
+            for (int j = 0, shift = 0; j < bytesInLong; j++) {
+                v = v | ((source[sourceOffset + j] & 0xFF) << shift);
+                shift += 8;
+            }
+            dest[destOffset++] = v;
+            sourceOffset += bytesInLong;
+        }
+    }
+
+    public static final int byteToIntLE(int bytesInInt, byte[] source, int sourceOffset, int[] dest, int destOffset) {
+        int v = 0;
+        for (int i = 0, shift = 0; i < bytesInInt; i++) {
+            v = v | ((source[sourceOffset++] & 0xFF) << shift);
+            shift += 8;
+        }
+        dest[destOffset] = v & 0xFFFFFFFF;
+        return sourceOffset;
+    }
+
+    public static final int byteToShortLE(int bytesInShort, byte[] source, int sourceOffset, short[] dest, int destOffset) {
+        int v = 0;
+        for (int i = 0, shift = 0; i < bytesInShort; i++) {
+            v = v | ((source[sourceOffset++] & 0xFF) << shift);
+            shift += 8;
+        }
+        dest[destOffset] = (short) v;
+        return sourceOffset;
+    }
+
+    public static final int byteToLongLE(int bytesInLong, byte[] source, int sourceOffset, long[] dest, int destOffset) {
+        long v = 0;
+        for (int i = 0, shift = 0; i < bytesInLong; i++) {
+            v = v | ((source[sourceOffset++] & 0xFF) << shift);
+            shift += 8;
+        }
+        dest[destOffset] = v;
+        return sourceOffset;
+    }
+}
Index: ocean/src/com/imagero/uio/xform/XtoByteBE.java
===================================================================
--- ocean/src/com/imagero/uio/xform/XtoByteBE.java	(revision 0)
+++ ocean/src/com/imagero/uio/xform/XtoByteBE.java	(revision 0)
@@ -0,0 +1,312 @@
+package com.imagero.uio.xform;
+
+
+
+/**
+ * Date: 10.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class XtoByteBE {
+    //private static final int[] SHIFTS_LONG_BE = new int[]{56, 48, 40, 32, 24, 16, 8, 0};
+    static final int[] SHIFTS_INT_BE = new int[]{24, 16, 8, 0};
+    static final int[] byte_mask = {1, 2, 4, 8, 16, 32, 64, 128, 256};
+    static final int[] shift_mask = {0, 8, 16, 24, 32, 40, 48, 56};
+
+    /**
+     * read int in BIG_ENDIAN order
+     *
+     * @param sourceOffset     offset in source array
+     * @param dest       byte array (destination)
+     * @param destOffset offset in destination array
+     *
+     * @return offset in destination array (updated)
+     */
+    public static final int intToByte(int[] source, int sourceOffset, byte[] dest, int destOffset) {
+        int v = source[sourceOffset];
+        dest[destOffset++] = (byte) ((v >> 24) & 0xFF);
+        dest[destOffset++] = (byte) ((v >> 16) & 0xFF);
+        dest[destOffset++] = (byte) ((v >> 8) & 0xFF);
+        dest[destOffset++] = (byte) (v & 0xFF);
+        return destOffset;
+    }
+
+    public static final int intToByte(int v, byte[] dest, int destOffset) {
+        dest[destOffset++] = (byte) ((v >> 24) & 0xFF);
+        dest[destOffset++] = (byte) ((v >> 16) & 0xFF);
+        dest[destOffset++] = (byte) ((v >> 8) & 0xFF);
+        dest[destOffset++] = (byte) (v & 0xFF);
+        return destOffset;
+    }
+
+    /**
+     * convert <code>count</code> ints to bytes (Big Endian)
+     * @param source int array
+     * @param srcOffset start offset in <code>source</code> array
+     * @param count how much ints to process
+     * @param dest destination byte array
+     * @param destOffset
+     */
+    public static final void intToByte(int[] source, int srcOffset, int count, byte[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = source[srcOffset + i];
+            dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+            dest[destOffset++] = (byte) (v & 0xFF);
+        }
+    }
+
+    /**
+     *
+     * convert <code>count</code> ints to bytes (Big Endian)
+     * @param source int array
+     * @param srcOffset start offset in <code>source</code> array
+     * @param count how much ints to process
+     * @param dest destination byte array
+     * @param skip how much bytes should be thrown away before start writing to destination (1 to 3)
+     */
+    public static final void intToByteBE(int[] source, int srcOffset, int count, byte[] dest, int destOffset, int skip) {
+        if (skip > 0) {
+            int v = source[srcOffset++];
+            for (int i = skip; i < 4; i++) {
+                dest[destOffset++] = (byte) ((v >>> SHIFTS_INT_BE[i]) & 0xFF);
+            }
+        }
+        intToByte(source, srcOffset, count, dest, destOffset);
+    }
+
+    public static int charToByte(char[] source, int srcOffset, byte[] dest, int destOffset) {
+        int v = source[srcOffset];
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) (v & 0xFF);
+        return destOffset;
+    }
+
+    public static int charToByte(char v, byte[] dest, int destOffset) {
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) (v & 0xFF);
+        return destOffset;
+    }
+
+    public static void charToByte(char[] source, int srcOffset, int count, byte[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = source[srcOffset + i];
+            dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+            dest[destOffset++] = (byte) (v & 0xFF);
+        }
+    }
+
+    public static int doubleToByteBE(double[] source, int srcOffset, byte[] dest, int destOffset) {
+        double d = source[srcOffset];
+        long v = Double.doubleToLongBits(d);
+        dest[destOffset++] = (byte) ((v >>> 56) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 48) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 40) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 32) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) (v & 0xFF);
+        return destOffset;
+    }
+
+    public static int doubleToByteBE(double d, byte[] dest, int destOffset) {
+        long v = Double.doubleToLongBits(d);
+        dest[destOffset++] = (byte) ((v >>> 56) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 48) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 40) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 32) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) (v & 0xFF);
+        return destOffset;
+    }
+
+    public static void doubleToByteBE(double[] source, int srcOffset, int count, byte[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            double d = source[srcOffset + i];
+            long v = Double.doubleToLongBits(d);
+            dest[destOffset++] = (byte) ((v >>> 56) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 48) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 40) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 32) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+            dest[destOffset++] = (byte) (v & 0xFF);
+        }
+    }
+
+    public static int floatToByteBE(float[] source, int offset, byte[] dest, int destOffset) {
+        float f = source[offset];
+        int v = Float.floatToIntBits(f);
+        dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) (v & 0xFF);
+        return destOffset;
+    }
+
+    public static int floatToByteBE(float f, byte[] dest, int destOffset) {
+        int v = Float.floatToIntBits(f);
+        dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) (v & 0xFF);
+        return destOffset;
+    }
+
+    public static void floatToByteBE(float[] source, int offset, int count, byte[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            float f = source[offset + i];
+            int v = Float.floatToIntBits(f);
+            dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+            dest[destOffset++] = (byte) (v & 0xFF);
+        }
+    }
+
+    public static int longToByteBE(long[] source, int offset, byte[] dest, int destOffset) {
+        long v = source[offset];
+        dest[destOffset++] = (byte) ((v >>> 56) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 48) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 40) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 32) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) (v & 0xFF);
+
+        return destOffset;
+    }
+
+    public static int longToByteBE(long v, byte[] dest, int destOffset) {
+        dest[destOffset++] = (byte) ((v >>> 56) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 48) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 40) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 32) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) (v & 0xFF);
+
+        return destOffset;
+    }
+
+    public static void longToByteBE(long[] source, int offset, int count, byte[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            long v = source[offset + i];
+            dest[destOffset++] = (byte) ((v >>> 56) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 48) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 40) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 32) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+            dest[destOffset++] = (byte) (v & 0xFF);
+        }
+    }
+
+    public static int shortToByteBE(short[] source, int offset, byte[] dest, int destOffset) {
+        int v = source[offset];
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) (v & 0xFF);
+        return destOffset;
+    }
+
+    public static int shortToByteBE(short v, byte[] dest, int destOffset) {
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) (v & 0xFF);
+        return destOffset;
+    }
+
+    public static void shortToByteBE(short[] source, int offset, int count, byte[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = source[offset + i];
+            dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+            dest[destOffset++] = (byte) (v & 0xFF);
+        }
+    }
+
+    public static int shortToByteBE(final byte mask, short[] source, int offset, byte[] dest, int destOffset) {
+        final int bytesInShort = 2;
+        int v = source[offset];
+        for (int j = 0; j < bytesInShort; j++) {
+            if ((mask & byte_mask[j]) != 0) {
+                dest[destOffset++] = (byte) ((v >>> shift_mask[bytesInShort - 1 - j]) & 0xFF);
+            }
+        }
+        return destOffset;
+    }
+
+    public static void shortToByteBE(final byte mask, short[] source, int offset, int count, byte[] dest, int destOffset) {
+        final int bytesInShort = 2;
+        for (int i = 0; i < count; i++) {
+            int v = source[offset + i];
+            for (int j = 0; j < bytesInShort; j++) {
+                if ((mask & byte_mask[j]) != 0) {
+                    dest[destOffset++] = (byte) ((v >>> shift_mask[bytesInShort - 1 - j]) & 0xFF);
+                }
+            }
+        }
+    }
+
+    public static int intToByteBE(final byte mask, int[] source, int offset, byte[] dest, int destOffset) {
+        final int bytesInInt = 4;
+        int v = source[offset];
+        for (int j = 0; j < bytesInInt; j++) {
+            if ((mask & byte_mask[j]) != 0) {
+                dest[destOffset++] = (byte) ((v >>> shift_mask[bytesInInt - 1 - j]) & 0xFF);
+            }
+        }
+        return destOffset;
+    }
+
+    public static void intToByteBE(final byte mask, int[] source, int offset, int count, byte[] dest, int destOffset) {
+        final int bytesInInt = 4;
+        for (int i = 0; i < count; i++) {
+            int v = source[offset + i];
+            for (int j = 0; j < bytesInInt; j++) {
+                if ((mask & byte_mask[j]) != 0) {
+                    dest[destOffset++] = (byte) ((v >>> shift_mask[bytesInInt - 1 - j]) & 0xFF);
+                }
+            }
+        }
+    }
+
+    public static int longToByteBE(final byte mask, long[] source, int offset, byte[] dest, int destOffset) {
+        final int bytesInLong = 8;
+        long v = source[offset];
+        for (int j = 0; j < bytesInLong; j++) {
+            if ((mask & byte_mask[j]) != 0) {
+                dest[destOffset++] = (byte) ((v >>> shift_mask[bytesInLong - 1 - j]) & 0xFF);
+            }
+        }
+        return destOffset;
+    }
+
+    public static void longToByteBE(final byte mask, long[] source, int offset, int count, byte[] dest, int destOffset) {
+        final int bytesInLong = 8;
+        for (int i = 0; i < count; i++) {
+            long v = source[offset + i];
+            for (int j = 0; j < bytesInLong; j++) {
+                if ((mask & byte_mask[j]) != 0) {
+                    dest[destOffset++] = (byte) ((v >>> shift_mask[bytesInLong - 1 - j]) & 0xFF);
+                }
+            }
+        }
+    }
+
+    public static final int getBytesPerNumber(long mask) {
+        int bpi = 0;
+        for (int i = 0; i < byte_mask.length; i++) {
+            if ((mask & byte_mask[i]) != 0) {
+                bpi++;
+            }
+        }
+        return bpi;
+    }
+}
Index: ocean/src/com/imagero/uio/xform/XtoByteLE.java
===================================================================
--- ocean/src/com/imagero/uio/xform/XtoByteLE.java	(revision 0)
+++ ocean/src/com/imagero/uio/xform/XtoByteLE.java	(revision 0)
@@ -0,0 +1,299 @@
+package com.imagero.uio.xform;
+
+import com.imagero.uio.Transformer;
+
+/**
+ * Date: 10.01.2008
+ *
+ * @author Andrey Kuznetsov
+ */
+public class XtoByteLE {
+    static final int[] SHIFTS_INT_LE = new int[]{0, 8, 16, 24};
+
+    /**
+     * read int in LITTLE_ENDIAN order
+     *
+     * @param sourceOffset     offset in source array
+     * @param dest       byte array (destination)
+     * @param destOffset offset in destination array
+     *
+     * @return offset in destination array (updated)
+     */
+    public static final int intToByte(int[] source, int sourceOffset, byte[] dest, int destOffset) {
+        int v = source[sourceOffset];
+        dest[destOffset++] = (byte) (v & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+        return destOffset;
+    }
+
+    public static final int intToByte(int v, byte[] dest, int destOffset) {
+        dest[destOffset++] = (byte) (v & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+        return destOffset;
+    }
+
+    /**
+     * convert <code>count</code> ints to bytes (Little Endian)
+     * @param source int array
+     * @param srcOffset start offset in <code>source</code> array
+     * @param count how much ints to process
+     * @param dest destination byte array
+     * @param destOffset start offset in <code>dest</code> array
+     */
+    public static final void intToByte(int[] source, int srcOffset, int count, byte[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = source[srcOffset + i];
+            dest[destOffset++] = (byte) (v & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+        }
+    }
+
+    /**
+     * convert <code>count</code> ints to bytes (Little Endian)
+     * @param source int array
+     * @param srcOffset start offset in <code>source</code> array
+     * @param count how much ints to process
+     * @param dest destination byte array
+     * @param destOffset start offset in <code>dest</code> array
+     * @param skip how much bytes should be thrown away before start writing to destination (1 to 3)
+     */
+    public static final void intToByteLE(int[] source, int srcOffset, int count, byte[] dest, int destOffset, int skip) {
+        if (skip > 0) {
+            int v = source[srcOffset++];
+            for (int i = skip; i < 4; i++) {
+                dest[destOffset++] = (byte) ((v >>> SHIFTS_INT_LE[i]) & 0xFF);
+            }
+        }
+        intToByte(source, srcOffset, count, dest, destOffset);
+    }
+
+    public static final int charToByte(char[] source, int srcOffset, byte[] dest, int destOffset) {
+        int v = source[srcOffset];
+        dest[destOffset++] = (byte) (v & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        return destOffset;
+    }
+
+    public static final int charToByte(char v, byte[] dest, int destOffset) {
+        dest[destOffset++] = (byte) (v & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        return destOffset;
+    }
+
+    public static final void charToByte(char[] source, int srcOffset, int count, byte[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = source[srcOffset + i];
+            dest[destOffset++] = (byte) (v & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        }
+    }
+
+    public static int doubleToByteLE(double[] source, int srcOffset, byte[] dest, int destOffset) {
+        double d = source[srcOffset];
+        long v = Double.doubleToLongBits(d);
+        dest[destOffset++] = (byte) (v & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 32) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 40) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 48) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 56) & 0xFF);
+        return destOffset;
+    }
+
+    public static int doubleToByteLE(double d, byte[] dest, int destOffset) {
+        long v = Double.doubleToLongBits(d);
+        dest[destOffset++] = (byte) (v & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 32) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 40) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 48) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 56) & 0xFF);
+        return destOffset;
+    }
+
+    public static void doubleToByteLE(double[] source, int srcOffset, int count, byte[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            double d = source[srcOffset + i];
+            long v = Double.doubleToLongBits(d);
+            dest[destOffset++] = (byte) (v & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 32) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 40) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 48) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 56) & 0xFF);
+        }
+    }
+
+    public static int floatToByteLE(float[] source, int offset, byte[] dest, int destOffset) {
+        float f = source[offset];
+        int v = Float.floatToIntBits(f);
+        dest[destOffset++] = (byte) (v & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+        return destOffset;
+    }
+
+    public static int floatToByteLE(float f, byte[] dest, int destOffset) {
+        int v = Float.floatToIntBits(f);
+        dest[destOffset++] = (byte) (v & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+        return destOffset;
+    }
+
+    public static void floatToByteLE(float[] source, int offset, int count, byte[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            float f = source[offset + i];
+            int v = Float.floatToIntBits(f);
+            dest[destOffset++] = (byte) (v & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+        }
+    }
+
+    public static int longToByteLE(long[] source, int offset, byte[] dest, int destOffset) {
+        long v = source[offset];
+        dest[destOffset++] = (byte) (v & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 32) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 40) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 48) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 56) & 0xFF);
+
+        return destOffset;
+    }
+
+    public static int longToByteLE(long v, byte[] dest, int destOffset) {
+        dest[destOffset++] = (byte) (v & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 32) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 40) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 48) & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 56) & 0xFF);
+
+        return destOffset;
+    }
+
+    public static void longToByteLE(long[] source, int offset, int count, byte[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            long v = source[offset + i];
+            dest[destOffset++] = (byte) (v & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 16) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 24) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 32) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 40) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 48) & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 56) & 0xFF);
+        }
+    }
+
+    public static int shortToByteLE(short[] source, int offset, byte[] dest, int destOffset) {
+        int v = source[offset];
+        dest[destOffset++] = (byte) (v & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        return destOffset;
+    }
+
+    public static int shortToByteLE(short v, byte[] dest, int destOffset) {
+        dest[destOffset++] = (byte) (v & 0xFF);
+        dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        return destOffset;
+    }
+
+    public static void shortToByteLE(short[] source, int offset, int count, byte[] dest, int destOffset) {
+        for (int i = 0; i < count; i++) {
+            int v = source[offset + i];
+            dest[destOffset++] = (byte) (v & 0xFF);
+            dest[destOffset++] = (byte) ((v >>> 8) & 0xFF);
+        }
+    }
+
+    public static int shortToByteLE(final byte mask, short[] source, int offset, byte[] dest, int destOffset) {
+        int v = source[offset];
+        final int bytesInShort = 2;
+        for (int j = 0; j < bytesInShort; j++) {
+            if ((mask & XtoByteBE.byte_mask[j]) != 0) {
+                dest[destOffset++] = (byte) ((v >>> XtoByteBE.shift_mask[j]) & 0xFF);
+            }
+        }
+        return destOffset;
+    }
+
+    public static void shortToByteLE(final byte mask, short[] source, int offset, int count, byte[] dest, int destOffset) {
+        final int bytesInShort = 2;
+        for (int i = 0; i < count; i++) {
+            int v = source[offset + i];
+            for (int j = 0; j < bytesInShort; j++) {
+                if ((mask & XtoByteBE.byte_mask[j]) != 0) {
+                    dest[destOffset++] = (byte) ((v >>> XtoByteBE.shift_mask[j]) & 0xFF);
+                }
+            }
+        }
+    }
+
+    public static int intToByteLE(final byte mask, int[] source, int offset, byte[] dest, int destOffset) {
+        int v = source[offset];
+        final int bytesInInt = 4;
+        for (int j = 0; j < bytesInInt; j++) {
+            if ((mask & XtoByteBE.byte_mask[j]) != 0) {
+                dest[destOffset++] = (byte) ((v >>> XtoByteBE.shift_mask[j]) & 0xFF);
+            }
+        }
+        return destOffset;
+    }
+
+    public static void intToByteLE(final byte mask, int[] source, int offset, int count, byte[] dest, int destOffset) {
+        final int bytesInInt = 4;
+        for (int i = 0; i < count; i++) {
+            int v = source[offset + i];
+            for (int j = 0; j < bytesInInt; j++) {
+                if ((mask & XtoByteBE.byte_mask[j]) != 0) {
+                    dest[destOffset++] = (byte) ((v >>> XtoByteBE.shift_mask[j]) & 0xFF);
+                }
+            }
+        }
+    }
+
+    public static int longToByteLE(final byte mask, long[] source, int offset, byte[] dest, int destOffset) {
+        long v = source[offset];
+        final int bytesInLong = 8;
+        for (int j = 0; j < bytesInLong; j++) {
+            if ((mask & XtoByteBE.byte_mask[j]) != 0) {
+                dest[destOffset++] = (byte) ((v >>> XtoByteBE.shift_mask[j]) & 0xFF);
+            }
+        }
+        return destOffset;
+    }
+
+    public static void longToByteLE(final byte mask, long[] source, int offset, int count, byte[] dest, int destOffset) {
+        final int bytesInLong = 8;
+        for (int i = 0; i < count; i++) {
+            long v = source[offset + i];
+            for (int j = 0; j < bytesInLong; j++) {
+                if ((mask & XtoByteBE.byte_mask[j]) != 0) {
+                    dest[destOffset++] = (byte) ((v >>> XtoByteBE.shift_mask[j]) & 0xFF);
+                }
+            }
+        }
+    }
+}
Index: ocean/src/com/imagero/uio/xform/XTransformer.java
===================================================================
--- ocean/src/com/imagero/uio/xform/XTransformer.java	(revision 0)
+++ ocean/src/com/imagero/uio/xform/XTransformer.java	(revision 0)
@@ -0,0 +1,567 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * http://uio.imagero.com
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.uio.xform;
+
+
+/**
+ * Primitive type conversion, array copying, etc.
+ * The difference to Transformer is that XTransformer does not restricted to big and little endiannes.
+ * User may read bytes in any order.
+ *
+ * @author Andrey Kuznetsov
+ */
+public class XTransformer {
+
+    protected static final int[] shifts = new int[]{0, 8, 16, 24, 32, 40, 48, 56};
+    protected static final int[] POSITIONS_BE = {0, 1, 2, 3, 4, 5, 6, 7};
+    protected static final int[] POSITIONS_LE_SHORT = {1, 0};
+    protected static final int[] POSITIONS_LE_INT = {3, 2, 1, 0};
+    protected static final int[] POSITIONS_LE_LONG = {7, 6, 5, 4, 3, 2, 1, 0};
+    protected static final int[][] POSITIONS_LE = {POSITIONS_BE, POSITIONS_LE_SHORT, POSITIONS_LE_INT, POSITIONS_LE_LONG};
+
+    public static final int TYPE_BE = 0;
+    public static final int TYPE_LE_SHORT = 1;
+    public static final int TYPE_LE_INT = 2;
+    public static final int TYPE_LE_LONG = 3;
+
+    public static final int[] get(int type) {
+        return POSITIONS_LE[type];
+    }
+
+    /**
+     * write int in BIG_ENDIAN order
+     *
+     * @param source       source byte array
+     * @param sourceOffset offset in source array
+     * @param destOffset   offset in destination array
+     *
+     * @return new offset in source array (for next writeUnitXX)
+     */
+    public static final int byteToInt(byte[] source, int sourceOffset, int[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        int v = ((source[sourceOffset + positions[p++]] & 0xFF) << 24)
+                | ((source[sourceOffset + positions[p++]] & 0xFF) << 16)
+                | ((source[sourceOffset + positions[p++]] & 0xFF) << 8)
+                | (source[sourceOffset + positions[p++]] & 0xFF);
+        dest[destOffset] = v;
+        return sourceOffset + p;
+    }
+
+    public static final int byteToInt(byte[] source, int sourceOffset, int[] positions) {
+        int p = 0;
+        return ((source[sourceOffset + positions[p++]] & 0xFF) << 24)
+                | ((source[sourceOffset + positions[p++]] & 0xFF) << 16)
+                | ((source[sourceOffset + positions[p++]] & 0xFF) << 8)
+                | (source[sourceOffset + positions[p++]] & 0xFF);
+    }
+
+    /**
+     * read int in BIG_ENDIAN order
+     *
+     * @param sourceOffset     offset in source array
+     * @param dest       byte array (destination)
+     * @param destOffset offset in destination array
+     *
+     * @return offset in destination array (updated)
+     */
+    public static final int intToByte(int[] source, int sourceOffset, byte[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        int v = source[sourceOffset];
+        dest[destOffset + positions[p++]] = (byte) ((v >> 24) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >> 16) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >> 8) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+        return destOffset + 4;
+    }
+
+    public static final int intToByte(int v, byte[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        dest[destOffset + positions[p++]] = (byte) ((v >> 24) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >> 16) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >> 8) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+        return destOffset + 4;
+    }
+
+    /**
+     * convert <code>count</code> ints to bytes (Big Endian)
+     * @param source int array
+     * @param srcOffset start offset in <code>source</code> array
+     * @param count how much ints to process
+     * @param dest destination byte array
+     * @param destOffset
+     */
+    public static final void intToByte(int[] source, int srcOffset, int count, byte[] dest, int destOffset, int[] positions) {
+        for (int i = 0; i < count; i++) {
+            int p = 0;
+            int v = source[srcOffset + i];
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 24) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 16) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 8) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+            destOffset += 4;
+        }
+    }
+
+
+    public static final void byteToInt(byte[] source, int sourceOffset, int count, int[] dest, int destOffset, int[] positions) {
+        for (int i = 0; i < count; i++) {
+            int p = 0;
+            int v = ((source[sourceOffset + positions[p++]] & 0xFF) << 24)
+                    | ((source[sourceOffset + positions[p++]] & 0xFF) << 16)
+                    | ((source[sourceOffset + positions[p++]] & 0xFF) << 8)
+                    | (source[sourceOffset + positions[p++]] & 0xFF);
+            dest[destOffset + i] = v;
+            sourceOffset += 4;
+        }
+    }
+
+
+/* **********************************************************************/
+
+    public static int byteToChar(byte[] source, int sourceOffset, char[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        int v = ((source[sourceOffset + positions[p++]] & 0xFF) << 8)
+                | (source[sourceOffset + positions[p++]] & 0xFF);
+        dest[destOffset] = (char) v;
+        return sourceOffset + 2;
+    }
+
+    public static int byteToChar(byte[] source, int sourceOffset, int[] positions) {
+        int p = 0;
+        return ((source[sourceOffset + positions[p++]] & 0xFF) << 8) | (source[sourceOffset + positions[p++]] & 0xFF);
+    }
+
+    public static void byteToChar(byte[] source, int sourceOffset, int count, char[] dest, int destOffset, int[] positions) {
+        for (int i = 0; i < count; i++) {
+            int p = 0;
+            int v = ((source[sourceOffset + positions[p++]] & 0xFF) << 8)
+                    | (source[sourceOffset + positions[p++]] & 0xFF);
+            dest[destOffset + i] = (char) v;
+        }
+    }
+
+/* **********************************************************************/
+
+    public static int charToByte(char[] source, int srcOffset, byte[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        int v = source[srcOffset];
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+        return destOffset + 2;
+    }
+
+    public static int charToByte(char v, byte[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+        return destOffset + 2;
+    }
+
+    public static void charToByte(char[] source, int srcOffset, int count, byte[] dest, int destOffset, int[] positions) {
+        for (int i = 0; i < count; i++) {
+            int p = 0;
+            int v = source[srcOffset + i];
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 8) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+            destOffset += 2;
+        }
+    }
+
+/* **********************************************************************/
+
+    public static int byteToDouble(byte[] source, int sourceOffset, double[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        long v = ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 56)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 48)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 40)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 32)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 24)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 16)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 8)
+                | (source[sourceOffset + positions[p++]] & 0xFF);
+        dest[destOffset] = Double.longBitsToDouble(v);
+        return sourceOffset + 8;
+    }
+
+    public static double byteToDouble(byte[] source, int sourceOffset, int[] positions) {
+        int p = 0;
+        long v = ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 56)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 48)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 40)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 32)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 24)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 16)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 8)
+                | (source[sourceOffset + positions[p++]] & 0xFF);
+        return Double.longBitsToDouble(v);
+    }
+
+    public static void byteToDouble(byte[] source, int sourceOffset, int count, double[] dest, int destOffset, int[] positions) {
+        for (int i = 0; i < count; i++) {
+            int p = 0;
+            long v = ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 56)
+                    | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 48)
+                    | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 40)
+                    | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 32)
+                    | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 24)
+                    | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 16)
+                    | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 8)
+                    | (source[sourceOffset + positions[p++]] & 0xFF);
+            dest[destOffset + i] = Double.longBitsToDouble(v);
+        }
+    }
+
+/* **********************************************************************/
+
+    public static int doubleToByte(double[] source, int srcOffset, byte[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        double d = source[srcOffset];
+        long v = Double.doubleToLongBits(d);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 56) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 48) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 40) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 32) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 24) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+        return destOffset + 8;
+    }
+
+    public static int doubleToByte(double d, byte[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        long v = Double.doubleToLongBits(d);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 56) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 48) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 40) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 32) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 24) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+        return destOffset + 8;
+    }
+
+    public static void doubleToByte(double[] source, int srcOffset, int count, byte[] dest, int destOffset, int[] positions) {
+        for (int i = 0; i < count; i++) {
+            int p = 0;
+            double d = source[srcOffset + i];
+            long v = Double.doubleToLongBits(d);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 56) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 48) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 40) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 32) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 24) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 16) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 8) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+            destOffset += 8;
+        }
+    }
+
+/* **********************************************************************/
+
+    public static int byteToFloat(byte[] source, int sourceOffset, float[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        int v = ((source[sourceOffset + positions[p++]] & 0xFF) << 24)
+                | ((source[sourceOffset + positions[p++]] & 0xFF) << 16)
+                | ((source[sourceOffset + positions[p++]] & 0xFF) << 8)
+                | (source[sourceOffset + positions[p++]] & 0xFF);
+        dest[destOffset] = Float.intBitsToFloat(v);
+        return sourceOffset;
+    }
+
+    public static float byteToFloat(byte[] source, int sourceOffset, int[] positions) {
+        int p = 0;
+        int v = ((source[sourceOffset + positions[p++]] & 0xFF) << 24)
+                | ((source[sourceOffset + positions[p++]] & 0xFF) << 16)
+                | ((source[sourceOffset + positions[p++]] & 0xFF) << 8)
+                | (source[sourceOffset + positions[p++]] & 0xFF);
+        return Float.intBitsToFloat(v);
+    }
+
+    public static void byteToFloat(byte[] source, int sourceOffset, int count, float[] dest, int destOffset, int[] positions) {
+        for (int i = 0; i < count; i++) {
+            int p = 0;
+            int v = ((source[sourceOffset + positions[p++]] & 0xFF) << 24)
+                    | ((source[sourceOffset + positions[p++]] & 0xFF) << 16)
+                    | ((source[sourceOffset + positions[p++]] & 0xFF) << 8)
+                    | (source[sourceOffset + positions[p++]] & 0xFF);
+            dest[destOffset + i] = Float.intBitsToFloat(v);
+        }
+    }
+
+/* **********************************************************************/
+
+    public static int floatToByte(float[] source, int offset, byte[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        float f = source[offset];
+        int v = Float.floatToIntBits(f);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 24) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+        return destOffset + 4;
+    }
+
+    public static int floatToByte(float f, byte[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        int v = Float.floatToIntBits(f);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 24) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+        return destOffset + 4;
+    }
+
+    public static void floatToByte(float[] source, int offset, int count, byte[] dest, int destOffset, int[] positions) {
+        for (int i = 0; i < count; i++) {
+            int p = 0;
+            float f = source[offset + i];
+            int v = Float.floatToIntBits(f);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 24) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 16) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 8) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+            destOffset += 4;
+        }
+    }
+
+/* **********************************************************************/
+
+    public static int byteToLong(byte[] source, int sourceOffset, long[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        long v = ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 56)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 48)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 40)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 32)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 24)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 16)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 8)
+                | (source[sourceOffset + positions[p++]] & 0xFF);
+
+        dest[destOffset] = v;
+        return sourceOffset + 8;
+    }
+
+    public static long byteToLong(byte[] source, int sourceOffset, int[] positions) {
+        int p = 0;
+        return ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 56)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 48)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 40)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 32)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 24)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 16)
+                | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 8)
+                | (source[sourceOffset + positions[p++]] & 0xFF);
+    }
+
+    public static void byteToLong(byte[] source, int sourceOffset, int count, long[] dest, int destOffset, int[] positions) {
+        for (int i = 0; i < count; i++) {
+            int p = 0;
+            long v = ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 56)
+                    | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 48)
+                    | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 40)
+                    | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 32)
+                    | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 24)
+                    | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 16)
+                    | ((long) (source[sourceOffset + positions[p++]] & 0xFF) << 8)
+                    | (source[sourceOffset + positions[p++]] & 0xFF);
+
+            dest[destOffset + i] = v;
+        }
+    }
+
+/* **********************************************************************/
+
+    public static int longToByte(long[] source, int offset, byte[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        long v = source[offset];
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 56) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 48) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 40) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 32) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 24) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+
+        return destOffset + 8;
+    }
+
+    public static int longToByte(long v, byte[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 56) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 48) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 40) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 32) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 24) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 16) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+
+        return destOffset;
+    }
+
+    public static void longToByte(long[] source, int offset, int count, byte[] dest, int destOffset, int[] positions) {
+        for (int i = 0; i < count; i++) {
+            int p = 0;
+            long v = source[offset + i];
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 56) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 48) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 40) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 32) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 24) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 16) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 8) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+            destOffset += 8;
+        }
+    }
+
+/* **********************************************************************/
+
+    public static int byteToShort(byte[] source, int sourceOffset, short[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        int v = ((source[sourceOffset + positions[p++]] & 0xFF) << 8)
+                | (source[sourceOffset + positions[p++]] & 0xFF);
+        dest[destOffset] = (short) v;
+        return sourceOffset + 2;
+    }
+
+    public static int byteToShort(byte[] source, int sourceOffset, int[] positions) {
+        int p = 0;
+        return ((source[sourceOffset + positions[p++]] & 0xFF) << 8) | (source[sourceOffset + positions[p++]] & 0xFF);
+    }
+
+    public static void byteToShort(byte[] source, int sourceOffset, int count, short[] dest, int destOffset, int[] positions) {
+        for (int i = 0; i < count; i++) {
+            int p = 0;
+            int v = ((source[sourceOffset + positions[p++]] & 0xFF) << 8)
+                    | (source[sourceOffset + positions[p++]] & 0xFF);
+            dest[destOffset + i] = (short) v;
+        }
+    }
+
+/* **********************************************************************/
+
+    public static int shortToByte(short[] source, int offset, byte[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        int v = source[offset];
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+        return destOffset + 2;
+    }
+
+    public static int shortToByte(short v, byte[] dest, int destOffset, int[] positions) {
+        int p = 0;
+        dest[destOffset + positions[p++]] = (byte) ((v >>> 8) & 0xFF);
+        dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+        return destOffset + 2;
+    }
+
+    public static void shortToByte(short[] source, int offset, int count, byte[] dest, int destOffset, int[] positions) {
+        for (int i = 0; i < count; i++) {
+            int p = 0;
+            int v = source[offset + i];
+            dest[destOffset + positions[p++]] = (byte) ((v >>> 8) & 0xFF);
+            dest[destOffset + positions[p++]] = (byte) (v & 0xFF);
+            destOffset += 2;
+        }
+    }
+
+    /* **********************************************************************/
+
+    public static final void byteToInt(final int bytesInInt, byte[] source, int sourceOffset, final int count, int[] dest, int destOffset, int[] positions) {
+        for (int i = 0; i < count; i++) {
+            int v = 0;
+            for (int j = 0; j < bytesInInt; j++) {
+                v = (v << 8) | (source[sourceOffset + positions[j]] & 0xFF);
+            }
+            dest[destOffset++] = v & 0xFFFFFFFF;
+            sourceOffset += bytesInInt;
+        }
+    }
+
+    public static final void byteToShort(final int bytesInShort, byte[] source, int sourceOffset, final int count, short[] dest, int destOffset, int[] positions) {
+        for (int i = 0; i < count; i++) {
+            int v = 0;
+            for (int j = 0; j < bytesInShort; j++) {
+                v = (v << 8) | (source[sourceOffset + positions[j]] & 0xFF);
+            }
+            dest[destOffset++] = (short) v;
+            sourceOffset += bytesInShort;
+        }
+    }
+
+    public static final void byteToLong(final int bytesInLong, byte[] source, int sourceOffset, final int count, long[] dest, int destOffset, int[] positions) {
+        for (int i = 0; i < count; i++) {
+            long v = 0;
+            for (int j = 0; j < bytesInLong; j++) {
+                v = (v << 8) | (source[sourceOffset + positions[j]] & 0xFF);
+            }
+            dest[destOffset++] = v;
+            sourceOffset += bytesInLong;
+        }
+    }
+
+/* **********************************************************************/
+
+
+    public static final int byteToInt(int bytesInInt, byte[] source, int sourceOffset, int[] dest, int destOffset, int[] positions) {
+        int v = 0;
+        for (int i = 0; i < bytesInInt; i++) {
+            v = (v << 8) | (source[sourceOffset + positions[i]] & 0xFF);
+        }
+        dest[destOffset] = v & 0xFFFFFFFF;
+        return sourceOffset + bytesInInt;
+    }
+
+    public static final int byteToShort(int bytesInShort, byte[] source, int sourceOffset, short[] dest, int destOffset, int[] positions) {
+        int v = 0;
+        for (int i = 0; i < bytesInShort; i++) {
+            v = (v << 8) | (source[sourceOffset + positions[i]] & 0xFF);
+        }
+        dest[destOffset] = (short) v;
+        return sourceOffset + bytesInShort;
+    }
+
+    public static final int byteToLong(int bytesInLong, byte[] source, int sourceOffset, long[] dest, int destOffset, int[] positions) {
+        long v = 0;
+        for (int i = 0; i < bytesInLong; i++) {
+            v = (v << 8) | (source[sourceOffset + positions[i]] & 0xFF);
+        }
+        dest[destOffset] = v;
+        return sourceOffset + bytesInLong;
+    }
+}
\ No newline at end of file
Index: ocean/src/com/imagero/util/Ring.java
===================================================================
--- ocean/src/com/imagero/util/Ring.java	(revision 0)
+++ ocean/src/com/imagero/util/Ring.java	(revision 0)
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of imagero Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+ package com.imagero.util;
+
+import java.util.EmptyStackException;
+
+/**
+ * Ring implementation.
+ */
+public class Ring {
+
+    Object[] elements;
+
+    int start;
+    int count;
+
+    int max;
+
+
+    public Ring() {
+        this(10);
+    }
+
+    /**
+     * create new Ring object
+     * @param size Ring size
+     */
+    public Ring(int size) {
+        this.elements = new Object[size * 2];
+        start = elements.length;
+        max = size;
+    }
+
+    public Object add(Object item) {
+        if (start > 0) {
+            elements[--start] = item;
+            if (count < max) {
+                count++;
+            }
+        }
+        else {
+            System.arraycopy(elements, 0, elements, max, max);
+            start = max;
+            elements[--start] = item;
+        }
+        return item;
+    }
+
+    public Object get(int index) {
+        if (count > 0) {
+            if (index >= 0 && index < count) {
+                return elements[start + index];
+            }
+            else {
+                throw new IndexOutOfBoundsException();
+            }
+        }
+        else {
+            throw new EmptyStackException();
+        }
+    }
+
+    public int getCount() {
+        return count;
+    }
+
+    public boolean isEmpty() {
+        return count == 0;
+    }
+}
Index: ocean/src/com/imagero/util/Vector.java
===================================================================
--- ocean/src/com/imagero/util/Vector.java	(revision 0)
+++ ocean/src/com/imagero/util/Vector.java	(revision 0)
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of imagero Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.util;
+
+import java.util.EmptyStackException;
+import java.util.Enumeration;
+
+/**
+ * Minimal Vector implementation without synchronization
+ */
+public final class Vector {
+
+    Object[] elements;
+
+    int size;
+    int startSize;
+
+
+    public Vector() {
+        this(3);
+    }
+
+    public Vector(int size) {
+        this.startSize = size;
+        this.elements = new Object[size];
+    }
+
+    public void add(Object item) {
+        checkSize(size + 1);
+        elements[size++] = item;
+    }
+
+    public void insert(Object item, int pos) {
+        checkSize(size + 1);
+        System.arraycopy(elements, pos, elements, pos + 1, size - pos);
+        elements[pos] = item;
+        size++;
+    }
+
+    public Object pop() {
+        if (size > 0) {
+            size--;
+            Object obj = elements[size];
+            elements[size] = null;
+            return obj;
+        }
+        throw new EmptyStackException();
+    }
+
+    public Object peek() {
+        if (size > 0) {
+            Object obj = elements[size - 1];
+            return obj;
+        }
+        throw new EmptyStackException();
+    }
+
+    public int indexOf(Object o) {
+        if (o != null) {
+            for (int i = 0; i < size; i++) {
+                if (elements[i] != null && elements[i].equals(o)) {
+                    return i;
+                }
+            }
+        }
+        return -1;
+    }
+
+    public boolean contains(Object o) {
+        return indexOf(o) >= 0;
+    }
+
+    public boolean remove(Object o) {
+        int index = indexOf(o);
+        if (index >= 0) {
+            remove(index);
+        }
+        return index >= 0;
+    }
+
+    public Object remove(int index) {
+        if (index >= 0 && index < size) {
+            Object obj = elements[index];
+            int length = size - index - 1;
+            if (length > 0) {
+                System.arraycopy(elements, index + 1, elements, index, length);
+                elements[size - 1] = null;
+            }
+            size--;
+            return obj;
+        } else {
+            throw new IndexOutOfBoundsException("" + index);
+        }
+    }
+
+    public void remove(int from, int count) {
+        if (from >= 0 && from + count <= size) {
+            int length = size - (from + count) - 1;
+            if (length > 0) {
+                System.arraycopy(elements, from + count + 1, elements, from, length);
+            }
+            size -= count;
+            for (int i = 0; i < length; i++) {
+                elements[size + i] = null;
+            }
+        } else {
+            throw new IndexOutOfBoundsException("" + from);
+        }
+    }
+
+    public Object elementAt(int index) {
+        if (index >= 0 && index < size) {
+            Object obj = elements[index];
+            return obj;
+        } else {
+            throw new IndexOutOfBoundsException("" + index);
+        }
+    }
+
+    /**
+     * replace element at given index.
+     * @param index element index
+     * @param o Object
+     * @return replaced Object
+     */
+    public Object setElementAt(int index, Object o) {
+        if (index >= 0 && index < size) {
+            Object tmp = elements[index];
+            elements[index] = o;
+            return tmp;
+        } else {
+            throw new IndexOutOfBoundsException("" + index);
+        }
+    }
+
+    public int getCount() {
+        return size;
+    }
+
+    public int size() {
+        return size;
+    }
+
+    public boolean isEmpty() {
+        return size == 0;
+    }
+
+    private void checkSize(int minSize) {
+        if (minSize > elements.length) {
+            Object oldData[] = elements;
+            elements = new Object[Math.max(minSize + 10, elements.length * 2)];
+            System.arraycopy(oldData, 0, elements, 0, size);
+        }
+    }
+
+    public Enumeration elements() {
+        return new Enumeration() {
+            int pos;
+
+            public boolean hasMoreElements() {
+                return pos < getCount();
+            }
+
+            public Object nextElement() {
+                if (hasMoreElements()) {
+                    return elements[pos++];
+                } else {
+                    throw new IndexOutOfBoundsException();
+                }
+            }
+        };
+    }
+
+    public int getCount(Class c) {
+        int count = 0;
+        for (int i = 0; i < elements.length; i++) {
+            if (c.isInstance(elements[i])) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    public void clear() {
+        elements = new Object[startSize];
+        size = 0;
+    }
+
+    public void toArray(Object[] dest) {
+        System.arraycopy(elements, 0, dest, 0, Math.min(size, dest.length));
+    }
+}
Index: ocean/src/com/imagero/util/Fifo.java
===================================================================
--- ocean/src/com/imagero/util/Fifo.java	(revision 0)
+++ ocean/src/com/imagero/util/Fifo.java	(revision 0)
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of imagero Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+ package com.imagero.util;
+
+import java.util.EmptyStackException;
+
+/**
+ *
+ * Optimized Firt-In-First-Out implementation.
+ *
+ * @author Andrey Kuznetsov
+ */
+public class Fifo {
+
+    protected Object[] elements;
+
+    protected int readPos;
+    protected int writePos;
+
+    int threshold = 10;
+
+    public Fifo() {
+        this(10);
+    }
+
+    public Fifo(int count) {
+        this.elements = new Object[count];
+    }
+
+    public void push(Object o) {
+        checkWritePos();
+        elements[writePos++] = o;
+    }
+
+    public int size() {
+        return writePos - readPos;
+    }
+
+    protected void checkWritePos() {
+        if(writePos >= elements.length) {
+            int length = size();
+            if(readPos > 0) {
+                System.arraycopy(elements, readPos, elements, 0, length);
+                readPos = 0;
+                int wp = writePos;
+                writePos = length;
+                for (int i = writePos; i < wp; i++) {
+                    elements[i] = null;
+                }
+            }
+            else {
+                int nl = elements.length + (int) Math.min(4, Math.log(elements.length));
+                Object [] tmp = new Object[nl];
+                System.arraycopy(elements, 0, tmp, 0, length);
+                elements = tmp;
+            }
+        }
+    }
+
+    public Object pop() {
+        if(readPos < writePos) {
+            return elements[readPos++];
+        }
+        else {
+            throw new EmptyStackException();
+        }
+    }
+}
Index: ocean/src/com/imagero/util/OpenVector.java
===================================================================
--- ocean/src/com/imagero/util/OpenVector.java	(revision 0)
+++ ocean/src/com/imagero/util/OpenVector.java	(revision 0)
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of imagero Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+ package com.imagero.util;
+
+
+
+/**
+ * OpenVector is rather an array with some check and management functions.
+ * Use on own risk!
+ */
+public class OpenVector {
+
+    private Object[] elements;
+
+    int startSize;
+
+
+    public OpenVector() {
+        this(3);
+    }
+
+    public OpenVector(int size) {
+        this.startSize = size;
+        this.elements = create();
+    }
+
+    public int indexOf(Object o) {
+        if (o != null) {
+            for (int i = 0; i < elements.length; i++) {
+                if (elements[i] != null && elements[i].equals(o)) {
+                    return i;
+                }
+            }
+        }
+        return -1;
+    }
+
+    public boolean contains(Object o) {
+        return indexOf(o) >= 0;
+    }
+
+    public boolean remove(Object o) {
+        int index = indexOf(o);
+        if (index >= 0) {
+            elements[index] = null;
+        }
+        return index >= 0;
+    }
+
+    public Object [] checkSize(int minSize) {
+        if (minSize > elements.length) {
+            Object oldData[] = elements;
+            elements = create((int) Math.max(minSize + 10, elements.length + Math.sqrt(elements.length)));
+            System.arraycopy(oldData, 0, elements, 0, elements.length);
+        }
+        return elements;
+    }
+
+    public int getCount(Class c) {
+        int count = 0;
+        for (int i = 0; i < elements.length; i++) {
+            if (c.isInstance(elements[i])) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    /**
+     * OpenVector allows direct access to its elements array.
+     * Use on own risc - e.g. use it only for reading, don't change anything in this array!
+     */
+    public Object[] getElements() {
+        return elements;
+    }
+
+    public Object [] clear() {
+        elements = create();
+        return elements;
+    }
+
+    protected Object[] create() {
+        return new Object[startSize];
+    }
+
+    protected Object[] create(int size) {
+        return new Object[size];
+    }
+}
Index: ocean/src/com/imagero/util/Stack.java
===================================================================
--- ocean/src/com/imagero/util/Stack.java	(revision 0)
+++ ocean/src/com/imagero/util/Stack.java	(revision 0)
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of imagero Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+ package com.imagero.util;
+
+import java.util.EmptyStackException;
+
+/**
+ * Stack implementation not based on Vector
+ */
+public class Stack {
+
+    Object[] elements;
+
+    int count;
+
+
+    public Stack() {
+        this(10);
+    }
+
+    public Stack(int count) {
+        this.elements = new Object[count];
+    }
+
+    public Object push(Object item) {
+        checkSize(count + 1);
+        elements[count++] = item;
+        return item;
+    }
+
+    public Object pop() {
+        if (count > 0) {
+            count--;
+            Object obj = elements[count];
+            elements[count] = null;
+            return obj;
+        }
+        throw new EmptyStackException();
+    }
+
+    public Object peek() {
+        if(count == 0) {
+            throw new EmptyStackException();
+        }
+        return elements[count - 1];
+    }
+
+    public Object peek(int index) {
+        if(index < count) {
+            return elements[index];
+        }
+        else {
+            throw new IndexOutOfBoundsException();
+        }
+    }
+
+    public int getCount() {
+        return count;
+    }
+
+    public boolean isEmpty() {
+        return count == 0;
+    }
+
+    private void checkSize(int minSize) {
+        if (minSize > elements.length) {
+            Object oldData[] = elements;
+            elements = new Object[Math.max(minSize + 10, elements.length * 2)];
+            System.arraycopy(oldData, 0, elements, 0, count);
+        }
+    }
+}
Index: ocean/src/com/imagero/util/HashBag.java
===================================================================
--- ocean/src/com/imagero/util/HashBag.java	(revision 0)
+++ ocean/src/com/imagero/util/HashBag.java	(revision 0)
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of imagero Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.util;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+/**
+ * HashBag - every key corresponds to multiple values
+ */
+public class HashBag {
+    private final Hashtable hashtable = new Hashtable();
+
+    public int size() {
+        return hashtable.size();
+    }
+
+    public boolean isEmpty() {
+        return hashtable.isEmpty();
+    }
+
+    public Enumeration keys() {
+        return hashtable.keys();
+    }
+
+    public Object get(Object key, int index) {
+        Vector list = (Vector) hashtable.get(key);
+        if (list != null) {
+            return list.elementAt(index);
+        }
+        return null;
+    }
+
+    public void put(Object key, Object value) {
+        Vector list = (Vector) hashtable.get(key);
+        if (list == null) {
+            list = new Vector(1);
+            hashtable.put(key, list);
+        }
+        list.add(value);
+    }
+
+    /**
+     * removes the key and all corresponding values from HashBag
+     * @param key
+     */
+    public Object[] remove(Object key) {
+        Vector list = (Vector) hashtable.remove(key);
+        if (list == null) {
+            return new Object[0];
+        }
+        Object o [] = new Object[list.size()];
+        for (int i = 0; i < o.length; i++) {
+            o[i] = list.elementAt(i);
+        }
+        return o;
+    }
+
+    /**
+     * remove Object from HashBag
+     * @param key key to search
+     * @param index index to remove
+     * @return Object
+     */
+    public Object remove(Object key, int index) {
+        Vector list = (Vector) hashtable.get(key);
+        if (list != null) {
+            Object o = list.elementAt(index);
+            list.remove(index);
+            if (list.size() == 0) {
+                hashtable.remove(key);
+            }
+            return o;
+        }
+        return null;
+    }
+
+    /**
+     * remove Object from HashBag
+     * @param key key to search
+     * @param obj Object to remove
+     * @return true if <code>obj</code> was found
+     */
+    public boolean remove(Object key, Object obj) {
+        Vector list = (Vector) hashtable.get(key);
+        try {
+            return list != null && list.remove(obj);
+        }
+        finally {
+            if (list != null && list.size() == 0) {
+                hashtable.remove(key);
+            }
+        }
+    }
+
+    /**
+     * get count of objects correspondings to <code>key</code>
+     * @param key
+     */
+    public int getCount(Object key) {
+        Vector list = (Vector) hashtable.get(key);
+        if (list != null) {
+            return list.size();
+        }
+        return 0;
+    }
+
+    /**
+     * Returns true if HashBag contains the specified element
+     * @param o object to be checked
+     * @return true if the specified element is contained in HashBag.
+     */
+    public boolean contains(Object o) {
+        Enumeration enums = hashtable.keys();
+        while (enums.hasMoreElements()) {
+            Object key = enums.nextElement();
+            Vector v = (Vector) hashtable.get(key);
+            if (v.contains(o)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if HashBag contains the specified element under given key.
+     * @param key key to search
+     * @param o Object to search
+     * @return true if specified element was found
+     */
+    public boolean contains(Object key, Object o) {
+        Vector v = (Vector) hashtable.get(key);
+        if (v.contains(o)) {
+            return true;
+        }
+        return false;
+    }
+
+
+}
Index: ocean/src/com/imagero/util/Fifo.java
===================================================================
--- ocean/src/com/imagero/util/Fifo.java	(revision 0)
+++ ocean/src/com/imagero/util/Fifo.java	(revision 0)
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of imagero Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+ package com.imagero.util;
+
+import java.util.EmptyStackException;
+
+/**
+ *
+ * Optimized Firt-In-First-Out implementation.
+ *
+ * @author Andrey Kuznetsov
+ */
+public class Fifo {
+
+    protected Object[] elements;
+
+    protected int readPos;
+    protected int writePos;
+
+    int threshold = 10;
+
+    public Fifo() {
+        this(10);
+    }
+
+    public Fifo(int count) {
+        this.elements = new Object[count];
+    }
+
+    public void push(Object o) {
+        checkWritePos();
+        elements[writePos++] = o;
+    }
+
+    public int size() {
+        return writePos - readPos;
+    }
+
+    protected void checkWritePos() {
+        if(writePos >= elements.length) {
+            int length = size();
+            if(readPos > 0) {
+                System.arraycopy(elements, readPos, elements, 0, length);
+                readPos = 0;
+                int wp = writePos;
+                writePos = length;
+                for (int i = writePos; i < wp; i++) {
+                    elements[i] = null;
+                }
+            }
+            else {
+                int nl = elements.length + (int) Math.min(4, Math.log(elements.length));
+                Object [] tmp = new Object[nl];
+                System.arraycopy(elements, 0, tmp, 0, length);
+                elements = tmp;
+            }
+        }
+    }
+
+    public Object pop() {
+        if(readPos < writePos) {
+            return elements[readPos++];
+        }
+        else {
+            throw new EmptyStackException();
+        }
+    }
+}
Index: ocean/src/com/imagero/util/HashBag.java
===================================================================
--- ocean/src/com/imagero/util/HashBag.java	(revision 0)
+++ ocean/src/com/imagero/util/HashBag.java	(revision 0)
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of imagero Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.imagero.util;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+/**
+ * HashBag - every key corresponds to multiple values
+ */
+public class HashBag {
+    private final Hashtable hashtable = new Hashtable();
+
+    public int size() {
+        return hashtable.size();
+    }
+
+    public boolean isEmpty() {
+        return hashtable.isEmpty();
+    }
+
+    public Enumeration keys() {
+        return hashtable.keys();
+    }
+
+    public Object get(Object key, int index) {
+        Vector list = (Vector) hashtable.get(key);
+        if (list != null) {
+            return list.elementAt(index);
+        }
+        return null;
+    }
+
+    public void put(Object key, Object value) {
+        Vector list = (Vector) hashtable.get(key);
+        if (list == null) {
+            list = new Vector(1);
+            hashtable.put(key, list);
+        }
+        list.add(value);
+    }
+
+    /**
+     * removes the key and all corresponding values from HashBag
+     * @param key
+     */
+    public Object[] remove(Object key) {
+        Vector list = (Vector) hashtable.remove(key);
+        if (list == null) {
+            return new Object[0];
+        }
+        Object o [] = new Object[list.size()];
+        for (int i = 0; i < o.length; i++) {
+            o[i] = list.elementAt(i);
+        }
+        return o;
+    }
+
+    /**
+     * remove Object from HashBag
+     * @param key key to search
+     * @param index index to remove
+     * @return Object
+     */
+    public Object remove(Object key, int index) {
+        Vector list = (Vector) hashtable.get(key);
+        if (list != null) {
+            Object o = list.elementAt(index);
+            list.remove(index);
+            if (list.size() == 0) {
+                hashtable.remove(key);
+            }
+            return o;
+        }
+        return null;
+    }
+
+    /**
+     * remove Object from HashBag
+     * @param key key to search
+     * @param obj Object to remove
+     * @return true if <code>obj</code> was found
+     */
+    public boolean remove(Object key, Object obj) {
+        Vector list = (Vector) hashtable.get(key);
+        try {
+            return list != null && list.remove(obj);
+        }
+        finally {
+            if (list != null && list.size() == 0) {
+                hashtable.remove(key);
+            }
+        }
+    }
+
+    /**
+     * get count of objects correspondings to <code>key</code>
+     * @param key
+     */
+    public int getCount(Object key) {
+        Vector list = (Vector) hashtable.get(key);
+        if (list != null) {
+            return list.size();
+        }
+        return 0;
+    }
+
+    /**
+     * Returns true if HashBag contains the specified element
+     * @param o object to be checked
+     * @return true if the specified element is contained in HashBag.
+     */
+    public boolean contains(Object o) {
+        Enumeration enums = hashtable.keys();
+        while (enums.hasMoreElements()) {
+            Object key = enums.nextElement();
+            Vector v = (Vector) hashtable.get(key);
+            if (v.contains(o)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if HashBag contains the specified element under given key.
+     * @param key key to search
+     * @param o Object to search
+     * @return true if specified element was found
+     */
+    public boolean contains(Object key, Object o) {
+        Vector v = (Vector) hashtable.get(key);
+        if (v.contains(o)) {
+            return true;
+        }
+        return false;
+    }
+
+
+}
Index: ocean/src/com/imagero/util/OpenVector.java
===================================================================
--- ocean/src/com/imagero/util/OpenVector.java	(revision 0)
+++ ocean/src/com/imagero/util/OpenVector.java	(revision 0)
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of imagero Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+ package com.imagero.util;
+
+
+
+/**
+ * OpenVector is rather an array with some check and management functions.
+ * Use on own risk!
+ */
+public class OpenVector {
+
+    private Object[] elements;
+
+    int startSize;
+
+
+    public OpenVector() {
+        this(3);
+    }
+
+    public OpenVector(int size) {
+        this.startSize = size;
+        this.elements = create();
+    }
+
+    public int indexOf(Object o) {
+        if (o != null) {
+            for (int i = 0; i < elements.length; i++) {
+                if (elements[i] != null && elements[i].equals(o)) {
+                    return i;
+                }
+            }
+        }
+        return -1;
+    }
+
+    public boolean contains(Object o) {
+        return indexOf(o) >= 0;
+    }
+
+    public boolean remove(Object o) {
+        int index = indexOf(o);
+        if (index >= 0) {
+            elements[index] = null;
+        }
+        return index >= 0;
+    }
+
+    public Object [] checkSize(int minSize) {
+        if (minSize > elements.length) {
+            Object oldData[] = elements;
+            elements = create((int) Math.max(minSize + 10, elements.length + Math.sqrt(elements.length)));
+            System.arraycopy(oldData, 0, elements, 0, elements.length);
+        }
+        return elements;
+    }
+
+    public int getCount(Class c) {
+        int count = 0;
+        for (int i = 0; i < elements.length; i++) {
+            if (c.isInstance(elements[i])) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    /**
+     * OpenVector allows direct access to its elements array.
+     * Use on own risc - e.g. use it only for reading, don't change anything in this array!
+     */
+    public Object[] getElements() {
+        return elements;
+    }
+
+    public Object [] clear() {
+        elements = create();
+        return elements;
+    }
+
+    protected Object[] create() {
+        return new Object[startSize];
+    }
+
+    protected Object[] create(int size) {
+        return new Object[size];
+    }
+}
Index: ocean/src/com/imagero/util/Ring.java
===================================================================
--- ocean/src/com/imagero/util/Ring.java	(revision 0)
+++ ocean/src/com/imagero/util/Ring.java	(revision 0)
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of imagero Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+ package com.imagero.util;
+
+import java.util.EmptyStackException;
+
+/**
+ * Ring implementation.
+ */
+public class Ring {
+
+    Object[] elements;
+
+    int start;
+    int count;
+
+    int max;
+
+
+    public Ring() {
+        this(10);
+    }
+
+    /**
+     * create new Ring object
+     * @param size Ring size
+     */
+    public Ring(int size) {
+        this.elements = new Object[size * 2];
+        start = elements.length;
+        max = size;
+    }
+
+    public Object add(Object item) {
+        if (start > 0) {
+            elements[--start] = item;
+            if (count < max) {
+                count++;
+            }
+        }
+        else {
+            System.arraycopy(elements, 0, elements, max, max);
+            start = max;
+            elements[--start] = item;
+        }
+        return item;
+    }
+
+    public Object get(int index) {
+        if (count > 0) {
+            if (index >= 0 && index < count) {
+                return elements[start + index];
+            }
+            else {
+                throw new IndexOutOfBoundsException();
+            }
+        }
+        else {
+            throw new EmptyStackException();
+        }
+    }
+
+    public int getCount() {
+        return count;
+    }
+
+    public boolean isEmpty() {
+        return count == 0;
+    }
+}
Index: ocean/src/com/imagero/util/Stack.java
===================================================================
--- ocean/src/com/imagero/util/Stack.java	(revision 0)
+++ ocean/src/com/imagero/util/Stack.java	(revision 0)
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of imagero Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+ package com.imagero.util;
+
+import java.util.EmptyStackException;
+
+/**
+ * Stack implementation not based on Vector
+ */
+public class Stack {
+
+    Object[] elements;
+
+    int count;
+
+
+    public Stack() {
+        this(10);
+    }
+
+    public Stack(int count) {
+        this.elements = new Object[count];
+    }
+
+    public Object push(Object item) {
+        checkSize(count + 1);
+        elements[count++] = item;
+        return item;
+    }
+
+    public Object pop() {
+        if (count > 0) {
+            count--;
+            Object obj = elements[count];
+            elements[count] = null;
+            return obj;
+        }
+        throw new EmptyStackException();
+    }
+
+    public Object peek() {
+        if(count == 0) {
+            throw new EmptyStackException();
+        }
+        return elements[count - 1];
+    }
+
+    public Object peek(int index) {
+        if(index < count) {
+            return elements[index];
+        }
+        else {
+            throw new IndexOutOfBoundsException();
+        }
+    }
+
+    public int getCount() {
+        return count;
+    }
+
+    public boolean isEmpty() {
+        return count == 0;
+    }
+
+    private void checkSize(int minSize) {
+        if (minSize > elements.length) {
+            Object oldData[] = elements;
+            elements = new Object[Math.max(minSize + 10, elements.length * 2)];
+            System.arraycopy(oldData, 0, elements, 0, count);
+        }
+    }
+}
Index: ocean/src/com/imagero/util/Vector.java
===================================================================
--- ocean/src/com/imagero/util/Vector.java	(revision 0)
+++ ocean/src/com/imagero/util/Vector.java	(revision 0)
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  o Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  o Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  o Neither the name of imagero Andrey Kuznetsov nor the names of
+ *    its contributors may be used to endorse or promote products derived
+ *    from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.imagero.util;
+
+import java.util.EmptyStackException;
+import java.util.Enumeration;
+
+/**
+ * Minimal Vector implementation without synchronization
+ */
+public final class Vector {
+
+    Object[] elements;
+
+    int size;
+    int startSize;
+
+
+    public Vector() {
+        this(3);
+    }
+
+    public Vector(int size) {
+        this.startSize = size;
+        this.elements = new Object[size];
+    }
+
+    public void add(Object item) {
+        checkSize(size + 1);
+        elements[size++] = item;
+    }
+
+    public void insert(Object item, int pos) {
+        checkSize(size + 1);
+        System.arraycopy(elements, pos, elements, pos + 1, size - pos);
+        elements[pos] = item;
+        size++;
+    }
+
+    public Object pop() {
+        if (size > 0) {
+            size--;
+            Object obj = elements[size];
+            elements[size] = null;
+            return obj;
+        }
+        throw new EmptyStackException();
+    }
+
+    public Object peek() {
+        if (size > 0) {
+            Object obj = elements[size - 1];
+            return obj;
+        }
+        throw new EmptyStackException();
+    }
+
+    public int indexOf(Object o) {
+        if (o != null) {
+            for (int i = 0; i < size; i++) {
+                if (elements[i] != null && elements[i].equals(o)) {
+                    return i;
+                }
+            }
+        }
+        return -1;
+    }
+
+    public boolean contains(Object o) {
+        return indexOf(o) >= 0;
+    }
+
+    public boolean remove(Object o) {
+        int index = indexOf(o);
+        if (index >= 0) {
+            remove(index);
+        }
+        return index >= 0;
+    }
+
+    public Object remove(int index) {
+        if (index >= 0 && index < size) {
+            Object obj = elements[index];
+            int length = size - index - 1;
+            if (length > 0) {
+                System.arraycopy(elements, index + 1, elements, index, length);
+                elements[size - 1] = null;
+            }
+            size--;
+            return obj;
+        } else {
+            throw new IndexOutOfBoundsException("" + index);
+        }
+    }
+
+    public void remove(int from, int count) {
+        if (from >= 0 && from + count <= size) {
+            int length = size - (from + count) - 1;
+            if (length > 0) {
+                System.arraycopy(elements, from + count + 1, elements, from, length);
+            }
+            size -= count;
+            for (int i = 0; i < length; i++) {
+                elements[size + i] = null;
+            }
+        } else {
+            throw new IndexOutOfBoundsException("" + from);
+        }
+    }
+
+    public Object elementAt(int index) {
+        if (index >= 0 && index < size) {
+            Object obj = elements[index];
+            return obj;
+        } else {
+            throw new IndexOutOfBoundsException("" + index);
+        }
+    }
+
+    /**
+     * replace element at given index.
+     * @param index element index
+     * @param o Object
+     * @return replaced Object
+     */
+    public Object setElementAt(int index, Object o) {
+        if (index >= 0 && index < size) {
+            Object tmp = elements[index];
+            elements[index] = o;
+            return tmp;
+        } else {
+            throw new IndexOutOfBoundsException("" + index);
+        }
+    }
+
+    public int getCount() {
+        return size;
+    }
+
+    public int size() {
+        return size;
+    }
+
+    public boolean isEmpty() {
+        return size == 0;
+    }
+
+    private void checkSize(int minSize) {
+        if (minSize > elements.length) {
+            Object oldData[] = elements;
+            elements = new Object[Math.max(minSize + 10, elements.length * 2)];
+            System.arraycopy(oldData, 0, elements, 0, size);
+        }
+    }
+
+    public Enumeration elements() {
+        return new Enumeration() {
+            int pos;
+
+            public boolean hasMoreElements() {
+                return pos < getCount();
+            }
+
+            public Object nextElement() {
+                if (hasMoreElements()) {
+                    return elements[pos++];
+                } else {
+                    throw new IndexOutOfBoundsException();
+                }
+            }
+        };
+    }
+
+    public int getCount(Class c) {
+        int count = 0;
+        for (int i = 0; i < elements.length; i++) {
+            if (c.isInstance(elements[i])) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    public void clear() {
+        elements = new Object[startSize];
+        size = 0;
+    }
+
+    public void toArray(Object[] dest) {
+        System.arraycopy(elements, 0, dest, 0, Math.min(size, dest.length));
+    }
+}
Index: ocean/src/net/sourceforge/jsorter/SortComparator.java
===================================================================
--- ocean/src/net/sourceforge/jsorter/SortComparator.java	(revision 0)
+++ ocean/src/net/sourceforge/jsorter/SortComparator.java	(revision 0)
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2002-2005 Robert Breidecker.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sourceforge.jsorter;
+
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * The default comparator for the Sort and SwingSorter classes. This comparator
+ * is used for sorting multiple columns.
+ * 
+ * @author Robert Breidecker
+ */
+public class SortComparator implements Comparator {
+	/**
+	 * The list of sort columns.
+	 */
+	private List sortColumns = null;
+
+	/**
+	 * Indicate how to treat null data values.
+	 */
+	private int nullBehavior = SorterConstants.NULLS_ARE_INVALID;
+
+	/**
+	 * Comparator constructor.
+	 * 
+	 * @param sortColumns
+	 *            A list of SortColumns that represent the positions of columns
+	 *            in your data that you want to be evaluated in the sort. The
+	 *            sort will start with the first column position in the list.
+	 *            Column positions not in the list will not be used in the sort.
+	 *            The number of items in this list should equal the number of
+	 *            items in the columnOrders list.
+	 * 
+	 * @param nullBehavior
+	 *            An integer representing the constant that indicates how null
+	 *            data values should behave while being sorted. See the null
+	 *            behavior constants in this class.
+	 */
+	public SortComparator(final List sortColumns, final int nullBehavior) {
+		this.sortColumns = sortColumns;
+		this.nullBehavior = nullBehavior;
+	}
+
+	/**
+	 * Overrides the java.util.Comparator compare method. This method is used
+	 * for comparing two dimensional data. See the standard JDK documention for
+	 * more information.
+	 * 
+	 * @param one
+	 *            Object - The first object used in the compare. This field
+	 *            should be a list of lists containing objects which implement
+	 *            the Comparable interface. Some of these object types include
+	 *            String, Integer, Long, Short, Float, Byte, Double and Date.
+	 *            See the standard JDK documention for Comparator for a complete
+	 *            list. The object type for each column of data must be
+	 *            consistent or a ClassCaseException will be thrown.
+	 * 
+	 * @param two
+	 *            Object - The second object used in the compare. This field
+	 *            should be a list of lists containing objects which implement
+	 *            the Comparable interface. Some of these object types include
+	 *            String, Integer, Long, Short, Float, Byte, Double and Date.
+	 *            See the standard JDK documention for Comparator for a complete
+	 *            list. The object type for each column of data must be
+	 *            consistent or a ClassCaseException will be thrown.
+	 * 
+	 * @return A negative integer, zero, or a positive integer as the first
+	 *         argument is less than, equal to, or greater than the second.
+	 * 
+	 * @exception ClassCastException
+	 *                Data in a column in not all of the same data type.
+	 */
+	public int compare(final Object one, final Object two) throws ClassCastException {
+		// The number of columns in the table.
+		int numColumns;
+
+		// The return value.
+		int rtn = 0;
+
+		// Used for counting the number of real fields in the data.
+		int ctr = 0;
+
+		// Holds the type of sort order being used.
+		int columnOrder;
+
+		// Used for counting the number of values in the sort columns.
+		int compareCtr;
+
+		// One row of data;
+		final List listOne = (List)one;
+
+		if (sortColumns == null) {
+			numColumns = listOne.size();
+		} else {
+			numColumns = sortColumns.size();
+		}
+
+		while (rtn == 0 && ctr < numColumns) {
+			// The first object to compare.
+			Comparable comparableOne;
+
+			// The second object to compare.
+			Comparable comparableTwo;
+
+			// Make sure compare column is within range.
+			if (sortColumns == null) {
+				compareCtr = ctr;
+			} else {
+				compareCtr = ((SortableColumn)sortColumns.get(ctr)).getColumnPosition();
+			}
+
+			if (compareCtr <= listOne.size()) {
+				// Another row of data;
+				final List listTwo = (List) two;
+
+				// Get the field to use in the compare.
+				if (sortColumns == null) {
+					comparableOne = (Comparable)listOne.get(compareCtr);
+					comparableTwo = (Comparable)listTwo.get(compareCtr);
+				} else {
+					comparableOne = (Comparable)listOne.get(((SortableColumn) sortColumns.get(ctr)).getColumnPosition());
+					comparableTwo = (Comparable)listTwo.get(((SortableColumn) sortColumns.get(ctr)).getColumnPosition());
+				}
+
+				// Get the sort type that goes with the sort column.
+				if (sortColumns == null) {
+					// If no sort columns were specified, then use ascending
+					// order.
+					columnOrder = SorterConstants.ASCENDING_ORDER;
+				} else {
+					columnOrder = ((SortableColumn)sortColumns.get(ctr)).getColumnOrder();
+				}
+
+				// Compare the objects.
+				if (comparableOne != null && comparableTwo != null) {
+					if (columnOrder == SorterConstants.ASCENDING_ORDER) {
+						try {
+							rtn = comparableOne.compareTo(comparableTwo);
+						} catch (ClassCastException exception) {
+							throw exception;
+						}
+					} else {
+						try {
+							rtn = comparableTwo.compareTo(comparableOne);
+						} catch (ClassCastException exception) {
+							throw exception;
+						}
+					}
+				} else {
+					if (nullBehavior == SorterConstants.NULLS_ARE_INVALID) {
+						throw new IllegalStateException("Null data values are not valid.");
+					} else if (comparableOne == null && comparableTwo != null) {
+						if (columnOrder == SorterConstants.ASCENDING_ORDER) {
+							rtn = -1;
+						} else {
+							rtn = 1;
+						}
+
+						if (nullBehavior == SorterConstants.NULLS_ARE_GREATEST) {
+							rtn = rtn * -1;
+						}
+					} else if (comparableOne != null && comparableTwo == null) {
+						if (columnOrder == SorterConstants.ASCENDING_ORDER) {
+							rtn = 1;
+						} else {
+							rtn = -1;
+						}
+
+						if (nullBehavior == SorterConstants.NULLS_ARE_GREATEST) {
+							rtn = rtn * -1;
+						}
+					} else {
+						rtn = 0;
+					}
+				}
+			}
+			ctr++;
+		}
+		return rtn;
+	}
+
+	/**
+	 * Returns the null behavior for this object.
+	 * 
+	 * @return An integer representing the constant that indicates how null data
+	 *         values should behave while being sorted. See the null behavior
+	 *         constants in this class. The default value for this class is
+	 *         NULLS_ARE_INVALID.
+	 */
+	public int getNullBehavior() {
+		return nullBehavior;
+	}
+
+	/**
+	 * Set the null behavior for this object.
+	 * 
+	 * @param nullBehavior
+	 *            An integer representing the constant that indicates how null
+	 *            data values should behave while being sorted. See the null
+	 *            behavior constants in this class.
+	 * 
+	 * @throws IllegalArgumentException
+	 *             Thrown if the null behavior value is not valid.
+	 */
+	public void setNullBehavior(final int nullBehavior) {
+		if (nullBehavior != SorterConstants.NULLS_ARE_GREATEST
+				&& nullBehavior != SorterConstants.NULLS_ARE_INVALID
+				&& nullBehavior != SorterConstants.NULLS_ARE_LEAST) {
+			throw new IllegalArgumentException("Invalid null behavior.");
+		}
+
+		this.nullBehavior = nullBehavior;
+	}
+}
\ No newline at end of file
Index: ocean/src/net/sourceforge/jsorter/FieldUtil.java
===================================================================
--- ocean/src/net/sourceforge/jsorter/FieldUtil.java	(revision 0)
+++ ocean/src/net/sourceforge/jsorter/FieldUtil.java	(revision 0)
@@ -0,0 +1,74 @@
+/*
+ * FieldUtil.java
+ *
+ * Created on February 3, 2006, 5:24 PM
+ */
+
+package net.sourceforge.jsorter;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ *
+ * @author  administrator
+ * @version
+ */
+public class FieldUtil {
+  private static Map fieldMap = new ConcurrentHashMap();
+  
+  public static void setFieldValue(String fieldName, Object object, Object value) {
+    try {
+      Field field = getField(object.getClass(), fieldName);
+      field.set(object, value);
+    } catch (Exception ex) { ex.printStackTrace(); }
+  }
+  
+  public static Object getFieldValue(String fieldName, Object object) {
+    try {
+      Field field = getField(object.getClass(), fieldName);
+      return field.get(object);
+    } catch (Exception ex) { ex.printStackTrace(); return null; }
+  }
+  
+  public static Field getField(Class clazz, String fieldName) {
+    Field[] fields = getFields(clazz);
+    for (int x=0; x < fields.length; x++) {
+      if (fields[x].getName().equals(fieldName)) {
+        return fields[x];
+      }
+    }
+    return null;
+  }
+  
+  public static Field[] getFields(Class clazz) {
+    Field[] fields = (Field[])fieldMap.get(clazz);
+    if (fields != null) return fields;
+    List list = new ArrayList();
+    getFields(clazz, list);
+    
+    fields = new Field[list.size()];
+    for (int x=0; x < fields.length; x++) {
+      fields[x] = (Field)list.get(x);
+    }
+    fieldMap.put(clazz, fields);
+    return fields;
+  }
+  
+  public static void getFields(Class clazz, List list) {
+    Class superClass = clazz.getSuperclass();
+    if (superClass != null) getFields(superClass, list);
+    Field[] fields = clazz.getDeclaredFields();
+    for (int x=0; x < fields.length; x++) {
+      int modifiers = fields[x].getModifiers();
+      if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers)
+      && !Modifier.isTransient(modifiers) && !Modifier.isFinal(modifiers))
+        list.add(fields[x]);
+    }
+  }
+}
+
Index: ocean/src/net/sourceforge/jsorter/ReflectColumns.java
===================================================================
--- ocean/src/net/sourceforge/jsorter/ReflectColumns.java	(revision 0)
+++ ocean/src/net/sourceforge/jsorter/ReflectColumns.java	(revision 0)
@@ -0,0 +1,66 @@
+
+
+package net.sourceforge.jsorter;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ *
+ * @author  Jason Rutherglen
+ */
+public class ReflectColumns {
+  public List<SortableColumn> list = new ArrayList<SortableColumn>();
+  
+  public ReflectColumns() {
+  }
+  
+  public SortReflect[] getSortReflects() {
+    SortReflect[] sortReflects = new SortReflect[list.size()];
+    int count = 0;
+    Iterator iterator = list.iterator();
+    while (iterator.hasNext()) {
+      SortableColumnReflect sortableColumnReflect = (SortableColumnReflect)iterator.next();
+      sortReflects[count] = sortableColumnReflect.sortReflect;
+      
+      count++;
+    }
+    return sortReflects;
+  }
+  
+  public List<SortableColumn> getColumns() {
+    return list;
+  }
+  
+  public class SortableColumnReflect implements SortableColumn {
+    int position;
+    String name;
+    SortReflect sortReflect;
+    int order;
+    
+    public SortableColumnReflect(int position, String name, SortReflect sortReflect, int order) {
+      this.position = position;
+      this.name = name;
+      this.sortReflect = sortReflect;
+      this.order = order;
+    }
+    
+    public String getColumnName() {
+      return name;
+    }
+    
+    public int getColumnOrder() {
+      return order;
+    }
+    
+    public int getColumnPosition() {
+      return position;
+    }
+  }
+  
+  public void add(String name, SortReflect sortReflect, int order) {
+    SortableColumnReflect sortableColumnReflect = new SortableColumnReflect(list.size(), name, sortReflect, order);
+    list.add(sortableColumnReflect);
+  }
+}
Index: ocean/src/net/sourceforge/jsorter/SortHolder.java
===================================================================
--- ocean/src/net/sourceforge/jsorter/SortHolder.java	(revision 0)
+++ ocean/src/net/sourceforge/jsorter/SortHolder.java	(revision 0)
@@ -0,0 +1,90 @@
+
+
+package net.sourceforge.jsorter;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import net.sourceforge.jsorter.SortReflect.MethodException;
+
+
+/**
+ *
+ * @author  Jason Rutherglen
+ */
+public class SortHolder<T> implements Comparable {
+  public Comparable attribute;
+  public T object;
+  public int nullBehavior = SorterConstants.NULLS_ARE_INVALID;
+  
+  public SortHolder(Comparable attribute, T object, int nullBehavior) {
+    this.attribute = attribute;
+    this.object = object;
+    this.nullBehavior = nullBehavior;
+  }
+  
+  public String toString() {
+    if (attribute == null) return "null";
+    return attribute.toString();
+  }
+  
+  public static List getTable(SortReflect[] sortReflect, Collection collection, int nullBehavior) throws MethodException {
+    List table = new ArrayList(collection.size());
+    Iterator iterator = collection.iterator();
+    while (iterator.hasNext()) {
+      Object value = iterator.next();
+      List row = new ArrayList(sortReflect.length); 
+      for (int x=0; x < sortReflect.length; x++) {
+        Object attribute = sortReflect[x].get(value);
+        if (attribute instanceof java.net.URL) {
+          attribute = ((java.net.URL)attribute).toString();
+        }
+        Comparable attributeComparable = (Comparable)attribute;
+        SortHolder sortHolder = new SortHolder(attributeComparable, value, nullBehavior);
+        row.add(sortHolder);
+      }
+      table.add(row);
+    }
+    return table;
+  }
+  
+  public int compareTo(Object obj) {
+    Comparable comparableOne = (Comparable)attribute;
+    Comparable comparableTwo = (Comparable)obj;
+    if (obj instanceof SortHolder) {
+      SortHolder sortHolder = (SortHolder)obj;
+      comparableTwo = (Comparable)sortHolder.attribute;
+    }
+    
+    int rtn;
+    
+    if (comparableOne != null && comparableTwo != null) {
+      try {
+        rtn = comparableOne.compareTo(comparableTwo);
+      } catch (ClassCastException exception) {
+        throw exception;
+      }
+    } else {
+      if (nullBehavior == SorterConstants.NULLS_ARE_INVALID) {
+        throw new IllegalStateException("Null data values are not valid.");
+      } else if (comparableOne == null && comparableTwo != null) {
+        rtn = -1;
+        
+        if (nullBehavior == SorterConstants.NULLS_ARE_GREATEST) {
+          rtn = rtn * -1;
+        }
+      } else if (comparableOne != null && comparableTwo == null) {
+        rtn = 1;
+        
+        if (nullBehavior == SorterConstants.NULLS_ARE_GREATEST) {
+          rtn = rtn * -1;
+        }
+      } else {
+        rtn = 0;
+      }
+    }
+    return rtn;
+  }
+}
Index: ocean/src/net/sourceforge/jsorter/SortableSwingComponent.java
===================================================================
--- ocean/src/net/sourceforge/jsorter/SortableSwingComponent.java	(revision 0)
+++ ocean/src/net/sourceforge/jsorter/SortableSwingComponent.java	(revision 0)
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2002-2005 Robert Breidecker.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sourceforge.jsorter;
+
+import java.util.List;
+
+/**
+ * This class represents a component that will be sorted by SwingSorter.
+ * 
+ * @author Robert Breidecker
+ */
+public interface SortableSwingComponent {
+	/**
+	 * Returns a lists of lists that contain the component's data values. Each
+	 * inner list contained in the outer list represents a row of data.
+	 * 
+	 * @return The data list for this component.
+	 */
+	public List getDataList();
+}
\ No newline at end of file
Index: ocean/src/net/sourceforge/jsorter/SwingSorter.java
===================================================================
--- ocean/src/net/sourceforge/jsorter/SwingSorter.java	(revision 0)
+++ ocean/src/net/sourceforge/jsorter/SwingSorter.java	(revision 0)
@@ -0,0 +1,580 @@
+/*
+ * Copyright 2002-2005 Robert Breidecker.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sourceforge.jsorter;
+
+import java.util.List;
+import java.util.Vector;
+
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.DefaultListModel;
+import javax.swing.JComboBox;
+import javax.swing.JList;
+import javax.swing.JTable;
+import javax.swing.table.DefaultTableModel;
+
+/**
+ * Used for sorting data in the Swing objects JTable, JList, JComboBox,
+ * DefaultTableModel, DefaultListModel, DefaultComboBoxModel and
+ * SortableComponent.
+ * 
+ * Note: This is class is not thread safe.
+ * 
+ * @author Robert Breidecker
+ */
+public class SwingSorter {
+	/**
+	 * Indicate how to treat null data values.
+	 */
+	private int nullBehavior = SorterConstants.NULLS_ARE_INVALID;
+
+	/**
+	 * Sorts a custom component that implements the SortableComponent interface.
+	 * This method assumes that the object type in the display column is made up
+	 * entirely of the same type and that the type implements the Comparable
+	 * interface. For example, the data in the display column is entirely made
+	 * of String or Integers. Some of the objects that implement the Comparable
+	 * interface include String, Integer, Long, Short, Float, Byte, Double and
+	 * Date.
+	 * 
+	 * @param component
+	 *            The component to be sorted in ascending order. The component
+	 *            model must implement the SortableComponent interface.
+	 */
+	public void sortComponent(final SortableSwingComponent component) {
+		sortComponent(component, SorterConstants.ASCENDING_ORDER);
+	}
+
+	/**
+	 * Sorts a custom component that implements the SortableComponent interface.
+	 * This method assumes that the object type in the display column is made up
+	 * entirely of the same type and that the type implements the Comparable
+	 * interface. For example, the data in the display column is entirely made
+	 * of String or Integers. Some of the objects that implement the Comparable
+	 * interface include String, Integer, Long, Short, Float, Byte, Double and
+	 * Date.
+	 * 
+	 * @param component
+	 *            The component to be sorted in ascending order. The component
+	 *            model must implement the SortableComponent interface.
+	 * 
+	 * @param columnOrder
+	 *            An integer that represents the type of ordering to be used
+	 *            when sorting by only the first column in the data. The type of
+	 *            sort can either be ascending or descending. The
+	 *            ASCENDING_ORDER and DESCENDING_ORDER constants in this class
+	 *            should be used.
+	 */
+	public void sortComponent(final SortableSwingComponent component,
+			final int columnOrder) {
+		final SortableColumn sortColumn = new SortableColumnImpl(0, columnOrder);
+		final List sortColumns = new Vector();
+		sortColumns.add(sortColumn);
+
+		sortComponent(component, sortColumns);
+	}
+
+	/**
+	 * Sorts a custom component that implements the SortableComponent interface.
+	 * This method assumes that the object type in the display column is made up
+	 * entirely of the same type and that the type implements the Comparable
+	 * interface. For example, the data in the display column is entirely made
+	 * of String or Integers. Some of the objects that implement the Comparable
+	 * interface include String, Integer, Long, Short, Float, Byte, Double and
+	 * Date.
+	 * 
+	 * @param component
+	 *            The component to be sorted in ascending order. The component
+	 *            model must implement the SortableComponent interface.
+	 * 
+	 * @param sortColumns
+	 *            Sort columns are a list of numbers specifying the colums to
+	 *            sort the table by. Each number in the list should be an
+	 *            instance of the SortColumn class represents a position of a
+	 *            column in the table. Column position start at zero just as
+	 *            they do in the standard Collection classes and Java arrays.
+	 *            When this field is null, there must be only one and only one
+	 *            column order specified. The sort routine will then sort using
+	 *            every column in the table starting from position 0 and up or
+	 *            left to right in ascending order.
+	 */
+	public void sortComponent(final SortableSwingComponent component,
+			final List sortColumns) {
+		// Create a new Sorter object.
+		final Sorter sorter = new Sorter(component.getDataList(), sortColumns);
+		sorter.setNullBehavior(nullBehavior);
+
+		// Sort the data!
+		sorter.sort();
+	}
+
+	/**
+	 * Sorts a combo box using Sorter. This method assumes that the object type
+	 * in the display column is made up entirely of the same type and that the
+	 * type implements the Comparable interface. For example, the data in the
+	 * display column is entirely made of String or Integers. Some of the
+	 * objects that implement the Comparable interface include String, Integer,
+	 * Long, Short, Float, Byte, Double and Date.
+	 * 
+	 * @param comboBox
+	 *            The combo box to be sorted in ascending order. The combo box
+	 *            model must be an instance or a descendent of
+	 *            DefaultComboBoxModel.
+	 */
+	public void sortComboBox(final JComboBox comboBox) {
+		sortComboBox(comboBox, SorterConstants.ASCENDING_ORDER);
+	}
+
+	/**
+	 * Sorts a combo box using Sorter. This method assumes that the object type
+	 * in the display column is made up entirely of the same type and that the
+	 * type implements the Comparable interface. For example, the data in the
+	 * display column is entirely made of String or Integers. Some of the
+	 * objects that implement the Comparable interface include String, Integer,
+	 * Long, Short, Float, Byte, Double and Date.
+	 * 
+	 * @param comboBox
+	 *            The combo box to be sorted. The combo box model must be an
+	 *            instance or a descendent of DefaultComboBoxModel or implement
+	 *            the SortableComponent.
+	 * 
+	 * @param columnOrder
+	 *            An integer that represents the type of ordering to be used
+	 *            when sorting by only the first column in the data. The type of
+	 *            sort can either be ascending or descending. The
+	 *            ASCENDING_ORDER and DESCENDING_ORDER constants in this class
+	 *            should be used.
+	 * 
+	 * @exception IllegalArgumentException
+	 *                Input data is invalid.
+	 */
+	public void sortComboBox(final JComboBox comboBox, final int columnOrder) {
+		if (comboBox.getModel() instanceof DefaultComboBoxModel) {
+			final DefaultComboBoxModel model = (DefaultComboBoxModel) comboBox
+					.getModel();
+
+			sortComboBoxModel(model, columnOrder);
+		} else if (comboBox.getModel() instanceof SortableSwingComponent) {
+			final SortableSwingComponent component = (SortableSwingComponent) comboBox
+					.getModel();
+
+			sortComponent(component, columnOrder);
+		} else {
+			throw new IllegalArgumentException(
+					"ComboBox model must be an "
+							+ "instance of decendent of DefaultComboBoxModel or implement the "
+							+ "SortableComponent interface.");
+		}
+	}
+
+	/**
+	 * Sorts a combo box model using Sorter. This method assumes that the object
+	 * type in the display column is made up entirely of the same type and that
+	 * the type implements the Comparable interface. For example, the data in
+	 * the display column is entirely made of String or Integers. Some of the
+	 * objects that implement the Comparable interface include String, Integer,
+	 * Long, Short, Float, Byte, Double and Date.
+	 * 
+	 * @param model
+	 *            The combo box model to be sorted in ascending order. The combo
+	 *            box model must be an instance or a descendent of
+	 *            DefaultComboBoxModel.
+	 */
+	public void sortComboBoxModel(final DefaultComboBoxModel model) {
+		sortComboBoxModel(model, SorterConstants.ASCENDING_ORDER);
+	}
+
+	/**
+	 * Sorts a combo box model using Sorter. This method assumes that the object
+	 * type in the display column is made up entirely of the same type and that
+	 * the type implements the Comparable interface. For example, the data in
+	 * the display column is entirely made of String or Integers. Some of the
+	 * objects that implement the Comparable interface include String, Integer,
+	 * Long, Short, Float, Byte, Double and Date.
+	 * 
+	 * @param model
+	 *            The combo box model to be sorted. The combo box model must be
+	 *            an instance or a descendent of DefaultComboBoxModel.
+	 * 
+	 * @param columnOrder
+	 *            An integer that represents the type of ordering to be used
+	 *            when sorting by only the first column in the data. The type of
+	 *            sort can either be ascending or descending. The
+	 *            ASCENDING_ORDER and DESCENDING_ORDER constants in this class
+	 *            should be used.
+	 */
+	public void sortComboBoxModel(final DefaultComboBoxModel model,
+			final int columnOrder) {
+		// Create the table of sort data.
+		final List table = new Vector();
+
+		// Get the model data.
+		for (int ctr = 0, size = model.getSize(); ctr < size; ctr++) {
+			// Create a new row.
+			final List row = new Vector();
+			row.add(model.getElementAt(ctr));
+			table.add(row);
+		}
+
+		// Create a new Sorter object.
+		final Sorter sorter = new Sorter(table, columnOrder);
+		sorter.setNullBehavior(nullBehavior);
+
+		// Sort the data!
+		sorter.sort();
+
+		// Clear the model data.
+		model.removeAllElements();
+
+		// Re-add the sorted data to the model.
+		for (int ctr = 0, size = table.size(); ctr < size; ctr++) {
+			final List row = (List) table.get(ctr);
+
+			// Get the first element from the row, because a list
+			// only has one column.
+			model.addElement(row.get(0));
+		}
+	}
+
+	/**
+	 * Sorts a list model using Sorter. This method assumes that the object type
+	 * in the display column is made up entirely of the same type and that the
+	 * type implements the Comparable interface. For example, the data in the
+	 * display column is entirely made of String or Integers. Some of the
+	 * objects that implement the Comparable interface include String, Integer,
+	 * Long, Short, Float, Byte, Double and Date.
+	 * 
+	 * @param model
+	 *            The list model to be sorted in ascending order. Must be a
+	 *            DefaultListModel or a descendent of DefaultListModel.
+	 */
+	public void sortListModel(final DefaultListModel model) {
+		sortListModel(model, SorterConstants.ASCENDING_ORDER);
+	}
+
+	/**
+	 * Sorts a list using Sorter. This method assumes that the object type in
+	 * the display column is made up entirely of the same type and that the type
+	 * implements the Comparable interface. For example, the data in the display
+	 * column is entirely made of String or Integers. Some of the objects that
+	 * implement the Comparable interface include String, Integer, Long, Short,
+	 * Float, Byte, Double and Date.
+	 * 
+	 * @param list
+	 *            The list to be sorted in ascending order. The list model used
+	 *            must be a DefaultListBoxModel or a descendent of
+	 *            DefaultListModel.
+	 */
+	public void sortList(final JList list) {
+		sortList(list, SorterConstants.ASCENDING_ORDER);
+	}
+
+	/**
+	 * Sorts a list using Sorter. This method assumes that the object type in
+	 * the display column is made up entirely of the same type and that the type
+	 * implements the Comparable interface. For example, the data in the display
+	 * column is entirely made of String or Integers. Some of the objects that
+	 * implement the Comparable interface include String, Integer, Long, Short,
+	 * Float, Byte, Double and Date.
+	 * 
+	 * @param list
+	 *            The list to be sorted. The list model used must be a
+	 *            DefaultListModel or a descendent of DefaultListModel or
+	 *            implement the SortableComponent interface.
+	 * 
+	 * @param columnOrder
+	 *            An integer that represents the type of ordering to be used
+	 *            when sorting by only the first column in the data. The type of
+	 *            sort can either be ascending or descending. The
+	 *            ASCENDING_ORDER and DESCENDING_ORDER constants in this class
+	 *            should be used.
+	 * 
+	 * @exception IllegalArgumentException
+	 *                Input data is invalid.
+	 */
+	public void sortList(final JList list, final int columnOrder) {
+		if (list.getModel() instanceof DefaultListModel) {
+			final DefaultListModel model = (DefaultListModel) list.getModel();
+
+			sortListModel(model, columnOrder);
+		} else if (list.getModel() instanceof SortableSwingComponent) {
+			final SortableSwingComponent model = (SortableSwingComponent) list
+					.getModel();
+
+			sortComponent(model, columnOrder);
+		} else {
+			throw new IllegalArgumentException(
+					"List model must be an "
+							+ "instance of decendent of DefaultListModel or implement the "
+							+ "SortableComponent interface.");
+		}
+	}
+
+	/**
+	 * Sorts a list model using Sorter. This method assumes that the object type
+	 * in the display column is made up entirely of the same type and that the
+	 * type implements the Comparable interface. For example, the data in the
+	 * display column is entirely made of String or Integers. Some of the
+	 * objects that implement the Comparable interface include String, Integer,
+	 * Long, Short, Float, Byte, Double and Date.
+	 * 
+	 * @param model
+	 *            The list model to be sorted. Must be a DefaultListModel or a
+	 *            descendent of DefaultListModel.
+	 * 
+	 * @param newSortOrder
+	 *            An integer that represents the type of ordering to be used
+	 *            when sorting by only the first column in the data. The type of
+	 *            sort can either be ascending or descending. The
+	 *            ASCENDING_ORDER and DESCENDING_ORDER constants in this class
+	 *            should be used.
+	 */
+	public void sortListModel(final DefaultListModel model,
+			final int newSortOrder) {
+		// Create the table of sort data.
+		final Vector table = new Vector();
+
+		// Get the model data.
+		for (int ctr = 0, size = model.getSize(); ctr < size; ctr++) {
+			// Create a new row.
+			final Vector row = new Vector();
+			row.add(model.getElementAt(ctr));
+			table.add(row);
+		}
+
+		// Create a new Sorter.
+		final Sorter sorter = new Sorter(table, newSortOrder);
+		sorter.setNullBehavior(nullBehavior);
+
+		// Sort the vector data.
+		sorter.sort();
+
+		// Clear the model data.
+		model.removeAllElements();
+
+		// Re-add the sorted data to the model.
+		for (int ctr = 0, size = table.size(); ctr < size; ctr++) {
+			final List row = (List) table.get(ctr);
+
+			// Get the first element from the row, because a list
+			// only has one column.
+			model.addElement(row.get(0));
+		}
+	}
+
+	/**
+	 * Sorts a table using Sorter. This method assumes that the object type in
+	 * each column is made up entirely of the same type and that the type
+	 * implements the Comparable interface. For example, the data in column one
+	 * can be entirely made of String types and column two can be entirely made
+	 * up of Integers. Some of the objects that implement the Comparable
+	 * interface include String, Integer, Long, Short, Float, Byte, Double and
+	 * Date. This method will sort using all columns in ascending order.
+	 * 
+	 * @param table
+	 *            JTable The table to be sorted. The table model of the table
+	 *            must be an instance or a descendent of DefaultTableModel or
+	 *            must implement the SortableComponent interface.
+	 */
+	public void sortTable(final JTable table) {
+		// Sort the table.
+		sortTable(table);
+	}
+
+	/**
+	 * Sorts a table using Sorter. This method assumes that the object type in
+	 * each column is made up entirely of the same type and that the type
+	 * implements the Comparable interface. For example, the data in column one
+	 * can be entirely made of String types and column two can be entirely made
+	 * up of Integers. Some of the objects that implement the Comparable
+	 * interface include String, Integer, Long, Short, Float, Byte, Double and
+	 * Date. This method will sort using all columns in the order specified.
+	 * 
+	 * @param table
+	 *            JTable The table to be sorted. The table model of the table
+	 *            must be an instance or a descendent of DefaultTableModel or
+	 *            must implement the SortableComponent interface.
+	 * 
+	 * @param columnOrder
+	 *            An integer that represents the type of ordering to be used
+	 *            when sorting by only the first column in the data. The type of
+	 *            sort can either be ascending or descending. The
+	 *            ASCENDING_ORDER and DESCENDING_ORDER constants in this class
+	 *            should be used.
+	 */
+	public void sortTable(final JTable table, final int columnOrder) {
+		// Sort the table!
+		sortTable(table, columnOrder);
+	}
+
+	/**
+	 * Sorts a table using Sorter. This method assumes that the object type in
+	 * each column is made up entirely of the same type and that the type
+	 * implements the Comparable interface. For example, the data in column one
+	 * can be entirely made of String types and column two is can be entriely
+	 * made up of Integers. Some of the objects that implement the Comparable
+	 * interface include String, Integer, Long, Short, Float, Byte, Double and
+	 * Date.
+	 * 
+	 * @param table
+	 *            JTable The table to be sorted. The table model of the table
+	 *            must be an instance or a descendent of DefaultTableModel or
+	 *            must implement the SortableComponent interface.
+	 * 
+	 * @param sortColumns
+	 *            Sort columns are a list of numbers specifying the colums to
+	 *            sort the table by. Each number in the list should be an
+	 *            instance of the SortColumn class represents a position of a
+	 *            column in the table. Column position start at zero just as
+	 *            they do in the standard Collection classes and Java arrays.
+	 *            When this field is null, there must be only one and only one
+	 *            column order specified. The sort routine will then sort using
+	 *            every column in the table starting from position 0 and up or
+	 *            left to right in ascending order.
+	 * 
+	 * @exception IllegalArgumentException
+	 *                Input data is invalid.
+	 */
+	public void sortTable(final JTable table, final List sortColumns) {
+		if (table.getModel() instanceof DefaultTableModel) {
+			final DefaultTableModel model = (DefaultTableModel) table
+					.getModel();
+
+			sortTableModel(model, sortColumns);
+		} else if (table.getModel() instanceof SortableSwingComponent) {
+			final SortableSwingComponent model = (SortableSwingComponent) table
+					.getModel();
+
+			sortComponent(model, sortColumns);
+		} else {
+			throw new IllegalArgumentException(
+					"Table model must be an "
+							+ "instance of decendent of DefaultTableModel or implement the "
+							+ "SortableComponent interface.");
+		}
+	}
+
+	/**
+	 * Sorts a table model using Sorter. This method assumes that the object
+	 * type in each column is made up entirely of the same type and that the
+	 * type implements the Comparable interface. For example, the data in column
+	 * one can be entirely made of String types and column two is can be
+	 * entriely made up of Integers. Some of the objects that implement the
+	 * Comparable interface include String, Integer, Long, Short, Float, Byte,
+	 * Double and Date.
+	 * 
+	 * @param model
+	 *            The table model to be sorted. The table model must be an
+	 *            instance or a descendent of DefaultTableModel.
+	 */
+	public void sortTableModel(final DefaultTableModel model) {
+		// Sort the data.
+		sortTableModel(model);
+	}
+
+	/**
+	 * Sorts a table model using Sorter. This method assumes that the object
+	 * type in each column is made up entirely of the same type and that the
+	 * type implements the Comparable interface. For example, the data in column
+	 * one can be entirely made of String types and column two is can be
+	 * entriely made up of Integers. Some of the objects that implement the
+	 * Comparable interface include String, Integer, Long, Short, Float, Byte,
+	 * Double and Date.
+	 * 
+	 * @param model
+	 *            The table model to be sorted. The table model must be an
+	 *            instance or a descendent of DefaultTableModel.
+	 * 
+	 * @param columnOrder
+	 *            An integer that represents the type of ordering to be used
+	 *            when sorting by only the first column in the data. The type of
+	 *            sort can either be ascending or descending. The
+	 *            ASCENDING_ORDER and DESCENDING_ORDER constants in this class
+	 *            should be used.
+	 */
+	public void sortTableModel(final DefaultTableModel model,
+			final int columnOrder) {
+		// Sort the table.
+		sortTableModel(model, columnOrder);
+	}
+
+	/**
+	 * Sorts a table model using Sorter. This method assumes that the object
+	 * type in each column is made up entirely of the same type and that the
+	 * type implements the Comparable interface. For example, the data in column
+	 * one can be entirely made of String types and column two is can be
+	 * entriely made up of Integers. Some of the objects that implement the
+	 * Comparable interface include String, Integer, Long, Short, Float, Byte,
+	 * Double and Date.
+	 * 
+	 * @param model
+	 *            The table model to be sorted. The table model must be an
+	 *            instance or a descendent of DefaultTableModel.
+	 * 
+	 * @param sortColumns
+	 *            Sort columns are a list of numbers specifying the colums to
+	 *            sort the table by. Each number in the list should be an
+	 *            instance of the SortColumn class represents a position of a
+	 *            column in the table. Column position start at zero just as
+	 *            they do in the standard Collection classes and Java arrays.
+	 *            When this field is null, there must be only one and only one
+	 *            column order specified. The sort routine will then sort using
+	 *            every column in the table starting from position 0 and up or
+	 *            left to right in ascending order.
+	 */
+	public void sortTableModel(final DefaultTableModel model,
+			final List sortColumns) {
+		// Create a new Sorter.
+		final Sorter sorter = new Sorter(model.getDataVector(), sortColumns);
+		sorter.setNullBehavior(nullBehavior);
+
+		// Sort the data.
+		sorter.sort();
+	}
+
+	/**
+	 * Returns the null behavior for this object.
+	 * 
+	 * @return An integer representing the constant that indicates how null data
+	 *         values should behave while being sorted. See the null behavior
+	 *         constants in this class. The default value for this class is
+	 *         NULLS_ARE_INVALID.
+	 */
+	public int getNullBehavior() {
+		return nullBehavior;
+	}
+
+	/**
+	 * Set the null behavior for this object.
+	 * 
+	 * @param nullBehavior
+	 *            An integer representing the constant that indicates how null
+	 *            data values should behave while being sorted. See the null
+	 *            behavior constants in this class.
+	 * 
+	 * @throws IllegalArgumentException
+	 *             Thrown if the null behavior value is not valid.
+	 */
+	public void setNullBehavior(final int nullBehavior) {
+		if (nullBehavior != SorterConstants.NULLS_ARE_GREATEST
+				&& nullBehavior != SorterConstants.NULLS_ARE_INVALID
+				&& nullBehavior != SorterConstants.NULLS_ARE_LEAST) {
+			throw new IllegalArgumentException("Invalid null behavior.");
+		}
+
+		this.nullBehavior = nullBehavior;
+	}
+}
\ No newline at end of file
Index: ocean/src/net/sourceforge/jsorter/SortableColumn.java
===================================================================
--- ocean/src/net/sourceforge/jsorter/SortableColumn.java	(revision 0)
+++ ocean/src/net/sourceforge/jsorter/SortableColumn.java	(revision 0)
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2002-2005 Robert Breidecker.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sourceforge.jsorter;
+
+/**
+ * This class represents a column that will be sorted by Sorter, SwingSorter, or
+ * SortComparator.
+ * 
+ * @author Robert Breidecker
+ */
+public interface SortableColumn {
+	/**
+	 * Returns the number position of the column to sort with.
+	 * 
+	 * @return The number position of the column to sort with.
+	 */
+	public int getColumnPosition();
+
+	/**
+	 * Returns the order to sort the column by.
+	 * 
+	 * @return The order to sort the column by.
+	 */
+	public int getColumnOrder();
+
+	/**
+	 * Returns the name of this column.
+	 * 
+	 * @return The name of this column.
+	 */
+	public String getColumnName();
+}
\ No newline at end of file
Index: ocean/src/net/sourceforge/jsorter/SortableBoolean.java
===================================================================
--- ocean/src/net/sourceforge/jsorter/SortableBoolean.java	(revision 0)
+++ ocean/src/net/sourceforge/jsorter/SortableBoolean.java	(revision 0)
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2002-2005 Robert Breidecker.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sourceforge.jsorter;
+
+/**
+ * This class is a wrapper on top of the Boolean class. Its purpose is to
+ * provide a class that allows for Boolean values to be sorted, because the
+ * Boolean class does not implement the Comparable interface.
+ * 
+ * @author Robert Breidecker
+ */
+public final class SortableBoolean implements Comparable {
+	/**
+	 * The Boolean value being wrapped.
+	 */
+	private Boolean booleanValue = null;
+
+	/**
+	 * Creates a new SortableBoolean object from the input boolean value.
+	 * 
+	 * @param value
+	 *            The boolean value used to create the new SortableBoolean.
+	 */
+	public SortableBoolean(final boolean value) {
+		booleanValue = new Boolean(value);
+	}
+
+	/**
+	 * Creates a new SortableBoolean object from the input Boolean.
+	 */
+	public SortableBoolean(final Boolean value) {
+		booleanValue = value;
+	}
+
+	/**
+	 * Creates a new SortableBoolean object from the input String. The new
+	 * object will have a true value if the input value is "true", otherwise it
+	 * will have a false value.
+	 */
+	public SortableBoolean(final String value) {
+		booleanValue = new Boolean(value);
+	}
+
+	/**
+	 * Returns the primitive boolean value for this object.
+	 * 
+	 * @return The primitive boolean value for this object.
+	 */
+	public boolean booleanValue() {
+		return booleanValue.booleanValue();
+	}
+
+	/**
+	 * Compares this object with the specified object for order. Returns a
+	 * negative integer, zero, or a positive integer as this object is less
+	 * than, equal to, or greater than the specified object.
+	 * 
+	 * @param object
+	 *            The object to compare this object to.
+	 * 
+	 * @return A negative integer, zero, or a positive integer as this object is
+	 *         less than, equal to, or greater than the specified object.
+	 */
+	public int compareTo(final Object object) {
+		int returnValue = -1;
+
+		if (object == null) {
+			throw new IllegalArgumentException(
+					"This object can not be compared " + "to a null value.");
+		}
+
+		if (!(object instanceof SortableBoolean)) {
+			throw new IllegalArgumentException("The input object must be an "
+					+ "instance of SortableBoolean.");
+		}
+
+		final SortableBoolean compareToBoolean = (SortableBoolean) object;
+
+		if (booleanValue() == false && compareToBoolean.booleanValue() == true) {
+			returnValue = -1;
+		} else if (booleanValue() == false
+				&& compareToBoolean.booleanValue() == false) {
+			returnValue = 0;
+		} else if (booleanValue() == true
+				&& compareToBoolean.booleanValue() == true) {
+			returnValue = 0;
+		} else if (booleanValue() == true
+				&& compareToBoolean.booleanValue() == false) {
+			returnValue = 1;
+		}
+
+		return returnValue;
+	}
+
+	/**
+	 * Returns true if and only if the argument is not null and is a
+	 * SortableBoolean object that represents the same boolean value as this
+	 * object.
+	 * 
+	 * @param object
+	 *            The object to compare this object to.
+	 * 
+	 * @return Returns true if the specified object represents the same value as
+	 *         this object.
+	 */
+	public boolean equals(final Object object) {
+		if (object == null) {
+			return false;
+		}
+
+		if (!(object instanceof SortableBoolean)) {
+			return false;
+		}
+
+		return booleanValue.equals(object);
+	}
+
+	/**
+	 * Returns a hash code for this object. This method calls the hashCode
+	 * method on the Boolean object it is wrapping.
+	 * 
+	 * @return The hash code for the Boolean object wrapped by this object.
+	 */
+	public int hashCode() {
+		return booleanValue.hashCode();
+	}
+
+	/**
+	 * Returns a String object representing this object's value. If this object
+	 * represents the value true, a string equal to "true" is returned.
+	 * Otherwise, a string equal to "false" is returned.
+	 * 
+	 * @return A string representation of this object.
+	 */
+	public String toString() {
+		return booleanValue.toString();
+	}
+
+	/**
+	 * Returns the Boolean wrapped by this object.
+	 * 
+	 * @return The Boolean wrapped by this object.
+	 */
+	public Boolean getBoolean() {
+		return booleanValue;
+	}
+}
\ No newline at end of file
Index: ocean/src/net/sourceforge/jsorter/SortableColumnImpl.java
===================================================================
--- ocean/src/net/sourceforge/jsorter/SortableColumnImpl.java	(revision 0)
+++ ocean/src/net/sourceforge/jsorter/SortableColumnImpl.java	(revision 0)
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2002-2005 Robert Breidecker.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sourceforge.jsorter;
+
+/**
+ * This class represents a column that will be sorted by Sorter, SwingSorter, or
+ * SortComparator.
+ * 
+ * @author Robert Breidecker
+ */
+public class SortableColumnImpl implements SortableColumn {
+	/**
+	 * The number position of the column to sort with.
+	 */
+	private int columnPosition = SorterConstants.FIRST_COLUMN_POSITION;
+
+	/**
+	 * The order to sort the column by.
+	 */
+	private int columnOrder = SorterConstants.ASCENDING_ORDER;
+
+	/**
+	 * The name of the sort column.
+	 */
+	private String columnName = null;
+
+	/**
+	 * SortColumn constructor. The constructor will set both the column number
+	 * and the column order.
+	 * 
+	 * @param columnPosition
+	 *            The number position of the column in the data to sort by.
+	 *            Column numbers start at zero for the first column.
+	 * 
+	 * @param columnOrder
+	 *            This value will be used for specifying the order in which the
+	 *            column sorted by. Sort order can either be ascending or
+	 *            descending. The ASCENDING_ORDER and DESCENDING_ORDER constants
+	 *            in the Sorter class should be used for this value.
+	 */
+	public SortableColumnImpl(final int columnPosition, final int columnOrder) {
+		this(columnPosition, columnOrder, null);
+	}
+
+	/**
+	 * SortColumn constructor. The constructor will set both the column number,
+	 * the column order and the column name.
+	 * 
+	 * @param columnPosition
+	 *            The number position of the column in the data to sort by.
+	 *            Column numbers start at zero for the first column.
+	 * 
+	 * @param columnOrder
+	 *            This value will be used for specifying the order in which the
+	 *            column sorted by. Sort order can either be ascending or
+	 *            descending. The ASCENDING_ORDER and DESCENDING_ORDER constants
+	 *            in the Sorter class should be used for this value.
+	 * 
+	 * @param columnName
+	 *            A name or description for the column. This field only needs to
+	 *            be specified if you are planning to display information about
+	 *            the column to the user or want to use the name as an
+	 *            identifier for the column. If you do not want to use this
+	 *            field, you can pass null in as a value.
+	 */
+	public SortableColumnImpl(final int columnPosition, final int columnOrder,
+			final String columnName) {
+		this.columnPosition = columnPosition;
+		this.columnOrder = columnOrder;
+		this.columnName = columnName;
+	}
+
+	/**
+	 * Returns the number position of the column to sort with.
+	 * 
+	 * @return The number position of the column to sort with.
+	 */
+	public int getColumnPosition() {
+		return columnPosition;
+	}
+
+	/**
+	 * Returns the order to sort the column by.
+	 * 
+	 * @return The order to sort the column by.
+	 */
+	public int getColumnOrder() {
+		return columnOrder;
+	}
+
+	/**
+	 * Returns the name of this column.
+	 * 
+	 * @return The name of this column.
+	 */
+	public String getColumnName() {
+		return columnName;
+	}
+}
\ No newline at end of file
Index: ocean/src/net/sourceforge/jsorter/SorterConstants.java
===================================================================
--- ocean/src/net/sourceforge/jsorter/SorterConstants.java	(revision 0)
+++ ocean/src/net/sourceforge/jsorter/SorterConstants.java	(revision 0)
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2002-2005 Robert Breidecker.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sourceforge.jsorter;
+
+/**
+ * This class contains constants used in the JSorter project.
+ * 
+ * @author Robert Breidecker
+ */
+public class SorterConstants {
+	/**
+	 * The constant for the first column position.
+	 */
+	public static final int FIRST_COLUMN_POSITION = 0;
+
+	/**
+	 * The constant for ascending order.
+	 */
+	public static final int ASCENDING_ORDER = 1;
+
+	/**
+	 * The constant for descending order.
+	 */
+	public static final int DESCENDING_ORDER = 0;
+
+	/**
+	 * The constant for stating that null data values are invalid. This is the
+	 * default value.
+	 */
+	public static final int NULLS_ARE_INVALID = 0;
+
+	/**
+	 * The constant for stating that null data values are valid and should be
+	 * treated as the least of possible values when sorting.
+	 */
+	public static final int NULLS_ARE_LEAST = 1;
+
+	/**
+	 * The constant for stating that null data values are valid and should be
+	 * treated as the greatest of possible values when sorting.
+	 */
+	public static final int NULLS_ARE_GREATEST = 2;
+}
\ No newline at end of file
Index: ocean/src/net/sourceforge/jsorter/SortReflect.java
===================================================================
--- ocean/src/net/sourceforge/jsorter/SortReflect.java	(revision 0)
+++ ocean/src/net/sourceforge/jsorter/SortReflect.java	(revision 0)
@@ -0,0 +1,63 @@
+package net.sourceforge.jsorter;
+
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * 
+ * @author Jason Rutherglen
+ */
+public class SortReflect {
+	public static final int METHOD = 1;
+	public static final int FIELD = 2;
+	public int type;
+	public String methodName;
+	public String fieldName;
+	public Object[] params;
+
+	private SortReflect() {
+	}
+  
+	public static class MethodException extends Exception {
+		public MethodException(Throwable throwable) {
+			super(throwable);
+		}
+	}
+	
+	public Object get(Object value) throws MethodException {
+    if (type == METHOD) {
+    	try {
+        return MethodUtil.call(methodName, params, value);
+    	} catch (InvocationTargetException invocationTargetException) {
+    		throw new MethodException(invocationTargetException.getCause());
+    	} catch (Exception exception) {
+    		throw new MethodException(exception);
+    	}
+    } else if (type == FIELD) {
+      return FieldUtil.getFieldValue(fieldName, value);
+    }
+    throw new RuntimeException("type is invalid");
+  }
+
+	public static SortReflect field(String fieldName) {
+		SortReflect sortReflect = new SortReflect();
+		sortReflect.type = FIELD;
+		sortReflect.fieldName = fieldName;
+		return sortReflect;
+	}
+
+	public static SortReflect method(String methodName) {
+		SortReflect sortReflect = new SortReflect();
+		sortReflect.type = METHOD;
+		sortReflect.methodName = methodName;
+		return sortReflect;
+	}
+
+	public static SortReflect method(String methodName, Object[] params) {
+		SortReflect sortReflect = new SortReflect();
+		sortReflect.type = METHOD;
+		sortReflect.methodName = methodName;
+		sortReflect.params = params;
+		return sortReflect;
+	}
+
+}
Index: ocean/src/net/sourceforge/jsorter/MethodUtil.java
===================================================================
--- ocean/src/net/sourceforge/jsorter/MethodUtil.java	(revision 0)
+++ ocean/src/net/sourceforge/jsorter/MethodUtil.java	(revision 0)
@@ -0,0 +1,38 @@
+package net.sourceforge.jsorter;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * 
+ * @author Jason Rutherglen
+ */
+public class MethodUtil {
+
+	public MethodUtil() {
+	}
+
+	public static Object call(String methodName, Object parameters, Object object) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
+		Object[] parameterArray = null;
+		Class[] classArray = null;
+		if (parameters != null) {
+			if (!parameters.getClass().isArray()) {
+				parameterArray = new Object[] { parameters };
+				classArray = new Class[] { parameters.getClass() };
+			} else {
+				classArray = new Class[parameterArray.length];
+				for (int x = 0; x < parameterArray.length; x++) {
+					if (parameterArray[x] != null) {
+						classArray[x] = parameterArray.getClass();
+					}
+				}
+			}
+		} else {
+			parameterArray = new Object[0];
+			classArray = new Class[0];
+		}
+
+		Method method = object.getClass().getMethod(methodName, classArray);
+		return method.invoke(object, parameterArray);
+	}
+}
Index: ocean/src/net/sourceforge/jsorter/Sorter.java
===================================================================
--- ocean/src/net/sourceforge/jsorter/Sorter.java	(revision 0)
+++ ocean/src/net/sourceforge/jsorter/Sorter.java	(revision 0)
@@ -0,0 +1,383 @@
+/*
+ * Copyright 2002-2005 Robert Breidecker.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sourceforge.jsorter;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+import net.sourceforge.jsorter.SortReflect.MethodException;
+
+/**
+ * Used for sorting lists of objects. This class is particularly good for
+ * sorting table data and multi-column lists.
+ * 
+ * Note: This is class is not thread safe.
+ * 
+ * @author Robert Breidecker
+ */
+public class Sorter<T> {
+	/**
+	 * Holds the data used for sorting.
+	 */
+	private List table = null;
+
+	/**
+	 * The columns to sort by.
+	 */
+	private List sortColumns = null;
+
+	/**
+	 * Indicate how to treat null data values.
+	 */
+	private int nullBehavior = SorterConstants.NULLS_ARE_INVALID;
+  
+  public List<T> getReflectResults() {
+    List<T> results = new ArrayList<T>(table.size());
+    Iterator<T> iterator = table.iterator();
+    while (iterator.hasNext()) {
+      List row = (List)iterator.next();
+      SortHolder<T> sortHolder = (SortHolder<T>)row.get(0);
+      results.add(sortHolder.object);
+    }
+    return results;
+  }
+  
+  public static Sorter createReflect(ReflectColumns reflectColumns, List objects, int nullBehavior) throws MethodException {
+    SortReflect[] sortReflects = reflectColumns.getSortReflects();
+    List table = SortHolder.getTable(sortReflects, objects, nullBehavior);
+    Sorter sorter = new Sorter(table, reflectColumns.getColumns());
+    sorter.setNullBehavior(nullBehavior);
+    return sorter;
+  }
+  
+	/**
+	 * Sorter constructor. This version of the constructor does not use any
+	 * parameters. If you use this constructor, you should use the setter
+	 * methods to set the values you want the sort routine to use.
+	 */
+	public Sorter() {
+		super();
+	}
+
+	/**
+	 * Sorter constructor. This version of the contructor takes only a table as
+	 * a parameter. Since no sort columns are specified, this will cause the
+	 * sort routine to sort the table using all of the columns in the table in
+	 * ascending order. To change the table or sort columns, you must use the
+	 * appropriate "set" methods.
+	 * 
+	 * @param table
+	 *            This field should be an object that implements the List
+	 *            interface. This list we will call the "table". Classes that
+	 *            implement the List interface include ArrayList, Vector and
+	 *            LinkedList. Each item in the table list should itself be an
+	 *            object that implements the List interface. These lists we will
+	 *            call "columns". Column lists should only contain objects that
+	 *            implement the Comparable interface. Classes that implement the
+	 *            Comparable interface include String, Integer, Long, Short,
+	 *            Float, Byte, Double and Date. See the standard JDK documention
+	 *            for Comparator for a complete list. The class type for each
+	 *            object in a column list must be consistent or a
+	 *            ClassCaseException will be thrown during the sort.
+	 */
+	public Sorter(final List table) {
+		// Set the values for the new sorter object.
+		setTable(table);
+	}
+
+	/**
+	 * Sorter constructor. This version of the contructor takes a table and a
+	 * column order as parameters. A sort column for each column in the data
+	 * will be created with the column order specified. This will cause the sort
+	 * routine to sort the table using all of the columns in the table (position
+	 * 0 and up or left to right) in the order specified in the column order. To
+	 * override the table or sort columns, you must use the appropriate "set"
+	 * methods. A vector will be used for storing the sort columns that are
+	 * dynamically created.
+	 * 
+	 * @param table
+	 *            This field should be an object that implements the List
+	 *            interface. This list we will call the "table". Classes that
+	 *            implement the List interface include ArrayList, Vector and
+	 *            LinkedList. Each item in the table list should itself be an
+	 *            object that implements the List interface. These lists we will
+	 *            call "columns". Column lists should only contain objects that
+	 *            implement the Comparable interface. Classes that implement the
+	 *            Comparable interface include String, Integer, Long, Short,
+	 *            Float, Byte, Double and Date. See the standard JDK documention
+	 *            for Comparator for a complete list. The class type for each
+	 *            object in a column list must be consistent or a
+	 *            ClassCaseException will be thrown during the sort.
+	 * 
+	 * @param columnOrder
+	 *            This value will be used for specifying the order in which the
+	 *            columns in the table are sorted. Sort order can either be
+	 *            ascending or descending. The ASCENDING_ORDER and
+	 *            DESCENDING_ORDER constants in this class should be used for
+	 *            this value.
+	 */
+	public Sorter(final List table, final int columnOrder) {
+		// Get the first row.
+		List firstRow = null;
+		if (table.size() > 0) {
+			firstRow = (List)table.get(0);
+		}
+
+		// Build the sort columns.
+		List sortColumns = null;
+		if (firstRow != null) {
+			sortColumns = new Vector();
+			final int numColumns = firstRow.size();
+			for (int columnCtr = 0; columnCtr < numColumns; columnCtr++) {
+				final SortableColumn sortColumn = new SortableColumnImpl(columnCtr, columnOrder);
+
+				sortColumns.add(sortColumn);
+			}
+		}
+
+		// Set the values for the new sorter object.
+		setTable(table);
+		setSortColumns(sortColumns);
+	}
+
+	/**
+	 * Sorter constructor. This version of the contructor takes a table and a
+	 * list of sort columns as parameters. To override the table or sort
+	 * columns, you must use the appropriate "set" methods.
+	 * 
+	 * @param table
+	 *            This field should be an object that implements the List
+	 *            interface. This list we will call the "table". Classes that
+	 *            implement the List interface include ArrayList, Vector and
+	 *            LinkedList. Each item in the table list should itself be an
+	 *            object that implements the List interface. These lists we will
+	 *            call "columns". Column lists should only contain objects that
+	 *            implement the Comparable interface. Classes that implement the
+	 *            Comparable interface include String, Integer, Long, Short,
+	 *            Float, Byte, Double and Date. See the standard JDK documention
+	 *            for Comparator for a complete list. The class type for each
+	 *            object in a column list must be consistent or a
+	 *            ClassCaseException will be thrown during the sort.
+	 * 
+	 * @param sortColumns
+	 *            Sort columns are a list of numbers specifying the colums to
+	 *            sort the table by. Each number in the list should be an
+	 *            instance of the SortColumn class represents a position of a
+	 *            column in the table. Column position start at zero just as
+	 *            they do in the standard Collection classes and Java arrays.
+	 *            When this field is null, there must be only one and only one
+	 *            column order specified. The sort routine will then sort using
+	 *            every column in the table starting from position 0 and up or
+	 *            left to right in ascending order.
+	 */
+	public Sorter(final List table, final List sortColumns) {
+		// Set the values for the new sorter object.
+		setTable(table);
+		setSortColumns(sortColumns);
+	}
+
+	/**
+	 * Returns the list of columns to sort by.
+	 * 
+	 * @return The list of columns to sort by.
+	 */
+	public List getSortColumns() {
+		return sortColumns;
+	}
+
+	/**
+	 * Returns the table of sort data.
+	 * 
+	 * @return The table of sort data.
+	 */
+	public List<T> getTable() {
+		return table;
+	}
+
+	/**
+	 * Updates the list of sort columns.
+	 * 
+	 * @param sortColumns
+	 *            Sort columns are a list of numbers specifying the colums to
+	 *            sort the table by. Each number in the list should be an
+	 *            instance of the SortColumn class represents a position of a
+	 *            column in the table. Column position start at zero just as
+	 *            they do in the standard Collection classes and Java arrays.
+	 *            When this field is null, there must be only one and only one
+	 *            column order specified. The sort routine will then sort using
+	 *            every column in the table starting from position 0 and up or
+	 *            left to right in ascending order.
+	 */
+	public void setSortColumns(final List sortColumns) {
+		validateSortColumns(sortColumns);
+		this.sortColumns = sortColumns;
+	}
+
+	/**
+	 * Updates the table of sort data.
+	 * 
+	 * @param table
+	 *            This field should be an object that implements the List
+	 *            interface. This list we will call the "table". Classes that
+	 *            implement the List interface include ArrayList, Vector and
+	 *            LinkedList. Each item in the table list should itself be an
+	 *            object that implements the List interface. These lists we will
+	 *            call "columns". Column lists should only contain objects that
+	 *            implement the Comparable interface. Classes that implement the
+	 *            Comparable interface include String, Integer, Long, Short,
+	 *            Float, Byte, Double and Date. See the standard JDK documention
+	 *            for Comparator for a complete list. The class type for each
+	 *            object in a column list must be consistent or a
+	 *            ClassCaseException will be thrown during the sort.
+	 */
+	public void setTable(final List table) {
+		validateTable(table);
+		this.table = table;
+	}
+
+	/**
+	 * Returns the null behavior for this object.
+	 * 
+	 * @return An integer representing the constant that indicates how null data
+	 *         values should behave while being sorted. See the null behavior
+	 *         constants in this class. The default value for this class is
+	 *         NULLS_ARE_INVALID.
+	 */
+	public int getNullBehavior() {
+		return nullBehavior;
+	}
+
+	/**
+	 * Set the null behavior for this object.
+	 * 
+	 * @param nullBehavior
+	 *            An integer representing the constant that indicates how null
+	 *            data values should behave while being sorted. See the null
+	 *            behavior constants in this class.
+	 * 
+	 * @throws IllegalArgumentException
+	 *             Thrown if the null behavior value is not valid.
+	 */
+	public void setNullBehavior(final int nullBehavior) {
+		if (nullBehavior != SorterConstants.NULLS_ARE_GREATEST
+				&& nullBehavior != SorterConstants.NULLS_ARE_INVALID
+				&& nullBehavior != SorterConstants.NULLS_ARE_LEAST) {
+			throw new IllegalArgumentException("Invalid null behavior.");
+		}
+
+		this.nullBehavior = nullBehavior;
+	}
+
+	/**
+	 * This routine sorts the table of data. The sort uses the sort columns to
+	 * determine how to sort the data.
+	 * 
+	 * @exception IllegalStateException
+	 *                The data in this class is in an invalid state.
+	 */
+	public void sort() {
+		// Sort the data.
+    List sortColumns = getSortColumns();
+    //System.out.println("sortColumns: "+sortColumns);
+		Collections.sort(table, new SortComparator(getSortColumns(), getNullBehavior()));
+	}
+  
+  public List<T> sortReflect() {
+    sort();
+    return getReflectResults();
+  }
+  
+	/**
+	 * This routine sorts the table of data using a comparator provided in the
+	 * parameters to do the sorting. The sort columns for this class will not be
+	 * used unless the input comparator has been coded to do so.
+	 * 
+	 * @param comparator
+	 *            A comparator to use for comparing the data rows in the table
+	 *            that has already been set on this class.
+	 * 
+	 * @exception IllegalStateException
+	 *                The data in Sorter is in an invalid state.
+	 */
+	public void sort(final Comparator comparator) {
+		// Sort the data.
+		Collections.sort(getTable(), comparator);
+	}
+
+	/**
+	 * Validates the list of sort columns.
+	 * 
+	 * @param sortColumns
+	 *            The list of sort columns to validate.
+	 * 
+	 * @exception IllegalArgumentException
+	 *                Input data is invalid.
+	 */
+	private void validateSortColumns(final List sortColumns) {
+		if (sortColumns != null) {
+			// Validate size.
+			if (sortColumns.size() < 1) {
+				throw new IllegalArgumentException("Sort columns can "+ "not be empty.");
+			}
+
+			for (int ctr = 0, size = sortColumns.size(); ctr < size; ctr++) {
+				// Validate for SortColumns.
+				if (!(sortColumns.get(ctr) instanceof SortableColumn)) {
+					throw new IllegalArgumentException("The list of sort "+ "columns does not contain all SortColumn objects.");
+				}
+
+				// Validate for greater than or equal to zero.
+				if (((SortableColumn) sortColumns.get(ctr)).getColumnPosition() < 0) {
+					throw new IllegalArgumentException("A sort column number is less than zero.");
+				}
+
+				// Validate for invalid column order.
+				if (((SortableColumn)sortColumns.get(ctr)).getColumnOrder() != SorterConstants.ASCENDING_ORDER
+						&& ((SortableColumn)sortColumns.get(ctr)).getColumnOrder() != SorterConstants.DESCENDING_ORDER) {
+					throw new IllegalArgumentException("A sort column order is invalid.");
+				}
+			}
+		}
+	}
+
+	/**
+	 * Validates the table of sort data.
+	 * 
+	 * @param table
+	 *            The table of sort data to validate.
+	 * 
+	 * @exception IllegalArgumentException
+	 *                Input data is invalid.
+	 */
+	private void validateTable(final List table) {
+		// Validate for null.
+		if (table == null) {
+			throw new IllegalArgumentException("The table of sort data "+ "can not be null.");
+		}
+
+		// Validate for Lists.
+		if (table.size() > 0) {
+			if (!(table.get(0) instanceof List)) {
+				throw new IllegalArgumentException("The table does not implement " + "the List interface.");
+			}
+		}
+	}
+}
\ No newline at end of file
Index: ocean/src/net/sourceforge/jsorter/FieldUtil.java
===================================================================
--- ocean/src/net/sourceforge/jsorter/FieldUtil.java	(revision 0)
+++ ocean/src/net/sourceforge/jsorter/FieldUtil.java	(revision 0)
@@ -0,0 +1,74 @@
+/*
+ * FieldUtil.java
+ *
+ * Created on February 3, 2006, 5:24 PM
+ */
+
+package net.sourceforge.jsorter;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ *
+ * @author  administrator
+ * @version
+ */
+public class FieldUtil {
+  private static Map fieldMap = new ConcurrentHashMap();
+  
+  public static void setFieldValue(String fieldName, Object object, Object value) {
+    try {
+      Field field = getField(object.getClass(), fieldName);
+      field.set(object, value);
+    } catch (Exception ex) { ex.printStackTrace(); }
+  }
+  
+  public static Object getFieldValue(String fieldName, Object object) {
+    try {
+      Field field = getField(object.getClass(), fieldName);
+      return field.get(object);
+    } catch (Exception ex) { ex.printStackTrace(); return null; }
+  }
+  
+  public static Field getField(Class clazz, String fieldName) {
+    Field[] fields = getFields(clazz);
+    for (int x=0; x < fields.length; x++) {
+      if (fields[x].getName().equals(fieldName)) {
+        return fields[x];
+      }
+    }
+    return null;
+  }
+  
+  public static Field[] getFields(Class clazz) {
+    Field[] fields = (Field[])fieldMap.get(clazz);
+    if (fields != null) return fields;
+    List list = new ArrayList();
+    getFields(clazz, list);
+    
+    fields = new Field[list.size()];
+    for (int x=0; x < fields.length; x++) {
+      fields[x] = (Field)list.get(x);
+    }
+    fieldMap.put(clazz, fields);
+    return fields;
+  }
+  
+  public static void getFields(Class clazz, List list) {
+    Class superClass = clazz.getSuperclass();
+    if (superClass != null) getFields(superClass, list);
+    Field[] fields = clazz.getDeclaredFields();
+    for (int x=0; x < fields.length; x++) {
+      int modifiers = fields[x].getModifiers();
+      if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers)
+      && !Modifier.isTransient(modifiers) && !Modifier.isFinal(modifiers))
+        list.add(fields[x]);
+    }
+  }
+}
+
Index: ocean/src/net/sourceforge/jsorter/MethodUtil.java
===================================================================
--- ocean/src/net/sourceforge/jsorter/MethodUtil.java	(revision 0)
+++ ocean/src/net/sourceforge/jsorter/MethodUtil.java	(revision 0)
@@ -0,0 +1,38 @@
+package net.sourceforge.jsorter;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * 
+ * @author Jason Rutherglen
+ */
+public class MethodUtil {
+
+	public MethodUtil() {
+	}
+
+	public static Object call(String methodName, Object parameters, Object object) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
+		Object[] parameterArray = null;
+		Class[] classArray = null;
+		if (parameters != null) {
+			if (!parameters.getClass().isArray()) {
+				parameterArray = new Object[] { parameters };
+				classArray = new Class[] { parameters.getClass() };
+			} else {
+				classArray = new Class[parameterArray.length];
+				for (int x = 0; x < parameterArray.length; x++) {
+					if (parameterArray[x] != null) {
+						classArray[x] = parameterArray.getClass();
+					}
+				}
+			}
+		} else {
+			parameterArray = new Object[0];
+			classArray = new Class[0];
+		}
+
+		Method method = object.getClass().getMethod(methodName, classArray);
+		return method.invoke(object, parameterArray);
+	}
+}
Index: ocean/src/net/sourceforge/jsorter/ReflectColumns.java
===================================================================
--- ocean/src/net/sourceforge/jsorter/ReflectColumns.java	(revision 0)
+++ ocean/src/net/sourceforge/jsorter/ReflectColumns.java	(revision 0)
@@ -0,0 +1,66 @@
+
+
+package net.sourceforge.jsorter;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ *
+ * @author  Jason Rutherglen
+ */
+public class ReflectColumns {
+  public List<SortableColumn> list = new ArrayList<SortableColumn>();
+  
+  public ReflectColumns() {
+  }
+  
+  public SortReflect[] getSortReflects() {
+    SortReflect[] sortReflects = new SortReflect[list.size()];
+    int count = 0;
+    Iterator iterator = list.iterator();
+    while (iterator.hasNext()) {
+      SortableColumnReflect sortableColumnReflect = (SortableColumnReflect)iterator.next();
+      sortReflects[count] = sortableColumnReflect.sortReflect;
+      
+      count++;
+    }
+    return sortReflects;
+  }
+  
+  public List<SortableColumn> getColumns() {
+    return list;
+  }
+  
+  public class SortableColumnReflect implements SortableColumn {
+    int position;
+    String name;
+    SortReflect sortReflect;
+    int order;
+    
+    public SortableColumnReflect(int position, String name, SortReflect sortReflect, int order) {
+      this.position = position;
+      this.name = name;
+      this.sortReflect = sortReflect;
+      this.order = order;
+    }
+    
+    public String getColumnName() {
+      return name;
+    }
+    
+    public int getColumnOrder() {
+      return order;
+    }
+    
+    public int getColumnPosition() {
+      return position;
+    }
+  }
+  
+  public void add(String name, SortReflect sortReflect, int order) {
+    SortableColumnReflect sortableColumnReflect = new SortableColumnReflect(list.size(), name, sortReflect, order);
+    list.add(sortableColumnReflect);
+  }
+}
Index: ocean/src/net/sourceforge/jsorter/SortableBoolean.java
===================================================================
--- ocean/src/net/sourceforge/jsorter/SortableBoolean.java	(revision 0)
+++ ocean/src/net/sourceforge/jsorter/SortableBoolean.java	(revision 0)
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2002-2005 Robert Breidecker.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sourceforge.jsorter;
+
+/**
+ * This class is a wrapper on top of the Boolean class. Its purpose is to
+ * provide a class that allows for Boolean values to be sorted, because the
+ * Boolean class does not implement the Comparable interface.
+ * 
+ * @author Robert Breidecker
+ */
+public final class SortableBoolean implements Comparable {
+	/**
+	 * The Boolean value being wrapped.
+	 */
+	private Boolean booleanValue = null;
+
+	/**
+	 * Creates a new SortableBoolean object from the input boolean value.
+	 * 
+	 * @param value
+	 *            The boolean value used to create the new SortableBoolean.
+	 */
+	public SortableBoolean(final boolean value) {
+		booleanValue = new Boolean(value);
+	}
+
+	/**
+	 * Creates a new SortableBoolean object from the input Boolean.
+	 */
+	public SortableBoolean(final Boolean value) {
+		booleanValue = value;
+	}
+
+	/**
+	 * Creates a new SortableBoolean object from the input String. The new
+	 * object will have a true value if the input value is "true", otherwise it
+	 * will have a false value.
+	 */
+	public SortableBoolean(final String value) {
+		booleanValue = new Boolean(value);
+	}
+
+	/**
+	 * Returns the primitive boolean value for this object.
+	 * 
+	 * @return The primitive boolean value for this object.
+	 */
+	public boolean booleanValue() {
+		return booleanValue.booleanValue();
+	}
+
+	/**
+	 * Compares this object with the specified object for order. Returns a
+	 * negative integer, zero, or a positive integer as this object is less
+	 * than, equal to, or greater than the specified object.
+	 * 
+	 * @param object
+	 *            The object to compare this object to.
+	 * 
+	 * @return A negative integer, zero, or a positive integer as this object is
+	 *         less than, equal to, or greater than the specified object.
+	 */
+	public int compareTo(final Object object) {
+		int returnValue = -1;
+
+		if (object == null) {
+			throw new IllegalArgumentException(
+					"This object can not be compared " + "to a null value.");
+		}
+
+		if (!(object instanceof SortableBoolean)) {
+			throw new IllegalArgumentException("The input object must be an "
+					+ "instance of SortableBoolean.");
+		}
+
+		final SortableBoolean compareToBoolean = (SortableBoolean) object;
+
+		if (booleanValue() == false && compareToBoolean.booleanValue() == true) {
+			returnValue = -1;
+		} else if (booleanValue() == false
+				&& compareToBoolean.booleanValue() == false) {
+			returnValue = 0;
+		} else if (booleanValue() == true
+				&& compareToBoolean.booleanValue() == true) {
+			returnValue = 0;
+		} else if (booleanValue() == true
+				&& compareToBoolean.booleanValue() == false) {
+			returnValue = 1;
+		}
+
+		return returnValue;
+	}
+
+	/**
+	 * Returns true if and only if the argument is not null and is a
+	 * SortableBoolean object that represents the same boolean value as this
+	 * object.
+	 * 
+	 * @param object
+	 *            The object to compare this object to.
+	 * 
+	 * @return Returns true if the specified object represents the same value as
+	 *         this object.
+	 */
+	public boolean equals(final Object object) {
+		if (object == null) {
+			return false;
+		}
+
+		if (!(object instanceof SortableBoolean)) {
+			return false;
+		}
+
+		return booleanValue.equals(object);
+	}
+
+	/**
+	 * Returns a hash code for this object. This method calls the hashCode
+	 * method on the Boolean object it is wrapping.
+	 * 
+	 * @return The hash code for the Boolean object wrapped by this object.
+	 */
+	public int hashCode() {
+		return booleanValue.hashCode();
+	}
+
+	/**
+	 * Returns a String object representing this object's value. If this object
+	 * represents the value true, a string equal to "true" is returned.
+	 * Otherwise, a string equal to "false" is returned.
+	 * 
+	 * @return A string representation of this object.
+	 */
+	public String toString() {
+		return booleanValue.toString();
+	}
+
+	/**
+	 * Returns the Boolean wrapped by this object.
+	 * 
+	 * @return The Boolean wrapped by this object.
+	 */
+	public Boolean getBoolean() {
+		return booleanValue;
+	}
+}
\ No newline at end of file
Index: ocean/src/net/sourceforge/jsorter/SortableColumn.java
===================================================================
--- ocean/src/net/sourceforge/jsorter/SortableColumn.java	(revision 0)
+++ ocean/src/net/sourceforge/jsorter/SortableColumn.java	(revision 0)
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2002-2005 Robert Breidecker.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sourceforge.jsorter;
+
+/**
+ * This class represents a column that will be sorted by Sorter, SwingSorter, or
+ * SortComparator.
+ * 
+ * @author Robert Breidecker
+ */
+public interface SortableColumn {
+	/**
+	 * Returns the number position of the column to sort with.
+	 * 
+	 * @return The number position of the column to sort with.
+	 */
+	public int getColumnPosition();
+
+	/**
+	 * Returns the order to sort the column by.
+	 * 
+	 * @return The order to sort the column by.
+	 */
+	public int getColumnOrder();
+
+	/**
+	 * Returns the name of this column.
+	 * 
+	 * @return The name of this column.
+	 */
+	public String getColumnName();
+}
\ No newline at end of file
Index: ocean/src/net/sourceforge/jsorter/SortableColumnImpl.java
===================================================================
--- ocean/src/net/sourceforge/jsorter/SortableColumnImpl.java	(revision 0)
+++ ocean/src/net/sourceforge/jsorter/SortableColumnImpl.java	(revision 0)
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2002-2005 Robert Breidecker.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sourceforge.jsorter;
+
+/**
+ * This class represents a column that will be sorted by Sorter, SwingSorter, or
+ * SortComparator.
+ * 
+ * @author Robert Breidecker
+ */
+public class SortableColumnImpl implements SortableColumn {
+	/**
+	 * The number position of the column to sort with.
+	 */
+	private int columnPosition = SorterConstants.FIRST_COLUMN_POSITION;
+
+	/**
+	 * The order to sort the column by.
+	 */
+	private int columnOrder = SorterConstants.ASCENDING_ORDER;
+
+	/**
+	 * The name of the sort column.
+	 */
+	private String columnName = null;
+
+	/**
+	 * SortColumn constructor. The constructor will set both the column number
+	 * and the column order.
+	 * 
+	 * @param columnPosition
+	 *            The number position of the column in the data to sort by.
+	 *            Column numbers start at zero for the first column.
+	 * 
+	 * @param columnOrder
+	 *            This value will be used for specifying the order in which the
+	 *            column sorted by. Sort order can either be ascending or
+	 *            descending. The ASCENDING_ORDER and DESCENDING_ORDER constants
+	 *            in the Sorter class should be used for this value.
+	 */
+	public SortableColumnImpl(final int columnPosition, final int columnOrder) {
+		this(columnPosition, columnOrder, null);
+	}
+
+	/**
+	 * SortColumn constructor. The constructor will set both the column number,
+	 * the column order and the column name.
+	 * 
+	 * @param columnPosition
+	 *            The number position of the column in the data to sort by.
+	 *            Column numbers start at zero for the first column.
+	 * 
+	 * @param columnOrder
+	 *            This value will be used for specifying the order in which the
+	 *            column sorted by. Sort order can either be ascending or
+	 *            descending. The ASCENDING_ORDER and DESCENDING_ORDER constants
+	 *            in the Sorter class should be used for this value.
+	 * 
+	 * @param columnName
+	 *            A name or description for the column. This field only needs to
+	 *            be specified if you are planning to display information about
+	 *            the column to the user or want to use the name as an
+	 *            identifier for the column. If you do not want to use this
+	 *            field, you can pass null in as a value.
+	 */
+	public SortableColumnImpl(final int columnPosition, final int columnOrder,
+			final String columnName) {
+		this.columnPosition = columnPosition;
+		this.columnOrder = columnOrder;
+		this.columnName = columnName;
+	}
+
+	/**
+	 * Returns the number position of the column to sort with.
+	 * 
+	 * @return The number position of the column to sort with.
+	 */
+	public int getColumnPosition() {
+		return columnPosition;
+	}
+
+	/**
+	 * Returns the order to sort the column by.
+	 * 
+	 * @return The order to sort the column by.
+	 */
+	public int getColumnOrder() {
+		return columnOrder;
+	}
+
+	/**
+	 * Returns the name of this column.
+	 * 
+	 * @return The name of this column.
+	 */
+	public String getColumnName() {
+		return columnName;
+	}
+}
\ No newline at end of file
Index: ocean/src/net/sourceforge/jsorter/SortableSwingComponent.java
===================================================================
--- ocean/src/net/sourceforge/jsorter/SortableSwingComponent.java	(revision 0)
+++ ocean/src/net/sourceforge/jsorter/SortableSwingComponent.java	(revision 0)
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2002-2005 Robert Breidecker.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sourceforge.jsorter;
+
+import java.util.List;
+
+/**
+ * This class represents a component that will be sorted by SwingSorter.
+ * 
+ * @author Robert Breidecker
+ */
+public interface SortableSwingComponent {
+	/**
+	 * Returns a lists of lists that contain the component's data values. Each
+	 * inner list contained in the outer list represents a row of data.
+	 * 
+	 * @return The data list for this component.
+	 */
+	public List getDataList();
+}
\ No newline at end of file
Index: ocean/src/net/sourceforge/jsorter/SortComparator.java
===================================================================
--- ocean/src/net/sourceforge/jsorter/SortComparator.java	(revision 0)
+++ ocean/src/net/sourceforge/jsorter/SortComparator.java	(revision 0)
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2002-2005 Robert Breidecker.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sourceforge.jsorter;
+
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * The default comparator for the Sort and SwingSorter classes. This comparator
+ * is used for sorting multiple columns.
+ * 
+ * @author Robert Breidecker
+ */
+public class SortComparator implements Comparator {
+	/**
+	 * The list of sort columns.
+	 */
+	private List sortColumns = null;
+
+	/**
+	 * Indicate how to treat null data values.
+	 */
+	private int nullBehavior = SorterConstants.NULLS_ARE_INVALID;
+
+	/**
+	 * Comparator constructor.
+	 * 
+	 * @param sortColumns
+	 *            A list of SortColumns that represent the positions of columns
+	 *            in your data that you want to be evaluated in the sort. The
+	 *            sort will start with the first column position in the list.
+	 *            Column positions not in the list will not be used in the sort.
+	 *            The number of items in this list should equal the number of
+	 *            items in the columnOrders list.
+	 * 
+	 * @param nullBehavior
+	 *            An integer representing the constant that indicates how null
+	 *            data values should behave while being sorted. See the null
+	 *            behavior constants in this class.
+	 */
+	public SortComparator(final List sortColumns, final int nullBehavior) {
+		this.sortColumns = sortColumns;
+		this.nullBehavior = nullBehavior;
+	}
+
+	/**
+	 * Overrides the java.util.Comparator compare method. This method is used
+	 * for comparing two dimensional data. See the standard JDK documention for
+	 * more information.
+	 * 
+	 * @param one
+	 *            Object - The first object used in the compare. This field
+	 *            should be a list of lists containing objects which implement
+	 *            the Comparable interface. Some of these object types include
+	 *            String, Integer, Long, Short, Float, Byte, Double and Date.
+	 *            See the standard JDK documention for Comparator for a complete
+	 *            list. The object type for each column of data must be
+	 *            consistent or a ClassCaseException will be thrown.
+	 * 
+	 * @param two
+	 *            Object - The second object used in the compare. This field
+	 *            should be a list of lists containing objects which implement
+	 *            the Comparable interface. Some of these object types include
+	 *            String, Integer, Long, Short, Float, Byte, Double and Date.
+	 *            See the standard JDK documention for Comparator for a complete
+	 *            list. The object type for each column of data must be
+	 *            consistent or a ClassCaseException will be thrown.
+	 * 
+	 * @return A negative integer, zero, or a positive integer as the first
+	 *         argument is less than, equal to, or greater than the second.
+	 * 
+	 * @exception ClassCastException
+	 *                Data in a column in not all of the same data type.
+	 */
+	public int compare(final Object one, final Object two) throws ClassCastException {
+		// The number of columns in the table.
+		int numColumns;
+
+		// The return value.
+		int rtn = 0;
+
+		// Used for counting the number of real fields in the data.
+		int ctr = 0;
+
+		// Holds the type of sort order being used.
+		int columnOrder;
+
+		// Used for counting the number of values in the sort columns.
+		int compareCtr;
+
+		// One row of data;
+		final List listOne = (List)one;
+
+		if (sortColumns == null) {
+			numColumns = listOne.size();
+		} else {
+			numColumns = sortColumns.size();
+		}
+
+		while (rtn == 0 && ctr < numColumns) {
+			// The first object to compare.
+			Comparable comparableOne;
+
+			// The second object to compare.
+			Comparable comparableTwo;
+
+			// Make sure compare column is within range.
+			if (sortColumns == null) {
+				compareCtr = ctr;
+			} else {
+				compareCtr = ((SortableColumn)sortColumns.get(ctr)).getColumnPosition();
+			}
+
+			if (compareCtr <= listOne.size()) {
+				// Another row of data;
+				final List listTwo = (List) two;
+
+				// Get the field to use in the compare.
+				if (sortColumns == null) {
+					comparableOne = (Comparable)listOne.get(compareCtr);
+					comparableTwo = (Comparable)listTwo.get(compareCtr);
+				} else {
+					comparableOne = (Comparable)listOne.get(((SortableColumn) sortColumns.get(ctr)).getColumnPosition());
+					comparableTwo = (Comparable)listTwo.get(((SortableColumn) sortColumns.get(ctr)).getColumnPosition());
+				}
+
+				// Get the sort type that goes with the sort column.
+				if (sortColumns == null) {
+					// If no sort columns were specified, then use ascending
+					// order.
+					columnOrder = SorterConstants.ASCENDING_ORDER;
+				} else {
+					columnOrder = ((SortableColumn)sortColumns.get(ctr)).getColumnOrder();
+				}
+
+				// Compare the objects.
+				if (comparableOne != null && comparableTwo != null) {
+					if (columnOrder == SorterConstants.ASCENDING_ORDER) {
+						try {
+							rtn = comparableOne.compareTo(comparableTwo);
+						} catch (ClassCastException exception) {
+							throw exception;
+						}
+					} else {
+						try {
+							rtn = comparableTwo.compareTo(comparableOne);
+						} catch (ClassCastException exception) {
+							throw exception;
+						}
+					}
+				} else {
+					if (nullBehavior == SorterConstants.NULLS_ARE_INVALID) {
+						throw new IllegalStateException("Null data values are not valid.");
+					} else if (comparableOne == null && comparableTwo != null) {
+						if (columnOrder == SorterConstants.ASCENDING_ORDER) {
+							rtn = -1;
+						} else {
+							rtn = 1;
+						}
+
+						if (nullBehavior == SorterConstants.NULLS_ARE_GREATEST) {
+							rtn = rtn * -1;
+						}
+					} else if (comparableOne != null && comparableTwo == null) {
+						if (columnOrder == SorterConstants.ASCENDING_ORDER) {
+							rtn = 1;
+						} else {
+							rtn = -1;
+						}
+
+						if (nullBehavior == SorterConstants.NULLS_ARE_GREATEST) {
+							rtn = rtn * -1;
+						}
+					} else {
+						rtn = 0;
+					}
+				}
+			}
+			ctr++;
+		}
+		return rtn;
+	}
+
+	/**
+	 * Returns the null behavior for this object.
+	 * 
+	 * @return An integer representing the constant that indicates how null data
+	 *         values should behave while being sorted. See the null behavior
+	 *         constants in this class. The default value for this class is
+	 *         NULLS_ARE_INVALID.
+	 */
+	public int getNullBehavior() {
+		return nullBehavior;
+	}
+
+	/**
+	 * Set the null behavior for this object.
+	 * 
+	 * @param nullBehavior
+	 *            An integer representing the constant that indicates how null
+	 *            data values should behave while being sorted. See the null
+	 *            behavior constants in this class.
+	 * 
+	 * @throws IllegalArgumentException
+	 *             Thrown if the null behavior value is not valid.
+	 */
+	public void setNullBehavior(final int nullBehavior) {
+		if (nullBehavior != SorterConstants.NULLS_ARE_GREATEST
+				&& nullBehavior != SorterConstants.NULLS_ARE_INVALID
+				&& nullBehavior != SorterConstants.NULLS_ARE_LEAST) {
+			throw new IllegalArgumentException("Invalid null behavior.");
+		}
+
+		this.nullBehavior = nullBehavior;
+	}
+}
\ No newline at end of file
Index: ocean/src/net/sourceforge/jsorter/Sorter.java
===================================================================
--- ocean/src/net/sourceforge/jsorter/Sorter.java	(revision 0)
+++ ocean/src/net/sourceforge/jsorter/Sorter.java	(revision 0)
@@ -0,0 +1,383 @@
+/*
+ * Copyright 2002-2005 Robert Breidecker.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sourceforge.jsorter;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+import net.sourceforge.jsorter.SortReflect.MethodException;
+
+/**
+ * Used for sorting lists of objects. This class is particularly good for
+ * sorting table data and multi-column lists.
+ * 
+ * Note: This is class is not thread safe.
+ * 
+ * @author Robert Breidecker
+ */
+public class Sorter<T> {
+	/**
+	 * Holds the data used for sorting.
+	 */
+	private List table = null;
+
+	/**
+	 * The columns to sort by.
+	 */
+	private List sortColumns = null;
+
+	/**
+	 * Indicate how to treat null data values.
+	 */
+	private int nullBehavior = SorterConstants.NULLS_ARE_INVALID;
+  
+  public List<T> getReflectResults() {
+    List<T> results = new ArrayList<T>(table.size());
+    Iterator<T> iterator = table.iterator();
+    while (iterator.hasNext()) {
+      List row = (List)iterator.next();
+      SortHolder<T> sortHolder = (SortHolder<T>)row.get(0);
+      results.add(sortHolder.object);
+    }
+    return results;
+  }
+  
+  public static Sorter createReflect(ReflectColumns reflectColumns, List objects, int nullBehavior) throws MethodException {
+    SortReflect[] sortReflects = reflectColumns.getSortReflects();
+    List table = SortHolder.getTable(sortReflects, objects, nullBehavior);
+    Sorter sorter = new Sorter(table, reflectColumns.getColumns());
+    sorter.setNullBehavior(nullBehavior);
+    return sorter;
+  }
+  
+	/**
+	 * Sorter constructor. This version of the constructor does not use any
+	 * parameters. If you use this constructor, you should use the setter
+	 * methods to set the values you want the sort routine to use.
+	 */
+	public Sorter() {
+		super();
+	}
+
+	/**
+	 * Sorter constructor. This version of the contructor takes only a table as
+	 * a parameter. Since no sort columns are specified, this will cause the
+	 * sort routine to sort the table using all of the columns in the table in
+	 * ascending order. To change the table or sort columns, you must use the
+	 * appropriate "set" methods.
+	 * 
+	 * @param table
+	 *            This field should be an object that implements the List
+	 *            interface. This list we will call the "table". Classes that
+	 *            implement the List interface include ArrayList, Vector and
+	 *            LinkedList. Each item in the table list should itself be an
+	 *            object that implements the List interface. These lists we will
+	 *            call "columns". Column lists should only contain objects that
+	 *            implement the Comparable interface. Classes that implement the
+	 *            Comparable interface include String, Integer, Long, Short,
+	 *            Float, Byte, Double and Date. See the standard JDK documention
+	 *            for Comparator for a complete list. The class type for each
+	 *            object in a column list must be consistent or a
+	 *            ClassCaseException will be thrown during the sort.
+	 */
+	public Sorter(final List table) {
+		// Set the values for the new sorter object.
+		setTable(table);
+	}
+
+	/**
+	 * Sorter constructor. This version of the contructor takes a table and a
+	 * column order as parameters. A sort column for each column in the data
+	 * will be created with the column order specified. This will cause the sort
+	 * routine to sort the table using all of the columns in the table (position
+	 * 0 and up or left to right) in the order specified in the column order. To
+	 * override the table or sort columns, you must use the appropriate "set"
+	 * methods. A vector will be used for storing the sort columns that are
+	 * dynamically created.
+	 * 
+	 * @param table
+	 *            This field should be an object that implements the List
+	 *            interface. This list we will call the "table". Classes that
+	 *            implement the List interface include ArrayList, Vector and
+	 *            LinkedList. Each item in the table list should itself be an
+	 *            object that implements the List interface. These lists we will
+	 *            call "columns". Column lists should only contain objects that
+	 *            implement the Comparable interface. Classes that implement the
+	 *            Comparable interface include String, Integer, Long, Short,
+	 *            Float, Byte, Double and Date. See the standard JDK documention
+	 *            for Comparator for a complete list. The class type for each
+	 *            object in a column list must be consistent or a
+	 *            ClassCaseException will be thrown during the sort.
+	 * 
+	 * @param columnOrder
+	 *            This value will be used for specifying the order in which the
+	 *            columns in the table are sorted. Sort order can either be
+	 *            ascending or descending. The ASCENDING_ORDER and
+	 *            DESCENDING_ORDER constants in this class should be used for
+	 *            this value.
+	 */
+	public Sorter(final List table, final int columnOrder) {
+		// Get the first row.
+		List firstRow = null;
+		if (table.size() > 0) {
+			firstRow = (List)table.get(0);
+		}
+
+		// Build the sort columns.
+		List sortColumns = null;
+		if (firstRow != null) {
+			sortColumns = new Vector();
+			final int numColumns = firstRow.size();
+			for (int columnCtr = 0; columnCtr < numColumns; columnCtr++) {
+				final SortableColumn sortColumn = new SortableColumnImpl(columnCtr, columnOrder);
+
+				sortColumns.add(sortColumn);
+			}
+		}
+
+		// Set the values for the new sorter object.
+		setTable(table);
+		setSortColumns(sortColumns);
+	}
+
+	/**
+	 * Sorter constructor. This version of the contructor takes a table and a
+	 * list of sort columns as parameters. To override the table or sort
+	 * columns, you must use the appropriate "set" methods.
+	 * 
+	 * @param table
+	 *            This field should be an object that implements the List
+	 *            interface. This list we will call the "table". Classes that
+	 *            implement the List interface include ArrayList, Vector and
+	 *            LinkedList. Each item in the table list should itself be an
+	 *            object that implements the List interface. These lists we will
+	 *            call "columns". Column lists should only contain objects that
+	 *            implement the Comparable interface. Classes that implement the
+	 *            Comparable interface include String, Integer, Long, Short,
+	 *            Float, Byte, Double and Date. See the standard JDK documention
+	 *            for Comparator for a complete list. The class type for each
+	 *            object in a column list must be consistent or a
+	 *            ClassCaseException will be thrown during the sort.
+	 * 
+	 * @param sortColumns
+	 *            Sort columns are a list of numbers specifying the colums to
+	 *            sort the table by. Each number in the list should be an
+	 *            instance of the SortColumn class represents a position of a
+	 *            column in the table. Column position start at zero just as
+	 *            they do in the standard Collection classes and Java arrays.
+	 *            When this field is null, there must be only one and only one
+	 *            column order specified. The sort routine will then sort using
+	 *            every column in the table starting from position 0 and up or
+	 *            left to right in ascending order.
+	 */
+	public Sorter(final List table, final List sortColumns) {
+		// Set the values for the new sorter object.
+		setTable(table);
+		setSortColumns(sortColumns);
+	}
+
+	/**
+	 * Returns the list of columns to sort by.
+	 * 
+	 * @return The list of columns to sort by.
+	 */
+	public List getSortColumns() {
+		return sortColumns;
+	}
+
+	/**
+	 * Returns the table of sort data.
+	 * 
+	 * @return The table of sort data.
+	 */
+	public List<T> getTable() {
+		return table;
+	}
+
+	/**
+	 * Updates the list of sort columns.
+	 * 
+	 * @param sortColumns
+	 *            Sort columns are a list of numbers specifying the colums to
+	 *            sort the table by. Each number in the list should be an
+	 *            instance of the SortColumn class represents a position of a
+	 *            column in the table. Column position start at zero just as
+	 *            they do in the standard Collection classes and Java arrays.
+	 *            When this field is null, there must be only one and only one
+	 *            column order specified. The sort routine will then sort using
+	 *            every column in the table starting from position 0 and up or
+	 *            left to right in ascending order.
+	 */
+	public void setSortColumns(final List sortColumns) {
+		validateSortColumns(sortColumns);
+		this.sortColumns = sortColumns;
+	}
+
+	/**
+	 * Updates the table of sort data.
+	 * 
+	 * @param table
+	 *            This field should be an object that implements the List
+	 *            interface. This list we will call the "table". Classes that
+	 *            implement the List interface include ArrayList, Vector and
+	 *            LinkedList. Each item in the table list should itself be an
+	 *            object that implements the List interface. These lists we will
+	 *            call "columns". Column lists should only contain objects that
+	 *            implement the Comparable interface. Classes that implement the
+	 *            Comparable interface include String, Integer, Long, Short,
+	 *            Float, Byte, Double and Date. See the standard JDK documention
+	 *            for Comparator for a complete list. The class type for each
+	 *            object in a column list must be consistent or a
+	 *            ClassCaseException will be thrown during the sort.
+	 */
+	public void setTable(final List table) {
+		validateTable(table);
+		this.table = table;
+	}
+
+	/**
+	 * Returns the null behavior for this object.
+	 * 
+	 * @return An integer representing the constant that indicates how null data
+	 *         values should behave while being sorted. See the null behavior
+	 *         constants in this class. The default value for this class is
+	 *         NULLS_ARE_INVALID.
+	 */
+	public int getNullBehavior() {
+		return nullBehavior;
+	}
+
+	/**
+	 * Set the null behavior for this object.
+	 * 
+	 * @param nullBehavior
+	 *            An integer representing the constant that indicates how null
+	 *            data values should behave while being sorted. See the null
+	 *            behavior constants in this class.
+	 * 
+	 * @throws IllegalArgumentException
+	 *             Thrown if the null behavior value is not valid.
+	 */
+	public void setNullBehavior(final int nullBehavior) {
+		if (nullBehavior != SorterConstants.NULLS_ARE_GREATEST
+				&& nullBehavior != SorterConstants.NULLS_ARE_INVALID
+				&& nullBehavior != SorterConstants.NULLS_ARE_LEAST) {
+			throw new IllegalArgumentException("Invalid null behavior.");
+		}
+
+		this.nullBehavior = nullBehavior;
+	}
+
+	/**
+	 * This routine sorts the table of data. The sort uses the sort columns to
+	 * determine how to sort the data.
+	 * 
+	 * @exception IllegalStateException
+	 *                The data in this class is in an invalid state.
+	 */
+	public void sort() {
+		// Sort the data.
+    List sortColumns = getSortColumns();
+    //System.out.println("sortColumns: "+sortColumns);
+		Collections.sort(table, new SortComparator(getSortColumns(), getNullBehavior()));
+	}
+  
+  public List<T> sortReflect() {
+    sort();
+    return getReflectResults();
+  }
+  
+	/**
+	 * This routine sorts the table of data using a comparator provided in the
+	 * parameters to do the sorting. The sort columns for this class will not be
+	 * used unless the input comparator has been coded to do so.
+	 * 
+	 * @param comparator
+	 *            A comparator to use for comparing the data rows in the table
+	 *            that has already been set on this class.
+	 * 
+	 * @exception IllegalStateException
+	 *                The data in Sorter is in an invalid state.
+	 */
+	public void sort(final Comparator comparator) {
+		// Sort the data.
+		Collections.sort(getTable(), comparator);
+	}
+
+	/**
+	 * Validates the list of sort columns.
+	 * 
+	 * @param sortColumns
+	 *            The list of sort columns to validate.
+	 * 
+	 * @exception IllegalArgumentException
+	 *                Input data is invalid.
+	 */
+	private void validateSortColumns(final List sortColumns) {
+		if (sortColumns != null) {
+			// Validate size.
+			if (sortColumns.size() < 1) {
+				throw new IllegalArgumentException("Sort columns can "+ "not be empty.");
+			}
+
+			for (int ctr = 0, size = sortColumns.size(); ctr < size; ctr++) {
+				// Validate for SortColumns.
+				if (!(sortColumns.get(ctr) instanceof SortableColumn)) {
+					throw new IllegalArgumentException("The list of sort "+ "columns does not contain all SortColumn objects.");
+				}
+
+				// Validate for greater than or equal to zero.
+				if (((SortableColumn) sortColumns.get(ctr)).getColumnPosition() < 0) {
+					throw new IllegalArgumentException("A sort column number is less than zero.");
+				}
+
+				// Validate for invalid column order.
+				if (((SortableColumn)sortColumns.get(ctr)).getColumnOrder() != SorterConstants.ASCENDING_ORDER
+						&& ((SortableColumn)sortColumns.get(ctr)).getColumnOrder() != SorterConstants.DESCENDING_ORDER) {
+					throw new IllegalArgumentException("A sort column order is invalid.");
+				}
+			}
+		}
+	}
+
+	/**
+	 * Validates the table of sort data.
+	 * 
+	 * @param table
+	 *            The table of sort data to validate.
+	 * 
+	 * @exception IllegalArgumentException
+	 *                Input data is invalid.
+	 */
+	private void validateTable(final List table) {
+		// Validate for null.
+		if (table == null) {
+			throw new IllegalArgumentException("The table of sort data "+ "can not be null.");
+		}
+
+		// Validate for Lists.
+		if (table.size() > 0) {
+			if (!(table.get(0) instanceof List)) {
+				throw new IllegalArgumentException("The table does not implement " + "the List interface.");
+			}
+		}
+	}
+}
\ No newline at end of file
Index: ocean/src/net/sourceforge/jsorter/SorterConstants.java
===================================================================
--- ocean/src/net/sourceforge/jsorter/SorterConstants.java	(revision 0)
+++ ocean/src/net/sourceforge/jsorter/SorterConstants.java	(revision 0)
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2002-2005 Robert Breidecker.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sourceforge.jsorter;
+
+/**
+ * This class contains constants used in the JSorter project.
+ * 
+ * @author Robert Breidecker
+ */
+public class SorterConstants {
+	/**
+	 * The constant for the first column position.
+	 */
+	public static final int FIRST_COLUMN_POSITION = 0;
+
+	/**
+	 * The constant for ascending order.
+	 */
+	public static final int ASCENDING_ORDER = 1;
+
+	/**
+	 * The constant for descending order.
+	 */
+	public static final int DESCENDING_ORDER = 0;
+
+	/**
+	 * The constant for stating that null data values are invalid. This is the
+	 * default value.
+	 */
+	public static final int NULLS_ARE_INVALID = 0;
+
+	/**
+	 * The constant for stating that null data values are valid and should be
+	 * treated as the least of possible values when sorting.
+	 */
+	public static final int NULLS_ARE_LEAST = 1;
+
+	/**
+	 * The constant for stating that null data values are valid and should be
+	 * treated as the greatest of possible values when sorting.
+	 */
+	public static final int NULLS_ARE_GREATEST = 2;
+}
\ No newline at end of file
Index: ocean/src/net/sourceforge/jsorter/SortHolder.java
===================================================================
--- ocean/src/net/sourceforge/jsorter/SortHolder.java	(revision 0)
+++ ocean/src/net/sourceforge/jsorter/SortHolder.java	(revision 0)
@@ -0,0 +1,90 @@
+
+
+package net.sourceforge.jsorter;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import net.sourceforge.jsorter.SortReflect.MethodException;
+
+
+/**
+ *
+ * @author  Jason Rutherglen
+ */
+public class SortHolder<T> implements Comparable {
+  public Comparable attribute;
+  public T object;
+  public int nullBehavior = SorterConstants.NULLS_ARE_INVALID;
+  
+  public SortHolder(Comparable attribute, T object, int nullBehavior) {
+    this.attribute = attribute;
+    this.object = object;
+    this.nullBehavior = nullBehavior;
+  }
+  
+  public String toString() {
+    if (attribute == null) return "null";
+    return attribute.toString();
+  }
+  
+  public static List getTable(SortReflect[] sortReflect, Collection collection, int nullBehavior) throws MethodException {
+    List table = new ArrayList(collection.size());
+    Iterator iterator = collection.iterator();
+    while (iterator.hasNext()) {
+      Object value = iterator.next();
+      List row = new ArrayList(sortReflect.length); 
+      for (int x=0; x < sortReflect.length; x++) {
+        Object attribute = sortReflect[x].get(value);
+        if (attribute instanceof java.net.URL) {
+          attribute = ((java.net.URL)attribute).toString();
+        }
+        Comparable attributeComparable = (Comparable)attribute;
+        SortHolder sortHolder = new SortHolder(attributeComparable, value, nullBehavior);
+        row.add(sortHolder);
+      }
+      table.add(row);
+    }
+    return table;
+  }
+  
+  public int compareTo(Object obj) {
+    Comparable comparableOne = (Comparable)attribute;
+    Comparable comparableTwo = (Comparable)obj;
+    if (obj instanceof SortHolder) {
+      SortHolder sortHolder = (SortHolder)obj;
+      comparableTwo = (Comparable)sortHolder.attribute;
+    }
+    
+    int rtn;
+    
+    if (comparableOne != null && comparableTwo != null) {
+      try {
+        rtn = comparableOne.compareTo(comparableTwo);
+      } catch (ClassCastException exception) {
+        throw exception;
+      }
+    } else {
+      if (nullBehavior == SorterConstants.NULLS_ARE_INVALID) {
+        throw new IllegalStateException("Null data values are not valid.");
+      } else if (comparableOne == null && comparableTwo != null) {
+        rtn = -1;
+        
+        if (nullBehavior == SorterConstants.NULLS_ARE_GREATEST) {
+          rtn = rtn * -1;
+        }
+      } else if (comparableOne != null && comparableTwo == null) {
+        rtn = 1;
+        
+        if (nullBehavior == SorterConstants.NULLS_ARE_GREATEST) {
+          rtn = rtn * -1;
+        }
+      } else {
+        rtn = 0;
+      }
+    }
+    return rtn;
+  }
+}
Index: ocean/src/net/sourceforge/jsorter/SortReflect.java
===================================================================
--- ocean/src/net/sourceforge/jsorter/SortReflect.java	(revision 0)
+++ ocean/src/net/sourceforge/jsorter/SortReflect.java	(revision 0)
@@ -0,0 +1,63 @@
+package net.sourceforge.jsorter;
+
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * 
+ * @author Jason Rutherglen
+ */
+public class SortReflect {
+	public static final int METHOD = 1;
+	public static final int FIELD = 2;
+	public int type;
+	public String methodName;
+	public String fieldName;
+	public Object[] params;
+
+	private SortReflect() {
+	}
+  
+	public static class MethodException extends Exception {
+		public MethodException(Throwable throwable) {
+			super(throwable);
+		}
+	}
+	
+	public Object get(Object value) throws MethodException {
+    if (type == METHOD) {
+    	try {
+        return MethodUtil.call(methodName, params, value);
+    	} catch (InvocationTargetException invocationTargetException) {
+    		throw new MethodException(invocationTargetException.getCause());
+    	} catch (Exception exception) {
+    		throw new MethodException(exception);
+    	}
+    } else if (type == FIELD) {
+      return FieldUtil.getFieldValue(fieldName, value);
+    }
+    throw new RuntimeException("type is invalid");
+  }
+
+	public static SortReflect field(String fieldName) {
+		SortReflect sortReflect = new SortReflect();
+		sortReflect.type = FIELD;
+		sortReflect.fieldName = fieldName;
+		return sortReflect;
+	}
+
+	public static SortReflect method(String methodName) {
+		SortReflect sortReflect = new SortReflect();
+		sortReflect.type = METHOD;
+		sortReflect.methodName = methodName;
+		return sortReflect;
+	}
+
+	public static SortReflect method(String methodName, Object[] params) {
+		SortReflect sortReflect = new SortReflect();
+		sortReflect.type = METHOD;
+		sortReflect.methodName = methodName;
+		sortReflect.params = params;
+		return sortReflect;
+	}
+
+}
Index: ocean/src/net/sourceforge/jsorter/SwingSorter.java
===================================================================
--- ocean/src/net/sourceforge/jsorter/SwingSorter.java	(revision 0)
+++ ocean/src/net/sourceforge/jsorter/SwingSorter.java	(revision 0)
@@ -0,0 +1,580 @@
+/*
+ * Copyright 2002-2005 Robert Breidecker.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sourceforge.jsorter;
+
+import java.util.List;
+import java.util.Vector;
+
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.DefaultListModel;
+import javax.swing.JComboBox;
+import javax.swing.JList;
+import javax.swing.JTable;
+import javax.swing.table.DefaultTableModel;
+
+/**
+ * Used for sorting data in the Swing objects JTable, JList, JComboBox,
+ * DefaultTableModel, DefaultListModel, DefaultComboBoxModel and
+ * SortableComponent.
+ * 
+ * Note: This is class is not thread safe.
+ * 
+ * @author Robert Breidecker
+ */
+public class SwingSorter {
+	/**
+	 * Indicate how to treat null data values.
+	 */
+	private int nullBehavior = SorterConstants.NULLS_ARE_INVALID;
+
+	/**
+	 * Sorts a custom component that implements the SortableComponent interface.
+	 * This method assumes that the object type in the display column is made up
+	 * entirely of the same type and that the type implements the Comparable
+	 * interface. For example, the data in the display column is entirely made
+	 * of String or Integers. Some of the objects that implement the Comparable
+	 * interface include String, Integer, Long, Short, Float, Byte, Double and
+	 * Date.
+	 * 
+	 * @param component
+	 *            The component to be sorted in ascending order. The component
+	 *            model must implement the SortableComponent interface.
+	 */
+	public void sortComponent(final SortableSwingComponent component) {
+		sortComponent(component, SorterConstants.ASCENDING_ORDER);
+	}
+
+	/**
+	 * Sorts a custom component that implements the SortableComponent interface.
+	 * This method assumes that the object type in the display column is made up
+	 * entirely of the same type and that the type implements the Comparable
+	 * interface. For example, the data in the display column is entirely made
+	 * of String or Integers. Some of the objects that implement the Comparable
+	 * interface include String, Integer, Long, Short, Float, Byte, Double and
+	 * Date.
+	 * 
+	 * @param component
+	 *            The component to be sorted in ascending order. The component
+	 *            model must implement the SortableComponent interface.
+	 * 
+	 * @param columnOrder
+	 *            An integer that represents the type of ordering to be used
+	 *            when sorting by only the first column in the data. The type of
+	 *            sort can either be ascending or descending. The
+	 *            ASCENDING_ORDER and DESCENDING_ORDER constants in this class
+	 *            should be used.
+	 */
+	public void sortComponent(final SortableSwingComponent component,
+			final int columnOrder) {
+		final SortableColumn sortColumn = new SortableColumnImpl(0, columnOrder);
+		final List sortColumns = new Vector();
+		sortColumns.add(sortColumn);
+
+		sortComponent(component, sortColumns);
+	}
+
+	/**
+	 * Sorts a custom component that implements the SortableComponent interface.
+	 * This method assumes that the object type in the display column is made up
+	 * entirely of the same type and that the type implements the Comparable
+	 * interface. For example, the data in the display column is entirely made
+	 * of String or Integers. Some of the objects that implement the Comparable
+	 * interface include String, Integer, Long, Short, Float, Byte, Double and
+	 * Date.
+	 * 
+	 * @param component
+	 *            The component to be sorted in ascending order. The component
+	 *            model must implement the SortableComponent interface.
+	 * 
+	 * @param sortColumns
+	 *            Sort columns are a list of numbers specifying the colums to
+	 *            sort the table by. Each number in the list should be an
+	 *            instance of the SortColumn class represents a position of a
+	 *            column in the table. Column position start at zero just as
+	 *            they do in the standard Collection classes and Java arrays.
+	 *            When this field is null, there must be only one and only one
+	 *            column order specified. The sort routine will then sort using
+	 *            every column in the table starting from position 0 and up or
+	 *            left to right in ascending order.
+	 */
+	public void sortComponent(final SortableSwingComponent component,
+			final List sortColumns) {
+		// Create a new Sorter object.
+		final Sorter sorter = new Sorter(component.getDataList(), sortColumns);
+		sorter.setNullBehavior(nullBehavior);
+
+		// Sort the data!
+		sorter.sort();
+	}
+
+	/**
+	 * Sorts a combo box using Sorter. This method assumes that the object type
+	 * in the display column is made up entirely of the same type and that the
+	 * type implements the Comparable interface. For example, the data in the
+	 * display column is entirely made of String or Integers. Some of the
+	 * objects that implement the Comparable interface include String, Integer,
+	 * Long, Short, Float, Byte, Double and Date.
+	 * 
+	 * @param comboBox
+	 *            The combo box to be sorted in ascending order. The combo box
+	 *            model must be an instance or a descendent of
+	 *            DefaultComboBoxModel.
+	 */
+	public void sortComboBox(final JComboBox comboBox) {
+		sortComboBox(comboBox, SorterConstants.ASCENDING_ORDER);
+	}
+
+	/**
+	 * Sorts a combo box using Sorter. This method assumes that the object type
+	 * in the display column is made up entirely of the same type and that the
+	 * type implements the Comparable interface. For example, the data in the
+	 * display column is entirely made of String or Integers. Some of the
+	 * objects that implement the Comparable interface include String, Integer,
+	 * Long, Short, Float, Byte, Double and Date.
+	 * 
+	 * @param comboBox
+	 *            The combo box to be sorted. The combo box model must be an
+	 *            instance or a descendent of DefaultComboBoxModel or implement
+	 *            the SortableComponent.
+	 * 
+	 * @param columnOrder
+	 *            An integer that represents the type of ordering to be used
+	 *            when sorting by only the first column in the data. The type of
+	 *            sort can either be ascending or descending. The
+	 *            ASCENDING_ORDER and DESCENDING_ORDER constants in this class
+	 *            should be used.
+	 * 
+	 * @exception IllegalArgumentException
+	 *                Input data is invalid.
+	 */
+	public void sortComboBox(final JComboBox comboBox, final int columnOrder) {
+		if (comboBox.getModel() instanceof DefaultComboBoxModel) {
+			final DefaultComboBoxModel model = (DefaultComboBoxModel) comboBox
+					.getModel();
+
+			sortComboBoxModel(model, columnOrder);
+		} else if (comboBox.getModel() instanceof SortableSwingComponent) {
+			final SortableSwingComponent component = (SortableSwingComponent) comboBox
+					.getModel();
+
+			sortComponent(component, columnOrder);
+		} else {
+			throw new IllegalArgumentException(
+					"ComboBox model must be an "
+							+ "instance of decendent of DefaultComboBoxModel or implement the "
+							+ "SortableComponent interface.");
+		}
+	}
+
+	/**
+	 * Sorts a combo box model using Sorter. This method assumes that the object
+	 * type in the display column is made up entirely of the same type and that
+	 * the type implements the Comparable interface. For example, the data in
+	 * the display column is entirely made of String or Integers. Some of the
+	 * objects that implement the Comparable interface include String, Integer,
+	 * Long, Short, Float, Byte, Double and Date.
+	 * 
+	 * @param model
+	 *            The combo box model to be sorted in ascending order. The combo
+	 *            box model must be an instance or a descendent of
+	 *            DefaultComboBoxModel.
+	 */
+	public void sortComboBoxModel(final DefaultComboBoxModel model) {
+		sortComboBoxModel(model, SorterConstants.ASCENDING_ORDER);
+	}
+
+	/**
+	 * Sorts a combo box model using Sorter. This method assumes that the object
+	 * type in the display column is made up entirely of the same type and that
+	 * the type implements the Comparable interface. For example, the data in
+	 * the display column is entirely made of String or Integers. Some of the
+	 * objects that implement the Comparable interface include String, Integer,
+	 * Long, Short, Float, Byte, Double and Date.
+	 * 
+	 * @param model
+	 *            The combo box model to be sorted. The combo box model must be
+	 *            an instance or a descendent of DefaultComboBoxModel.
+	 * 
+	 * @param columnOrder
+	 *            An integer that represents the type of ordering to be used
+	 *            when sorting by only the first column in the data. The type of
+	 *            sort can either be ascending or descending. The
+	 *            ASCENDING_ORDER and DESCENDING_ORDER constants in this class
+	 *            should be used.
+	 */
+	public void sortComboBoxModel(final DefaultComboBoxModel model,
+			final int columnOrder) {
+		// Create the table of sort data.
+		final List table = new Vector();
+
+		// Get the model data.
+		for (int ctr = 0, size = model.getSize(); ctr < size; ctr++) {
+			// Create a new row.
+			final List row = new Vector();
+			row.add(model.getElementAt(ctr));
+			table.add(row);
+		}
+
+		// Create a new Sorter object.
+		final Sorter sorter = new Sorter(table, columnOrder);
+		sorter.setNullBehavior(nullBehavior);
+
+		// Sort the data!
+		sorter.sort();
+
+		// Clear the model data.
+		model.removeAllElements();
+
+		// Re-add the sorted data to the model.
+		for (int ctr = 0, size = table.size(); ctr < size; ctr++) {
+			final List row = (List) table.get(ctr);
+
+			// Get the first element from the row, because a list
+			// only has one column.
+			model.addElement(row.get(0));
+		}
+	}
+
+	/**
+	 * Sorts a list model using Sorter. This method assumes that the object type
+	 * in the display column is made up entirely of the same type and that the
+	 * type implements the Comparable interface. For example, the data in the
+	 * display column is entirely made of String or Integers. Some of the
+	 * objects that implement the Comparable interface include String, Integer,
+	 * Long, Short, Float, Byte, Double and Date.
+	 * 
+	 * @param model
+	 *            The list model to be sorted in ascending order. Must be a
+	 *            DefaultListModel or a descendent of DefaultListModel.
+	 */
+	public void sortListModel(final DefaultListModel model) {
+		sortListModel(model, SorterConstants.ASCENDING_ORDER);
+	}
+
+	/**
+	 * Sorts a list using Sorter. This method assumes that the object type in
+	 * the display column is made up entirely of the same type and that the type
+	 * implements the Comparable interface. For example, the data in the display
+	 * column is entirely made of String or Integers. Some of the objects that
+	 * implement the Comparable interface include String, Integer, Long, Short,
+	 * Float, Byte, Double and Date.
+	 * 
+	 * @param list
+	 *            The list to be sorted in ascending order. The list model used
+	 *            must be a DefaultListBoxModel or a descendent of
+	 *            DefaultListModel.
+	 */
+	public void sortList(final JList list) {
+		sortList(list, SorterConstants.ASCENDING_ORDER);
+	}
+
+	/**
+	 * Sorts a list using Sorter. This method assumes that the object type in
+	 * the display column is made up entirely of the same type and that the type
+	 * implements the Comparable interface. For example, the data in the display
+	 * column is entirely made of String or Integers. Some of the objects that
+	 * implement the Comparable interface include String, Integer, Long, Short,
+	 * Float, Byte, Double and Date.
+	 * 
+	 * @param list
+	 *            The list to be sorted. The list model used must be a
+	 *            DefaultListModel or a descendent of DefaultListModel or
+	 *            implement the SortableComponent interface.
+	 * 
+	 * @param columnOrder
+	 *            An integer that represents the type of ordering to be used
+	 *            when sorting by only the first column in the data. The type of
+	 *            sort can either be ascending or descending. The
+	 *            ASCENDING_ORDER and DESCENDING_ORDER constants in this class
+	 *            should be used.
+	 * 
+	 * @exception IllegalArgumentException
+	 *                Input data is invalid.
+	 */
+	public void sortList(final JList list, final int columnOrder) {
+		if (list.getModel() instanceof DefaultListModel) {
+			final DefaultListModel model = (DefaultListModel) list.getModel();
+
+			sortListModel(model, columnOrder);
+		} else if (list.getModel() instanceof SortableSwingComponent) {
+			final SortableSwingComponent model = (SortableSwingComponent) list
+					.getModel();
+
+			sortComponent(model, columnOrder);
+		} else {
+			throw new IllegalArgumentException(
+					"List model must be an "
+							+ "instance of decendent of DefaultListModel or implement the "
+							+ "SortableComponent interface.");
+		}
+	}
+
+	/**
+	 * Sorts a list model using Sorter. This method assumes that the object type
+	 * in the display column is made up entirely of the same type and that the
+	 * type implements the Comparable interface. For example, the data in the
+	 * display column is entirely made of String or Integers. Some of the
+	 * objects that implement the Comparable interface include String, Integer,
+	 * Long, Short, Float, Byte, Double and Date.
+	 * 
+	 * @param model
+	 *            The list model to be sorted. Must be a DefaultListModel or a
+	 *            descendent of DefaultListModel.
+	 * 
+	 * @param newSortOrder
+	 *            An integer that represents the type of ordering to be used
+	 *            when sorting by only the first column in the data. The type of
+	 *            sort can either be ascending or descending. The
+	 *            ASCENDING_ORDER and DESCENDING_ORDER constants in this class
+	 *            should be used.
+	 */
+	public void sortListModel(final DefaultListModel model,
+			final int newSortOrder) {
+		// Create the table of sort data.
+		final Vector table = new Vector();
+
+		// Get the model data.
+		for (int ctr = 0, size = model.getSize(); ctr < size; ctr++) {
+			// Create a new row.
+			final Vector row = new Vector();
+			row.add(model.getElementAt(ctr));
+			table.add(row);
+		}
+
+		// Create a new Sorter.
+		final Sorter sorter = new Sorter(table, newSortOrder);
+		sorter.setNullBehavior(nullBehavior);
+
+		// Sort the vector data.
+		sorter.sort();
+
+		// Clear the model data.
+		model.removeAllElements();
+
+		// Re-add the sorted data to the model.
+		for (int ctr = 0, size = table.size(); ctr < size; ctr++) {
+			final List row = (List) table.get(ctr);
+
+			// Get the first element from the row, because a list
+			// only has one column.
+			model.addElement(row.get(0));
+		}
+	}
+
+	/**
+	 * Sorts a table using Sorter. This method assumes that the object type in
+	 * each column is made up entirely of the same type and that the type
+	 * implements the Comparable interface. For example, the data in column one
+	 * can be entirely made of String types and column two can be entirely made
+	 * up of Integers. Some of the objects that implement the Comparable
+	 * interface include String, Integer, Long, Short, Float, Byte, Double and
+	 * Date. This method will sort using all columns in ascending order.
+	 * 
+	 * @param table
+	 *            JTable The table to be sorted. The table model of the table
+	 *            must be an instance or a descendent of DefaultTableModel or
+	 *            must implement the SortableComponent interface.
+	 */
+	public void sortTable(final JTable table) {
+		// Sort the table.
+		sortTable(table);
+	}
+
+	/**
+	 * Sorts a table using Sorter. This method assumes that the object type in
+	 * each column is made up entirely of the same type and that the type
+	 * implements the Comparable interface. For example, the data in column one
+	 * can be entirely made of String types and column two can be entirely made
+	 * up of Integers. Some of the objects that implement the Comparable
+	 * interface include String, Integer, Long, Short, Float, Byte, Double and
+	 * Date. This method will sort using all columns in the order specified.
+	 * 
+	 * @param table
+	 *            JTable The table to be sorted. The table model of the table
+	 *            must be an instance or a descendent of DefaultTableModel or
+	 *            must implement the SortableComponent interface.
+	 * 
+	 * @param columnOrder
+	 *            An integer that represents the type of ordering to be used
+	 *            when sorting by only the first column in the data. The type of
+	 *            sort can either be ascending or descending. The
+	 *            ASCENDING_ORDER and DESCENDING_ORDER constants in this class
+	 *            should be used.
+	 */
+	public void sortTable(final JTable table, final int columnOrder) {
+		// Sort the table!
+		sortTable(table, columnOrder);
+	}
+
+	/**
+	 * Sorts a table using Sorter. This method assumes that the object type in
+	 * each column is made up entirely of the same type and that the type
+	 * implements the Comparable interface. For example, the data in column one
+	 * can be entirely made of String types and column two is can be entriely
+	 * made up of Integers. Some of the objects that implement the Comparable
+	 * interface include String, Integer, Long, Short, Float, Byte, Double and
+	 * Date.
+	 * 
+	 * @param table
+	 *            JTable The table to be sorted. The table model of the table
+	 *            must be an instance or a descendent of DefaultTableModel or
+	 *            must implement the SortableComponent interface.
+	 * 
+	 * @param sortColumns
+	 *            Sort columns are a list of numbers specifying the colums to
+	 *            sort the table by. Each number in the list should be an
+	 *            instance of the SortColumn class represents a position of a
+	 *            column in the table. Column position start at zero just as
+	 *            they do in the standard Collection classes and Java arrays.
+	 *            When this field is null, there must be only one and only one
+	 *            column order specified. The sort routine will then sort using
+	 *            every column in the table starting from position 0 and up or
+	 *            left to right in ascending order.
+	 * 
+	 * @exception IllegalArgumentException
+	 *                Input data is invalid.
+	 */
+	public void sortTable(final JTable table, final List sortColumns) {
+		if (table.getModel() instanceof DefaultTableModel) {
+			final DefaultTableModel model = (DefaultTableModel) table
+					.getModel();
+
+			sortTableModel(model, sortColumns);
+		} else if (table.getModel() instanceof SortableSwingComponent) {
+			final SortableSwingComponent model = (SortableSwingComponent) table
+					.getModel();
+
+			sortComponent(model, sortColumns);
+		} else {
+			throw new IllegalArgumentException(
+					"Table model must be an "
+							+ "instance of decendent of DefaultTableModel or implement the "
+							+ "SortableComponent interface.");
+		}
+	}
+
+	/**
+	 * Sorts a table model using Sorter. This method assumes that the object
+	 * type in each column is made up entirely of the same type and that the
+	 * type implements the Comparable interface. For example, the data in column
+	 * one can be entirely made of String types and column two is can be
+	 * entriely made up of Integers. Some of the objects that implement the
+	 * Comparable interface include String, Integer, Long, Short, Float, Byte,
+	 * Double and Date.
+	 * 
+	 * @param model
+	 *            The table model to be sorted. The table model must be an
+	 *            instance or a descendent of DefaultTableModel.
+	 */
+	public void sortTableModel(final DefaultTableModel model) {
+		// Sort the data.
+		sortTableModel(model);
+	}
+
+	/**
+	 * Sorts a table model using Sorter. This method assumes that the object
+	 * type in each column is made up entirely of the same type and that the
+	 * type implements the Comparable interface. For example, the data in column
+	 * one can be entirely made of String types and column two is can be
+	 * entriely made up of Integers. Some of the objects that implement the
+	 * Comparable interface include String, Integer, Long, Short, Float, Byte,
+	 * Double and Date.
+	 * 
+	 * @param model
+	 *            The table model to be sorted. The table model must be an
+	 *            instance or a descendent of DefaultTableModel.
+	 * 
+	 * @param columnOrder
+	 *            An integer that represents the type of ordering to be used
+	 *            when sorting by only the first column in the data. The type of
+	 *            sort can either be ascending or descending. The
+	 *            ASCENDING_ORDER and DESCENDING_ORDER constants in this class
+	 *            should be used.
+	 */
+	public void sortTableModel(final DefaultTableModel model,
+			final int columnOrder) {
+		// Sort the table.
+		sortTableModel(model, columnOrder);
+	}
+
+	/**
+	 * Sorts a table model using Sorter. This method assumes that the object
+	 * type in each column is made up entirely of the same type and that the
+	 * type implements the Comparable interface. For example, the data in column
+	 * one can be entirely made of String types and column two is can be
+	 * entriely made up of Integers. Some of the objects that implement the
+	 * Comparable interface include String, Integer, Long, Short, Float, Byte,
+	 * Double and Date.
+	 * 
+	 * @param model
+	 *            The table model to be sorted. The table model must be an
+	 *            instance or a descendent of DefaultTableModel.
+	 * 
+	 * @param sortColumns
+	 *            Sort columns are a list of numbers specifying the colums to
+	 *            sort the table by. Each number in the list should be an
+	 *            instance of the SortColumn class represents a position of a
+	 *            column in the table. Column position start at zero just as
+	 *            they do in the standard Collection classes and Java arrays.
+	 *            When this field is null, there must be only one and only one
+	 *            column order specified. The sort routine will then sort using
+	 *            every column in the table starting from position 0 and up or
+	 *            left to right in ascending order.
+	 */
+	public void sortTableModel(final DefaultTableModel model,
+			final List sortColumns) {
+		// Create a new Sorter.
+		final Sorter sorter = new Sorter(model.getDataVector(), sortColumns);
+		sorter.setNullBehavior(nullBehavior);
+
+		// Sort the data.
+		sorter.sort();
+	}
+
+	/**
+	 * Returns the null behavior for this object.
+	 * 
+	 * @return An integer representing the constant that indicates how null data
+	 *         values should behave while being sorted. See the null behavior
+	 *         constants in this class. The default value for this class is
+	 *         NULLS_ARE_INVALID.
+	 */
+	public int getNullBehavior() {
+		return nullBehavior;
+	}
+
+	/**
+	 * Set the null behavior for this object.
+	 * 
+	 * @param nullBehavior
+	 *            An integer representing the constant that indicates how null
+	 *            data values should behave while being sorted. See the null
+	 *            behavior constants in this class.
+	 * 
+	 * @throws IllegalArgumentException
+	 *             Thrown if the null behavior value is not valid.
+	 */
+	public void setNullBehavior(final int nullBehavior) {
+		if (nullBehavior != SorterConstants.NULLS_ARE_GREATEST
+				&& nullBehavior != SorterConstants.NULLS_ARE_INVALID
+				&& nullBehavior != SorterConstants.NULLS_ARE_LEAST) {
+			throw new IllegalArgumentException("Invalid null behavior.");
+		}
+
+		this.nullBehavior = nullBehavior;
+	}
+}
\ No newline at end of file
Index: ocean/src/org/apache/lucene/ocean/WriteableMemoryIndex.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/WriteableMemoryIndex.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/WriteableMemoryIndex.java	(revision 0)
@@ -0,0 +1,185 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.index.FilterIndexReader;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.ocean.util.SortedList;
+import org.apache.lucene.ocean.util.Util;
+import org.apache.lucene.store.instantiated.InstantiatedIndex;
+import org.apache.lucene.store.instantiated.InstantiatedIndexReader;
+import org.apache.lucene.store.instantiated.InstantiatedIndexWriter;
+
+/**
+ * Encapsulates org.apache.lucene.store.instantiated.InstantiatedIndex for use with the
+ * Ocean system.  This is the only index that is writeable, meaning new documents may
+ * be added to.  
+ * 
+ */
+//TODO: release old MemoryIndexSnapshots from map
+public class WriteableMemoryIndex extends Index {
+	private WriteableIndexWriter indexWriter;
+	//private InstantiatedIndexReader indexReader;
+	private InstantiatedIndex instantiatedIndex;
+  private SortedList<Long,MemoryIndexSnapshot> snapshotMap = new SortedList<Long,MemoryIndexSnapshot>();
+	
+	public WriteableMemoryIndex(IndexID id, TransactionSystem system) throws IOException {
+		super(id, system);
+		instantiatedIndex = new InstantiatedIndex();
+		indexWriter = new WriteableIndexWriter(instantiatedIndex);
+		//indexReader = new InstantiatedIndexReader(instantiatedIndex);
+	}
+  
+	public boolean rollback(Long snapshotId) {
+	  return snapshotMap.remove(snapshotId) != null;
+	}
+	
+	// called by Category.runTransactionsNotInIndex
+	void addDocuments(Documents documents, Analyzer analyzer) throws IOException {
+		for (Document document : documents) {
+			indexWriter.addDocument(document, analyzer);
+		}
+	}
+
+	// called by Category.runTransactionsNotInIndex
+	MemoryIndexSnapshot setSnapshot(Long snapshotId) {//, List<Deletes> deletesList) throws Exception {
+		//int maxDoc = indexReader.maxDoc();
+		//HashSet<Integer> deletedSet = new HashSet<Integer>();
+		//if (deletesList != null) {
+		//	for (Deletes deletes : deletesList) {
+		//		applyDeletes(false, deletes, null, indexReader);
+		//	}
+		//}
+	  int maxDoc = instantiatedIndex.getDocumentsByNumber().length;
+	  HashSet<Integer> deletedSet = new HashSet<Integer>();
+		MemoryIndexSnapshot memoryIndexSnapshot = new MemoryIndexSnapshot(snapshotId, maxDoc, deletedSet);
+		snapshotMap.put(snapshotId, memoryIndexSnapshot);
+		return memoryIndexSnapshot;
+	}
+
+	public MemoryIndexSnapshot getIndexSnapshot(Long snapshotId) {
+		return snapshotMap.get(snapshotId);
+	}
+
+	public MemoryIndexSnapshot getLatestIndexSnapshot() {
+		return snapshotMap.lastValue();
+	}
+
+	public class MemoryIndexSnapshot extends IndexSnapshot {
+		private final int maxDoc;
+		private HashSet<Integer> deletedDocs;
+		private OceanInstantiatedIndexReader indexReader;
+
+		public MemoryIndexSnapshot(Long snapshotId, int maxDoc, HashSet<Integer> deletedDocs) {
+			super(snapshotId);
+			this.maxDoc = maxDoc;
+			this.deletedDocs = deletedDocs;
+			indexReader = new OceanInstantiatedIndexReader(maxDoc, instantiatedIndex, deletedDocs);
+		}
+		
+		public int deletedDoc() {
+		  return deletedDocs.size();
+		}
+		
+		public int maxDoc() {
+		  return maxDoc;
+		}
+
+		public IndexReader getIndexReader() {
+			return indexReader;
+		}
+	}
+
+	Long getLatestSnapshotId() {
+		return snapshotMap.lastKey();
+	}
+
+	private HashSet<Integer> getLatestSnapshotDeletedDocSet() {
+		MemoryIndexSnapshot memoryIndexSnapshot = snapshotMap.lastValue();
+		if (memoryIndexSnapshot == null || memoryIndexSnapshot.deletedDocs == null)
+			return null;
+		HashSet<Integer> deletedDocSet = memoryIndexSnapshot.deletedDocs;
+		return deletedDocSet;
+	}
+
+	public int getDocumentCount() {
+		return instantiatedIndex.getDocumentsByNumber().length;
+	}
+
+	public DeletesResult commitDeletes(Deletes deletes, Transaction transaction) throws Exception, InterruptedException, IOException {
+		return commitChanges(null, deletes, null, transaction);
+	}
+  
+	public void commitNothing(Transaction transaction) throws IndexException, InterruptedException, IOException {
+	  Long snapshotId = transaction.getId();
+	  MemoryIndexSnapshot latestIndexSnapshot = getLatestIndexSnapshot();
+	  int maxDoc = latestIndexSnapshot.maxDoc();
+	  HashSet<Integer> deletedDocSet = latestIndexSnapshot.deletedDocs;
+	  MemoryIndexSnapshot memoryIndexSnapshot = new MemoryIndexSnapshot(snapshotId, maxDoc, deletedDocSet);
+    snapshotMap.put(snapshotId, memoryIndexSnapshot);
+    removeOldSnapshots(snapshotMap);
+	}
+	
+	public DeletesResult commitChanges(Documents documents, Deletes deletes, Analyzer analyzer, Transaction transaction) throws InterruptedException, Exception, IOException {
+		try {
+			if (isClosed()) {
+				throw new IOException("index is closed");
+			}
+			if (isReadOnly() && documents != null && documents.size() > 0) {
+				throw new IOException("index not accepting new documents");
+			}
+			DeletesResult deletesResult = new DeletesResult(getId());
+			HashSet<Integer> deletedSet = null;
+			if (deletes != null && deletes.hasDeletes()) {
+			  HashSet<Integer> previousDeletedSet = getLatestSnapshotDeletedDocSet();
+				if (previousDeletedSet != null) {
+					deletedSet = (HashSet<Integer>) previousDeletedSet.clone();
+				} else {
+					deletedSet = new HashSet<Integer>();
+				}
+				IndexSnapshot indexSnapshot = getLatestIndexSnapshot();
+				deletesResult = applyDeletes(false, deletes, deletedSet, indexSnapshot.getIndexReader());
+			} else if (deletes == null || !deletes.hasDeletes()) { // if no deletes
+				// just use same
+				deletedSet = getLatestSnapshotDeletedDocSet();
+			}
+			if (documents != null) {
+				for (Document document : documents) {
+					indexWriter.addDocument(document, analyzer);
+				}
+			}
+			transaction.ready(this);
+			if (transaction.go()) {
+				indexWriter.commit();
+				int maxDoc = instantiatedIndex.getDocumentsByNumber().length;
+				Long snapshotId = transaction.getId();
+				MemoryIndexSnapshot memoryIndexSnapshot = new MemoryIndexSnapshot(snapshotId, maxDoc, deletedSet);
+				snapshotMap.put(snapshotId, memoryIndexSnapshot);
+				removeOldSnapshots(snapshotMap);
+				return deletesResult;
+			} else {
+				indexWriter.abort();
+				return null;
+			}
+		} catch (Throwable throwable) {
+			transaction.failed(this, throwable);
+			if (throwable instanceof Exception) {
+			  throw (Exception)throwable;
+			} else {
+			  throw new Exception(throwable);
+			}
+		}
+		//return null;
+	}
+
+	public class WriteableIndexWriter extends InstantiatedIndexWriter {
+		public WriteableIndexWriter(InstantiatedIndex index) throws IOException {
+			super(index);
+		}
+	}
+}
Index: ocean/src/org/apache/lucene/ocean/Snapshot.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/Snapshot.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/Snapshot.java	(revision 0)
@@ -0,0 +1,280 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.TreeMap;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.ocean.DiskIndex.DiskIndexSnapshot;
+import org.apache.lucene.ocean.Index.IndexSnapshot;
+import org.apache.lucene.ocean.RamIndex.RamIndexSnapshot;
+import org.apache.lucene.ocean.SnapshotInfo.IndexInfo;
+import org.apache.lucene.ocean.WriteableMemoryIndex.MemoryIndexSnapshot;
+import org.apache.lucene.ocean.util.SortedList;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.MultiSearcher;
+import org.apache.lucene.search.Searcher;
+
+public class Snapshot implements Comparable<Snapshot> {
+  private BigDecimal id;
+  private SortedList<IndexID,IndexSnapshot> indexSnapshotMap;
+  private MemoryIndexSnapshot writeableSnapshot;
+  private IndexReader indexReader;
+  private int maxDoc;
+  private int[] starts;
+  private TransactionSystem system;
+  private long timestamp;
+
+  public Snapshot(Long snapshotId, int minorVersion, MemoryIndexSnapshot writeableSnapshot, List<IndexSnapshot> indexSnapshots,
+      TransactionSystem system, long timestamp) throws IOException {
+    id = toId(snapshotId, minorVersion);
+    this.writeableSnapshot = writeableSnapshot;
+    this.system = system;
+    this.timestamp = timestamp;
+    assert snapshotIdsMatch(indexSnapshots);
+    indexSnapshotMap = new SortedList<IndexID,IndexSnapshot>();
+    IndexReader[] readerArray = new IndexReader[indexSnapshots.size()];
+    int x = 0;
+    for (IndexSnapshot indexSnapshot : indexSnapshots) {
+      indexSnapshotMap.put(indexSnapshot.getIndex().getId(), indexSnapshot);
+      readerArray[x] = indexSnapshot.getIndexReader();
+      x++;
+    }
+    IndexSnapshot[] indexSnapshotsArray = indexSnapshotMap.values().toArray(new IndexSnapshot[0]);
+    // build starts array
+    int[] starts = new int[indexSnapshotsArray.length + 1];
+    for (int i = 0; i < indexSnapshotsArray.length; i++) {
+      starts[i] = maxDoc;
+      maxDoc += indexSnapshotsArray[i].maxDoc(); // compute maxDocs
+    }
+    starts[indexSnapshotsArray.length] = maxDoc;
+  }
+
+  public long getTimestamp() {
+    return timestamp;
+  }
+
+  public int compareTo(Snapshot other) {
+    return id.compareTo(other.id);
+  }
+
+  public Searcher getSearcher() throws IOException {
+    MultiSearcher multiSearcher = new MultiSearcher(getSearchers());
+    return multiSearcher;
+  }
+
+  public Searcher[] getSearchers() {
+    IndexSnapshot[] indexSnapshots = (IndexSnapshot[]) indexSnapshotMap.values().toArray(new IndexSnapshot[0]);
+    Searcher[] searchers = new Searcher[indexSnapshotMap.size()];
+    for (int x = 0; x < indexSnapshots.length; x++) {
+      searchers[x] = new IndexSearcher(indexSnapshots[x].getIndexReader());
+    }
+    return searchers;
+  }
+
+  public IndexReader getIndexReader() {
+    return indexReader;
+  }
+
+  public int maxDoc() {
+    return maxDoc;
+  }
+
+  public int[] getStarts() {
+    return starts;
+  }
+
+  public List<RamIndexSnapshot> getRamIndexSnapshots() {
+    List<RamIndexSnapshot> ramIndexSnapshots = new ArrayList<RamIndexSnapshot>();
+    for (IndexSnapshot indexSnapshot : indexSnapshotMap.values()) {
+      if (indexSnapshot instanceof RamIndexSnapshot) {
+        ramIndexSnapshots.add((RamIndexSnapshot) indexSnapshot);
+      }
+    }
+    return ramIndexSnapshots;
+  }
+
+  public IndexReader[] getIndexReaders() {
+    IndexReader[] indexReaders = new IndexReader[indexSnapshotMap.size()];
+    int i = 0;
+    for (IndexSnapshot indexSnapshot : indexSnapshotMap.values()) {
+      indexReaders[i] = indexSnapshot.getIndexReader();
+      i++;
+    }
+    return indexReaders;
+  }
+
+  public int getMaxDoc() {
+    int maxDoc = 0;
+    for (IndexSnapshot indexSnapshot : indexSnapshotMap.values()) {
+      maxDoc += indexSnapshot.getIndexReader().maxDoc();
+    }
+    return maxDoc;
+  }
+
+  public int getMinorVersion() {
+    return getMinorVersion(id);
+  }
+
+  public static void main(String[] args) {
+    BigDecimal id = toId(210l, 1);
+    String string = formatId(id);
+    System.out.println(string);
+  }
+
+  public static BigDecimal toId(Long snapshotId, int minorVersion) {
+    StringBuilder builder = new StringBuilder();
+    builder.append(snapshotId);
+    builder.append(".");
+    if (10 > minorVersion)
+      builder.append("0");
+    builder.append(minorVersion);
+    BigDecimal value = new BigDecimal(builder.toString());
+    return value;
+  }
+
+  public static int getMinorVersion(BigDecimal value) {
+    value = value.subtract(new BigDecimal(value.longValue()));
+    BigDecimal decimal = value.scaleByPowerOfTen(2);
+    return decimal.intValue();
+  }
+
+  public static boolean snapshotIdsMatch(Collection<IndexSnapshot> indexSnapshots) {
+    Long current = null;
+    for (IndexSnapshot indexSnapshot : indexSnapshots) {
+      if (current == null) {
+        current = indexSnapshot.getSnapshotId();
+      } else if (!current.equals(indexSnapshot.getSnapshotId())) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  public Snapshot(BigDecimal id, MemoryIndexSnapshot writeableSnapshot, Collection<IndexSnapshot> indexSnapshots, TransactionSystem system,
+      long timestamp) throws IOException {
+    this.id = id;
+    this.writeableSnapshot = writeableSnapshot;
+    this.timestamp = timestamp;
+    assert snapshotIdsMatch(indexSnapshots);
+    indexSnapshotMap = new SortedList<IndexID,IndexSnapshot>();
+    for (IndexSnapshot indexSnapshot : indexSnapshots) {
+      indexSnapshotMap.put(indexSnapshot.getIndex().getId(), indexSnapshot);
+    }
+  }
+
+  public SnapshotInfo getSnapshotInfo() throws IOException {
+    SnapshotInfo snapshotInfo = new SnapshotInfo(id);
+    for (IndexSnapshot indexSnapshot : indexSnapshotMap.values()) {
+      Index index = indexSnapshot.getIndex();
+      String type = null;
+      Long segmentGeneration = null;
+      if (index instanceof DiskIndex) {
+        segmentGeneration = indexSnapshot.getIndexReader().getIndexCommit().getGeneration();
+        type = "disk";
+      } else if (index instanceof WriteableMemoryIndex)
+        type = "memory";
+      else if (index instanceof RamIndex)
+        type = "ram";
+      IndexInfo indexInfo = new IndexInfo(index.getId().id, segmentGeneration, type, indexSnapshot.maxDoc(), indexSnapshot.deletedDoc(), indexSnapshot.getMaxDocumentId(), indexSnapshot.getMaxSnapshotId());
+      snapshotInfo.add(indexInfo);
+    }
+    return snapshotInfo;
+  }
+
+  public static String formatId(BigDecimal id) {
+    DecimalFormat format = new DecimalFormat("##0.00");
+    return format.format(id);
+  }
+
+  public static String getFileName(BigDecimal id) {
+    String string = formatId(id);
+    String replaced = string.replace('.', '_');
+    return "snapshot_" + replaced + ".xml";
+  }
+
+  /**
+   * Create minor snapshot (meaning a merged snapshot with no real index
+   * changes) reusing the existing writeableSnapshot.
+   * 
+   * @param removeIndexIds
+   * @param newIndexSnapshot
+   * @return
+   * @throws IOException
+   */
+  public Snapshot createMinor(List<IndexID> removeIndexIds, IndexSnapshot newIndexSnapshot) throws IOException {
+    return createMinor(removeIndexIds, writeableSnapshot, newIndexSnapshot);
+  }
+
+  public Snapshot createMinor(List<IndexID> removeIndexIds, MemoryIndexSnapshot writeableSnapshot, IndexSnapshot newIndexSnapshot)
+      throws IOException {
+    HashMap<IndexID,IndexSnapshot> mapCopy = new HashMap<IndexID,IndexSnapshot>(indexSnapshotMap);
+    for (IndexID indexid : removeIndexIds) {
+      mapCopy.remove(indexid);
+    }
+    IndexID newIndexId = newIndexSnapshot.getIndex().getId();
+    assert !mapCopy.containsKey(newIndexId);
+    mapCopy.put(newIndexId, newIndexSnapshot);
+    int minorVersion = getMinorVersion();
+    Long snapshotId = getSnapshotId();
+    int newMinorVersion = minorVersion + 1;
+    System.out.println("snapshotId: " + snapshotId + " newMinorVersion: " + newMinorVersion);
+    // BigDecimal newId = toId(snapshotId, minorVersion);
+    Snapshot newSnapshot = new Snapshot(snapshotId, newMinorVersion, writeableSnapshot, new ArrayList(mapCopy.values()), system, System
+        .currentTimeMillis());
+    return newSnapshot;
+  }
+
+  public List<DiskIndex> getDiskIndices() {
+    List<DiskIndex> diskIndices = new ArrayList<DiskIndex>();
+    for (IndexSnapshot indexSnapshot : indexSnapshotMap.values()) {
+      indexSnapshot.getIndex();
+    }
+    return diskIndices;
+  }
+
+  public List<Index> getDeleteOnlyIndices() {
+    HashMap<IndexID,IndexSnapshot> mapCopy = new HashMap<IndexID,IndexSnapshot>(indexSnapshotMap);
+    mapCopy.remove(writeableSnapshot.getIndex().getId());
+    List<Index> indices = new ArrayList<Index>();
+    for (IndexSnapshot indexSnapshot : mapCopy.values()) {
+      indices.add(indexSnapshot.getIndex());
+    }
+    return indices;
+  }
+
+  public MemoryIndexSnapshot getWriteableSnapshot() {
+    return writeableSnapshot;
+  }
+
+  public boolean containsIndex(long indexid) {
+    return indexSnapshotMap.containsKey(indexid);
+  }
+
+  public List<DiskIndexSnapshot> getDiskIndexSnapshots() {
+    List<DiskIndexSnapshot> diskIndexSnapshots = new ArrayList<DiskIndexSnapshot>();
+    for (IndexSnapshot indexSnapshot : indexSnapshotMap.values()) {
+      if (indexSnapshot instanceof DiskIndexSnapshot) {
+        diskIndexSnapshots.add((DiskIndexSnapshot) indexSnapshot);
+      }
+    }
+    return diskIndexSnapshots;
+  }
+
+  public List<IndexSnapshot> getIndexSnapshots() {
+    return new ArrayList(indexSnapshotMap.values());
+  }
+
+  public Long getSnapshotId() {
+    return id.longValue();
+  }
+
+  public BigDecimal getId() {
+    return id;
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/Indexes.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/Indexes.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/Indexes.java	(revision 0)
@@ -0,0 +1,38 @@
+package org.apache.lucene.ocean;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.lucene.ocean.util.Util;
+
+public class Indexes {
+  private Map<IndexID,Index> indexMap = new HashMap<IndexID,Index>();
+  
+  public IndexID getMaxId(String type) {
+    List<IndexID> list = new ArrayList<IndexID>();
+    for (IndexID indexId : indexMap.keySet()) {
+      if (indexId.type.equals(type)) {
+        list.add(indexId);
+      }
+    }
+    if (list.size() == 0) return null;
+    return Util.max(list);
+  }
+  
+  public List<Index> getIndexes() {
+    return new ArrayList(indexMap.values());
+  }
+  
+  public Indexes() {
+  }
+
+  public Index get(IndexID indexId) {
+    return indexMap.get(indexId);
+  }
+
+  public void add(Index index) {
+    indexMap.put(index.getId(), index);
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/FSLogDirectory.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/FSLogDirectory.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/FSLogDirectory.java	(revision 0)
@@ -0,0 +1,110 @@
+package org.apache.lucene.ocean;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.lucene.ocean.util.Util;
+
+import com.imagero.uio.RandomAccessInput;
+import com.imagero.uio.bio.BufferedRandomAccessIO;
+import com.imagero.uio.bio.IOController;
+import com.imagero.uio.bio.content.RandomAccessFileContent;
+
+public class FSLogDirectory extends LogDirectory {
+  private File fileDirectory;
+  private Map<String,FSFile> outputMap = new HashMap<String,FSFile>();
+  private ReentrantLock outputLock = new ReentrantLock();
+  private ReentrantLock inputLock = new ReentrantLock();
+
+  public FSLogDirectory(File fileDirectory) {
+    Util.mkdir(fileDirectory);
+    this.fileDirectory = fileDirectory;
+  }
+
+  public static class FSFile {
+    RandomAccessFileContent content;
+    
+    public FSFile(RandomAccessFileContent content) {
+      this.content = content;
+    }
+  }
+  
+  public String[] list() throws IOException {
+    List<String> list = new ArrayList<String>();
+    for (File file : fileDirectory.listFiles()) {
+      if (!file.isDirectory()) {
+        list.add(file.getName());
+      }
+    }
+    return (String[]) list.toArray(new String[0]);
+  }
+
+  public boolean fileExists(String name) throws IOException {
+    File file = new File(fileDirectory, name);
+    return file.exists();
+  }
+
+  public long fileModified(String name) throws IOException {
+    File file = new File(fileDirectory, name);
+    return file.lastModified();
+  }
+
+  public void deleteFile(String name) throws IOException {
+    FSFile fsFile = outputMap.get(name);
+    if (fsFile != null) {
+      fsFile.content.close();
+    }
+    File file = new File(fileDirectory, name);
+    boolean deleted = file.delete();
+    if (!deleted) {
+      throw new IOException("file: "+name+" not deleted");
+    }
+  }
+
+  public long fileLength(String name) throws IOException {
+    File file = new File(fileDirectory, name);
+    return file.length();
+  }
+
+  public RandomAccessInput openInput(String name) throws IOException {
+    inputLock.lock();
+    try {
+      BufferedRandomAccessIO access = getOutput(name, false);
+      return access.createIOChild(0, 0, access.getByteOrder(), false);
+    } finally {
+      inputLock.unlock();
+    }
+  }
+
+  public BufferedRandomAccessIO getOutput(String name, boolean overwrite) throws IOException {
+    outputLock.lock();
+    try {
+      FSFile fsFile = outputMap.get(name);
+      if (fsFile == null) {
+        File file = new File(fileDirectory, name);
+        RandomAccessFileContent content = new RandomAccessFileContent(file);
+        fsFile = new FSFile(content);
+        outputMap.put(name, fsFile);
+      }
+      
+      if (overwrite) {
+        //if (fsFile != null) {
+        //  buffered.close();
+        //  buffered = null;
+       // }
+        //boolean deleted = file.delete();
+
+      }
+      IOController contoller = new IOController(1024 * 16, fsFile.content);
+      BufferedRandomAccessIO buffered = new BufferedRandomAccessIO(contoller);
+      return buffered;
+    } finally {
+      outputLock.unlock();
+    }
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/Documents.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/Documents.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/Documents.java	(revision 0)
@@ -0,0 +1,17 @@
+package org.apache.lucene.ocean;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.apache.lucene.document.Document;
+
+public class Documents extends ArrayList<Document> {
+  
+  public Documents() {}
+  
+  public Documents(Collection<Document> documents) {
+    for (Document document : documents) {
+      add(document);
+    }
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/RAMDirectoryMap.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/RAMDirectoryMap.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/RAMDirectoryMap.java	(revision 0)
@@ -0,0 +1,51 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.RAMDirectory;
+
+public class RAMDirectoryMap extends DirectoryMap {
+  private Map<String,RAMDirectory> map = new HashMap<String,RAMDirectory>();
+  private RAMLogDirectory directory;
+  private RAMLogDirectory logDirectory;
+  
+  public RAMDirectoryMap() {
+    directory = new RAMLogDirectory();
+    logDirectory = new RAMLogDirectory();
+  }
+  
+  public LogDirectory getLogDirectory() {
+    return logDirectory;
+  }
+  
+  public Directory create(String name) throws IOException {
+    RAMDirectory dir = new RAMDirectory();
+    map.put(name, dir);
+    return dir;
+  }
+  
+  public LogDirectory getDirectory() {
+    return directory;
+  }
+  
+  public void delete(String name) throws IOException {
+    map.remove(name);
+  }
+
+  public Directory get(String name) throws IOException {
+    return map.get(name);
+  }
+
+  public String[] list() throws IOException {
+    int x = 0;
+    String[] array = new String[map.size()];
+    for (String string : map.keySet()) {
+      array[x] = string;
+      x++;
+    }
+    return array;
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/DirectoryMap.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/DirectoryMap.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/DirectoryMap.java	(revision 0)
@@ -0,0 +1,19 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+
+import org.apache.lucene.store.Directory;
+
+public abstract class DirectoryMap {
+  public abstract Directory create(String name) throws IOException;
+  
+  public abstract void delete(String name) throws IOException;
+  
+  public abstract Directory get(String name) throws IOException;
+  
+  public abstract String[] list() throws IOException;
+  
+  public abstract LogDirectory getDirectory();
+  
+  public abstract LogDirectory getLogDirectory();
+}
Index: ocean/src/org/apache/lucene/ocean/OceanSearcher.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/OceanSearcher.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/OceanSearcher.java	(revision 0)
@@ -0,0 +1,18 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+
+import org.apache.lucene.search.MultiSearcher;
+
+public class OceanSearcher extends MultiSearcher {
+  Snapshot snapshot;
+  
+  public OceanSearcher(Snapshot snapshot) throws IOException {
+    super(snapshot.getSearchers());
+    this.snapshot = snapshot;
+  }
+  
+  public Snapshot getSnapshot() {
+    return snapshot;
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/LogDirectory.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/LogDirectory.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/LogDirectory.java	(revision 0)
@@ -0,0 +1,22 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+
+import com.imagero.uio.RandomAccessIO;
+import com.imagero.uio.RandomAccessInput;
+
+public abstract class LogDirectory {
+  public abstract String[] list() throws IOException;
+
+  public abstract boolean fileExists(String name) throws IOException;
+
+  public abstract long fileModified(String name) throws IOException;
+
+  public abstract void deleteFile(String name) throws IOException;
+
+  public abstract long fileLength(String name) throws IOException;
+  
+  public abstract RandomAccessInput openInput(String name) throws IOException;
+  
+  public abstract RandomAccessIO getOutput(String name, boolean overwrite) throws IOException;
+}
Index: ocean/src/org/apache/lucene/ocean/RAMLogDirectory.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/RAMLogDirectory.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/RAMLogDirectory.java	(revision 0)
@@ -0,0 +1,66 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.imagero.uio.RandomAccessIO;
+import com.imagero.uio.RandomAccessInput;
+import com.imagero.uio.bio.ByteArrayRandomAccessIO;
+
+public class RAMLogDirectory extends LogDirectory {
+  private Map<String,RAMLogFile> map = new HashMap<String,RAMLogFile>();
+  
+  public static class RAMLogFile {
+    ByteArrayRandomAccessIO randomAccess;
+    long modified;
+    
+    public RAMLogFile() {
+      randomAccess = new ByteArrayRandomAccessIO(1024);
+      modified = System.currentTimeMillis();
+    }
+  }
+  
+  public String[] list() throws IOException {
+    int x = 0;
+    String[] array = new String[map.size()];
+    for (String string : map.keySet()) {
+      array[x] = string;
+      x++;
+    }
+    return array;
+  }
+
+  public boolean fileExists(String name) throws IOException {
+    return map.containsKey(name);
+  }
+
+  public long fileModified(String name) throws IOException {
+    RAMLogFile ramLogFile = map.get(name);
+    return ramLogFile.modified;
+  }
+
+  public void deleteFile(String name) throws IOException {
+    map.remove(name);
+  }
+
+  public long fileLength(String name) throws IOException {
+    RAMLogFile ramLogFile = map.get(name);
+    return ramLogFile.randomAccess.length();
+  }
+  
+  public RandomAccessIO getOutput(String name, boolean overwrite) throws IOException {
+    RAMLogFile ramLogFile = map.get(name);
+    if (ramLogFile == null || overwrite) {
+      ramLogFile = new RAMLogFile();
+      map.put(name, ramLogFile);
+    }
+    ramLogFile.randomAccess.seek(0);
+    return ramLogFile.randomAccess;
+  }
+  
+  public RandomAccessInput openInput(String name) throws IOException {
+    RAMLogFile ramLogFile = map.get(name);
+    return ramLogFile.randomAccess.createInputChild(0, 0, ramLogFile.randomAccess.getByteOrder(), false);
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/RamIndex.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/RamIndex.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/RamIndex.java	(revision 0)
@@ -0,0 +1,110 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.KeepOnlyLastCommitDeletionPolicy;
+import org.apache.lucene.index.SerialMergeScheduler;
+import org.apache.lucene.ocean.WriteableMemoryIndex.MemoryIndexSnapshot;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.RAMDirectory;
+
+public class RamIndex extends DirectoryIndex {
+	private RAMDirectory ramDirectory;
+	private Long maxSnapshotId;
+	private Long maxDocumentId;
+  
+	public RamIndex(IndexID id, List<? extends IndexSnapshot> indexSnapshots, TransactionSystem system) throws Exception {
+		super(id, system);
+		ramDirectory = new RAMDirectory();
+		IndexReader[] indexReaders = getIndexReaders(indexSnapshots);
+		RAMDirectory ramDirectory = new RAMDirectory();
+		IndexWriter indexWriter = new IndexWriter(ramDirectory, false, system.getDefaultAnalyzer(), true);
+		indexWriter.setMergeScheduler(new SerialMergeScheduler());
+		indexWriter.setUseCompoundFile(true);
+		indexWriter.addIndexes(indexReaders);
+		indexWriter.close();
+		maxSnapshotId = getMaxSnapshotId(indexSnapshots);
+		maxDocumentId = getMaxDocumentId(indexSnapshots);
+	}
+	
+	// TODO: add timestamp so ramindex can be removed from indices
+	public RamIndex(IndexID id, Long snapshotId, List<Deletes> deletesList, RAMDirectory ramDirectory, TransactionSystem system) throws Exception {
+		super(id, system);
+		this.ramDirectory = ramDirectory;
+		initialIndexReader = IndexReader.open(ramDirectory, indexDeletionPolicy);
+		createNewSnapshot(snapshotId, initialIndexReader);
+		if (deletesList != null) {
+			for (Deletes deletes : deletesList) {
+				applyDeletes(true, deletes, null, initialIndexReader);
+			}
+		}
+	}
+  
+	// converts memoryIndexSnapshot into ramindexsnapshot
+	public RamIndex(IndexID id, MemoryIndexSnapshot memoryIndexSnapshot) throws Exception, IOException {
+		super(id, memoryIndexSnapshot.getIndex().getSystem());
+		this.maxSnapshotId = memoryIndexSnapshot.getMaxSnapshotId();
+		this.maxDocumentId = memoryIndexSnapshot.getMaxDocumentId();
+		ramDirectory = new RAMDirectory();
+		Analyzer defaultAnalyzer = memoryIndexSnapshot.getIndex().getSystem().getDefaultAnalyzer();
+		IndexWriter indexWriter = new IndexWriter(ramDirectory, false, defaultAnalyzer, true, new KeepOnlyLastCommitDeletionPolicy());
+		indexWriter.addIndexes(new IndexReader[] {memoryIndexSnapshot.getIndexReader()});
+		indexWriter.close();
+		initialIndexReader = IndexReader.open(ramDirectory, indexDeletionPolicy);
+		List<IndexSnapshot> indexSnapshots = new ArrayList<IndexSnapshot>(1);
+		indexSnapshots.add(memoryIndexSnapshot);
+		createNewSnapshot(memoryIndexSnapshot.getSnapshotId(), initialIndexReader);
+	}
+  
+	public RamIndexSnapshot commitIndex(Transaction transaction) throws IndexException, InterruptedException, IOException {
+		try {
+			transaction.ready(this);
+			if (transaction.go()) {
+				Long snapshotID = transaction.getId();
+				RamIndexSnapshot indexSnapshot = createNewSnapshot(snapshotID, initialIndexReader);
+				return indexSnapshot;
+			} else {
+				// if commit fails this snapshot and ramindex won't make it
+				return null;
+			}
+		} catch (Throwable throwable) {
+			LOG.error("", throwable);
+			transaction.failed(this, throwable);
+			return null;
+		}
+	}
+	
+	protected RamIndexSnapshot createNewSnapshot(Long snapshotId, IndexReader newIndexReader) throws IOException {
+		RamIndexSnapshot ramIndexSnapshot = new RamIndexSnapshot(snapshotId, newIndexReader);
+		registerSnapshot(ramIndexSnapshot);
+		return ramIndexSnapshot;
+	}
+
+	public class RamIndexSnapshot extends DirectoryIndexSnapshot {
+		public RamIndexSnapshot(Long snapshotId, IndexReader indexReader) {
+			super(snapshotId, indexReader);
+		}
+		
+		public String toString() {
+		  return "RamIndexSnapshot index: "+RamIndex.this.getId()+" snapshotid: "+snapshotId;
+		}
+		
+		//public Long getMaxSnapshotId() throws IOException {
+		//	return maxSnapshotId;
+		//}
+
+		//public Long getMaxDocumentId() throws IOException {
+		//	return maxDocumentId;
+		//}
+	}
+
+	public Directory getDirectory() {
+		return ramDirectory;
+	}
+}
Index: ocean/src/org/apache/lucene/ocean/Index.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/Index.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/Index.java	(revision 0)
@@ -0,0 +1,340 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.lucene.index.CorruptIndexException;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.StaleReaderException;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.index.TermDocs;
+import org.apache.lucene.index.TermEnum;
+import org.apache.lucene.ocean.Deletes.DeleteByQuery;
+import org.apache.lucene.ocean.util.Constants;
+import org.apache.lucene.ocean.util.SortedList;
+import org.apache.lucene.ocean.util.Util;
+import org.apache.lucene.search.ExtendedFieldCache;
+import org.apache.lucene.search.HitCollector;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.ExtendedFieldCache.LongParser;
+
+public abstract class Index {
+  private final IndexID id;
+  protected boolean isClosed = false;
+  private final TransactionSystem transactionSystem;
+  private boolean isReadOnly = false;
+  private boolean isDeleteOnly = false;
+
+  public Index(IndexID id, TransactionSystem transactionSystem) {
+    this.id = id;
+    this.transactionSystem = transactionSystem;
+  }
+  
+  public void removeOldSnapshots(SortedList<Long,? extends IndexSnapshot> snapshotMap) {
+    Long last = snapshotMap.lastKey();
+    for (Iterator<Long> iterator = snapshotMap.keySet().iterator(); iterator.hasNext();) {
+      Long snapshotId = iterator.next(); 
+      if (!transactionSystem.snapshots.contains(snapshotId) && (last != null && !last.equals(snapshotId))) {
+        iterator.remove();
+      }
+    }
+  }
+  
+  public void close() throws IOException {}
+  
+  public TransactionSystem getSystem() {
+    return transactionSystem;
+  }
+
+  public static IndexReader[] getIndexReaders(List<? extends IndexSnapshot> indexSnapshots) {
+    IndexReader[] indexReaders = new IndexReader[indexSnapshots.size()];
+    for (int x = 0; x < indexSnapshots.size(); x++) {
+      indexReaders[x] = indexSnapshots.get(x).getIndexReader();
+    }
+    return indexReaders;
+  }
+
+  public static Long getMaxSnapshotId(List<? extends IndexSnapshot> indexSnapshots) throws Exception {
+    List<Long> snapshotIdList = new ArrayList<Long>(indexSnapshots.size());
+    for (IndexSnapshot indexSnapshot : indexSnapshots) {
+      snapshotIdList.add(indexSnapshot.getMaxSnapshotId());
+    }
+    if (snapshotIdList.size() > 0) {
+      return Collections.max(snapshotIdList);
+    } else {
+      return null;
+    }
+  }
+
+  public static Long getMaxDocumentId(List<? extends IndexSnapshot> indexSnapshots) throws Exception {
+    List<Long> documentIdList = new ArrayList<Long>(indexSnapshots.size());
+    for (IndexSnapshot indexSnapshot : indexSnapshots) {
+      documentIdList.add(indexSnapshot.getMaxDocumentId());
+    }
+    if (documentIdList.size() > 0) {
+      return Collections.max(documentIdList);
+    } else {
+      return null;
+    }
+  }
+
+  public void setDeleteOnly(boolean isDeleteOnly) {
+    this.isDeleteOnly = isDeleteOnly;
+  }
+
+  public boolean isDeleteOnly() {
+    return isDeleteOnly;
+  }
+
+  public boolean isReadOnly() {
+    return isReadOnly;
+  }
+
+  public void setReadOnly(boolean isReadOnly) {
+    this.isReadOnly = isReadOnly;
+  }
+
+  protected DeletesResult applyDeletes(boolean deleteFromReader, Deletes deletes, Collection<Integer> deletedDocs, IndexReader indexReader)
+      throws CorruptIndexException, IOException, Exception {
+    long[] ids = ExtendedFieldCache.EXT_DEFAULT.getLongs(indexReader, Constants.DOCUMENTID, new LongParser() {
+      public long parseLong(String string) {
+        return Util.longFromEncoded(string);
+      }
+    });
+    DeletesResult deletesResult = new DeletesResult(getId());
+    if (deletes.hasDeletes()) {
+      if (deletes.hasDocIds()) {
+        int docsDeleted = 0;
+        long[] docIdsArray = deletes.getDocIds();
+        for (long id : docIdsArray) {
+          int doc = Util.getDoc(Constants.DOCUMENTID, id, indexReader);
+          indexReader.deleteDocument(doc);
+          docsDeleted++;
+        }
+        deletesResult.add(new DeletesResult.Result(docIdsArray, docsDeleted));
+      } else {
+        List<Long> docIds = deletesResult.getDocIds();
+        if (deletes.hasTerms()) {
+          List<Term> terms = deletes.getTerms();
+          for (Term term : terms) {
+            int docsDeleted = deleteByTerm(deleteFromReader, term, deletedDocs, docIds, ids, indexReader);
+            deletesResult.add(new DeletesResult.Result(term, docsDeleted));
+          }
+        }
+        if (deletes.hasDeleteByQueries()) {
+          List<DeleteByQuery> deleteByQueries = deletes.getDeleteByQueries();
+          for (DeleteByQuery deleteByQuery : deleteByQueries) {
+            int docsDeleted = deleteByQuery(deleteFromReader, deleteByQuery, deletedDocs, ids, docIds, indexReader);
+            deletesResult.add(new DeletesResult.Result(deleteByQuery, docsDeleted));
+          }
+        }
+      }
+    }
+    return deletesResult;
+  }
+
+  protected int deleteByTerm(boolean deleteFromReader, Term term, Collection<Integer> deletedDocs, List<Long> docIds, long[] ids,
+      IndexReader indexReader) throws IOException {
+    TermDocs docs = indexReader.termDocs(term);
+    if (docs == null)
+      return 0;
+    int n = 0;
+    try {
+      while (docs.next()) {
+        int doc = docs.doc();
+        if (deletedDocs != null)
+          deletedDocs.add(doc);
+        Long docId = ids[doc];
+        docIds.add(docId);
+        if (deleteFromReader)
+          indexReader.deleteDocument(doc);
+        n++;
+      }
+    } finally {
+      docs.close();
+    }
+    return n;
+  }
+
+  protected int deleteByQuery(final boolean deleteFromReader, DeleteByQuery deleteByQuery, final Collection<Integer> deletedDocs,
+      final long[] ids, final List<Long> deletedIds, final IndexReader indexReader) throws IOException {
+    Query query = deleteByQuery.getQuery();
+    IndexSearcher indexSearcher = new IndexSearcher(indexReader);
+    final int[] numDeleted = new int[1];
+    indexSearcher.search(query, new HitCollector() {
+      public void collect(int doc, float score) {
+        try {
+          if (deleteFromReader)
+            indexReader.deleteDocument(doc);
+          if (deletedDocs != null)
+            deletedDocs.add(doc);
+          Long docId = ids[doc];
+          deletedIds.add(docId);
+          numDeleted[0]++;
+        } catch (StaleReaderException staleReaderException) {
+          throw new RuntimeException(staleReaderException);
+        } catch (IOException ioException) {
+          throw new RuntimeException(ioException);
+        }
+      }
+    });
+    return numDeleted[0];
+  }
+
+  public static class IndexException extends Exception {
+    public IndexException(String message) {
+      super(message);
+    }
+
+    public IndexException(String message, Exception exception) {
+      super(message, exception);
+    }
+  }
+
+  public static class IndexNeverCompletedCopyException extends IndexException {
+    public IndexNeverCompletedCopyException(String message) {
+      super(message);
+    }
+  }
+  
+  public abstract boolean rollback(Long snapshotId) throws Exception;
+  
+  public abstract IndexSnapshot getIndexSnapshot(Long snapshotID);
+
+  public abstract IndexSnapshot getLatestIndexSnapshot();
+
+  public abstract class IndexSnapshot {
+    protected final Long snapshotId;
+    private Long maxSnapshotId;
+    private Long maxDocumentId;
+
+    public IndexSnapshot(Long snapshotId) {
+      this.snapshotId = snapshotId;
+    }
+    
+    public abstract int deletedDoc();
+    
+    public abstract int maxDoc();
+
+    public Long getSnapshotId() {
+      return snapshotId;
+    }
+
+    public Index getIndex() {
+      return Index.this;
+    }
+
+    /**
+     * Iterates terms until next field is reached, returns text
+     * 
+     * @param field
+     * @return
+     * @throws Exception
+     */
+    public String getMax(String field) throws IOException {
+      IndexReader indexReader = getIndexReader();
+      TermEnum termEnum = indexReader.terms(new Term(field, ""));
+      try {
+        String text = null;
+        do {
+          Term term = termEnum.term();
+          if (term == null || term.field() != field)
+            break;
+          text = term.text();
+        } while (termEnum.next());
+        return text;
+      } finally {
+        termEnum.close();
+      }
+    }
+
+    public Long getMaxSnapshotId() throws IOException {
+      if (maxSnapshotId == null) {
+        String string = getMax(Constants.SNAPSHOTID);
+        if (string == null) return null;
+        maxSnapshotId = Util.longFromEncoded(string);
+      }
+      return maxSnapshotId;
+    }
+
+    public Long getMaxDocumentId() throws IOException {
+      if (maxDocumentId == null) {
+        String string = getMax(Constants.DOCUMENTID);
+        if (string == null) return null;
+        maxDocumentId = Util.longFromEncoded(string);
+      }
+      return maxDocumentId;
+    }
+
+    public abstract IndexReader getIndexReader();
+  }
+
+  public static class MergedDocMap {
+    private Map<IndexSnapshot,int[]> oldMap; // maps old doc to new doc
+    private RI[] merged; // maps new doc to old doc and reader
+
+    public MergedDocMap(List<? extends IndexSnapshot> indexSnapshots) {
+      int newMaxDoc = 0;
+      for (IndexSnapshot indexSnapshot : indexSnapshots) {
+        newMaxDoc += indexSnapshot.getIndexReader().numDocs();
+      }
+      oldMap = new HashMap<IndexSnapshot,int[]>(indexSnapshots.size());
+      RI[] merged = new RI[newMaxDoc];
+      int pos = 0;
+      for (IndexSnapshot indexSnapshot : indexSnapshots) {
+        IndexReader indexReader = indexSnapshot.getIndexReader();
+        int maxDoc = indexReader.maxDoc();
+        int[] old = new int[maxDoc];
+        for (int x = 0; x < maxDoc; x++) {
+          if (indexReader.hasDeletions() && indexReader.isDeleted(x)) {
+            merged[pos] = null;
+            old[x] = -1;
+          } else {
+            merged[pos] = new RI(x, indexSnapshot);
+            old[x] = pos;
+            pos++;
+          }
+        }
+        oldMap.put(indexSnapshot, old);
+      }
+    }
+
+    public static class RI {
+      public int doc;
+      public IndexSnapshot oldIndexSnapshot;
+
+      public RI(int doc, IndexSnapshot oldIndexSnapshot) {
+        this.doc = doc;
+        this.oldIndexSnapshot = oldIndexSnapshot;
+      }
+    }
+
+    public Map<IndexSnapshot,int[]> getOldMap() {
+      return oldMap;
+    }
+
+    public RI[] getMerged() {
+      return merged;
+    }
+  }
+
+  public boolean isClosed() {
+    return isClosed;
+  }
+
+  public IndexID getId() {
+    return id;
+  }
+  
+  public abstract void commitNothing(Transaction transaction) throws IndexException, InterruptedException, IOException;
+  
+  public abstract DeletesResult commitDeletes(Deletes deletes, Transaction transaction) throws Exception, IndexException, InterruptedException,
+      IOException;
+}
Index: ocean/src/org/apache/lucene/ocean/OceanInstantiatedIndexReader.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/OceanInstantiatedIndexReader.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/OceanInstantiatedIndexReader.java	(revision 0)
@@ -0,0 +1,31 @@
+package org.apache.lucene.ocean;
+
+import java.util.Set;
+
+import org.apache.lucene.store.instantiated.InstantiatedIndex;
+import org.apache.lucene.store.instantiated.InstantiatedIndexReader;
+
+/**
+ * Simulates a multiple version IndexReader with InstantiatedIndexReader by
+ * having documents over the set maxDoc be deleted.  
+ *
+ */
+public class OceanInstantiatedIndexReader extends InstantiatedIndexReader {
+  private int maxDoc;
+  private Set<Integer> deletedDocs;
+  
+  public OceanInstantiatedIndexReader(int maxDoc, InstantiatedIndex index, Set<Integer> deletedDocs) {
+    super(index);
+    this.maxDoc = maxDoc;
+  }
+  
+  public boolean isDeleted(int n) {
+    if (n > maxDoc) return true;
+    if (deletedDocs != null && deletedDocs.contains(n)) return true;
+    return false;
+  }
+  
+  public boolean hasDeletions() {
+    return true;
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/OceanConsole.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/OceanConsole.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/OceanConsole.java	(revision 0)
@@ -0,0 +1,11 @@
+package org.apache.lucene.ocean;
+
+/**
+ * Display indexes
+ * Display index snapshots
+ * Run merge
+ *
+ */
+public class OceanConsole {
+
+}
Index: ocean/src/org/apache/lucene/ocean/SnapshotInfo.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/SnapshotInfo.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/SnapshotInfo.java	(revision 0)
@@ -0,0 +1,151 @@
+package org.apache.lucene.ocean;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.lucene.ocean.util.CElement;
+import org.apache.lucene.ocean.util.XMLUtil;
+import org.jdom.Element;
+
+import com.imagero.uio.RandomAccessIO;
+
+public class SnapshotInfo implements CElement, Comparable<SnapshotInfo> {
+  private BigDecimal id;
+  private Map<IndexID,IndexInfo> indexInfos;
+
+  public SnapshotInfo(BigDecimal id) {
+    assert id != null;
+    this.id = id;
+    indexInfos = new HashMap<IndexID,IndexInfo>();
+  }
+  
+  public IndexInfo getIndexInfo(IndexID id) {
+    return indexInfos.get(id);
+  }
+  
+  public int compareTo(SnapshotInfo other) {
+    return id.compareTo(other.id);
+  }
+  
+  public void add(IndexInfo indexInfo) {
+    indexInfos.put(indexInfo.getIndexID(), indexInfo);
+  }
+
+  public SnapshotInfo(Element element) {
+    indexInfos = new HashMap<IndexID,IndexInfo>();
+    id = new BigDecimal(element.getAttributeValue("id"));
+    for (Element indexElement : XMLUtil.getChildren("index", element)) {
+      IndexInfo indexInfo = new IndexInfo(indexElement);
+      indexInfos.put(indexInfo.getIndexID(), indexInfo);
+    }
+  }
+
+  public Long getSnapshotId() {
+    return id.longValue();
+  }
+
+  public BigDecimal getId() {
+    return id;
+  }
+
+  public Collection<IndexInfo> getIndexInfos() {
+    return indexInfos.values();
+  }
+
+  public SnapshotInfo(BigDecimal id, Map<IndexID,IndexInfo> indexInfos) {
+    this.id = id;
+    this.indexInfos = indexInfos;
+  }
+
+  public static class IndexInfo implements CElement {
+    private Long id;
+    private Long segmentGeneration;
+    private String type;
+    private int maxDoc;
+    private int deletedDoc;
+    private Long maxDocumentId;
+    private Long maxSnapshotId;
+
+    public IndexInfo(Long id, Long segmentGeneration, String type, int maxDoc, int deletedDoc, Long maxDocumentId, Long maxSnapshotId) {
+      this.id = id;
+      this.segmentGeneration = segmentGeneration;
+      this.type = type;
+      this.maxDoc = maxDoc;
+      this.deletedDoc = deletedDoc;
+      this.maxDocumentId = maxDocumentId;
+      this.maxSnapshotId = maxSnapshotId;
+    }
+    
+    public IndexID getIndexID() {
+      return new IndexID(id, type);
+    }
+    
+    public IndexInfo(Element element) {
+      id = XMLUtil.getAttributeLong("id", element);
+      segmentGeneration = XMLUtil.getAttributeLong("segmentGeneration", element);
+      type = XMLUtil.getAttributeString("type", element);
+      maxDoc = XMLUtil.getAttributeInteger("maxDoc", element);
+      deletedDoc = XMLUtil.getAttributeInteger("deletedDoc", element);
+      maxDocumentId = XMLUtil.getAttributeLong("maxDocumentId", element);
+      maxSnapshotId = XMLUtil.getAttributeLong("maxSnapshotId", element);
+    }
+    
+    public Long getMaxSnapshotId() {
+      return maxSnapshotId;
+    }
+    
+    public Long getMaxDocumentId() {
+      return maxDocumentId;
+    }
+    
+    public int getDeletedDoc() {
+      return deletedDoc;
+    }
+    
+    public Long getSegmentGeneration() {
+      return segmentGeneration;
+    }
+    
+    public int getMaxDoc() {
+      return maxDoc;
+    }
+    
+    public Long getId() {
+      return id;
+    }
+
+    public String getType() {
+      return type;
+    }
+
+    public Element toElement() {
+      Element element = new Element("index");
+      XMLUtil.setAttribute("id", id, element);
+      XMLUtil.setAttribute("segmentGeneration", segmentGeneration, element);
+      XMLUtil.setAttribute("type", type, element);
+      XMLUtil.setAttribute("maxDoc", maxDoc, element);
+      XMLUtil.setAttribute("deletedDoc", deletedDoc, element);
+      XMLUtil.setAttribute("maxDocumentId", maxDocumentId, element);
+      XMLUtil.setAttribute("maxSnapshotId", maxSnapshotId, element);
+      return element;
+    }
+  }
+
+  public void writeTo(RandomAccessIO output) throws Exception {
+    Element element = toElement();
+    String xml = XMLUtil.outputElement(element);
+    byte[] bytes = xml.getBytes("UTF-8");
+    output.write(bytes);
+  }
+
+  public Element toElement() {
+    Element element = new Element("snapshot");
+    XMLUtil.setAttribute("id", id, element);
+    for (IndexInfo indexInfo : indexInfos.values()) {
+      element.addContent(indexInfo.toElement());
+    }
+    return element;
+  }
+}
\ No newline at end of file
Index: ocean/src/org/apache/lucene/ocean/Batch.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/Batch.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/Batch.java	(revision 0)
@@ -0,0 +1,215 @@
+package org.apache.lucene.ocean;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.io.Serializable;
+import java.util.Date;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.store.RAMDirectory;
+
+/**
+ * Stays in memory
+ * 
+ */
+public abstract class Batch {
+	protected Documents documents;
+	protected RAMDirectory ramDirectory;
+	protected Analyzer analyzer;
+	protected long schemaVersion;
+	protected Deletes deletes;
+	protected Date timestamp;
+	protected boolean isClosed = false;
+
+	public Batch() {
+		timestamp = new Date();
+	}
+	
+	public boolean hasRAMDirectory() {
+		return ramDirectory != null;
+	}
+	
+	public RAMDirectory getRamDirectory() {
+		return ramDirectory;
+	}
+
+	public long getSchemaVersion() {
+		return schemaVersion;
+	}
+	
+	public static class MasterBatch extends Batch {
+	  private TransactionSystem transactionSystem;
+	  
+	  public MasterBatch(TransactionSystem transactionSystem) {
+	  	this.transactionSystem = transactionSystem;
+	  }
+	  
+	  public void setRAMDirectory(RAMDirectory ramDirectory) {
+	    documents = null;
+	    if (isClosed) throw new RuntimeException("batch closed");
+	    this.ramDirectory = ramDirectory;
+	  }
+	  
+	  public void setAnalyzer(Analyzer analyzer) {
+	    this.analyzer = analyzer;
+	  }
+	  
+	  public void setDeletes(Deletes deletes) {
+	    if (isClosed) throw new RuntimeException("batch closed");
+	    this.deletes = deletes;
+	  }
+	  
+	  public void addDocument(Document document) {
+	    if (this.documents == null)
+	      this.documents = new Documents();
+	    this.documents.add(document);
+	  }
+	  
+	  public void addDocuments(Documents documents) {
+	    if (isClosed) throw new RuntimeException("batch closed");
+	    if (this.documents == null)
+	      this.documents = new Documents();
+	    this.documents.addAll(documents);
+	  }
+	  
+	  /**
+	  public Serializable getSerializableRamDirectory(Long id) {
+	  	assert ramDirectory != null;
+	  	return new SerializableRamDirectory(id, ramDirectory);
+	  }
+	  
+	  public Serializable getSerializableBatchDeletes(Long id) {
+	  	return new SerializableBatchDeletes(id, deletes);
+	  }
+	  
+	  public Serializable getSerializableBatchDocuments(Long id) {
+			return new SerializableBatchDocuments(id, documents); 
+		}
+	  **/
+	  public void commit() throws Exception {
+	    transactionSystem.commitBatch(this); 
+		}
+	}
+	
+	public static class SlaveBatch extends Batch {
+	  private Long id;
+	  
+	  public SlaveBatch(Long id, Documents documents, Deletes deletes) {
+	  	this.id = id;
+	  	this.documents = documents;
+	  	this.deletes = deletes;
+	  }
+	  
+	  public SlaveBatch(Long id, RAMDirectory ramDirectory, Deletes deletes) {
+	  	this.id = id;
+	  	this.ramDirectory = ramDirectory;
+	  	this.deletes = deletes;
+	  }
+	  
+	  public Long getId() {
+	  	return id;
+	  }
+	}
+	/**
+	public static class SerializableRamDirectory extends SerializableBatch implements Externalizable {
+		private static final long serialVersionUID = 1l;
+		private RAMDirectory ramDirectory;
+		
+		public SerializableRamDirectory(Long id, RAMDirectory ramDirectory) {
+			super(id);
+			this.ramDirectory = ramDirectory;
+		}
+    
+		// TODO: use native version not externalizable
+		public void readExternal(ObjectInput objectInput) throws IOException, ClassNotFoundException {
+			long objectVersion = objectInput.readLong();
+			ramDirectory = RamDirectorySerializer.deserialize(objectInput);
+		}
+		
+		public void writeExternal(ObjectOutput objectOutput) throws IOException {
+			objectOutput.writeLong(serialVersionUID);
+			RamDirectorySerializer.serialize(ramDirectory, objectOutput);
+		}
+		
+		public RAMDirectory getRamDirectory() {
+			return ramDirectory;
+		}
+	}
+	
+	public static class SerializableBatchDocuments extends SerializableBatch {
+		private static final long serialVersionUID = 1l;
+		private Documents documents;
+		
+		public SerializableBatchDocuments(Long id, Documents documents) {
+			super(id);
+			this.documents = documents;
+		}
+		
+		public Documents getDocuments() {
+			return documents;
+		}
+	}
+	
+	public static class SerializableBatchDeletes extends SerializableBatch {
+		private static final long serialVersionUID = 1l;
+		private Deletes deletes;
+		
+		public SerializableBatchDeletes(Long id, Deletes deletes) {
+			super(id);
+		}
+		
+		public Deletes getDeletes() {
+			return deletes;
+		}
+	}
+	
+	public abstract static class SerializableBatch implements Serializable {
+		private Long id;
+		
+		public SerializableBatch(Long id) {
+			this.id = id;
+		}
+		public Long getId() {
+			return id;
+		}
+	}
+	**/
+	public Analyzer getAnalyzer() {
+		return analyzer;
+	}
+  
+	public boolean hasDocuments() {
+		if (documents == null || documents.size() == 0)
+			return false;
+		else
+			return true;
+	}
+
+	public boolean hasDeletes() {
+		if (deletes == null || !deletes.hasDeletes()) {
+			return false;
+		} else {
+			return true;
+		}
+	}
+
+	// disallow any more additions
+	public void close() {
+		isClosed = true;
+	}
+
+	public Documents getDocuments() {
+		return documents;
+	}
+
+	public Deletes getDeletes() {
+		return deletes;
+	}
+
+	public Date getTimestamp() {
+		return timestamp;
+	}
+}
Index: ocean/src/org/apache/lucene/ocean/DiskIndex.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/DiskIndex.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/DiskIndex.java	(revision 0)
@@ -0,0 +1,182 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.logging.Logger;
+
+import org.apache.lucene.index.IndexCommit;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.SerialMergeScheduler;
+import org.apache.lucene.ocean.util.Util;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.RAMDirectory;
+
+/**
+ * On disk index.  Only deletes are allowed to occur to the index. 
+ * There is a unique IndexReader per snapshot.
+ *
+ */
+// TODO: may be issue with reader having same version but multiple snapshots when transaction has no deletes for this index
+public class DiskIndex extends DirectoryIndex {
+	public static Logger log = Logger.getLogger(DiskIndex.class.getName());
+	private final Directory directory;
+	//private IndexInfo indexInfo;
+
+	// load existing index
+	public DiskIndex(IndexID id, Directory directory, Long snapshotId, Long segmentGeneration, TransactionSystem system) throws Exception, IndexException, IOException {
+		super(id, system);
+		assert segmentGeneration != null;
+		this.directory = directory;
+		if (directory.fileExists("writing.index")) {
+			throw new IndexNeverCompletedCopyException("index never completed copying");
+		}
+		if (IndexWriter.isLocked(directory)) {
+		  LOG.info("directory: "+directory+" locked.  being unlocked");
+		  IndexWriter.unlock(directory);
+		}
+		initialIndexReader = IndexReader.open(directory, indexDeletionPolicy);
+		long readerGeneration = initialIndexReader.getIndexCommit().getGeneration();
+		assert segmentGeneration.longValue() == readerGeneration;
+		//indexInfo = loadIndexInfo();
+		createNewSnapshot(snapshotId, initialIndexReader);
+	}
+
+	// merge indexes creating new index
+	public DiskIndex(IndexID id, Directory directory, List<? extends IndexSnapshot> indexSnapshots, TransactionSystem system) throws Exception, IOException {
+		super(id, system);
+		this.directory = directory;
+		Util.touchFile("writing.index", directory);
+		IndexReader[] indexReaders = getIndexReaders(indexSnapshots);
+		// create in ram first, is faster than copying to disk due to less hard disk head movement
+		RAMDirectory ramDirectory = new RAMDirectory();
+		IndexWriter indexWriter = new IndexWriter(ramDirectory, false, system.getDefaultAnalyzer(), true);
+		indexWriter.setMergeScheduler(new SerialMergeScheduler());
+		indexWriter.setUseCompoundFile(true);
+		indexWriter.addIndexes(indexReaders);
+		indexWriter.close();
+		Directory.copy(ramDirectory, directory, true);
+		
+		//indexInfo = new IndexInfo();
+		//indexInfo.setMaxDocumentID(maxDocumentId);
+		//indexInfo.setMaxSnapshotID(maxSnapshotId);
+		//saveIndexInfo(indexInfo);
+		directory.deleteFile("writing.index");
+		initialIndexReader = IndexReader.open(directory, indexDeletionPolicy);
+	}
+  
+	public Directory getDirectory() {
+		return directory;
+	}
+  /**
+	private IndexInfo loadIndexInfo() throws Exception {
+		String xml = Util.getString("indexinfo.xml", directory);
+		Element element = XMLUtil.parseElement(xml);
+		return new IndexInfo(element);
+	}
+
+	private void saveIndexInfo(IndexInfo indexInfo) throws Exception {
+		Element element = indexInfo.toElement();
+		String xml = XMLUtil.outputElement(element);
+		Util.save(xml, "indexinfo.xml", directory);
+	}
+
+	public static class IndexInfo implements CElement {
+		private Long maxSnapshotId;
+		private Long maxDocumentId;
+
+		public IndexInfo() {
+		}
+
+		public Long getMaxSnapshotID() {
+			return maxSnapshotId;
+		}
+
+		public void setMaxSnapshotID(Long maxSnapshotId) {
+			this.maxSnapshotId = maxSnapshotId;
+		}
+
+		public Long getMaxDocumentID() {
+			return maxDocumentId;
+		}
+
+		public void setMaxDocumentID(Long maxDocumentId) {
+			this.maxDocumentId = maxDocumentId;
+		}
+
+		public IndexInfo(Element element) throws Exception {
+		  maxSnapshotId = XMLUtil.getAttributeLong("maxSnapshotId", element);
+		  maxDocumentId = XMLUtil.getAttributeLong("maxDocumentId", element);
+		}
+
+		public Element toElement() throws Exception {
+		  Element element = new Element("indexinfo");
+		  XMLUtil.setAttribute("maxSnapshotId", maxSnapshotId, element);
+		  XMLUtil.setAttribute("maxDocumentId", maxDocumentId, element);
+		  return element;
+		}
+	}
+  **/
+	public boolean hasTooManyDeletedDocs(double percent) {
+	  assert percent <= 1.0;
+		DirectoryIndexSnapshot indexSnapshot = getLatestIndexSnapshot();
+		if (indexSnapshot != null) {
+			IndexReader indexReader = indexSnapshot.getIndexReader();
+			int maxDoc = indexReader.maxDoc();
+			int deletedDocs = maxDoc - indexReader.numDocs();
+			if (deletedDocs > (maxDoc * percent))
+				return true;
+		}
+		return false;
+	}
+
+	public class DiskIndexSnapshot extends DirectoryIndexSnapshot {
+		private Collection<String> indexReaderFileNames;
+
+		public DiskIndexSnapshot(Long snapshotID, IndexReader indexReader, Collection<String> indexReaderFileNames) {
+			super(snapshotID, indexReader);
+			this.indexReaderFileNames = indexReaderFileNames;
+		}
+
+		//public Long getMaxSnapshotId() throws IOException {
+		//	return indexInfo.getMaxSnapshotID();
+		//}
+
+		//public Long getMaxDocumentId() throws IOException {
+		//	return indexInfo.getMaxDocumentID();
+		//}
+    
+		protected void delete() throws Exception {
+			super.delete();
+			deleteFiles();
+		}
+		
+		public boolean hasRef() throws Exception {
+			return getSystem().getSnapshots().contains(snapshotId);
+		}
+
+		public void deleteFiles() throws IOException {
+		}
+
+		public List<String> getFiles() throws Exception {
+			List<String> files = new ArrayList<String>();
+			for (String fileName : indexReaderFileNames) {
+				files.add(fileName);
+			}
+			return files;
+		}
+	}
+  
+	protected void onCommit() throws Exception {
+	}
+
+	protected DiskIndexSnapshot createNewSnapshot(Long snapshotId, IndexReader newIndexReader) throws IOException {
+	  IndexCommit indexCommit = newIndexReader.getIndexCommit();
+		Collection<String> fileNames = indexCommit.getFileNames();
+		DiskIndexSnapshot diskIndexSnapshot = new DiskIndexSnapshot(snapshotId, newIndexReader, fileNames);
+		registerSnapshot(diskIndexSnapshot);
+		return diskIndexSnapshot;
+	}
+}
Index: ocean/src/org/apache/lucene/ocean/Snapshots.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/Snapshots.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/Snapshots.java	(revision 0)
@@ -0,0 +1,209 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.lucene.ocean.util.Util;
+import org.apache.lucene.ocean.util.XMLUtil;
+import org.jdom.Element;
+
+import com.imagero.uio.RandomAccessIO;
+
+public class Snapshots {
+  private List<Snapshot> list = new ArrayList<Snapshot>();
+  private TransactionSystem system;
+  private ReentrantLock writeLock = new ReentrantLock();
+
+  public Snapshots(TransactionSystem system) {
+    this.system = system;
+  }
+
+  public void remove(int max, long durationMillis) {
+    writeLock.lock();
+    try {
+      if (list.size() > max) {
+        long now = System.currentTimeMillis();
+        int numToCheck = list.size() - max;
+        Iterator<Snapshot> iterator = list.iterator();
+        for (int x = 0; x < numToCheck; x++) {
+          Snapshot snapshot = iterator.next();
+          if ((snapshot.getTimestamp() + durationMillis) > now) {
+            iterator.remove();
+          }
+        }
+      }
+    } finally {
+      writeLock.unlock();
+    }
+  }
+
+  /**
+   * Loads the ids from the file names rather than loading each xml file.
+   * 
+   * @param directory
+   * @return
+   * @throws Exception
+   */
+  public static List<BigDecimal> loadSnapshotInfoIds(LogDirectory directory) throws Exception {
+    List<BigDecimal> list = new ArrayList<BigDecimal>();
+    for (String file : directory.list()) {
+      if (directory.fileLength(file) > 0) {
+        String str = "snapshot_";
+        if (file.startsWith(str)) {
+          String main = file.substring(str.length(), file.lastIndexOf('.'));
+          String[] split = StringUtils.split(main, "_");
+          if (split.length > 1) {
+            String replace = main.replace('_', '.');
+            //System.out.println("replace: "+replace);
+            list.add(new BigDecimal(replace));
+          } else {
+            Long snapshotId = new Long(split[0]);
+            list.add(new BigDecimal(snapshotId));
+          }
+        }
+      }
+    }
+    Collections.sort(list);
+    return list;
+  }
+
+  public static List<SnapshotInfo> loadSnapshotInfos(LogDirectory directory) throws Exception {
+    List<SnapshotInfo> snapshotInfos = new ArrayList<SnapshotInfo>();
+    for (String file : directory.list()) {
+      if (directory.fileLength(file) > 0) {
+        String str = "snapshot_";
+        if (file.startsWith(str)) {
+          String main = file.substring(str.length(), file.lastIndexOf('.'));
+          String[] split = StringUtils.split(main, "_");
+          Long snapshotId = new Long(split[0]);
+          Integer version = new Integer(0);
+          if (split.length > 1)
+            version = new Integer(split[1]);
+          String xml = Util.getString(file, directory);
+          Element element = XMLUtil.parseElement(xml);
+          snapshotInfos.add(new SnapshotInfo(element));
+          // sorted.add(new BigDecimal(snapshotId + "." + version));
+        }
+      }
+    }
+    Collections.sort(snapshotInfos);
+    return snapshotInfos;
+  }
+
+  // TODO: load max id using loadSnapshotInfoIds, then load max snapshotinfo
+  public static SnapshotInfo loadMaxSnapshotInfo(LogDirectory directory) throws Exception {
+    List<SnapshotInfo> list = loadSnapshotInfos(directory);
+    if (list.size() == 0)
+      return null;
+    return Util.max(list);
+    // TreeSet<BigDecimal> sortedSet = new TreeSet<BigDecimal>();
+    /**
+     * List<BigDecimal> sorted = new ArrayList<BigDecimal>(); for (String file :
+     * directory.list()) { if (directory.fileLength(file) > 0) { String str =
+     * "snapshot_"; if (file.startsWith(str)) { String main =
+     * file.substring(str.length(), file.lastIndexOf('.')); String[] split =
+     * StringUtils.split(main, "_"); Long snapshotId = new Long(split[0]);
+     * Integer version = new Integer(0); if (split.length > 1) version = new
+     * Integer(split[1]); sorted.add(new BigDecimal(snapshotId + "." +
+     * version)); } } } BigDecimal maxId = null; if (sorted.size() > 0) maxId =
+     * Collections.max(sorted); if (maxId == null) return null; String fileName =
+     * Snapshot.getFileName(maxId); String xml = Util.getString(fileName,
+     * directory); Element element = XMLUtil.parseElement(xml); return new
+     * SnapshotInfo(element);
+     */
+  }
+
+  public Snapshot get(long snapshotId) {
+    List<Snapshot> snapshots = getForSnapshot(snapshotId);
+    return Util.max(snapshots);
+  }
+
+  public List<Snapshot> getForSnapshot(long snapshotId) {
+    List<Snapshot> inrange = new ArrayList<Snapshot>();
+    for (Snapshot snapshot : list) {
+      long l = snapshot.getId().toBigInteger().longValue();
+      if (l == snapshotId) {
+        inrange.add(snapshot);
+      }
+    }
+    return inrange;
+  }
+
+  public boolean contains(BigDecimal id) {
+    for (Snapshot s : list) {
+      if (s.getId().compareTo(id) == 0)
+        return true;
+    }
+    return false;
+  }
+
+  public boolean contains(Long snapshotId) {
+    return get(snapshotId) != null;
+  }
+
+  public boolean containsIndex(long indexid) {
+    for (Snapshot snapshot : list) {
+      if (snapshot.containsIndex(indexid))
+        return true;
+    }
+    return false;
+  }
+
+  private void remove(Snapshot snapshot) throws IOException {
+    Iterator<Snapshot> iterator = list.iterator();
+    while (iterator.hasNext()) {
+      Snapshot s = iterator.next();
+      if (s.getId().equals(snapshot.getId())) {
+        iterator.remove();
+        String file = Snapshot.getFileName(snapshot.getId());
+        system.directoryMap.getDirectory().deleteFile(file);
+      }
+    }
+
+  }
+
+  public Snapshot getLatestSnapshot() {
+    if (list.size() == 0)
+      return null;
+    return list.get(list.size() - 1);
+  }
+
+  void add(Snapshot snapshot, boolean createFile) throws Exception {
+    writeLock.lock();
+    try {
+      if (createFile) {
+        addCreateFile(snapshot);
+      } else {
+        list.add(snapshot);
+      }
+    } finally {
+      writeLock.unlock();
+    }
+  }
+
+  private void addCreateFile(Snapshot snapshot) throws Exception {
+    BigDecimal id = snapshot.getId();
+    SnapshotInfo snapshotInfo = snapshot.getSnapshotInfo();
+    String fileName = Snapshot.getFileName(id);
+    LogDirectory directory = system.directoryMap.getDirectory();
+    if (directory.fileExists(fileName)) {
+      throw new IOException("fileName: " + fileName + " already exists");
+    }
+    RandomAccessIO output = directory.getOutput(fileName, true);
+    snapshotInfo.writeTo(output);
+    list.add(snapshot);
+    output.flush();
+    // remove previous versions for this id
+    // SortedMap<BigDecimal,Snapshot> headMap = snapshotMap.headMap(id);
+    // for (Snapshot removeSnapshot : headMap.values()) {
+    // remove(removeSnapshot);
+    // }
+    Collections.sort(list);
+  }
+}
\ No newline at end of file
Index: ocean/src/org/apache/lucene/ocean/DeletesResult.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/DeletesResult.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/DeletesResult.java	(revision 0)
@@ -0,0 +1,51 @@
+package org.apache.lucene.ocean;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+public class DeletesResult {
+  private IndexID indexId;
+  private List<Result> results = new ArrayList<Result>();
+  private int numDeleted = 0;
+  private List<Long> docIds = new LinkedList<Long>(); 
+
+  public DeletesResult(IndexID indexId) {
+    this.indexId = indexId;
+  }
+
+  public List<Long> getDocIds() {
+    return docIds;
+  }
+
+  public IndexID getIndexId() {
+    return indexId;
+  }
+
+  public void add(Result result) {
+    numDeleted += result.getNumDeleted();
+    results.add(result);
+  }
+
+  public int getNumDeleted() {
+    return numDeleted;
+  }
+
+  public static class Result {
+    private Object delete;
+    private int numDeleted;
+
+    public Result(Object delete, int numDeleted) {
+      this.delete = delete;
+      this.numDeleted = numDeleted;
+    }
+
+    public Object getDelete() {
+      return delete;
+    }
+
+    public int getNumDeleted() {
+      return numDeleted;
+    }
+  }
+}
\ No newline at end of file
Index: ocean/src/org/apache/lucene/ocean/IndexID.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/IndexID.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/IndexID.java	(revision 0)
@@ -0,0 +1,48 @@
+package org.apache.lucene.ocean;
+
+import org.apache.commons.lang.builder.CompareToBuilder;
+
+public class IndexID implements Comparable<IndexID> {
+  public final Long id;
+  public final String type;
+  
+  public IndexID(Long id, String type) {
+    this.id = id;
+    this.type = type;
+  }
+  
+  public int compareTo(IndexID other) {
+    return new CompareToBuilder().append(id, other.id).append(type, type).toComparison();
+  }
+  
+  @Override
+  public int hashCode() {
+    final int prime = 31;
+    int result = 1;
+    result = prime * result + ((id == null) ? 0 : id.hashCode());
+    result = prime * result + ((type == null) ? 0 : type.hashCode());
+    return result;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj)
+      return true;
+    if (obj == null)
+      return false;
+    if (getClass() != obj.getClass())
+      return false;
+    final IndexID other = (IndexID) obj;
+    if (id == null) {
+      if (other.id != null)
+        return false;
+    } else if (!id.equals(other.id))
+      return false;
+    if (type == null) {
+      if (other.type != null)
+        return false;
+    } else if (!type.equals(other.type))
+      return false;
+    return true;
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/Deletes.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/Deletes.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/Deletes.java	(revision 0)
@@ -0,0 +1,141 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.lang.SerializationUtils;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.store.IndexInput;
+import org.apache.lucene.store.IndexOutput;
+
+/**
+ * Encapsulates deletes for a transaction
+ *
+ */
+public class Deletes implements Serializable {
+  public static final int DOC_IDS = 1;
+  public static final int TERMS = 2;
+  private static final long serialVersionUID = 1l;
+  private List<Term> terms = new ArrayList<Term>();
+  private List<DeleteByQuery> deleteByQueries = new ArrayList<DeleteByQuery>();
+  private long[] docIds;
+  /**
+  public Deletes(IndexInput input) throws IOException {
+    int docIdsLength = input.readVInt();
+    if (docIdsLength > 0) {
+      docIds = new long[docIdsLength];
+      for (int x=0; x < docIdsLength; x++) {
+        docIds[x] = input.readVLong();
+      }
+    }
+    int termsLength = input.readVInt();
+    for (int x=0; x < termsLength; x++) {
+      String field = input.readString();
+      String text = input.readString();
+      terms.add(new Term(field, text));
+    }
+    int dqLength = input.readVInt();
+    for (int x=0; x < dqLength; x++) {
+      int blen = input.readVInt();
+      byte[] bytes = new byte[blen];
+      input.readBytes(bytes, 0, blen);
+      deleteByQueries.add((DeleteByQuery)SerializationUtils.deserialize(bytes));
+    }
+  }
+  **/
+  public Deletes() {
+  }
+  
+  /**
+  public void writeTo(IndexOutput output) throws IOException {
+    if (docIds == null) {
+      output.writeVInt(0);
+    } else {
+      output.writeVInt(docIds.length);
+      for (int x = 0; x < docIds.length; x++) {
+        output.writeVLong(docIds[x]);
+      }
+    }
+    output.writeVInt(terms.size());
+    for (int x = 0; x < terms.size(); x++) {
+      output.writeString(terms.get(x).field());
+      output.writeString(terms.get(x).text());
+    }
+    output.writeVInt(deleteByQueries.size());
+    for (int x = 0; x < deleteByQueries.size(); x++) {
+      byte[] bytes = SerializationUtils.serialize(deleteByQueries.get(x));
+      output.writeVInt(bytes.length);
+      output.writeBytes(bytes, bytes.length);
+    }
+  }
+  **/
+  public void merge(Deletes deletes) {
+    terms.addAll(deletes.getTerms());
+    deleteByQueries.addAll(deletes.getDeleteByQueries());
+  }
+
+  public boolean hasDocIds() {
+    return docIds != null;
+  }
+
+  void setDocIds(long[] docIds) {
+    assert docIds == null;
+    this.docIds = docIds;
+  }
+
+  public void addTerm(Term term) {
+    terms.add(term);
+  }
+  
+  public void addQuery(Query query) {
+    deleteByQueries.add(new DeleteByQuery(query));
+  }
+  
+  public void addDeleteByQuery(DeleteByQuery deleteByQuery) {
+    deleteByQueries.add(deleteByQuery);
+  }
+
+  public long[] getDocIds() {
+    return docIds;
+  }
+
+  public boolean hasDeletes() {
+    // TODO: implement
+    return true;
+  }
+
+  public boolean hasDeleteByQueries() {
+    if (deleteByQueries != null && deleteByQueries.size() > 0)
+      return true;
+    return false;
+  }
+
+  public List<DeleteByQuery> getDeleteByQueries() {
+    return deleteByQueries;
+  }
+
+  public static class DeleteByQuery implements Serializable {
+    private Query query;
+
+    public DeleteByQuery(Query query) {
+      this.query = query;
+    }
+
+    public Query getQuery() {
+      return query;
+    }
+  }
+
+  public List<Term> getTerms() {
+    return terms;
+  }
+
+  public boolean hasTerms() {
+    if (terms != null && terms.size() > 0)
+      return true;
+    return false;
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/Transaction.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/Transaction.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/Transaction.java	(revision 0)
@@ -0,0 +1,272 @@
+package org.apache.lucene.ocean;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.commons.lang.exception.ExceptionUtils;
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.ocean.Batch.MasterBatch;
+import org.apache.lucene.ocean.Index.IndexSnapshot;
+import org.apache.lucene.ocean.log.TransactionLog;
+import org.apache.lucene.store.RAMDirectory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Coordinates a multithreaded transaction commit between multiple indexes.  
+ * Utilizes java.util.concurrent.CountDownLatch for synchronization between the indexes as each 
+ * index operation is performed in it's own thread.
+ *
+ */
+// TODO: add timeout to transaction
+public class Transaction {
+  final static Logger LOG = LoggerFactory.getLogger(Transaction.class);
+  private Batch batch;
+  private CountDownLatch latch;
+  private List<Failure> failures = new ArrayList<Failure>();
+  private List<DeletesResult> deletesResults = new ArrayList<DeletesResult>();
+  private List<IndexSnapshot> newIndexSnapshots = new ArrayList<IndexSnapshot>();
+  private ReentrantLock lock = new ReentrantLock();
+  private CountDownLatch goLatch = new CountDownLatch(1);
+  private Long id;
+  private Long previousId;
+  private TransactionLog transactionLog;
+  private CommitResult commitResult;
+  private TransactionSystem system;
+
+  public Transaction(Long id, Long previousId, Batch batch, WriteableMemoryIndex writeableIndex, List<Index> nonWriteableIndices,
+      ExecutorService commitThreadPool, TransactionSystem system) throws Exception {
+    this.id = id;
+    this.previousId = previousId;
+    this.batch = batch;
+    this.transactionLog = system.getTransactionLog();
+    List<Callable> tasks = new ArrayList<Callable>();
+    Deletes deletes = batch.getDeletes();
+    if (batch.hasDeletes()) {
+      for (Index index : nonWriteableIndices) {
+        tasks.add(new DeletesTask(deletes, index, this));
+      }
+    } else {
+      for (Index index : nonWriteableIndices) {
+        tasks.add(new NothingTask(index, this));
+      }
+    }
+    int numDocsAdded = 0;
+    // handle changes to writeable index, or if a ram directory create a ram index
+    if (batch.hasRAMDirectory()) {
+      tasks.add(new AddRamIndexDocumentsTask(batch.getRamDirectory()));
+    } else if (batch.hasDocuments()) {
+      Documents documents = batch.getDocuments();
+      Analyzer analyzer = batch.getAnalyzer();
+      tasks.add(new AddWriteableMemoryDocumentsTask(documents, analyzer, deletes, writeableIndex));
+      numDocsAdded += documents.size();
+    } else {
+      tasks.add(new DeletesTask(deletes, writeableIndex, this));
+    }
+    latch = new CountDownLatch(tasks.size());
+    List<Future> futures = new ArrayList<Future>(tasks.size());
+    for (Callable callable : tasks) {
+      futures.add(commitThreadPool.submit(callable));
+    }
+    latch.await();
+    goLatch.countDown();
+    // need rollback here for failures during commit
+    for (Future future : futures) {
+      try {
+        future.get();
+      } catch (ExecutionException executionException) {
+        Throwable cause = executionException.getCause();
+        LOG.info(cause.getMessage());
+      }
+    }
+    if (failures.size() == 0) {
+      commitResult = new CommitResult(id, deletesResults, numDocsAdded, writeableIndex.getId());
+    } else {
+      // rollback indexes
+      LOG.info("rolling back snapshot: "+id);
+      writeableIndex.rollback(id);
+      for (Index index : nonWriteableIndices) {
+        index.rollback(id);
+      }
+      throw new Exception("transaction failed " + failures);
+    }
+  }
+  // TODO: no snapshots added
+  public List<IndexSnapshot> getNewIndexSnapshots() {
+    return newIndexSnapshots;
+  }
+
+  public Long getPreviousId() {
+    return previousId;
+  }
+
+  public CommitResult getCommitResult() {
+    assert commitResult != null; // should have thrown exception before this
+    // point
+    return commitResult;
+  }
+
+  public Long getId() {
+    return id;
+  }
+
+  public abstract static class Failure extends Exception {
+    private String string;
+    
+    public Failure(Throwable throwable) {
+      super(throwable);
+      string = ExceptionUtils.getFullStackTrace(throwable);
+    }
+    
+    public String toString() {
+      return string;
+    }
+  }
+
+  public static class LogFailure extends Failure {
+    public LogFailure(Throwable throwable) {
+      super(throwable);
+    }
+  }
+
+  public static class IndexFailure extends Failure {
+    Index index;
+
+    public IndexFailure(Index index, Throwable throwable) {
+      super(throwable);
+      this.index = index;
+    }
+  }
+  
+  public static class NothingTask implements Callable {
+    private Index index;
+    private Transaction transaction;
+    
+    public NothingTask(Index index, Transaction transaction) {
+      this.index = index;
+      this.transaction = transaction;
+    }
+    
+    public Object call() throws Exception {
+      index.commitNothing(transaction);
+      return null;
+    }
+  }
+  
+  public static class DeletesTask implements Callable<DeletesResult> {
+    private Index index;
+    private Deletes deletes;
+    private Transaction transaction;
+
+    public DeletesTask(Deletes deletes, Index index, Transaction transaction) {
+      this.deletes = deletes;
+      this.index = index;
+      this.transaction = transaction;
+    }
+
+    public DeletesResult call() throws Exception {
+      DeletesResult deletesResult = index.commitDeletes(deletes, transaction);
+      transaction.addDeletesResult(deletesResult);
+      return deletesResult;
+    }
+  }
+
+  public class AddRamIndexDocumentsTask implements Callable<DeletesResult> {
+    private RAMDirectory ramDirectory;
+
+    public AddRamIndexDocumentsTask(RAMDirectory ramDirectory) {
+      this.ramDirectory = ramDirectory;
+    }
+
+    public DeletesResult call() throws Exception {
+      // TODO: create new ramindex
+      long indexIdNum = system.getNextRamIndexId();
+      IndexID indexId = new IndexID(indexIdNum, "ram");
+      Analyzer analyzer = batch.getAnalyzer();
+      RamIndex ramIndex = new RamIndex(indexId, id, null, ramDirectory, system);
+      IndexSnapshot indexSnapshot = ramIndex.commitIndex(Transaction.this);
+      DeletesResult deletesResult = new DeletesResult(indexId);
+      addDeletesResult(deletesResult);
+      return deletesResult;
+    }
+  }
+
+  public class AddWriteableMemoryDocumentsTask implements Callable<DeletesResult> {
+    private Documents documents;
+    private Analyzer analyzer;
+    private Deletes deletes;
+    private WriteableMemoryIndex writeableIndex;
+
+    public AddWriteableMemoryDocumentsTask(Documents documents, Analyzer analyzer, Deletes deletes, WriteableMemoryIndex writeableIndex) {
+      this.documents = documents;
+      this.analyzer = analyzer;
+      this.deletes = deletes;
+      this.writeableIndex = writeableIndex;
+    }
+
+    public DeletesResult call() throws Exception {
+      DeletesResult deletesResult = writeableIndex.commitChanges(documents, deletes, analyzer, Transaction.this);
+      addDeletesResult(deletesResult);
+      return deletesResult;
+    }
+  }
+
+  void addDeletesResult(DeletesResult deletesResult) {
+    assert deletesResult != null;
+    deletesResults.add(deletesResult);
+  }
+
+  void failed(Index index, Throwable throwable) {
+    failures.add(new IndexFailure(index, throwable));
+    latch.countDown();
+  }
+
+  void ready(Index index) {
+    latch.countDown();
+  }
+
+  public boolean go() throws InterruptedException {
+    lock.lock();
+    try {
+      goLatch.await();
+      if (failures.size() == 0) {
+        try {
+          if (batch instanceof MasterBatch) {
+            MasterBatch masterBatch = (MasterBatch) batch;
+            if (masterBatch.hasDeletes()) {
+              int numDocIds = 0;
+              for (DeletesResult deletesResult : deletesResults) {
+                numDocIds += deletesResult.getDocIds().size();
+              }
+              long[] docIds = new long[numDocIds];
+              int x = 0;
+              for (DeletesResult deletesResult : deletesResults) {
+                for (Long docId : deletesResult.getDocIds()) {
+                  docIds[x] = docId;
+                  x++;
+                }
+              }
+              masterBatch.getDeletes().setDocIds(docIds);
+            }
+            transactionLog.writeMasterBatch(id, previousId, masterBatch);
+          }
+        } catch (Throwable throwable) {
+          LOG.error("", throwable);
+          failures.add(new LogFailure(throwable));
+          return false;
+        }
+        return true;
+      } else {
+        return false;
+      }
+    } finally {
+      lock.unlock();
+    }
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/CommitResult.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/CommitResult.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/CommitResult.java	(revision 0)
@@ -0,0 +1,45 @@
+package org.apache.lucene.ocean;
+
+import java.util.List;
+
+public class CommitResult {
+  private Long snapshotId;
+  private List<DeletesResult> deletesResults;
+  private Integer numAdded;
+  private IndexID addedIndexId;
+
+  public CommitResult(Long snapshotId, List<DeletesResult> deletesResults, Integer numAdded, IndexID addedIndexId) {
+    this.snapshotId = snapshotId;
+    this.deletesResults = deletesResults;
+    this.numAdded = numAdded;
+    this.addedIndexId = addedIndexId;
+  }
+
+  public int getNumDocChanges() {
+    int numChanged = 0;
+    if (numAdded != null)
+      numChanged += numAdded;
+    if (deletesResults != null) {
+      for (DeletesResult deletesResult : deletesResults) {
+        numChanged += deletesResult.getNumDeleted();
+      }
+    }
+    return numChanged;
+  }
+
+  public Long getSnapshotId() {
+    return snapshotId;
+  }
+
+  public List<DeletesResult> getDeletesResults() {
+    return deletesResults;
+  }
+
+  public Integer getNumAdded() {
+    return numAdded;
+  }
+
+  public IndexID getAddedIndexId() {
+    return addedIndexId;
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/IndexCreator.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/IndexCreator.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/IndexCreator.java	(revision 0)
@@ -0,0 +1,142 @@
+package org.apache.lucene.ocean;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.RAMDirectory;
+
+/**
+ * Allows creation of an index using multiple threads by feeding documents into
+ * a BlockingQueue.
+ * 
+ */
+// TODO: after create called make object unusable
+public class IndexCreator {
+  private Directory directory;
+  private long maxSize;
+  private int threads;
+  private ExecutorService threadPool;
+  private IndexWriter indexWriter;
+  private boolean isFinished;
+  private List<Future<Object>> futures = new ArrayList<Future<Object>>();
+  private Analyzer analyzer;
+  private BlockingQueue<Add> queue;
+
+  public IndexCreator(Directory directory, long maxSize, int threads, Analyzer analyzer, ExecutorService threadPool) {
+    this.directory = directory;
+    this.maxSize = maxSize;
+    this.threads = threads;
+    this.analyzer = analyzer;
+    this.threadPool = threadPool;
+    isFinished = false;
+  }
+
+  public static class Add {
+    private Document document;
+
+    // private RAMDirectory ramDirectory;
+
+    // public Add(RAMDirectory ramDirectory) {
+    // this.ramDirectory = ramDirectory;
+    // }
+
+    public Add(Document document) {
+      this.document = document;
+    }
+
+    // public RAMDirectory getRamDirectory() {
+    // return ramDirectory;
+    // }
+
+    public Document getDocument() {
+      return document;
+    }
+  }
+
+  public void start(BlockingQueue<Add> queue) throws Exception {
+    this.queue = queue;
+    indexWriter = new IndexWriter(directory, false, analyzer, true);
+    indexWriter.setUseCompoundFile(true);
+    indexWriter.setRAMBufferSizeMB(500.0); // set impossibly high to never be
+                                            // triggered, setting both to
+                                            // DISABLE_AUTO_FLUSH causes an
+                                            // exception
+    indexWriter.setMaxBufferedDocs(IndexWriter.DISABLE_AUTO_FLUSH);
+    //List<Callable<Object>> callables = new ArrayList<Callable<Object>>(threads);
+    for (int x = 0; x < threads; x++) {
+      //callables.add(new Task(queue));
+      futures.add(threadPool.submit(new Task(queue)));
+    }
+    //futures = threadPool.invokeAll(callables);
+    
+  }
+
+  public void create() throws Exception {
+    while (queue.peek() != null) { 
+      Thread.sleep(5);
+    }
+    setFinished(true);
+    try {
+      for (Future<Object> future : futures) {
+        if (future.isDone()) {
+          try {
+            future.get();
+          } catch (ExecutionException executionException) {
+            Throwable cause = executionException.getCause();
+            if (cause instanceof Exception) {
+              throw (Exception) cause;
+            } else {
+              throw new Exception(cause);
+            }
+          }
+        }
+        Thread.sleep(10);
+      }
+      indexWriter.optimize(); // should not be necessary
+    } finally {
+      indexWriter.close();
+    }
+  }
+
+  public void setFinished(boolean isFinished) {
+    this.isFinished = isFinished;
+  }
+
+  private boolean isFinished() {
+    if (isFinished)
+      return true;
+    if (indexWriter.ramSizeInBytes() >= maxSize) {
+      isFinished = true;
+    }
+    return isFinished;
+  }
+
+  public class Task implements Callable {
+    private BlockingQueue<Add> queue;
+
+    public Task(BlockingQueue<Add> queue) {
+      this.queue = queue;
+    }
+
+    public Object call() throws Exception {
+      while (!isFinished()) {
+        Add add = queue.poll(3, TimeUnit.MILLISECONDS);
+        if (add != null) {
+          Document document = add.getDocument();
+          indexWriter.addDocument(document, analyzer);
+        }
+      }
+      return null;
+    }
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/TransactionSystem.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/TransactionSystem.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/TransactionSystem.java	(revision 0)
@@ -0,0 +1,662 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.ocean.Batch.MasterBatch;
+import org.apache.lucene.ocean.Batch.SlaveBatch;
+import org.apache.lucene.ocean.DiskIndex.DiskIndexSnapshot;
+import org.apache.lucene.ocean.Index.IndexException;
+import org.apache.lucene.ocean.Index.IndexSnapshot;
+import org.apache.lucene.ocean.RamIndex.RamIndexSnapshot;
+import org.apache.lucene.ocean.SnapshotInfo.IndexInfo;
+import org.apache.lucene.ocean.WriteableMemoryIndex.MemoryIndexSnapshot;
+import org.apache.lucene.ocean.log.TransactionLog;
+import org.apache.lucene.ocean.log.TransactionLog.SlaveBatchIterator;
+import org.apache.lucene.ocean.util.Constants;
+import org.apache.lucene.ocean.util.LongSequence;
+import org.apache.lucene.ocean.util.Util;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.RAMDirectory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Main class for search transaction system.
+ * 
+ * Indexes on disk are immutable, they can only be deleted from or merged
+ * periodically. Merges occur in the background. There is always one active
+ * WriteableMemoryIndex that new documents are written to.
+ * 
+ * A snapshot corresponds to a transaction. Each transaction creates a new
+ * snapshot. Snapshot ids have both major and minor version represented as a
+ * decimal. The major represents the transaction. The minor increments with
+ * index merges. Transaction data is known as a batch. There is a MasterBatch
+ * and SlaveBatch. A MasterBatch is what is created the initial call to
+ * TransactionSystem such as addDocument. A SlaveBatch is what is loaded from
+ * the transactionlog during a recovery.
+ * 
+ * IndexWriter like methods such as addDocument, updateDocument are provided.
+ * Also commitTransaction is provided complete transaction access.
+ * 
+ * A _documentid field is added to each document. This is an internal number for
+ * tracking a document and allows the transaction log system to be recovered
+ * properly. During recovery a delete will use the _documentid rather than the
+ * actual query or term to insure the exact documents are deleted at the point
+ * in time the transaction occurred.
+ * 
+ * 
+ */
+// TODO: need test case of maybeMergeDiskIndices
+// TODO: create recovery system if disk index creation fails
+// TODO: add .index suffix to index directory names
+// TODO: custom efficient document serializer
+// TODO: add writeVLong writeVInt to LogDirectory output
+// TODO: not sure how to handle Document fields with a TokenStream
+public class TransactionSystem {
+  final static Logger LOG = LoggerFactory.getLogger(TransactionSystem.class);
+  public static final int DEFAULT_MEMORY_INDEX_MAX_DOCS = 50;
+  public static final int DEFAULT_MAYBE_MERGE_DOC_CHANGES = 2000;
+  public static final int DEFAULT_MAX_RAM_INDEXES_SIZE = 1024 * 1024 * 30;
+  private ExecutorService commitThreadPool;
+  private ExecutorService mergeThreadPool;
+  private TransactionLog transactionLog;
+  private Indexes indexes = new Indexes();
+  private ReentrantLock commitLock = new ReentrantLock();
+  Snapshots snapshots;
+  private ReentrantLock mergeIndexesLock = new ReentrantLock();
+  private int docChangesSinceLastMerge = 0;
+  private Analyzer defaultAnalyzer;
+  private int serverNumber = 0;
+  private LongSequence documentSequence;
+  private LongSequence diskIndexSequence;
+  private LongSequence ramIndexSequence;
+  private int memoryIndexMaxDocs = DEFAULT_MEMORY_INDEX_MAX_DOCS;
+  private int maybeMergeDocChanges = DEFAULT_MAYBE_MERGE_DOC_CHANGES;
+  private int maxRamIndexesSize = DEFAULT_MAX_RAM_INDEXES_SIZE;
+  private int maxDocsIndexes = -1;
+  private int maxSnapshots = 5;
+  private long snapshotExpiration = 20 * 1000;
+  DirectoryMap directoryMap;
+  private ArrayBlockingQueue<Runnable> mergeQueue;
+
+  public TransactionSystem(TransactionLog transactionLog, Analyzer defaultAnalyzer, DirectoryMap directoryMap) throws Exception {
+    this(transactionLog, defaultAnalyzer, directoryMap, DEFAULT_MAYBE_MERGE_DOC_CHANGES, -1, DEFAULT_MEMORY_INDEX_MAX_DOCS);
+  }
+
+  public TransactionSystem(TransactionLog transactionLog, Analyzer defaultAnalyzer, DirectoryMap directoryMap, int maybeMergeDocChanges,
+      int maxDocsIndexes, int memoryIndexMaxDocs) throws Exception {
+    this.transactionLog = transactionLog;
+    this.defaultAnalyzer = defaultAnalyzer;
+    this.directoryMap = directoryMap;
+    this.maybeMergeDocChanges = maybeMergeDocChanges;
+    this.maxDocsIndexes = maxDocsIndexes;
+    this.memoryIndexMaxDocs = memoryIndexMaxDocs;
+    mergeQueue = new ArrayBlockingQueue<Runnable>(2);
+    mergeThreadPool = new ThreadPoolExecutor(1, 1, 1000 * 2, TimeUnit.MILLISECONDS, mergeQueue);
+    commitThreadPool = Executors.newFixedThreadPool(5);
+    snapshots = new Snapshots(this);
+    if (LOG.isInfoEnabled())
+      LOG.info("TransactionSystem");
+    load();
+  }
+
+  public void setMaybeMergeDocChanges(int maybeMergeDocChanges) {
+    this.maybeMergeDocChanges = maybeMergeDocChanges;
+  }
+
+  public void setMaxDocsIndexes(int maxDocsIndexes) {
+    this.maxDocsIndexes = maxDocsIndexes;
+  }
+
+  public void close() throws IOException {
+    if (LOG.isInfoEnabled())
+      LOG.info("close");
+    mergeThreadPool.shutdown();
+    commitThreadPool.shutdown();
+    transactionLog.close();
+    for (Index index : indexes.getIndexes()) {
+      index.close();
+    }
+
+  }
+
+  public OceanSearcher getSearcher() throws IOException {
+    Snapshot snapshot = snapshots.getLatestSnapshot();
+    return new OceanSearcher(snapshot);
+  }
+
+  public CommitResult deleteDocument(Term term) throws Exception {
+    List<Term> dterms = new ArrayList<Term>(1);
+    dterms.add(term);
+    return commitTransaction(null, null, dterms, null);
+  }
+
+  public CommitResult updateDocument(Term term, Document document) throws Exception {
+    return updateDocument(term, document, defaultAnalyzer);
+  }
+
+  public CommitResult updateDocument(Term term, Document document, Analyzer analyzer) throws Exception {
+    List<Document> list = new ArrayList<Document>(1);
+    list.add(document);
+    List<Term> dterms = new ArrayList<Term>(1);
+    dterms.add(term);
+    return commitTransaction(list, analyzer, dterms, null);
+  }
+
+  public CommitResult addDocument(Document document) throws Exception {
+    return addDocument(document, defaultAnalyzer);
+  }
+
+  public CommitResult addDocument(Document document, Analyzer analyzer) throws Exception {
+    List<Document> list = new ArrayList<Document>(1);
+    list.add(document);
+    return commitTransaction(list, analyzer, null, null);
+  }
+
+  public CommitResult commitTransaction(List<Document> documents, Analyzer analyzer, List<Term> deleteByTerms, List<Query> deleteByQueries)
+      throws Exception {
+    MasterBatch masterBatch = new MasterBatch(this);
+    if (documents != null) masterBatch.addDocuments(new Documents(documents));
+    masterBatch.setAnalyzer(analyzer);
+    Deletes deletes = new Deletes();
+    if (deleteByTerms != null) {
+      for (Term deleteTerm : deleteByTerms) {
+        deletes.addTerm(deleteTerm);
+      }
+    }
+    if (deleteByQueries != null) {
+      for (Query query : deleteByQueries) {
+        deletes.addQuery(query);
+      }
+    }
+    masterBatch.setDeletes(deletes);
+    return commitBatch(masterBatch);
+  }
+
+  public Analyzer getDefaultAnalyzer() {
+    return defaultAnalyzer;
+  }
+
+  public long getNextRamIndexId() {
+    return ramIndexSequence.getAndIncrement();
+  }
+
+  public long getNextDiskIndexId() {
+    return diskIndexSequence.getAndIncrement();
+  }
+
+  public TransactionLog getTransactionLog() {
+    return transactionLog;
+  }
+
+  public ExecutorService getCommitThreadPool() {
+    return commitThreadPool;
+  }
+
+  public void load() throws Exception {
+    BigDecimal id;
+    Long snapshotId;
+    List<IndexSnapshot> indexSnapshots = null;
+    SnapshotInfo snapshotInfo = Snapshots.loadMaxSnapshotInfo(directoryMap.getDirectory());
+    if (LOG.isDebugEnabled())
+      LOG.debug("snapshotInfo: " + snapshotInfo);
+    long timestamp = System.currentTimeMillis();
+    if (snapshotInfo != null) {
+      id = snapshotInfo.getId();
+      snapshotId = snapshotInfo.getSnapshotId();
+      assert snapshotId == transactionLog.getMaxId();
+      loadDiskIndexes(snapshotInfo, indexes);
+      IndexID diskMaxId = indexes.getMaxId("disk");
+      if (diskMaxId != null)
+        diskIndexSequence = new LongSequence(diskMaxId.id.longValue() + 1, 1);
+      else
+        diskIndexSequence = new LongSequence(1, 1);
+      ramIndexSequence = new LongSequence(1, 1);
+      indexSnapshots = new ArrayList<IndexSnapshot>();
+      List<Long> snapshotIds = new LinkedList<Long>();
+      // TODO: what if index directory is deleted and it is still referenced
+      for (IndexInfo indexInfo : snapshotInfo.getIndexInfos()) {
+        if (indexInfo.getType().equals("disk")) {
+          DiskIndex diskIndex = (DiskIndex) indexes.get(indexInfo.getIndexID());
+          if (diskIndex != null) {
+            IndexSnapshot indexSnapshot = diskIndex.getIndexSnapshot(snapshotInfo.getSnapshotId());
+            indexSnapshots.add(indexSnapshot);
+            snapshotIds.add(indexSnapshot.getMaxSnapshotId());
+          }
+        }
+      }
+      Long maxDiskIndexSnapshotId = Util.max(snapshotIds);
+      System.out.println("maxDiskIndexSnapshotId: " + maxDiskIndexSnapshotId);
+      if (maxDiskIndexSnapshotId != null) {
+        maxDiskIndexSnapshotId = new Long(maxDiskIndexSnapshotId.longValue() + 1);
+      }
+      List<RamIndexSnapshot> ramIndexSnapshots = runTransactionsNotInIndex(maxDiskIndexSnapshotId);
+      System.out.println("ramIndexSnapshots: " + ramIndexSnapshots);
+      // TODO: verify all snapshots have same id
+      indexSnapshots.addAll(ramIndexSnapshots);
+      List<Long> documentIds = new ArrayList<Long>(indexSnapshots.size());
+      for (IndexSnapshot indexSnapshot : indexSnapshots) {
+        documentIds.add(indexSnapshot.getMaxDocumentId());
+      }
+      Long maxDocumentId = Util.max(documentIds);
+      if (maxDocumentId != null) {
+        Long documentSequenceId = Util.getNextServerSequence(maxDocumentId, serverNumber);
+        documentSequence = new LongSequence(documentSequenceId, 100);
+      } else {
+        documentSequence = new LongSequence(serverNumber, 100);
+      }
+    } else {
+      snapshotId = new Long(0);
+      id = new BigDecimal(snapshotId.toString());
+      documentSequence = new LongSequence(serverNumber, 100);
+      diskIndexSequence = new LongSequence(1, 1);
+      ramIndexSequence = new LongSequence(1, 1);
+    }
+    WriteableMemoryIndex writeableMemoryIndex = newWriteableMemoryIndex();
+    MemoryIndexSnapshot writeableSnapshot = writeableMemoryIndex.setSnapshot(snapshotId);
+    if (indexSnapshots == null) {
+      indexSnapshots = new ArrayList<IndexSnapshot>();
+      indexSnapshots.add(writeableSnapshot);
+    }
+    Snapshot snapshot = new Snapshot(id, writeableSnapshot, indexSnapshots, this, timestamp);
+    snapshots.add(snapshot, false);
+    deleteUnreferencedSnapshots();
+    new MaybeMergeIndices().run();
+  }
+
+  /**
+   * Delete snapshotinfo if no longer referenced in Snapshots
+   * 
+   * @throws Exception
+   */
+  private void deleteUnreferencedSnapshots() throws Exception {
+    snapshots.remove(maxSnapshots, snapshotExpiration);
+    LogDirectory directory = directoryMap.getDirectory();
+    List<BigDecimal> ids = Snapshots.loadSnapshotInfoIds(directory);
+    for (BigDecimal id : ids) {
+      if (!snapshots.contains(id)) {
+        // not referenced, delete it
+        String fileName = Snapshot.getFileName(id);
+        System.out.println("deleteFile: " + fileName + " id: " + Snapshot.formatId(id));
+        try {
+          directory.deleteFile(fileName);
+          if (LOG.isDebugEnabled())
+            LOG.debug("deleteFile: " + fileName);
+        } catch (Exception exception) {
+          LOG.error(exception.getMessage());
+        }
+      }
+    }
+  }
+
+  public Indexes getIndices() {
+    return indexes;
+  }
+
+  public Snapshots getSnapshots() {
+    return snapshots;
+  }
+
+  /**
+   * Runs the transactions from the transaction log that are not already in
+   * Lucene indices
+   * 
+   * @param startSnapshotId
+   * @return loaded ram snapshots
+   * @throws Exception
+   * @throws CategoryException
+   * @throws IOException
+   */
+  private List<RamIndexSnapshot> runTransactionsNotInIndex(Long startSnapshotId) throws Exception, IOException {
+    LOG.info("startSnapshotId: " + startSnapshotId);
+    SlaveBatchIterator iterator = transactionLog.getSlaveBatchIterator(startSnapshotId);
+    if (!iterator.hasNext())
+      return new ArrayList<RamIndexSnapshot>();
+    try {
+      long indexIdNum = ramIndexSequence.getAndIncrement();
+      IndexID indexId = new IndexID(indexIdNum, "ram");
+      RAMDirectory ramDirectory = new RAMDirectory();
+      ExecutorService threadPool = getCommitThreadPool();
+      IndexCreator indexCreator = new IndexCreator(ramDirectory, Long.MAX_VALUE, 4, defaultAnalyzer, threadPool);
+      BlockingQueue<IndexCreator.Add> addQueue = new ArrayBlockingQueue<IndexCreator.Add>(4000, true);
+      List<Deletes> deletesList = new ArrayList<Deletes>(); // deletes are
+      // recorded and run
+      // against all of the
+      // snapshots at the
+      // end
+      indexCreator.start(addQueue);
+      List<RAMDirectory> ramDirectories = new ArrayList<RAMDirectory>();
+      int docCount = 0;
+      while (iterator.hasNext()) {
+        SlaveBatch slaveBatch = iterator.next(true, true);
+        Analyzer analyzer = slaveBatch.getAnalyzer();
+        if (slaveBatch.hasDocuments()) {
+          Documents documents = slaveBatch.getDocuments();
+          for (Document document : documents) {
+            addQueue.add(new IndexCreator.Add(document));
+            docCount++;
+          }
+        } else if (slaveBatch.hasRAMDirectory()) {
+          ramDirectories.add(slaveBatch.getRamDirectory());
+        }
+        if (slaveBatch.hasDeletes()) {
+          deletesList.add(slaveBatch.getDeletes());
+        }
+      }
+      LOG.info("docCount: " + docCount);
+      indexCreator.create();
+      ramDirectories.add(ramDirectory);
+      Long snapshotId = transactionLog.getMaxId();
+      List<RamIndexSnapshot> indexSnapshots = new ArrayList<RamIndexSnapshot>(ramDirectories.size());
+      for (RAMDirectory rd : ramDirectories) {
+        RamIndex ramIndex = new RamIndex(indexId, snapshotId, deletesList, rd, this);
+        indexes.add(ramIndex);
+        RamIndexSnapshot indexSnapshot = (RamIndexSnapshot) ramIndex.getIndexSnapshot(snapshotId);
+        assert indexSnapshot != null;
+        indexSnapshots.add(indexSnapshot);
+      }
+      // TODO: run maybe merge here
+      return indexSnapshots;
+    } finally {
+      if (iterator != null)
+        iterator.close();
+    }
+  }
+
+  private void loadDiskIndexes(SnapshotInfo snapshotInfo, Indexes indices) throws Exception, IOException {
+    for (String name : directoryMap.list()) {
+      try {
+        Directory directory = directoryMap.get(name);
+        Long indexIdNum = new Long(name);
+        IndexID indexId = new IndexID(indexIdNum, "disk");
+        try {
+          IndexInfo indexInfo = snapshotInfo.getIndexInfo(indexId);
+          if (indexInfo != null) {
+            Long snapshotId = snapshotInfo.getSnapshotId();
+            Long segmentGeneration = indexInfo.getSegmentGeneration();
+            DiskIndex diskIndex = new DiskIndex(indexId, directory, snapshotId, segmentGeneration, this);
+            indices.add(diskIndex);
+          } else {
+            LOG.info("index no longer referenced deleting: " + name);
+            // directoryMap.delete(name);
+          }
+        } catch (IndexException indexException) {
+          LOG.error("index not ready, deleting: " + name, indexException);
+          // directoryMap.delete(name);
+        } catch (IOException ioException) {
+          LOG.error("index not ready, deleting: " + name, ioException);
+          // directoryMap.delete(name);
+        }
+      } catch (Exception exception) {
+        LOG.error("", exception);
+        // if exception simply skip over the index
+      }
+    }
+  }
+
+  public MasterBatch createMasterBatch() throws Exception {
+    return new MasterBatch(this);
+  }
+
+  public class MaybeMergeIndices implements Runnable {
+    public MaybeMergeIndices() {
+    }
+
+    public void run() {
+      if (LOG.isDebugEnabled())
+        LOG.debug("MaybeMergeIndices");
+      mergeIndexesLock.lock();
+      try {
+        Snapshot snapshot = snapshots.getLatestSnapshot();
+        maybeMergeWriteable(snapshot);
+        snapshot = snapshots.getLatestSnapshot();
+        maybeMergeRamIndexes(snapshot);
+        snapshot = snapshots.getLatestSnapshot();
+        maybeMergeDiskIndexes(snapshot);
+      } catch (Throwable throwable) {
+        LOG.error("", throwable);
+      } finally {
+        mergeIndexesLock.unlock();
+      }
+    }
+
+    /**
+     * If the existing ram indexes are above maxRamIndexesSize, then they are
+     * merged and a new disk index is created from them. Or if the number of
+     * documents exceeds maxDocsIndexesSize.
+     * 
+     * @param snapshot
+     * @throws Exception
+     */
+    private void maybeMergeRamIndexes(Snapshot snapshot) throws Exception {
+      long size = 0;
+      int numDocs = 0;
+      List<RamIndexSnapshot> ramIndexSnapshots = snapshot.getRamIndexSnapshots();
+      for (RamIndexSnapshot ramIndexSnapshot : ramIndexSnapshots) {
+        RamIndex ramIndex = (RamIndex) ramIndexSnapshot.getIndex();
+        size += ramIndex.getSize();
+        numDocs += ramIndexSnapshot.getIndexReader().maxDoc();
+      }
+      // if merging based on number of docs
+      if (maxDocsIndexes > 0 && numDocs > maxDocsIndexes) {
+        if (LOG.isDebugEnabled())
+          LOG.debug("executeMerge because numDocs: " + numDocs + " more than maxDocsIndexes: " + maxDocsIndexes);
+        executeMerge(ramIndexSnapshots, snapshot);
+      } else if (size > maxRamIndexesSize) {
+        // merging based on size of ram indexes
+        executeMerge(ramIndexSnapshots, snapshot);
+      }
+    }
+
+    private void maybeMergeDiskIndexes(Snapshot snapshot) throws Exception {
+      Long snapshotId = snapshot.getSnapshotId();
+      List<IndexSnapshot> indexSnapshotsToMerge = new ArrayList<IndexSnapshot>();
+      for (DiskIndex diskIndex : snapshot.getDiskIndices()) {
+        DiskIndexSnapshot indexSnapshot = (DiskIndexSnapshot) diskIndex.getIndexSnapshot(snapshotId);
+        // TODO: create config attribute for percentage of deleted docs
+        if (diskIndex.hasTooManyDeletedDocs(0.3)) {
+          indexSnapshotsToMerge.add(indexSnapshot);
+        }
+      }
+      if (indexSnapshotsToMerge.size() > 0) {
+        executeMerge(indexSnapshotsToMerge, snapshot);
+      }
+    }
+
+    /**
+     * converts current memorywriteableindex to a ramindex
+     * 
+     * @param snapshot
+     * @throws Exception
+     */
+    private void maybeMergeWriteable(Snapshot snapshot) throws Exception {
+      MemoryIndexSnapshot writeableIndexSnapshot = snapshot.getWriteableSnapshot();
+      int maxDoc = writeableIndexSnapshot.getIndexReader().maxDoc();
+      if (maxDoc >= memoryIndexMaxDocs) {
+        if (LOG.isInfoEnabled())
+          LOG.info("merge writeable");
+        commitLock.lock();
+        try {
+          long indexIdNum = ramIndexSequence.getAndIncrement();
+          IndexID indexId = new IndexID(indexIdNum, "memory");
+          RamIndex ramIndex = new RamIndex(indexId, writeableIndexSnapshot);
+          indexes.add(ramIndex);
+          Snapshot currentSnapshot = snapshots.getLatestSnapshot();
+          List<IndexID> removeIndexIds = new ArrayList<IndexID>();
+          removeIndexIds.add(writeableIndexSnapshot.getIndex().getId());
+          
+          // create new WriteableMemoryIndex for the new snapshot because the one that was there
+          // has been converted to a RamIndex
+          WriteableMemoryIndex newWriteableMemoryIndex = newWriteableMemoryIndex();
+          MemoryIndexSnapshot newMemoryIndexSnapshot = newWriteableMemoryIndex.setSnapshot(snapshot.getSnapshotId());
+          Snapshot newSnapshot = currentSnapshot.createMinor(removeIndexIds, newMemoryIndexSnapshot, ramIndex.getLatestIndexSnapshot());
+          snapshots.add(newSnapshot, true);
+          if (LOG.isInfoEnabled())
+            LOG.info("merge writeable completed");
+        } finally {
+          commitLock.unlock();
+        }
+      }
+    }
+
+    /**
+     * Takes snapshots and makes a DiskIndex.
+     * 
+     * @param indexSnapshots
+     * @param snapshot
+     * @throws Exception
+     */
+    private void executeMerge(List<? extends IndexSnapshot> indexSnapshots, Snapshot snapshot) throws Exception {
+      if (indexSnapshots.size() == 0)
+        return;
+      Long snapshotId = snapshot.getSnapshotId();
+      Long indexIdNum = diskIndexSequence.getAndIncrement();
+      IndexID indexId = new IndexID(indexIdNum, "disk");
+      Directory directory = directoryMap.create(indexIdNum.toString());
+      // initial creation happens outside of commitlock because it is the most
+      // time consuming
+      // the deletes occur inside the commitlock as they are fast
+      DiskIndex newDiskIndex = new DiskIndex(indexId, directory, indexSnapshots, TransactionSystem.this);
+      indexes.add(newDiskIndex);
+      commitLock.lock();
+      try {
+        // TODO: probably can just save deletes from the batches
+        List<SlaveBatch> deleteOnlySlaveBatches = new ArrayList<SlaveBatch>();
+        Snapshot currentSnapshot = snapshots.getLatestSnapshot();
+        Long latestSnapshotId = currentSnapshot.getSnapshotId();
+        if (!snapshotId.equals(latestSnapshotId)) {
+          SlaveBatchIterator iterator = transactionLog.getSlaveBatchIterator(snapshotId);
+          while (iterator.hasNext()) {
+            SlaveBatch slaveBatch = iterator.next(false, true);
+            deleteOnlySlaveBatches.add(slaveBatch);
+          }
+        }
+        IndexSnapshot newIndexSnapshot = newDiskIndex.initialize(latestSnapshotId, deleteOnlySlaveBatches, TransactionSystem.this);
+        List<IndexID> removeIndexIds = new ArrayList<IndexID>();
+        for (IndexSnapshot indexSnapshot : indexSnapshots) {
+          Index index = indexSnapshot.getIndex();
+          removeIndexIds.add(index.getId());
+        }
+        Snapshot newSnapshot = currentSnapshot.createMinor(removeIndexIds, newIndexSnapshot);
+        snapshots.add(newSnapshot, true);
+      } finally {
+        commitLock.unlock();
+      }
+    }
+  }
+
+  /**
+   * Commits a batch to the transaction log
+   * 
+   * @param batch
+   * @return CommitResult
+   * @throws Exception
+   * @throws IOException
+   */
+  CommitResult commitBatch(Batch batch) throws Exception, IOException {
+    batch.close();
+    commitLock.lock();
+    try {
+      Long snapshotId = null;
+      if (batch instanceof SlaveBatch) {
+        snapshotId = ((SlaveBatch) batch).getId();
+      } else {
+        MasterBatch masterBatch = (MasterBatch) batch;
+        snapshotId = transactionLog.getNextId();
+        if (batch.hasDocuments()) {
+          Documents documents = batch.getDocuments();
+          for (Document document : documents) {
+            Long documentId = documentSequence.getAndIncrement();
+            Util.setValue(Constants.DOCUMENTID, documentId, document);
+            Util.setValue(Constants.SNAPSHOTID, snapshotId, document);
+          }
+          if (documents.size() >= memoryIndexMaxDocs) {
+            RAMDirectory ramDirectory = createRamDirectory(documents, batch.getAnalyzer());
+            masterBatch.setRAMDirectory(ramDirectory);
+          }
+        }
+      }
+      ExecutorService threadPool = getCommitThreadPool();
+      Snapshot currentSnapshot = snapshots.getLatestSnapshot();
+      MemoryIndexSnapshot writeableIndexSnapshot = currentSnapshot.getWriteableSnapshot();
+      WriteableMemoryIndex writeableMemoryIndex = (WriteableMemoryIndex) writeableIndexSnapshot.getIndex();
+      List<Index> nonWriteableIndices = currentSnapshot.getDeleteOnlyIndices();
+      Transaction transaction = null;
+      CommitResult commitResult = null;
+      try {
+        Long previousId = transactionLog.getPreviousId(snapshotId);
+        transaction = new Transaction(snapshotId, previousId, batch, writeableMemoryIndex, nonWriteableIndices, threadPool, this);
+        commitResult = transaction.getCommitResult();
+      } catch (Exception exception) {
+        LOG.error("transaction failed");
+        throw new Exception("transaction failed", exception);
+      }
+      List<IndexSnapshot> indexSnapshots = new ArrayList<IndexSnapshot>(nonWriteableIndices.size() + 1);
+      for (Index index : nonWriteableIndices) {
+        indexSnapshots.add(index.getIndexSnapshot(snapshotId));
+      }
+      for (IndexSnapshot newIndexSnapshot : transaction.getNewIndexSnapshots()) {
+        indexes.add(newIndexSnapshot.getIndex());
+        indexSnapshots.add(newIndexSnapshot);
+      }
+      assert snapshotId == transaction.getId();
+      MemoryIndexSnapshot newWriteableSnapshot = writeableMemoryIndex.getIndexSnapshot(snapshotId);
+      assert newWriteableSnapshot != null;
+      indexSnapshots.add(newWriteableSnapshot);
+
+      Snapshot newSnapshot = new Snapshot(snapshotId, 0, newWriteableSnapshot, indexSnapshots, this, System.currentTimeMillis());
+      snapshots.add(newSnapshot, true);
+      docChangesSinceLastMerge += commitResult.getNumDocChanges();
+      int writeableMaxDoc = writeableMemoryIndex.getLatestIndexSnapshot().getIndexReader().maxDoc();
+      if (docChangesSinceLastMerge > maybeMergeDocChanges || writeableMaxDoc >= memoryIndexMaxDocs) {
+        System.out.println("docChangesSinceLastMerge: " + docChangesSinceLastMerge + " maybeMergeDocChanges: " + maybeMergeDocChanges);
+        System.out.println("writeableMaxDoc: " + writeableMaxDoc + " memoryIndexMaxDocs: " + memoryIndexMaxDocs);
+        if (mergeQueue.size() == 0) {
+          mergeThreadPool.submit(new MaybeMergeIndices());
+          docChangesSinceLastMerge = 0;
+        }
+      }
+      deleteUnreferencedSnapshots();
+      // TODO: reset document sequence
+      return commitResult;
+    } finally {
+      commitLock.unlock();
+    }
+  }
+
+  RAMDirectory createRamDirectory(Documents documents, Analyzer analyzer) throws Exception {
+    RAMDirectory ramDirectory = new RAMDirectory();
+    ExecutorService threadPool = getCommitThreadPool();
+    IndexCreator indexCreator = new IndexCreator(ramDirectory, Long.MAX_VALUE, 4, analyzer, threadPool);
+    BlockingQueue<IndexCreator.Add> addQueue = new ArrayBlockingQueue<IndexCreator.Add>(1000, true);
+    indexCreator.start(addQueue);
+    for (Document document : documents) {
+      addQueue.add(new IndexCreator.Add(document));
+    }
+    indexCreator.create();
+    return ramDirectory;
+  }
+
+  private WriteableMemoryIndex newWriteableMemoryIndex() throws Exception {
+    Long indexIdNum = ramIndexSequence.getAndIncrement();
+    IndexID indexId = new IndexID(indexIdNum, "memory");
+    WriteableMemoryIndex writeableMemoryIndex = new WriteableMemoryIndex(indexId, this);
+    indexes.add(writeableMemoryIndex);
+    return writeableMemoryIndex;
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/FSDirectoryMap.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/FSDirectoryMap.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/FSDirectoryMap.java	(revision 0)
@@ -0,0 +1,117 @@
+package org.apache.lucene.ocean;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.ocean.util.Util;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.FSDirectory;
+import org.apache.lucene.store.Lock;
+import org.apache.lucene.store.LockObtainFailedException;
+import org.apache.lucene.store.NativeFSLockFactory;
+
+public class FSDirectoryMap extends DirectoryMap {
+  public static final String WRITE_LOCK_NAME = "write.lock";
+  private Map<String,FSDirectory> map = new HashMap<String,FSDirectory>();
+  private File fileDirectory;
+  private LogDirectory rootDirectory;
+  private LogDirectory logDirectory;
+  private static MessageDigest DIGESTER;
+  private static final char[] HEX_DIGITS =
+  {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
+  private NativeFSLockFactory lockFactory;
+  
+  static {
+    try {
+      DIGESTER = MessageDigest.getInstance("MD5");
+    } catch (NoSuchAlgorithmException e) {
+        throw new RuntimeException(e.toString(), e);
+    }
+  }
+  
+  public FSDirectoryMap(File fileDirectory, String logDirectoryName) throws IOException {
+    this.fileDirectory = fileDirectory;
+    Util.mkdir(fileDirectory);
+    
+    lockFactory = new NativeFSLockFactory(fileDirectory);
+    //lockFactory.clearLock(WRITE_LOCK_NAME);
+    lockFactory.setLockPrefix(getLockID());
+    Lock lock = lockFactory.makeLock(WRITE_LOCK_NAME);
+    boolean obtained = lock.obtain(1000*5);
+    System.out.println("lock obtained: "+obtained);
+    if (!obtained) throw new LockObtainFailedException("Index locked for write: " + lock);
+    
+    rootDirectory = new FSLogDirectory(fileDirectory);
+    for (File file : fileDirectory.listFiles()) {
+      if (file.isDirectory() && !file.getName().equals(logDirectoryName)) {
+        FSDirectory dir = FSDirectory.getDirectory(file);
+        map.put(file.getName(), dir);
+      }
+    }
+    logDirectory = new FSLogDirectory(new File(fileDirectory, logDirectoryName));
+  }
+  
+  public String getLockID() {
+    String dirName;                               // name to be hashed
+    try {
+      dirName = fileDirectory.getCanonicalPath();
+    } catch (IOException e) {
+      throw new RuntimeException(e.toString(), e);
+    }
+
+    byte digest[];
+    synchronized (DIGESTER) {
+      digest = DIGESTER.digest(dirName.getBytes());
+    }
+    StringBuilder buf = new StringBuilder();
+    buf.append("ocean-");
+    for (int i = 0; i < digest.length; i++) {
+      int b = digest[i];
+      buf.append(HEX_DIGITS[(b >> 4) & 0xf]);
+      buf.append(HEX_DIGITS[b & 0xf]);
+    }
+    return buf.toString();
+  }
+  
+  public LogDirectory getLogDirectory() {
+    return logDirectory;
+  }
+  
+  public LogDirectory getDirectory() {
+    return rootDirectory;
+  }
+  
+  public Directory create(String name) throws IOException {
+    return FSDirectory.getDirectory(new File(fileDirectory, name));
+  }
+
+  public void delete(String name) throws IOException {
+    FSDirectory directory = get(name);
+    directory.close();
+    File file = directory.getFile();
+    FileUtils.deleteDirectory(file);
+    map.remove(name);
+  }
+
+  public FSDirectory get(String name) throws IOException {
+    return map.get(name);
+  }
+
+  public String[] list() throws IOException {
+    int x = 0;
+    String[] array = new String[map.size()];
+    for (String string : map.keySet()) {
+      array[x] = string;
+      x++;
+    }
+    return array;
+  }
+  
+  
+}
Index: ocean/src/org/apache/lucene/ocean/DirectoryIndex.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/DirectoryIndex.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/DirectoryIndex.java	(revision 0)
@@ -0,0 +1,264 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.lucene.index.IndexCommit;
+import org.apache.lucene.index.IndexDeletionPolicy;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.ocean.Batch.SlaveBatch;
+import org.apache.lucene.ocean.util.SortedList;
+import org.apache.lucene.ocean.util.Util;
+import org.apache.lucene.store.Directory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Abstract class used by RamIndex and DiskIndex.  Assumes a org.apache.lucene.store.Directory
+ * based IndexReader implementation.  
+ *
+ */
+public abstract class DirectoryIndex extends Index {
+  final static Logger LOG = LoggerFactory.getLogger(DirectoryIndex.class);
+	protected final SortedList<Long, DirectoryIndexSnapshot> indexSnapshotMap = new SortedList<Long, DirectoryIndexSnapshot>();
+  
+	protected DirectoryIndexDeletionPolicy indexDeletionPolicy = new DirectoryIndexDeletionPolicy();
+	protected IndexReader initialIndexReader;
+	
+	public DirectoryIndex(IndexID id, TransactionSystem system) {
+		super(id, system);
+	}
+  
+	public void close() throws IOException {
+	  for (DirectoryIndexSnapshot snapshot : indexSnapshotMap.values()) {
+	    snapshot.close();
+	  }
+	}
+	
+	public DirectoryIndexSnapshot getLatestIndexSnapshot() {
+		return indexSnapshotMap.lastValue();
+	}
+
+	public DirectoryIndexSnapshot getIndexSnapshot(Long snapshotId) {
+		return indexSnapshotMap.get(snapshotId);
+	}
+  
+	public IndexSnapshot initialize(Long snapshotId, List<SlaveBatch> deleteOnlySlaveBatches, TransactionSystem system) throws Exception, IndexException, IOException {
+		IndexReader indexReader = initialIndexReader;
+		if (deleteOnlySlaveBatches == null || deleteOnlySlaveBatches.size() == 0) {
+			createNewSnapshot(snapshotId, indexReader);
+		} else {
+			for (SlaveBatch slaveBatch : deleteOnlySlaveBatches) {
+				if (slaveBatch.hasDeletes()) {
+					applyDeletes(true, slaveBatch.getDeletes(), null, indexReader);
+				}
+				indexReader = indexReader.reopen();
+				createNewSnapshot(slaveBatch.getId(), indexReader);
+			}
+		}
+		assert snapshotId.equals(indexSnapshotMap.lastKey());
+		return indexSnapshotMap.get(indexSnapshotMap.lastKey());
+	}
+
+	protected void onCommit() throws Exception {
+
+	}
+  
+	private List<DirectoryIndexSnapshot> getSnapshotsByGeneration(long generation) throws IOException {
+	  List<DirectoryIndexSnapshot> snapshots = new ArrayList<DirectoryIndexSnapshot>();
+	  for (DirectoryIndexSnapshot indexSnapshot : indexSnapshotMap.values()) {
+      if (indexSnapshot.getIndexReader().getIndexCommit().getGeneration() == generation) {
+        snapshots.add(indexSnapshot);
+      }
+    }
+	  return snapshots;
+	}
+	
+	/**
+	 * Finds reader by version by iterating over snapshots and comparing versions
+	 * @param version
+	 * @return
+	 */
+	private DirectoryIndexSnapshot getSnapshotByReaderVersion(long version) {
+	  for (DirectoryIndexSnapshot indexSnapshot : indexSnapshotMap.values()) {
+	    if (indexSnapshot.getIndexReaderVersion() == version) {
+	      return indexSnapshot;
+	    }
+	  }
+	  return null;
+	}
+	
+	public class DirectoryIndexDeletionPolicy implements IndexDeletionPolicy {
+		private IndexCommit lastCommit;
+		SortedList<Long,IndexCommit> commitPoints = new SortedList<Long,IndexCommit>(); // key is generation
+
+		public void onInit(List commits) throws IOException {
+			onCommit(commits);
+		}
+
+		public IndexCommit getLastIndexCommitPoint() {
+			return lastCommit;
+		}
+
+		public void onCommit(List commits) throws IOException {
+			try {
+			  commitPoints.clear();
+			  for (int x = 0; x < commits.size(); x++) {
+          IndexCommit indexCommit = (IndexCommit) commits.get(x);
+          commitPoints.put(indexCommit.getGeneration(), indexCommit);
+			  }
+				DirectoryIndex.this.onCommit();
+				lastCommit = (IndexCommit) commits.get(commits.size() - 1);
+				for (int x = 0; x < commits.size() - 1; x++) {
+					IndexCommit indexCommitPoint = (IndexCommit) commits.get(x);
+					// Multiple snapshots may have the same generation
+					// so deleting a commitpoint may affect multiple snapshots
+					long generation = indexCommitPoint.getGeneration();
+					List<DirectoryIndexSnapshot> snapshots = getSnapshotsByGeneration(generation);
+					// if there are no snapshots it needs to be deleted, nothing
+					// is using it anymore
+					if (snapshots.size() == 0) {
+					  indexCommitPoint.delete();	
+					  commitPoints.remove(indexCommitPoint.getGeneration());
+					}
+					for (DirectoryIndexSnapshot indexSnapshot : snapshots) {
+					  if (!indexSnapshot.hasRef()) {
+					    // not referenced in Snapshots anymore
+	            indexCommitPoint.delete();
+	            indexSnapshot.delete();
+	          }
+					}
+				}
+			} catch (Exception exception) {
+				throw Util.asIOException(exception);
+			}
+		}
+	}
+
+	public abstract class DirectoryIndexSnapshot extends IndexSnapshot {
+		protected IndexReader indexReader;
+
+		public DirectoryIndexSnapshot(Long snapshotId, IndexReader indexReader) {
+			super(snapshotId);
+			this.indexReader = indexReader;
+		}
+		
+		void delete() throws Exception {
+		  long generation = getGeneration();
+		  List<DirectoryIndexSnapshot> snapshotsGreaterWithGeneration = new ArrayList<DirectoryIndexSnapshot>();
+		  for (DirectoryIndexSnapshot snapshot : indexSnapshotMap.values()) {
+		    if (snapshot.getGeneration() == generation && snapshot.snapshotId.longValue() > snapshotId) {
+		      snapshotsGreaterWithGeneration.add(snapshot);
+		    }
+		  }
+		  indexReader.close();
+		  if (snapshotsGreaterWithGeneration.size() == 0) {
+		    IndexCommit indexCommit = indexDeletionPolicy.commitPoints.get(generation);
+		    indexCommit.delete();
+		  }
+			indexSnapshotMap.remove(snapshotId);
+		}
+    
+		public long getGeneration() throws IOException {
+		  return indexReader.getIndexCommit().getGeneration();
+		}
+		
+		public void close() throws IOException {
+		  indexReader.close();
+		  indexSnapshotMap.remove(snapshotId);
+		}
+		
+		public int deletedDoc() {
+		  return indexReader.maxDoc() - indexReader.numDocs();
+		}
+		
+		public int maxDoc() {
+      return indexReader.maxDoc();
+    }
+		
+		public IndexReader getIndexReader() {
+			return indexReader;
+		}
+
+		public long getIndexReaderVersion() {
+			return indexReader.getVersion();
+		}
+
+		public boolean hasRef() throws Exception {
+			return getSystem().getSnapshots().contains(snapshotId);
+		}
+	}
+
+	public long getSize() throws IOException {
+		Directory directory = getDirectory();
+		return Util.getSize(directory);
+	}
+  
+	/**
+	 * Creates a new snapshot only without performing any changes to the index
+	 * @param transaction
+	 * @throws IndexException
+	 * @throws InterruptedException
+	 * @throws IOException
+	 */
+	public void commitNothing(Transaction transaction) throws IndexException, InterruptedException, IOException {
+	  IndexSnapshot latestIndexSnapshot = getLatestIndexSnapshot();
+    assert latestIndexSnapshot != null;
+    assert latestIndexSnapshot.getSnapshotId().equals(transaction.getPreviousId());
+    transaction.ready(this); 
+    if (transaction.go()) {
+      Long snapshotId = transaction.getId();
+      IndexReader previousIndexReader = latestIndexSnapshot.getIndexReader();
+      createNewSnapshot(snapshotId, previousIndexReader);
+      removeOldSnapshots(indexSnapshotMap);
+    }
+	}
+	
+	public boolean rollback(Long snapshotId) throws Exception {
+    LOG.info("rollback "+snapshotId);
+    DirectoryIndexSnapshot indexSnapshot = indexSnapshotMap.get(snapshotId);
+    if (indexSnapshot != null) {
+      indexSnapshot.delete();
+      return true;
+    }
+    return false;
+  }
+	
+	public DeletesResult commitDeletes(Deletes deletes, Transaction transaction) throws Exception, IndexException, InterruptedException, IOException {
+		IndexSnapshot latestIndexSnapshot = getLatestIndexSnapshot();
+		assert latestIndexSnapshot != null;
+		assert latestIndexSnapshot.getSnapshotId().equals(transaction.getPreviousId());
+		IndexReader previousIndexReader = latestIndexSnapshot.getIndexReader();
+		IndexReader newIndexReader = previousIndexReader.reopen();
+		try {
+			DeletesResult deletesResult = applyDeletes(true, deletes, null, newIndexReader);
+			transaction.ready(this); 
+			if (transaction.go()) {
+				if (deletesResult.getNumDeleted() > 0) {
+					newIndexReader.flush();
+				}
+				Long snapshotId = transaction.getId();
+				createNewSnapshot(snapshotId, newIndexReader);
+				removeOldSnapshots(indexSnapshotMap);
+				return deletesResult;
+			} else {
+				rollback(transaction.getId());
+				return null;
+			}
+		} catch (Throwable throwable) {
+			LOG.error("", throwable);
+			transaction.failed(this, throwable);
+			rollback(transaction.getId());
+			return null;
+		}
+	}
+
+	protected abstract DirectoryIndexSnapshot createNewSnapshot(Long snapshotId, IndexReader newIndexReader) throws IOException;
+
+	protected void registerSnapshot(DirectoryIndexSnapshot indexSnapshot) throws IOException {
+		indexSnapshotMap.put(indexSnapshot.getSnapshotId(), indexSnapshot);
+	}
+
+	public abstract Directory getDirectory();
+}
Index: ocean/src/org/apache/lucene/ocean/RamDirectorySerializer.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/RamDirectorySerializer.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/RamDirectorySerializer.java	(revision 0)
@@ -0,0 +1,54 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+
+import org.apache.lucene.store.IndexInput;
+import org.apache.lucene.store.IndexOutput;
+import org.apache.lucene.store.RAMDirectory;
+
+public class RamDirectorySerializer {
+	static final int BUFFER_SIZE = 16384;
+	
+  public RamDirectorySerializer() {
+  }
+  
+  public static RAMDirectory deserialize(IndexInput input) throws IOException {
+  	int numFiles = input.readVInt();
+  	RAMDirectory ramDirectory = new RAMDirectory();
+  	byte[] buffer = new byte[BUFFER_SIZE];
+  	for (int x=0; x < numFiles; x++) {
+  		String file = input.readString();
+  		int length = input.readVInt();
+  		IndexOutput indexOutput = ramDirectory.createOutput(file);
+  		int readCount = 0;
+      while (readCount < length) {
+        int toRead = readCount + BUFFER_SIZE > length ? (int)(length - readCount) : BUFFER_SIZE;
+        input.readBytes(buffer, 0, toRead);//readFully(buffer, 0, toRead);
+        indexOutput.writeBytes(buffer, toRead);
+        readCount += toRead;
+      }
+      indexOutput.close();
+  	}
+  	return ramDirectory;
+  }
+  
+  public static void serialize(RAMDirectory ramDirectory, IndexOutput output) throws IOException {
+  	String[] files = ramDirectory.list();
+  	output.writeVInt(files.length);
+  	byte[] buffer = new byte[BUFFER_SIZE];
+  	for (String file : files) {
+  		int length = (int)ramDirectory.fileLength(file);
+  		output.writeString(file);
+  		output.writeVInt(length);
+  		IndexInput indexInput = ramDirectory.openInput(file);
+  		int readCount = 0;
+      while (readCount < length) {
+        int toRead = readCount + BUFFER_SIZE > length ? (int)(length - readCount) : BUFFER_SIZE;
+        indexInput.readBytes(buffer, 0, toRead);
+        output.writeBytes(buffer, toRead);//write(buffer, 0, toRead);
+        readCount += toRead;
+      }
+      indexInput.close();
+  	}
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/Batch.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/Batch.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/Batch.java	(revision 0)
@@ -0,0 +1,215 @@
+package org.apache.lucene.ocean;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.io.Serializable;
+import java.util.Date;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.store.RAMDirectory;
+
+/**
+ * Stays in memory
+ * 
+ */
+public abstract class Batch {
+	protected Documents documents;
+	protected RAMDirectory ramDirectory;
+	protected Analyzer analyzer;
+	protected long schemaVersion;
+	protected Deletes deletes;
+	protected Date timestamp;
+	protected boolean isClosed = false;
+
+	public Batch() {
+		timestamp = new Date();
+	}
+	
+	public boolean hasRAMDirectory() {
+		return ramDirectory != null;
+	}
+	
+	public RAMDirectory getRamDirectory() {
+		return ramDirectory;
+	}
+
+	public long getSchemaVersion() {
+		return schemaVersion;
+	}
+	
+	public static class MasterBatch extends Batch {
+	  private TransactionSystem transactionSystem;
+	  
+	  public MasterBatch(TransactionSystem transactionSystem) {
+	  	this.transactionSystem = transactionSystem;
+	  }
+	  
+	  public void setRAMDirectory(RAMDirectory ramDirectory) {
+	    documents = null;
+	    if (isClosed) throw new RuntimeException("batch closed");
+	    this.ramDirectory = ramDirectory;
+	  }
+	  
+	  public void setAnalyzer(Analyzer analyzer) {
+	    this.analyzer = analyzer;
+	  }
+	  
+	  public void setDeletes(Deletes deletes) {
+	    if (isClosed) throw new RuntimeException("batch closed");
+	    this.deletes = deletes;
+	  }
+	  
+	  public void addDocument(Document document) {
+	    if (this.documents == null)
+	      this.documents = new Documents();
+	    this.documents.add(document);
+	  }
+	  
+	  public void addDocuments(Documents documents) {
+	    if (isClosed) throw new RuntimeException("batch closed");
+	    if (this.documents == null)
+	      this.documents = new Documents();
+	    this.documents.addAll(documents);
+	  }
+	  
+	  /**
+	  public Serializable getSerializableRamDirectory(Long id) {
+	  	assert ramDirectory != null;
+	  	return new SerializableRamDirectory(id, ramDirectory);
+	  }
+	  
+	  public Serializable getSerializableBatchDeletes(Long id) {
+	  	return new SerializableBatchDeletes(id, deletes);
+	  }
+	  
+	  public Serializable getSerializableBatchDocuments(Long id) {
+			return new SerializableBatchDocuments(id, documents); 
+		}
+	  **/
+	  public void commit() throws Exception {
+	    transactionSystem.commitBatch(this); 
+		}
+	}
+	
+	public static class SlaveBatch extends Batch {
+	  private Long id;
+	  
+	  public SlaveBatch(Long id, Documents documents, Deletes deletes) {
+	  	this.id = id;
+	  	this.documents = documents;
+	  	this.deletes = deletes;
+	  }
+	  
+	  public SlaveBatch(Long id, RAMDirectory ramDirectory, Deletes deletes) {
+	  	this.id = id;
+	  	this.ramDirectory = ramDirectory;
+	  	this.deletes = deletes;
+	  }
+	  
+	  public Long getId() {
+	  	return id;
+	  }
+	}
+	/**
+	public static class SerializableRamDirectory extends SerializableBatch implements Externalizable {
+		private static final long serialVersionUID = 1l;
+		private RAMDirectory ramDirectory;
+		
+		public SerializableRamDirectory(Long id, RAMDirectory ramDirectory) {
+			super(id);
+			this.ramDirectory = ramDirectory;
+		}
+    
+		// TODO: use native version not externalizable
+		public void readExternal(ObjectInput objectInput) throws IOException, ClassNotFoundException {
+			long objectVersion = objectInput.readLong();
+			ramDirectory = RamDirectorySerializer.deserialize(objectInput);
+		}
+		
+		public void writeExternal(ObjectOutput objectOutput) throws IOException {
+			objectOutput.writeLong(serialVersionUID);
+			RamDirectorySerializer.serialize(ramDirectory, objectOutput);
+		}
+		
+		public RAMDirectory getRamDirectory() {
+			return ramDirectory;
+		}
+	}
+	
+	public static class SerializableBatchDocuments extends SerializableBatch {
+		private static final long serialVersionUID = 1l;
+		private Documents documents;
+		
+		public SerializableBatchDocuments(Long id, Documents documents) {
+			super(id);
+			this.documents = documents;
+		}
+		
+		public Documents getDocuments() {
+			return documents;
+		}
+	}
+	
+	public static class SerializableBatchDeletes extends SerializableBatch {
+		private static final long serialVersionUID = 1l;
+		private Deletes deletes;
+		
+		public SerializableBatchDeletes(Long id, Deletes deletes) {
+			super(id);
+		}
+		
+		public Deletes getDeletes() {
+			return deletes;
+		}
+	}
+	
+	public abstract static class SerializableBatch implements Serializable {
+		private Long id;
+		
+		public SerializableBatch(Long id) {
+			this.id = id;
+		}
+		public Long getId() {
+			return id;
+		}
+	}
+	**/
+	public Analyzer getAnalyzer() {
+		return analyzer;
+	}
+  
+	public boolean hasDocuments() {
+		if (documents == null || documents.size() == 0)
+			return false;
+		else
+			return true;
+	}
+
+	public boolean hasDeletes() {
+		if (deletes == null || !deletes.hasDeletes()) {
+			return false;
+		} else {
+			return true;
+		}
+	}
+
+	// disallow any more additions
+	public void close() {
+		isClosed = true;
+	}
+
+	public Documents getDocuments() {
+		return documents;
+	}
+
+	public Deletes getDeletes() {
+		return deletes;
+	}
+
+	public Date getTimestamp() {
+		return timestamp;
+	}
+}
Index: ocean/src/org/apache/lucene/ocean/CommitResult.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/CommitResult.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/CommitResult.java	(revision 0)
@@ -0,0 +1,45 @@
+package org.apache.lucene.ocean;
+
+import java.util.List;
+
+public class CommitResult {
+  private Long snapshotId;
+  private List<DeletesResult> deletesResults;
+  private Integer numAdded;
+  private IndexID addedIndexId;
+
+  public CommitResult(Long snapshotId, List<DeletesResult> deletesResults, Integer numAdded, IndexID addedIndexId) {
+    this.snapshotId = snapshotId;
+    this.deletesResults = deletesResults;
+    this.numAdded = numAdded;
+    this.addedIndexId = addedIndexId;
+  }
+
+  public int getNumDocChanges() {
+    int numChanged = 0;
+    if (numAdded != null)
+      numChanged += numAdded;
+    if (deletesResults != null) {
+      for (DeletesResult deletesResult : deletesResults) {
+        numChanged += deletesResult.getNumDeleted();
+      }
+    }
+    return numChanged;
+  }
+
+  public Long getSnapshotId() {
+    return snapshotId;
+  }
+
+  public List<DeletesResult> getDeletesResults() {
+    return deletesResults;
+  }
+
+  public Integer getNumAdded() {
+    return numAdded;
+  }
+
+  public IndexID getAddedIndexId() {
+    return addedIndexId;
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/Deletes.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/Deletes.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/Deletes.java	(revision 0)
@@ -0,0 +1,141 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.lang.SerializationUtils;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.store.IndexInput;
+import org.apache.lucene.store.IndexOutput;
+
+/**
+ * Encapsulates deletes for a transaction
+ *
+ */
+public class Deletes implements Serializable {
+  public static final int DOC_IDS = 1;
+  public static final int TERMS = 2;
+  private static final long serialVersionUID = 1l;
+  private List<Term> terms = new ArrayList<Term>();
+  private List<DeleteByQuery> deleteByQueries = new ArrayList<DeleteByQuery>();
+  private long[] docIds;
+  /**
+  public Deletes(IndexInput input) throws IOException {
+    int docIdsLength = input.readVInt();
+    if (docIdsLength > 0) {
+      docIds = new long[docIdsLength];
+      for (int x=0; x < docIdsLength; x++) {
+        docIds[x] = input.readVLong();
+      }
+    }
+    int termsLength = input.readVInt();
+    for (int x=0; x < termsLength; x++) {
+      String field = input.readString();
+      String text = input.readString();
+      terms.add(new Term(field, text));
+    }
+    int dqLength = input.readVInt();
+    for (int x=0; x < dqLength; x++) {
+      int blen = input.readVInt();
+      byte[] bytes = new byte[blen];
+      input.readBytes(bytes, 0, blen);
+      deleteByQueries.add((DeleteByQuery)SerializationUtils.deserialize(bytes));
+    }
+  }
+  **/
+  public Deletes() {
+  }
+  
+  /**
+  public void writeTo(IndexOutput output) throws IOException {
+    if (docIds == null) {
+      output.writeVInt(0);
+    } else {
+      output.writeVInt(docIds.length);
+      for (int x = 0; x < docIds.length; x++) {
+        output.writeVLong(docIds[x]);
+      }
+    }
+    output.writeVInt(terms.size());
+    for (int x = 0; x < terms.size(); x++) {
+      output.writeString(terms.get(x).field());
+      output.writeString(terms.get(x).text());
+    }
+    output.writeVInt(deleteByQueries.size());
+    for (int x = 0; x < deleteByQueries.size(); x++) {
+      byte[] bytes = SerializationUtils.serialize(deleteByQueries.get(x));
+      output.writeVInt(bytes.length);
+      output.writeBytes(bytes, bytes.length);
+    }
+  }
+  **/
+  public void merge(Deletes deletes) {
+    terms.addAll(deletes.getTerms());
+    deleteByQueries.addAll(deletes.getDeleteByQueries());
+  }
+
+  public boolean hasDocIds() {
+    return docIds != null;
+  }
+
+  void setDocIds(long[] docIds) {
+    assert docIds == null;
+    this.docIds = docIds;
+  }
+
+  public void addTerm(Term term) {
+    terms.add(term);
+  }
+  
+  public void addQuery(Query query) {
+    deleteByQueries.add(new DeleteByQuery(query));
+  }
+  
+  public void addDeleteByQuery(DeleteByQuery deleteByQuery) {
+    deleteByQueries.add(deleteByQuery);
+  }
+
+  public long[] getDocIds() {
+    return docIds;
+  }
+
+  public boolean hasDeletes() {
+    // TODO: implement
+    return true;
+  }
+
+  public boolean hasDeleteByQueries() {
+    if (deleteByQueries != null && deleteByQueries.size() > 0)
+      return true;
+    return false;
+  }
+
+  public List<DeleteByQuery> getDeleteByQueries() {
+    return deleteByQueries;
+  }
+
+  public static class DeleteByQuery implements Serializable {
+    private Query query;
+
+    public DeleteByQuery(Query query) {
+      this.query = query;
+    }
+
+    public Query getQuery() {
+      return query;
+    }
+  }
+
+  public List<Term> getTerms() {
+    return terms;
+  }
+
+  public boolean hasTerms() {
+    if (terms != null && terms.size() > 0)
+      return true;
+    return false;
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/DeletesResult.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/DeletesResult.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/DeletesResult.java	(revision 0)
@@ -0,0 +1,51 @@
+package org.apache.lucene.ocean;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+public class DeletesResult {
+  private IndexID indexId;
+  private List<Result> results = new ArrayList<Result>();
+  private int numDeleted = 0;
+  private List<Long> docIds = new LinkedList<Long>(); 
+
+  public DeletesResult(IndexID indexId) {
+    this.indexId = indexId;
+  }
+
+  public List<Long> getDocIds() {
+    return docIds;
+  }
+
+  public IndexID getIndexId() {
+    return indexId;
+  }
+
+  public void add(Result result) {
+    numDeleted += result.getNumDeleted();
+    results.add(result);
+  }
+
+  public int getNumDeleted() {
+    return numDeleted;
+  }
+
+  public static class Result {
+    private Object delete;
+    private int numDeleted;
+
+    public Result(Object delete, int numDeleted) {
+      this.delete = delete;
+      this.numDeleted = numDeleted;
+    }
+
+    public Object getDelete() {
+      return delete;
+    }
+
+    public int getNumDeleted() {
+      return numDeleted;
+    }
+  }
+}
\ No newline at end of file
Index: ocean/src/org/apache/lucene/ocean/DirectoryIndex.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/DirectoryIndex.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/DirectoryIndex.java	(revision 0)
@@ -0,0 +1,264 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.lucene.index.IndexCommit;
+import org.apache.lucene.index.IndexDeletionPolicy;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.ocean.Batch.SlaveBatch;
+import org.apache.lucene.ocean.util.SortedList;
+import org.apache.lucene.ocean.util.Util;
+import org.apache.lucene.store.Directory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Abstract class used by RamIndex and DiskIndex.  Assumes a org.apache.lucene.store.Directory
+ * based IndexReader implementation.  
+ *
+ */
+public abstract class DirectoryIndex extends Index {
+  final static Logger LOG = LoggerFactory.getLogger(DirectoryIndex.class);
+	protected final SortedList<Long, DirectoryIndexSnapshot> indexSnapshotMap = new SortedList<Long, DirectoryIndexSnapshot>();
+  
+	protected DirectoryIndexDeletionPolicy indexDeletionPolicy = new DirectoryIndexDeletionPolicy();
+	protected IndexReader initialIndexReader;
+	
+	public DirectoryIndex(IndexID id, TransactionSystem system) {
+		super(id, system);
+	}
+  
+	public void close() throws IOException {
+	  for (DirectoryIndexSnapshot snapshot : indexSnapshotMap.values()) {
+	    snapshot.close();
+	  }
+	}
+	
+	public DirectoryIndexSnapshot getLatestIndexSnapshot() {
+		return indexSnapshotMap.lastValue();
+	}
+
+	public DirectoryIndexSnapshot getIndexSnapshot(Long snapshotId) {
+		return indexSnapshotMap.get(snapshotId);
+	}
+  
+	public IndexSnapshot initialize(Long snapshotId, List<SlaveBatch> deleteOnlySlaveBatches, TransactionSystem system) throws Exception, IndexException, IOException {
+		IndexReader indexReader = initialIndexReader;
+		if (deleteOnlySlaveBatches == null || deleteOnlySlaveBatches.size() == 0) {
+			createNewSnapshot(snapshotId, indexReader);
+		} else {
+			for (SlaveBatch slaveBatch : deleteOnlySlaveBatches) {
+				if (slaveBatch.hasDeletes()) {
+					applyDeletes(true, slaveBatch.getDeletes(), null, indexReader);
+				}
+				indexReader = indexReader.reopen();
+				createNewSnapshot(slaveBatch.getId(), indexReader);
+			}
+		}
+		assert snapshotId.equals(indexSnapshotMap.lastKey());
+		return indexSnapshotMap.get(indexSnapshotMap.lastKey());
+	}
+
+	protected void onCommit() throws Exception {
+
+	}
+  
+	private List<DirectoryIndexSnapshot> getSnapshotsByGeneration(long generation) throws IOException {
+	  List<DirectoryIndexSnapshot> snapshots = new ArrayList<DirectoryIndexSnapshot>();
+	  for (DirectoryIndexSnapshot indexSnapshot : indexSnapshotMap.values()) {
+      if (indexSnapshot.getIndexReader().getIndexCommit().getGeneration() == generation) {
+        snapshots.add(indexSnapshot);
+      }
+    }
+	  return snapshots;
+	}
+	
+	/**
+	 * Finds reader by version by iterating over snapshots and comparing versions
+	 * @param version
+	 * @return
+	 */
+	private DirectoryIndexSnapshot getSnapshotByReaderVersion(long version) {
+	  for (DirectoryIndexSnapshot indexSnapshot : indexSnapshotMap.values()) {
+	    if (indexSnapshot.getIndexReaderVersion() == version) {
+	      return indexSnapshot;
+	    }
+	  }
+	  return null;
+	}
+	
+	public class DirectoryIndexDeletionPolicy implements IndexDeletionPolicy {
+		private IndexCommit lastCommit;
+		SortedList<Long,IndexCommit> commitPoints = new SortedList<Long,IndexCommit>(); // key is generation
+
+		public void onInit(List commits) throws IOException {
+			onCommit(commits);
+		}
+
+		public IndexCommit getLastIndexCommitPoint() {
+			return lastCommit;
+		}
+
+		public void onCommit(List commits) throws IOException {
+			try {
+			  commitPoints.clear();
+			  for (int x = 0; x < commits.size(); x++) {
+          IndexCommit indexCommit = (IndexCommit) commits.get(x);
+          commitPoints.put(indexCommit.getGeneration(), indexCommit);
+			  }
+				DirectoryIndex.this.onCommit();
+				lastCommit = (IndexCommit) commits.get(commits.size() - 1);
+				for (int x = 0; x < commits.size() - 1; x++) {
+					IndexCommit indexCommitPoint = (IndexCommit) commits.get(x);
+					// Multiple snapshots may have the same generation
+					// so deleting a commitpoint may affect multiple snapshots
+					long generation = indexCommitPoint.getGeneration();
+					List<DirectoryIndexSnapshot> snapshots = getSnapshotsByGeneration(generation);
+					// if there are no snapshots it needs to be deleted, nothing
+					// is using it anymore
+					if (snapshots.size() == 0) {
+					  indexCommitPoint.delete();	
+					  commitPoints.remove(indexCommitPoint.getGeneration());
+					}
+					for (DirectoryIndexSnapshot indexSnapshot : snapshots) {
+					  if (!indexSnapshot.hasRef()) {
+					    // not referenced in Snapshots anymore
+	            indexCommitPoint.delete();
+	            indexSnapshot.delete();
+	          }
+					}
+				}
+			} catch (Exception exception) {
+				throw Util.asIOException(exception);
+			}
+		}
+	}
+
+	public abstract class DirectoryIndexSnapshot extends IndexSnapshot {
+		protected IndexReader indexReader;
+
+		public DirectoryIndexSnapshot(Long snapshotId, IndexReader indexReader) {
+			super(snapshotId);
+			this.indexReader = indexReader;
+		}
+		
+		void delete() throws Exception {
+		  long generation = getGeneration();
+		  List<DirectoryIndexSnapshot> snapshotsGreaterWithGeneration = new ArrayList<DirectoryIndexSnapshot>();
+		  for (DirectoryIndexSnapshot snapshot : indexSnapshotMap.values()) {
+		    if (snapshot.getGeneration() == generation && snapshot.snapshotId.longValue() > snapshotId) {
+		      snapshotsGreaterWithGeneration.add(snapshot);
+		    }
+		  }
+		  indexReader.close();
+		  if (snapshotsGreaterWithGeneration.size() == 0) {
+		    IndexCommit indexCommit = indexDeletionPolicy.commitPoints.get(generation);
+		    indexCommit.delete();
+		  }
+			indexSnapshotMap.remove(snapshotId);
+		}
+    
+		public long getGeneration() throws IOException {
+		  return indexReader.getIndexCommit().getGeneration();
+		}
+		
+		public void close() throws IOException {
+		  indexReader.close();
+		  indexSnapshotMap.remove(snapshotId);
+		}
+		
+		public int deletedDoc() {
+		  return indexReader.maxDoc() - indexReader.numDocs();
+		}
+		
+		public int maxDoc() {
+      return indexReader.maxDoc();
+    }
+		
+		public IndexReader getIndexReader() {
+			return indexReader;
+		}
+
+		public long getIndexReaderVersion() {
+			return indexReader.getVersion();
+		}
+
+		public boolean hasRef() throws Exception {
+			return getSystem().getSnapshots().contains(snapshotId);
+		}
+	}
+
+	public long getSize() throws IOException {
+		Directory directory = getDirectory();
+		return Util.getSize(directory);
+	}
+  
+	/**
+	 * Creates a new snapshot only without performing any changes to the index
+	 * @param transaction
+	 * @throws IndexException
+	 * @throws InterruptedException
+	 * @throws IOException
+	 */
+	public void commitNothing(Transaction transaction) throws IndexException, InterruptedException, IOException {
+	  IndexSnapshot latestIndexSnapshot = getLatestIndexSnapshot();
+    assert latestIndexSnapshot != null;
+    assert latestIndexSnapshot.getSnapshotId().equals(transaction.getPreviousId());
+    transaction.ready(this); 
+    if (transaction.go()) {
+      Long snapshotId = transaction.getId();
+      IndexReader previousIndexReader = latestIndexSnapshot.getIndexReader();
+      createNewSnapshot(snapshotId, previousIndexReader);
+      removeOldSnapshots(indexSnapshotMap);
+    }
+	}
+	
+	public boolean rollback(Long snapshotId) throws Exception {
+    LOG.info("rollback "+snapshotId);
+    DirectoryIndexSnapshot indexSnapshot = indexSnapshotMap.get(snapshotId);
+    if (indexSnapshot != null) {
+      indexSnapshot.delete();
+      return true;
+    }
+    return false;
+  }
+	
+	public DeletesResult commitDeletes(Deletes deletes, Transaction transaction) throws Exception, IndexException, InterruptedException, IOException {
+		IndexSnapshot latestIndexSnapshot = getLatestIndexSnapshot();
+		assert latestIndexSnapshot != null;
+		assert latestIndexSnapshot.getSnapshotId().equals(transaction.getPreviousId());
+		IndexReader previousIndexReader = latestIndexSnapshot.getIndexReader();
+		IndexReader newIndexReader = previousIndexReader.reopen();
+		try {
+			DeletesResult deletesResult = applyDeletes(true, deletes, null, newIndexReader);
+			transaction.ready(this); 
+			if (transaction.go()) {
+				if (deletesResult.getNumDeleted() > 0) {
+					newIndexReader.flush();
+				}
+				Long snapshotId = transaction.getId();
+				createNewSnapshot(snapshotId, newIndexReader);
+				removeOldSnapshots(indexSnapshotMap);
+				return deletesResult;
+			} else {
+				rollback(transaction.getId());
+				return null;
+			}
+		} catch (Throwable throwable) {
+			LOG.error("", throwable);
+			transaction.failed(this, throwable);
+			rollback(transaction.getId());
+			return null;
+		}
+	}
+
+	protected abstract DirectoryIndexSnapshot createNewSnapshot(Long snapshotId, IndexReader newIndexReader) throws IOException;
+
+	protected void registerSnapshot(DirectoryIndexSnapshot indexSnapshot) throws IOException {
+		indexSnapshotMap.put(indexSnapshot.getSnapshotId(), indexSnapshot);
+	}
+
+	public abstract Directory getDirectory();
+}
Index: ocean/src/org/apache/lucene/ocean/DirectoryMap.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/DirectoryMap.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/DirectoryMap.java	(revision 0)
@@ -0,0 +1,19 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+
+import org.apache.lucene.store.Directory;
+
+public abstract class DirectoryMap {
+  public abstract Directory create(String name) throws IOException;
+  
+  public abstract void delete(String name) throws IOException;
+  
+  public abstract Directory get(String name) throws IOException;
+  
+  public abstract String[] list() throws IOException;
+  
+  public abstract LogDirectory getDirectory();
+  
+  public abstract LogDirectory getLogDirectory();
+}
Index: ocean/src/org/apache/lucene/ocean/DiskIndex.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/DiskIndex.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/DiskIndex.java	(revision 0)
@@ -0,0 +1,182 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.logging.Logger;
+
+import org.apache.lucene.index.IndexCommit;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.SerialMergeScheduler;
+import org.apache.lucene.ocean.util.Util;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.RAMDirectory;
+
+/**
+ * On disk index.  Only deletes are allowed to occur to the index. 
+ * There is a unique IndexReader per snapshot.
+ *
+ */
+// TODO: may be issue with reader having same version but multiple snapshots when transaction has no deletes for this index
+public class DiskIndex extends DirectoryIndex {
+	public static Logger log = Logger.getLogger(DiskIndex.class.getName());
+	private final Directory directory;
+	//private IndexInfo indexInfo;
+
+	// load existing index
+	public DiskIndex(IndexID id, Directory directory, Long snapshotId, Long segmentGeneration, TransactionSystem system) throws Exception, IndexException, IOException {
+		super(id, system);
+		assert segmentGeneration != null;
+		this.directory = directory;
+		if (directory.fileExists("writing.index")) {
+			throw new IndexNeverCompletedCopyException("index never completed copying");
+		}
+		if (IndexWriter.isLocked(directory)) {
+		  LOG.info("directory: "+directory+" locked.  being unlocked");
+		  IndexWriter.unlock(directory);
+		}
+		initialIndexReader = IndexReader.open(directory, indexDeletionPolicy);
+		long readerGeneration = initialIndexReader.getIndexCommit().getGeneration();
+		assert segmentGeneration.longValue() == readerGeneration;
+		//indexInfo = loadIndexInfo();
+		createNewSnapshot(snapshotId, initialIndexReader);
+	}
+
+	// merge indexes creating new index
+	public DiskIndex(IndexID id, Directory directory, List<? extends IndexSnapshot> indexSnapshots, TransactionSystem system) throws Exception, IOException {
+		super(id, system);
+		this.directory = directory;
+		Util.touchFile("writing.index", directory);
+		IndexReader[] indexReaders = getIndexReaders(indexSnapshots);
+		// create in ram first, is faster than copying to disk due to less hard disk head movement
+		RAMDirectory ramDirectory = new RAMDirectory();
+		IndexWriter indexWriter = new IndexWriter(ramDirectory, false, system.getDefaultAnalyzer(), true);
+		indexWriter.setMergeScheduler(new SerialMergeScheduler());
+		indexWriter.setUseCompoundFile(true);
+		indexWriter.addIndexes(indexReaders);
+		indexWriter.close();
+		Directory.copy(ramDirectory, directory, true);
+		
+		//indexInfo = new IndexInfo();
+		//indexInfo.setMaxDocumentID(maxDocumentId);
+		//indexInfo.setMaxSnapshotID(maxSnapshotId);
+		//saveIndexInfo(indexInfo);
+		directory.deleteFile("writing.index");
+		initialIndexReader = IndexReader.open(directory, indexDeletionPolicy);
+	}
+  
+	public Directory getDirectory() {
+		return directory;
+	}
+  /**
+	private IndexInfo loadIndexInfo() throws Exception {
+		String xml = Util.getString("indexinfo.xml", directory);
+		Element element = XMLUtil.parseElement(xml);
+		return new IndexInfo(element);
+	}
+
+	private void saveIndexInfo(IndexInfo indexInfo) throws Exception {
+		Element element = indexInfo.toElement();
+		String xml = XMLUtil.outputElement(element);
+		Util.save(xml, "indexinfo.xml", directory);
+	}
+
+	public static class IndexInfo implements CElement {
+		private Long maxSnapshotId;
+		private Long maxDocumentId;
+
+		public IndexInfo() {
+		}
+
+		public Long getMaxSnapshotID() {
+			return maxSnapshotId;
+		}
+
+		public void setMaxSnapshotID(Long maxSnapshotId) {
+			this.maxSnapshotId = maxSnapshotId;
+		}
+
+		public Long getMaxDocumentID() {
+			return maxDocumentId;
+		}
+
+		public void setMaxDocumentID(Long maxDocumentId) {
+			this.maxDocumentId = maxDocumentId;
+		}
+
+		public IndexInfo(Element element) throws Exception {
+		  maxSnapshotId = XMLUtil.getAttributeLong("maxSnapshotId", element);
+		  maxDocumentId = XMLUtil.getAttributeLong("maxDocumentId", element);
+		}
+
+		public Element toElement() throws Exception {
+		  Element element = new Element("indexinfo");
+		  XMLUtil.setAttribute("maxSnapshotId", maxSnapshotId, element);
+		  XMLUtil.setAttribute("maxDocumentId", maxDocumentId, element);
+		  return element;
+		}
+	}
+  **/
+	public boolean hasTooManyDeletedDocs(double percent) {
+	  assert percent <= 1.0;
+		DirectoryIndexSnapshot indexSnapshot = getLatestIndexSnapshot();
+		if (indexSnapshot != null) {
+			IndexReader indexReader = indexSnapshot.getIndexReader();
+			int maxDoc = indexReader.maxDoc();
+			int deletedDocs = maxDoc - indexReader.numDocs();
+			if (deletedDocs > (maxDoc * percent))
+				return true;
+		}
+		return false;
+	}
+
+	public class DiskIndexSnapshot extends DirectoryIndexSnapshot {
+		private Collection<String> indexReaderFileNames;
+
+		public DiskIndexSnapshot(Long snapshotID, IndexReader indexReader, Collection<String> indexReaderFileNames) {
+			super(snapshotID, indexReader);
+			this.indexReaderFileNames = indexReaderFileNames;
+		}
+
+		//public Long getMaxSnapshotId() throws IOException {
+		//	return indexInfo.getMaxSnapshotID();
+		//}
+
+		//public Long getMaxDocumentId() throws IOException {
+		//	return indexInfo.getMaxDocumentID();
+		//}
+    
+		protected void delete() throws Exception {
+			super.delete();
+			deleteFiles();
+		}
+		
+		public boolean hasRef() throws Exception {
+			return getSystem().getSnapshots().contains(snapshotId);
+		}
+
+		public void deleteFiles() throws IOException {
+		}
+
+		public List<String> getFiles() throws Exception {
+			List<String> files = new ArrayList<String>();
+			for (String fileName : indexReaderFileNames) {
+				files.add(fileName);
+			}
+			return files;
+		}
+	}
+  
+	protected void onCommit() throws Exception {
+	}
+
+	protected DiskIndexSnapshot createNewSnapshot(Long snapshotId, IndexReader newIndexReader) throws IOException {
+	  IndexCommit indexCommit = newIndexReader.getIndexCommit();
+		Collection<String> fileNames = indexCommit.getFileNames();
+		DiskIndexSnapshot diskIndexSnapshot = new DiskIndexSnapshot(snapshotId, newIndexReader, fileNames);
+		registerSnapshot(diskIndexSnapshot);
+		return diskIndexSnapshot;
+	}
+}
Index: ocean/src/org/apache/lucene/ocean/Documents.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/Documents.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/Documents.java	(revision 0)
@@ -0,0 +1,17 @@
+package org.apache.lucene.ocean;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.apache.lucene.document.Document;
+
+public class Documents extends ArrayList<Document> {
+  
+  public Documents() {}
+  
+  public Documents(Collection<Document> documents) {
+    for (Document document : documents) {
+      add(document);
+    }
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/FSDirectoryMap.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/FSDirectoryMap.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/FSDirectoryMap.java	(revision 0)
@@ -0,0 +1,117 @@
+package org.apache.lucene.ocean;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.ocean.util.Util;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.FSDirectory;
+import org.apache.lucene.store.Lock;
+import org.apache.lucene.store.LockObtainFailedException;
+import org.apache.lucene.store.NativeFSLockFactory;
+
+public class FSDirectoryMap extends DirectoryMap {
+  public static final String WRITE_LOCK_NAME = "write.lock";
+  private Map<String,FSDirectory> map = new HashMap<String,FSDirectory>();
+  private File fileDirectory;
+  private LogDirectory rootDirectory;
+  private LogDirectory logDirectory;
+  private static MessageDigest DIGESTER;
+  private static final char[] HEX_DIGITS =
+  {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
+  private NativeFSLockFactory lockFactory;
+  
+  static {
+    try {
+      DIGESTER = MessageDigest.getInstance("MD5");
+    } catch (NoSuchAlgorithmException e) {
+        throw new RuntimeException(e.toString(), e);
+    }
+  }
+  
+  public FSDirectoryMap(File fileDirectory, String logDirectoryName) throws IOException {
+    this.fileDirectory = fileDirectory;
+    Util.mkdir(fileDirectory);
+    
+    lockFactory = new NativeFSLockFactory(fileDirectory);
+    //lockFactory.clearLock(WRITE_LOCK_NAME);
+    lockFactory.setLockPrefix(getLockID());
+    Lock lock = lockFactory.makeLock(WRITE_LOCK_NAME);
+    boolean obtained = lock.obtain(1000*5);
+    System.out.println("lock obtained: "+obtained);
+    if (!obtained) throw new LockObtainFailedException("Index locked for write: " + lock);
+    
+    rootDirectory = new FSLogDirectory(fileDirectory);
+    for (File file : fileDirectory.listFiles()) {
+      if (file.isDirectory() && !file.getName().equals(logDirectoryName)) {
+        FSDirectory dir = FSDirectory.getDirectory(file);
+        map.put(file.getName(), dir);
+      }
+    }
+    logDirectory = new FSLogDirectory(new File(fileDirectory, logDirectoryName));
+  }
+  
+  public String getLockID() {
+    String dirName;                               // name to be hashed
+    try {
+      dirName = fileDirectory.getCanonicalPath();
+    } catch (IOException e) {
+      throw new RuntimeException(e.toString(), e);
+    }
+
+    byte digest[];
+    synchronized (DIGESTER) {
+      digest = DIGESTER.digest(dirName.getBytes());
+    }
+    StringBuilder buf = new StringBuilder();
+    buf.append("ocean-");
+    for (int i = 0; i < digest.length; i++) {
+      int b = digest[i];
+      buf.append(HEX_DIGITS[(b >> 4) & 0xf]);
+      buf.append(HEX_DIGITS[b & 0xf]);
+    }
+    return buf.toString();
+  }
+  
+  public LogDirectory getLogDirectory() {
+    return logDirectory;
+  }
+  
+  public LogDirectory getDirectory() {
+    return rootDirectory;
+  }
+  
+  public Directory create(String name) throws IOException {
+    return FSDirectory.getDirectory(new File(fileDirectory, name));
+  }
+
+  public void delete(String name) throws IOException {
+    FSDirectory directory = get(name);
+    directory.close();
+    File file = directory.getFile();
+    FileUtils.deleteDirectory(file);
+    map.remove(name);
+  }
+
+  public FSDirectory get(String name) throws IOException {
+    return map.get(name);
+  }
+
+  public String[] list() throws IOException {
+    int x = 0;
+    String[] array = new String[map.size()];
+    for (String string : map.keySet()) {
+      array[x] = string;
+      x++;
+    }
+    return array;
+  }
+  
+  
+}
Index: ocean/src/org/apache/lucene/ocean/FSLogDirectory.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/FSLogDirectory.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/FSLogDirectory.java	(revision 0)
@@ -0,0 +1,110 @@
+package org.apache.lucene.ocean;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.lucene.ocean.util.Util;
+
+import com.imagero.uio.RandomAccessInput;
+import com.imagero.uio.bio.BufferedRandomAccessIO;
+import com.imagero.uio.bio.IOController;
+import com.imagero.uio.bio.content.RandomAccessFileContent;
+
+public class FSLogDirectory extends LogDirectory {
+  private File fileDirectory;
+  private Map<String,FSFile> outputMap = new HashMap<String,FSFile>();
+  private ReentrantLock outputLock = new ReentrantLock();
+  private ReentrantLock inputLock = new ReentrantLock();
+
+  public FSLogDirectory(File fileDirectory) {
+    Util.mkdir(fileDirectory);
+    this.fileDirectory = fileDirectory;
+  }
+
+  public static class FSFile {
+    RandomAccessFileContent content;
+    
+    public FSFile(RandomAccessFileContent content) {
+      this.content = content;
+    }
+  }
+  
+  public String[] list() throws IOException {
+    List<String> list = new ArrayList<String>();
+    for (File file : fileDirectory.listFiles()) {
+      if (!file.isDirectory()) {
+        list.add(file.getName());
+      }
+    }
+    return (String[]) list.toArray(new String[0]);
+  }
+
+  public boolean fileExists(String name) throws IOException {
+    File file = new File(fileDirectory, name);
+    return file.exists();
+  }
+
+  public long fileModified(String name) throws IOException {
+    File file = new File(fileDirectory, name);
+    return file.lastModified();
+  }
+
+  public void deleteFile(String name) throws IOException {
+    FSFile fsFile = outputMap.get(name);
+    if (fsFile != null) {
+      fsFile.content.close();
+    }
+    File file = new File(fileDirectory, name);
+    boolean deleted = file.delete();
+    if (!deleted) {
+      throw new IOException("file: "+name+" not deleted");
+    }
+  }
+
+  public long fileLength(String name) throws IOException {
+    File file = new File(fileDirectory, name);
+    return file.length();
+  }
+
+  public RandomAccessInput openInput(String name) throws IOException {
+    inputLock.lock();
+    try {
+      BufferedRandomAccessIO access = getOutput(name, false);
+      return access.createIOChild(0, 0, access.getByteOrder(), false);
+    } finally {
+      inputLock.unlock();
+    }
+  }
+
+  public BufferedRandomAccessIO getOutput(String name, boolean overwrite) throws IOException {
+    outputLock.lock();
+    try {
+      FSFile fsFile = outputMap.get(name);
+      if (fsFile == null) {
+        File file = new File(fileDirectory, name);
+        RandomAccessFileContent content = new RandomAccessFileContent(file);
+        fsFile = new FSFile(content);
+        outputMap.put(name, fsFile);
+      }
+      
+      if (overwrite) {
+        //if (fsFile != null) {
+        //  buffered.close();
+        //  buffered = null;
+       // }
+        //boolean deleted = file.delete();
+
+      }
+      IOController contoller = new IOController(1024 * 16, fsFile.content);
+      BufferedRandomAccessIO buffered = new BufferedRandomAccessIO(contoller);
+      return buffered;
+    } finally {
+      outputLock.unlock();
+    }
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/Index.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/Index.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/Index.java	(revision 0)
@@ -0,0 +1,340 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.lucene.index.CorruptIndexException;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.StaleReaderException;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.index.TermDocs;
+import org.apache.lucene.index.TermEnum;
+import org.apache.lucene.ocean.Deletes.DeleteByQuery;
+import org.apache.lucene.ocean.util.Constants;
+import org.apache.lucene.ocean.util.SortedList;
+import org.apache.lucene.ocean.util.Util;
+import org.apache.lucene.search.ExtendedFieldCache;
+import org.apache.lucene.search.HitCollector;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.ExtendedFieldCache.LongParser;
+
+public abstract class Index {
+  private final IndexID id;
+  protected boolean isClosed = false;
+  private final TransactionSystem transactionSystem;
+  private boolean isReadOnly = false;
+  private boolean isDeleteOnly = false;
+
+  public Index(IndexID id, TransactionSystem transactionSystem) {
+    this.id = id;
+    this.transactionSystem = transactionSystem;
+  }
+  
+  public void removeOldSnapshots(SortedList<Long,? extends IndexSnapshot> snapshotMap) {
+    Long last = snapshotMap.lastKey();
+    for (Iterator<Long> iterator = snapshotMap.keySet().iterator(); iterator.hasNext();) {
+      Long snapshotId = iterator.next(); 
+      if (!transactionSystem.snapshots.contains(snapshotId) && (last != null && !last.equals(snapshotId))) {
+        iterator.remove();
+      }
+    }
+  }
+  
+  public void close() throws IOException {}
+  
+  public TransactionSystem getSystem() {
+    return transactionSystem;
+  }
+
+  public static IndexReader[] getIndexReaders(List<? extends IndexSnapshot> indexSnapshots) {
+    IndexReader[] indexReaders = new IndexReader[indexSnapshots.size()];
+    for (int x = 0; x < indexSnapshots.size(); x++) {
+      indexReaders[x] = indexSnapshots.get(x).getIndexReader();
+    }
+    return indexReaders;
+  }
+
+  public static Long getMaxSnapshotId(List<? extends IndexSnapshot> indexSnapshots) throws Exception {
+    List<Long> snapshotIdList = new ArrayList<Long>(indexSnapshots.size());
+    for (IndexSnapshot indexSnapshot : indexSnapshots) {
+      snapshotIdList.add(indexSnapshot.getMaxSnapshotId());
+    }
+    if (snapshotIdList.size() > 0) {
+      return Collections.max(snapshotIdList);
+    } else {
+      return null;
+    }
+  }
+
+  public static Long getMaxDocumentId(List<? extends IndexSnapshot> indexSnapshots) throws Exception {
+    List<Long> documentIdList = new ArrayList<Long>(indexSnapshots.size());
+    for (IndexSnapshot indexSnapshot : indexSnapshots) {
+      documentIdList.add(indexSnapshot.getMaxDocumentId());
+    }
+    if (documentIdList.size() > 0) {
+      return Collections.max(documentIdList);
+    } else {
+      return null;
+    }
+  }
+
+  public void setDeleteOnly(boolean isDeleteOnly) {
+    this.isDeleteOnly = isDeleteOnly;
+  }
+
+  public boolean isDeleteOnly() {
+    return isDeleteOnly;
+  }
+
+  public boolean isReadOnly() {
+    return isReadOnly;
+  }
+
+  public void setReadOnly(boolean isReadOnly) {
+    this.isReadOnly = isReadOnly;
+  }
+
+  protected DeletesResult applyDeletes(boolean deleteFromReader, Deletes deletes, Collection<Integer> deletedDocs, IndexReader indexReader)
+      throws CorruptIndexException, IOException, Exception {
+    long[] ids = ExtendedFieldCache.EXT_DEFAULT.getLongs(indexReader, Constants.DOCUMENTID, new LongParser() {
+      public long parseLong(String string) {
+        return Util.longFromEncoded(string);
+      }
+    });
+    DeletesResult deletesResult = new DeletesResult(getId());
+    if (deletes.hasDeletes()) {
+      if (deletes.hasDocIds()) {
+        int docsDeleted = 0;
+        long[] docIdsArray = deletes.getDocIds();
+        for (long id : docIdsArray) {
+          int doc = Util.getDoc(Constants.DOCUMENTID, id, indexReader);
+          indexReader.deleteDocument(doc);
+          docsDeleted++;
+        }
+        deletesResult.add(new DeletesResult.Result(docIdsArray, docsDeleted));
+      } else {
+        List<Long> docIds = deletesResult.getDocIds();
+        if (deletes.hasTerms()) {
+          List<Term> terms = deletes.getTerms();
+          for (Term term : terms) {
+            int docsDeleted = deleteByTerm(deleteFromReader, term, deletedDocs, docIds, ids, indexReader);
+            deletesResult.add(new DeletesResult.Result(term, docsDeleted));
+          }
+        }
+        if (deletes.hasDeleteByQueries()) {
+          List<DeleteByQuery> deleteByQueries = deletes.getDeleteByQueries();
+          for (DeleteByQuery deleteByQuery : deleteByQueries) {
+            int docsDeleted = deleteByQuery(deleteFromReader, deleteByQuery, deletedDocs, ids, docIds, indexReader);
+            deletesResult.add(new DeletesResult.Result(deleteByQuery, docsDeleted));
+          }
+        }
+      }
+    }
+    return deletesResult;
+  }
+
+  protected int deleteByTerm(boolean deleteFromReader, Term term, Collection<Integer> deletedDocs, List<Long> docIds, long[] ids,
+      IndexReader indexReader) throws IOException {
+    TermDocs docs = indexReader.termDocs(term);
+    if (docs == null)
+      return 0;
+    int n = 0;
+    try {
+      while (docs.next()) {
+        int doc = docs.doc();
+        if (deletedDocs != null)
+          deletedDocs.add(doc);
+        Long docId = ids[doc];
+        docIds.add(docId);
+        if (deleteFromReader)
+          indexReader.deleteDocument(doc);
+        n++;
+      }
+    } finally {
+      docs.close();
+    }
+    return n;
+  }
+
+  protected int deleteByQuery(final boolean deleteFromReader, DeleteByQuery deleteByQuery, final Collection<Integer> deletedDocs,
+      final long[] ids, final List<Long> deletedIds, final IndexReader indexReader) throws IOException {
+    Query query = deleteByQuery.getQuery();
+    IndexSearcher indexSearcher = new IndexSearcher(indexReader);
+    final int[] numDeleted = new int[1];
+    indexSearcher.search(query, new HitCollector() {
+      public void collect(int doc, float score) {
+        try {
+          if (deleteFromReader)
+            indexReader.deleteDocument(doc);
+          if (deletedDocs != null)
+            deletedDocs.add(doc);
+          Long docId = ids[doc];
+          deletedIds.add(docId);
+          numDeleted[0]++;
+        } catch (StaleReaderException staleReaderException) {
+          throw new RuntimeException(staleReaderException);
+        } catch (IOException ioException) {
+          throw new RuntimeException(ioException);
+        }
+      }
+    });
+    return numDeleted[0];
+  }
+
+  public static class IndexException extends Exception {
+    public IndexException(String message) {
+      super(message);
+    }
+
+    public IndexException(String message, Exception exception) {
+      super(message, exception);
+    }
+  }
+
+  public static class IndexNeverCompletedCopyException extends IndexException {
+    public IndexNeverCompletedCopyException(String message) {
+      super(message);
+    }
+  }
+  
+  public abstract boolean rollback(Long snapshotId) throws Exception;
+  
+  public abstract IndexSnapshot getIndexSnapshot(Long snapshotID);
+
+  public abstract IndexSnapshot getLatestIndexSnapshot();
+
+  public abstract class IndexSnapshot {
+    protected final Long snapshotId;
+    private Long maxSnapshotId;
+    private Long maxDocumentId;
+
+    public IndexSnapshot(Long snapshotId) {
+      this.snapshotId = snapshotId;
+    }
+    
+    public abstract int deletedDoc();
+    
+    public abstract int maxDoc();
+
+    public Long getSnapshotId() {
+      return snapshotId;
+    }
+
+    public Index getIndex() {
+      return Index.this;
+    }
+
+    /**
+     * Iterates terms until next field is reached, returns text
+     * 
+     * @param field
+     * @return
+     * @throws Exception
+     */
+    public String getMax(String field) throws IOException {
+      IndexReader indexReader = getIndexReader();
+      TermEnum termEnum = indexReader.terms(new Term(field, ""));
+      try {
+        String text = null;
+        do {
+          Term term = termEnum.term();
+          if (term == null || term.field() != field)
+            break;
+          text = term.text();
+        } while (termEnum.next());
+        return text;
+      } finally {
+        termEnum.close();
+      }
+    }
+
+    public Long getMaxSnapshotId() throws IOException {
+      if (maxSnapshotId == null) {
+        String string = getMax(Constants.SNAPSHOTID);
+        if (string == null) return null;
+        maxSnapshotId = Util.longFromEncoded(string);
+      }
+      return maxSnapshotId;
+    }
+
+    public Long getMaxDocumentId() throws IOException {
+      if (maxDocumentId == null) {
+        String string = getMax(Constants.DOCUMENTID);
+        if (string == null) return null;
+        maxDocumentId = Util.longFromEncoded(string);
+      }
+      return maxDocumentId;
+    }
+
+    public abstract IndexReader getIndexReader();
+  }
+
+  public static class MergedDocMap {
+    private Map<IndexSnapshot,int[]> oldMap; // maps old doc to new doc
+    private RI[] merged; // maps new doc to old doc and reader
+
+    public MergedDocMap(List<? extends IndexSnapshot> indexSnapshots) {
+      int newMaxDoc = 0;
+      for (IndexSnapshot indexSnapshot : indexSnapshots) {
+        newMaxDoc += indexSnapshot.getIndexReader().numDocs();
+      }
+      oldMap = new HashMap<IndexSnapshot,int[]>(indexSnapshots.size());
+      RI[] merged = new RI[newMaxDoc];
+      int pos = 0;
+      for (IndexSnapshot indexSnapshot : indexSnapshots) {
+        IndexReader indexReader = indexSnapshot.getIndexReader();
+        int maxDoc = indexReader.maxDoc();
+        int[] old = new int[maxDoc];
+        for (int x = 0; x < maxDoc; x++) {
+          if (indexReader.hasDeletions() && indexReader.isDeleted(x)) {
+            merged[pos] = null;
+            old[x] = -1;
+          } else {
+            merged[pos] = new RI(x, indexSnapshot);
+            old[x] = pos;
+            pos++;
+          }
+        }
+        oldMap.put(indexSnapshot, old);
+      }
+    }
+
+    public static class RI {
+      public int doc;
+      public IndexSnapshot oldIndexSnapshot;
+
+      public RI(int doc, IndexSnapshot oldIndexSnapshot) {
+        this.doc = doc;
+        this.oldIndexSnapshot = oldIndexSnapshot;
+      }
+    }
+
+    public Map<IndexSnapshot,int[]> getOldMap() {
+      return oldMap;
+    }
+
+    public RI[] getMerged() {
+      return merged;
+    }
+  }
+
+  public boolean isClosed() {
+    return isClosed;
+  }
+
+  public IndexID getId() {
+    return id;
+  }
+  
+  public abstract void commitNothing(Transaction transaction) throws IndexException, InterruptedException, IOException;
+  
+  public abstract DeletesResult commitDeletes(Deletes deletes, Transaction transaction) throws Exception, IndexException, InterruptedException,
+      IOException;
+}
Index: ocean/src/org/apache/lucene/ocean/IndexCreator.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/IndexCreator.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/IndexCreator.java	(revision 0)
@@ -0,0 +1,142 @@
+package org.apache.lucene.ocean;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.RAMDirectory;
+
+/**
+ * Allows creation of an index using multiple threads by feeding documents into
+ * a BlockingQueue.
+ * 
+ */
+// TODO: after create called make object unusable
+public class IndexCreator {
+  private Directory directory;
+  private long maxSize;
+  private int threads;
+  private ExecutorService threadPool;
+  private IndexWriter indexWriter;
+  private boolean isFinished;
+  private List<Future<Object>> futures = new ArrayList<Future<Object>>();
+  private Analyzer analyzer;
+  private BlockingQueue<Add> queue;
+
+  public IndexCreator(Directory directory, long maxSize, int threads, Analyzer analyzer, ExecutorService threadPool) {
+    this.directory = directory;
+    this.maxSize = maxSize;
+    this.threads = threads;
+    this.analyzer = analyzer;
+    this.threadPool = threadPool;
+    isFinished = false;
+  }
+
+  public static class Add {
+    private Document document;
+
+    // private RAMDirectory ramDirectory;
+
+    // public Add(RAMDirectory ramDirectory) {
+    // this.ramDirectory = ramDirectory;
+    // }
+
+    public Add(Document document) {
+      this.document = document;
+    }
+
+    // public RAMDirectory getRamDirectory() {
+    // return ramDirectory;
+    // }
+
+    public Document getDocument() {
+      return document;
+    }
+  }
+
+  public void start(BlockingQueue<Add> queue) throws Exception {
+    this.queue = queue;
+    indexWriter = new IndexWriter(directory, false, analyzer, true);
+    indexWriter.setUseCompoundFile(true);
+    indexWriter.setRAMBufferSizeMB(500.0); // set impossibly high to never be
+                                            // triggered, setting both to
+                                            // DISABLE_AUTO_FLUSH causes an
+                                            // exception
+    indexWriter.setMaxBufferedDocs(IndexWriter.DISABLE_AUTO_FLUSH);
+    //List<Callable<Object>> callables = new ArrayList<Callable<Object>>(threads);
+    for (int x = 0; x < threads; x++) {
+      //callables.add(new Task(queue));
+      futures.add(threadPool.submit(new Task(queue)));
+    }
+    //futures = threadPool.invokeAll(callables);
+    
+  }
+
+  public void create() throws Exception {
+    while (queue.peek() != null) { 
+      Thread.sleep(5);
+    }
+    setFinished(true);
+    try {
+      for (Future<Object> future : futures) {
+        if (future.isDone()) {
+          try {
+            future.get();
+          } catch (ExecutionException executionException) {
+            Throwable cause = executionException.getCause();
+            if (cause instanceof Exception) {
+              throw (Exception) cause;
+            } else {
+              throw new Exception(cause);
+            }
+          }
+        }
+        Thread.sleep(10);
+      }
+      indexWriter.optimize(); // should not be necessary
+    } finally {
+      indexWriter.close();
+    }
+  }
+
+  public void setFinished(boolean isFinished) {
+    this.isFinished = isFinished;
+  }
+
+  private boolean isFinished() {
+    if (isFinished)
+      return true;
+    if (indexWriter.ramSizeInBytes() >= maxSize) {
+      isFinished = true;
+    }
+    return isFinished;
+  }
+
+  public class Task implements Callable {
+    private BlockingQueue<Add> queue;
+
+    public Task(BlockingQueue<Add> queue) {
+      this.queue = queue;
+    }
+
+    public Object call() throws Exception {
+      while (!isFinished()) {
+        Add add = queue.poll(3, TimeUnit.MILLISECONDS);
+        if (add != null) {
+          Document document = add.getDocument();
+          indexWriter.addDocument(document, analyzer);
+        }
+      }
+      return null;
+    }
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/Indexes.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/Indexes.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/Indexes.java	(revision 0)
@@ -0,0 +1,38 @@
+package org.apache.lucene.ocean;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.lucene.ocean.util.Util;
+
+public class Indexes {
+  private Map<IndexID,Index> indexMap = new HashMap<IndexID,Index>();
+  
+  public IndexID getMaxId(String type) {
+    List<IndexID> list = new ArrayList<IndexID>();
+    for (IndexID indexId : indexMap.keySet()) {
+      if (indexId.type.equals(type)) {
+        list.add(indexId);
+      }
+    }
+    if (list.size() == 0) return null;
+    return Util.max(list);
+  }
+  
+  public List<Index> getIndexes() {
+    return new ArrayList(indexMap.values());
+  }
+  
+  public Indexes() {
+  }
+
+  public Index get(IndexID indexId) {
+    return indexMap.get(indexId);
+  }
+
+  public void add(Index index) {
+    indexMap.put(index.getId(), index);
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/IndexID.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/IndexID.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/IndexID.java	(revision 0)
@@ -0,0 +1,48 @@
+package org.apache.lucene.ocean;
+
+import org.apache.commons.lang.builder.CompareToBuilder;
+
+public class IndexID implements Comparable<IndexID> {
+  public final Long id;
+  public final String type;
+  
+  public IndexID(Long id, String type) {
+    this.id = id;
+    this.type = type;
+  }
+  
+  public int compareTo(IndexID other) {
+    return new CompareToBuilder().append(id, other.id).append(type, type).toComparison();
+  }
+  
+  @Override
+  public int hashCode() {
+    final int prime = 31;
+    int result = 1;
+    result = prime * result + ((id == null) ? 0 : id.hashCode());
+    result = prime * result + ((type == null) ? 0 : type.hashCode());
+    return result;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj)
+      return true;
+    if (obj == null)
+      return false;
+    if (getClass() != obj.getClass())
+      return false;
+    final IndexID other = (IndexID) obj;
+    if (id == null) {
+      if (other.id != null)
+        return false;
+    } else if (!id.equals(other.id))
+      return false;
+    if (type == null) {
+      if (other.type != null)
+        return false;
+    } else if (!type.equals(other.type))
+      return false;
+    return true;
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/log/LogFile.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/log/LogFile.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/log/LogFile.java	(revision 0)
@@ -0,0 +1,200 @@
+package org.apache.lucene.ocean.log;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import org.apache.lucene.ocean.LogDirectory;
+import org.apache.lucene.ocean.log.RawLogFile.FileStreamRecord;
+import org.apache.lucene.ocean.log.RawLogFile.StreamRecord;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.imagero.uio.RandomAccessInput;
+
+/**
+ * Log file.  Contains all record headers for loading of the actual record.
+ *
+ */
+public class LogFile implements Comparable<LogFile> {
+  final static Logger LOG = LoggerFactory.getLogger(LogFile.class);
+  private List<RecordHeader> recordHeaders; // sorted by id
+  private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
+  private RawLogFile rawLogFile;
+  private Long id;
+  
+  LogFile(Long id) {
+    this.id = id;
+  }
+  
+  public LogFile(Long id, String file, LogDirectory logDirectory) throws IOException {
+    this.id = id;
+    rawLogFile = new RawLogFile(file, logDirectory);
+    recordHeaders = rawLogFile.loadRecordHeaders();
+  }
+  
+  public int getNumRecords() {
+    return recordHeaders.size();
+  }
+  
+  public void close() throws IOException {
+    rawLogFile.close();
+  }
+  
+  public long getPreviousId(long id) {
+    long pos = getPosition(id);
+    if (pos == 0) return -1;
+    return pos-1;
+  }
+  
+  public int compareTo(LogFile other) {
+    return (id < other.id ? -1 : (id == other.id ? 0 : 1));
+  }
+
+  private void add(RecordHeader recordHeader) {
+    recordHeaders.add(recordHeader);
+  }
+
+  public long size() throws IOException {
+    return rawLogFile.size();
+  }
+
+  private int getPosition(long id) {
+    RecordHeader h = new RecordHeader();
+    h.id = id;
+    int pos = Collections.binarySearch(recordHeaders, h);
+    if (pos < 0)
+      pos = -1 - pos;
+    return pos;
+  }
+
+  public RecordHeader get(long id) {
+    if (recordHeaders.size() > 0) {
+      int pos = getPosition(id);
+      if (pos >= recordHeaders.size()) return null;
+      return recordHeaders.get(pos);
+    } else return null;
+  }
+
+  public boolean delete(long id) throws IOException {
+    lock.writeLock().lock();
+    try {
+      RecordHeader recordHeader = get(id);
+      boolean deleted = rawLogFile.delete(recordHeader);
+      if (deleted) {
+        int pos = getPosition(id);
+        recordHeaders.remove(pos);
+      }
+      return deleted;
+    } finally {
+      lock.writeLock().unlock();
+    }
+  }
+
+  public long getId() {
+    return id;
+  }
+
+  public long getMinId() {
+    if (recordHeaders.size() == 0)
+      return -1;
+    else
+      return recordHeaders.get(0).id;
+  }
+
+  public boolean containsId(Long id) {
+    if (recordHeaders.size() == 0) return false;
+    return id <= getMaxId() && id >= getMinId();
+  }
+
+  public long getMaxId() {
+    return recordHeaders.get(recordHeaders.size() - 1).id;
+  }
+
+  public List<RecordHeader> getRecordHeaders(long from, int num) {
+    int pos = getPosition(from);
+    int count = 0;
+    List<RecordHeader> recordHeaders = new ArrayList<RecordHeader>(num);
+    while (count < num) {
+      RecordHeader recordHeader = recordHeaders.get(pos);
+      recordHeaders.add(recordHeader);
+      count++;
+    }
+    return recordHeaders;
+  }
+
+  public void commit(RecordHeader recordHeader) {
+    // TODO: commit
+  }
+
+  public RecordHeader writeRecord(Long id, byte[] docBytes, byte[] otherBytes) throws IOException {
+    lock.writeLock().lock();
+    try {
+      RecordHeader recordHeader = rawLogFile.write(id, docBytes, otherBytes);
+      add(recordHeader);
+      return recordHeader;
+    } finally {
+      lock.writeLock().unlock();
+    }
+  }
+
+  public RecordIterator getRecordIterator(Long snapshotId) throws IOException {
+    RandomAccessInput input = rawLogFile.openInput();
+    return new RecordIterator(snapshotId, input);
+  }
+
+  public class RecordIterator {
+    private RandomAccessInput input;
+    int pos;
+
+    public RecordIterator(Long snapshotId, RandomAccessInput input) throws IOException {
+      this.input = input;
+      if (snapshotId == null) {
+        pos = 0;
+      } else {
+        pos = getPosition(snapshotId);
+      }
+    }
+
+    public Record next() throws IOException {
+      RecordHeader recordHeader = recordHeaders.get(pos);
+      if (LOG.isDebugEnabled()) LOG.debug("recordHeader: "+recordHeader);
+      pos++;
+      return getRecord(recordHeader);
+    }
+
+    private Record getRecord(RecordHeader recordHeader) throws IOException {
+      FileStreamRecord fileStreamRecord = rawLogFile.readRecordData(recordHeader, input);
+      Record record = new Record(recordHeader.id, fileStreamRecord);
+      return record;
+    }
+
+    public boolean hasNext() {
+      return pos < recordHeaders.size();
+    }
+
+    public void close() throws IOException {
+      input.close();
+    }
+  }
+
+  public static class Record {
+    private long id;
+    private StreamRecord streamRecord;
+
+    public Record(long id, StreamRecord streamRecord) {
+      this.id = id;
+      this.streamRecord = streamRecord;
+    }
+
+    public Long getId() {
+      return id;
+    }
+
+    public StreamRecord getStreamRecord() {
+      return streamRecord;
+    }
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/log/RecordHeader.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/log/RecordHeader.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/log/RecordHeader.java	(revision 0)
@@ -0,0 +1,23 @@
+package org.apache.lucene.ocean.log;
+
+import org.apache.commons.lang.builder.ReflectionToStringBuilder;
+
+/**
+ * Log record header
+ *
+ */
+public class RecordHeader implements Comparable<RecordHeader> {
+  public long id;
+  public int headerPosition;
+  public int docsLength;
+  public int otherLength;
+  public long docsPosition;
+
+  public int compareTo(RecordHeader other) {
+    return (id < other.id ? -1 : (id == other.id ? 0 : 1));
+  }
+
+  public String toString() {
+    return ReflectionToStringBuilder.toString(this);
+  }
+}
\ No newline at end of file
Index: ocean/src/org/apache/lucene/ocean/log/TransactionLog.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/log/TransactionLog.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/log/TransactionLog.java	(revision 0)
@@ -0,0 +1,134 @@
+package org.apache.lucene.ocean.log;
+
+import java.io.IOException;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.commons.lang.SerializationUtils;
+import org.apache.lucene.ocean.Deletes;
+import org.apache.lucene.ocean.Documents;
+import org.apache.lucene.ocean.LogDirectory;
+import org.apache.lucene.ocean.Batch.MasterBatch;
+import org.apache.lucene.ocean.Batch.SlaveBatch;
+import org.apache.lucene.ocean.log.LogFile.Record;
+import org.apache.lucene.ocean.log.LogFileManager.RecordIterator;
+import org.apache.lucene.ocean.log.RawLogFile.StreamData;
+import org.apache.lucene.ocean.util.ByteBufferPool;
+import org.apache.lucene.ocean.util.LongSequence;
+import org.apache.lucene.store.RAMDirectory;
+
+/**
+ * Serializes transactions known internally as batches to an underlying log
+ * file. Provides an iterator over the batches.
+ * 
+ */
+public class TransactionLog {
+  private ByteBufferPool byteBufferPool = new ByteBufferPool(50 * 1024, 5, 5);
+  LogFileManager logFileManager;
+  private ReentrantLock writeLock = new ReentrantLock();
+  private LogDirectory logDirectory;
+  private LongSequence snapshotIdSequence;
+
+  public TransactionLog(LogDirectory logDirectory) throws IOException {
+    this.logDirectory = logDirectory;
+    logFileManager = new LogFileManager(logDirectory);
+    long maxId = logFileManager.getMaxId();
+    if (maxId == -1) maxId = 0;
+    snapshotIdSequence = new LongSequence(maxId+1, 1);
+  }
+  
+  public void close() throws IOException {
+    logFileManager.close();
+  }
+
+  public void writeMasterBatch(final Long id, final Long previousId, final MasterBatch masterBatch) throws Exception {
+    byte[] docBytes = null;
+    byte[] otherBytes = null;
+    if (masterBatch.hasRAMDirectory()) {
+      docBytes = SerializationUtils.serialize(masterBatch.getRamDirectory());
+    } else if (masterBatch.hasDocuments()) {
+      Documents documents = masterBatch.getDocuments();
+      docBytes = SerializationUtils.serialize(documents);
+    }
+    if (masterBatch.hasDeletes()) {
+      Deletes deletes = masterBatch.getDeletes();
+      otherBytes = SerializationUtils.serialize(deletes);
+    }
+    writeLock.lock();
+    try {
+      logFileManager.writeRecord(id, docBytes, otherBytes);
+    } finally {
+      writeLock.unlock();
+    }
+  }
+  
+  public int getNumRecords() {
+    return logFileManager.getNumRecords();
+  }
+  
+  public long getNextId() {
+    return snapshotIdSequence.getAndIncrement();
+  }
+  
+  public long getMinId() {
+    return logFileManager.getMinId();
+  }
+  
+  public long getMaxId() {
+    return logFileManager.getMaxId();
+  }
+
+  public Long getPreviousId(long id) {
+    return logFileManager.getPreviousId(id);
+  }
+
+  public SlaveBatchIterator getSlaveBatchIterator(Long snapshotId) throws Exception {
+    return new SlaveBatchIterator(snapshotId);
+  }
+
+  public class SlaveBatchIterator {
+    RecordIterator recordIterator;
+
+    public SlaveBatchIterator(Long snapshotId) throws IOException {
+      recordIterator = logFileManager.getRecordIterator(snapshotId);
+    }
+
+    public boolean hasNext() throws IOException {
+      return recordIterator.hasNext();
+    }
+
+    public SlaveBatch next(boolean loadDocuments, boolean loadOther) throws Exception {
+      Record record = recordIterator.next();
+      Documents documents = null;
+      RAMDirectory ramDirectory = null;
+      Deletes deletes = null;
+      if (loadDocuments) {
+        StreamData docData = record.getStreamRecord().getDocuments();
+        if (docData != null) {
+          byte[] docBytes = docData.getBytes();
+          Object object = SerializationUtils.deserialize(docBytes);
+          if (object instanceof RAMDirectory) {
+            ramDirectory = (RAMDirectory) object;
+          } else {
+            documents = (Documents) object;
+          }
+        }
+      }
+      if (loadOther) {
+        StreamData otherData = record.getStreamRecord().getOther();
+        if (otherData != null) {
+          byte[] otherBytes = otherData.getBytes();
+          deletes = (Deletes) SerializationUtils.deserialize(otherBytes);
+        }
+      }
+      if (ramDirectory != null) {
+        return new SlaveBatch(record.getId(), ramDirectory, deletes);
+      } else {
+        return new SlaveBatch(record.getId(), documents, deletes);
+      }
+    }
+
+    public void close() throws IOException {
+      recordIterator.close();
+    }
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/log/RawLogFile.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/log/RawLogFile.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/log/RawLogFile.java	(revision 0)
@@ -0,0 +1,230 @@
+package org.apache.lucene.ocean.log;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.lucene.ocean.LogDirectory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.imagero.uio.RandomAccessIO;
+import com.imagero.uio.RandomAccessInput;
+
+/**
+ * Performs raw file access including serializing records to and from the underlying file
+ *
+ */
+public class RawLogFile {
+  final static Logger LOG = LoggerFactory.getLogger(RawLogFile.class);
+  public static final byte DELETED = 101;
+  public static final byte OK = 9;
+  public static final byte[] HEADER = new byte[] { 'O', 'c', 'N' };
+  private long currentWritePosition = 0;
+  private ReentrantLock writeLock = new ReentrantLock();
+  private String file;
+  private RandomAccessIO output;
+  private LogDirectory logDirectory;
+
+  public RawLogFile(String file, LogDirectory logDirectory) throws IOException {
+    this.file = file;
+    this.logDirectory = logDirectory;
+    output = logDirectory.getOutput(file, false);
+  }
+
+  public RandomAccessInput openInput() throws IOException {
+    return logDirectory.openInput(file);
+  }
+
+  public long size() throws IOException {
+    return output.length();
+  }
+
+  public static interface StreamRecord {
+    public StreamData getDocuments();
+
+    public StreamData getOther();
+  }
+
+  public static interface StreamData {
+    public byte[] getBytes() throws IOException;
+
+    public int getLength();
+  }
+
+  public static class FileStreamRecord implements StreamRecord {
+    private RecordHeader recordHeader;
+    private RandomAccessInput input;
+
+    public FileStreamRecord(RecordHeader recordHeader, RandomAccessInput input) {
+      this.recordHeader = recordHeader;
+      this.input = input;
+    }
+
+    public FileStreamData getDocuments() {
+      if (recordHeader.docsLength == 0) return null;
+      return new FileStreamData(recordHeader.docsLength, recordHeader.docsPosition, input);
+    }
+
+    public FileStreamData getOther() {
+      if (recordHeader.otherLength == 0) return null;
+      return new FileStreamData(recordHeader.otherLength, recordHeader.docsPosition + recordHeader.docsLength, input);
+    }
+  }
+
+  public static class FileStreamData implements StreamData {
+    private int length;
+    private long position;
+    private RandomAccessInput input;
+
+    public FileStreamData(int length, long position, RandomAccessInput input) {
+      this.length = length;
+      this.position = position;
+      this.input = input;
+    }
+
+    public byte[] getBytes() throws IOException {
+      input.seek(position);
+      byte[] bytes = new byte[length];
+      input.readFully(bytes);
+      return bytes;
+    }
+
+    public int getLength() {
+      return length;
+    }
+  }
+
+  public FileStreamRecord readRecordData(RecordHeader recordHeader, RandomAccessInput input) {
+    return new FileStreamRecord(recordHeader, input);
+  }
+
+  public List<RecordHeader> loadRecordHeaders() throws IOException {
+    RandomAccessInput input = openInput();
+    if (input.length() > 0) {
+      LoadRecordHeaders loadRecordHeaders = new LoadRecordHeaders(input);
+      return loadRecordHeaders.getRecordHeaders();
+    } else {
+      return new ArrayList<RecordHeader>();
+    }
+  }
+
+  public boolean delete(RecordHeader recordHeader) throws IOException {
+    long lastPosition = -1;
+    writeLock.lock();
+    try {
+      lastPosition = output.getFilePointer();
+      output.seek(recordHeader.headerPosition);
+      byte[] header = new byte[3];
+      output.readFully(header, 0, header.length);
+      if (!Arrays.equals(HEADER, header)) {
+        throw new IOException("no header");
+      }
+      byte first = (byte) output.readByte();
+      if (first == OK) {
+        int nextHeaderPosition = output.readInt();
+        long id = output.readLong();
+        assert id == recordHeader.id;
+        output.seek(recordHeader.headerPosition + 4);
+        output.writeByte(DELETED);
+        output.flush();
+        return true;
+      } else {
+        // already deleted
+        return false;
+      }
+    } finally {
+      if (lastPosition != -1) output.seek(lastPosition);
+      writeLock.unlock();
+    }
+  }
+
+  public class LoadRecordHeaders {
+    private long currentReadPosition = 0;
+    private List<RecordHeader> recordHeaders = new ArrayList<RecordHeader>();
+
+    public LoadRecordHeaders(RandomAccessInput input) throws IOException {
+      try {
+        while (true) {
+          input.seek(currentReadPosition);
+          RecordHeader recordHeader = new RecordHeader();
+          byte[] header = new byte[3];
+          recordHeader.headerPosition = (int) input.getFilePointer();
+          input.readFully(header, 0, header.length);
+          if (!Arrays.equals(HEADER, header)) {
+            // TODO: scan to next header if there is one
+            LOG.error("header incorrect");
+            while (true) {
+              input.readFully(header, 0, header.length);
+              if (Arrays.equals(HEADER, header)) {
+                break;
+              }
+            }
+            // continue;
+          }
+          byte status = input.readByte();
+          recordHeader.id = input.readLong();
+          if (status == DELETED) {
+            // TODO: skip it
+            if (LOG.isDebugEnabled()) LOG.debug("record "+recordHeader.id+" status deleted");
+          }
+          recordHeader.docsLength = input.readInt();
+          recordHeader.otherLength = input.readInt();
+          recordHeader.docsPosition = input.getFilePointer();
+          if (status != DELETED)
+            recordHeaders.add(recordHeader);
+          currentReadPosition = input.getFilePointer() + recordHeader.docsLength + recordHeader.otherLength;
+        }
+      } catch (EOFException eofException) {
+        // at end of file
+      } finally {
+        if (input != null)
+          input.close();
+      }
+    }
+
+    public List<RecordHeader> getRecordHeaders() {
+      return recordHeaders;
+    }
+  }
+
+  // TODO: add read to footer to insure the record was fully written
+  // TODO: handle write failure cleanly
+  public RecordHeader write(Long id, byte[] docBytes, byte[] otherBytes) throws IOException {
+    writeLock.lock();
+    try {
+      int documentsLength = 0;
+      if (docBytes != null) documentsLength = docBytes.length;
+      int otherLength = 0;
+      if (otherBytes != null) otherLength = otherBytes.length;
+      RecordHeader recordHeader = new RecordHeader();
+      recordHeader.id = id;
+      recordHeader.headerPosition = (int) currentWritePosition;
+      recordHeader.docsLength = documentsLength;
+      recordHeader.otherLength = otherLength;
+
+      output.seek(currentWritePosition);
+      output.write(HEADER);
+      output.writeByte(OK);
+      output.writeLong(id);
+      output.writeInt(recordHeader.docsLength);
+      output.writeInt(recordHeader.otherLength);
+      recordHeader.docsPosition = output.getFilePointer();
+      if (docBytes != null) output.write(docBytes);
+      if (otherBytes != null) output.write(otherBytes);
+      output.flush();
+      currentWritePosition = output.getFilePointer();
+      return recordHeader;
+    } finally {
+      writeLock.unlock();
+    }
+  }
+
+  public void close() throws IOException {
+    output.close();
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/log/LogFileManager.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/log/LogFileManager.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/log/LogFileManager.java	(revision 0)
@@ -0,0 +1,266 @@
+package org.apache.lucene.ocean.log;
+
+import java.io.IOException;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.lucene.ocean.LogDirectory;
+import org.apache.lucene.ocean.log.LogFile.Record;
+import org.apache.lucene.ocean.util.LongSequence;
+
+/**
+ * Manages the log files. There is one active log file at a time that updates
+ * are written to. When the active log file reaches the MAX_FILE_SIZE a new
+ * active log file is created.
+ * 
+ */
+// TODO: delete log files that are no longer needed
+public class LogFileManager {
+  public static Logger log = Logger.getLogger(LogFileManager.class.getName());
+  public static final long MAX_FILE_SIZE = 1024 * 1024 * 64;
+  private List<LogFile> logFiles = new ArrayList<LogFile>();
+  private LongSequence logIdSequence = new LongSequence(1, 1);
+  private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
+  private ScheduledExecutorService logFileCheckTimer;
+  private LogDirectory logDirectory;
+
+  // TODO: load existing max snapshot id
+  public LogFileManager(LogDirectory logDirectory) throws IOException {
+    this.logDirectory = logDirectory;
+    String[] list = logDirectory.list();
+    for (String file : list) {
+      long id = getLogFileNumber(file);
+      LogFile logFile = new LogFile(id, file, logDirectory);
+      logFiles.add(logFile);
+    }
+    Collections.sort(logFiles);
+    if (logFiles.size() > 0) {
+      long last = logFiles.get(logFiles.size() - 1).getId();
+      long next = last + 1;
+      logIdSequence.set(next);
+    }
+    logFileCheckTimer = Executors.newSingleThreadScheduledExecutor();
+    logFileCheckTimer.scheduleWithFixedDelay(new LogFileSizeCheck(), 1000, 10 * 1000, TimeUnit.MILLISECONDS);
+  }
+
+  public int getNumRecords() {
+    int num = 0;
+    for (LogFile logFile : logFiles) {
+      num += logFile.getNumRecords();
+    }
+    return num;
+  }
+
+  public void close() throws IOException {
+    for (LogFile logFile : logFiles) {
+      logFile.close();
+    }
+  }
+
+  private int getPosition(long id) {
+    LogFile lf = new LogFile(id);
+    int pos = Collections.binarySearch(logFiles, lf);
+    if (pos < 0)
+      pos = -1 - pos;
+    return pos;
+  }
+
+  public long getMinSnapshotId() {
+    if (logFiles.size() > 0) {
+      LogFile logFile = logFiles.get(0);
+      return logFile.getMinId();
+    } else {
+      return -1;
+    }
+  }
+
+  public long getMaxSnapshotId() {
+    if (logFiles.size() > 0) {
+      LogFile logFile = logFiles.get(logFiles.size() - 1);
+      return logFile.getMaxId();
+    } else {
+      return -1;
+    }
+  }
+
+  public long getMaxId() {
+    if (logFiles.size() == 0)
+      return -1;
+    return logFiles.get(logFiles.size() - 1).getMaxId();
+  }
+
+  private LogFile getLast() {
+    if (logFiles.size() == 0)
+      return null;
+    return logFiles.get(logFiles.size() - 1);
+  }
+
+  public class LogFileSizeCheck implements Runnable {
+    public void run() {
+      try {
+        LogFile logFile = getLast();
+        if (logFile != null) {
+          if (logFile.size() >= MAX_FILE_SIZE) {
+            LogFile newLogFile = createNewLogFile();
+            logFiles.add(newLogFile);
+          }
+        }
+      } catch (IOException ioException) {
+        log.log(Level.SEVERE, "", ioException);
+      }
+    }
+  }
+
+  public boolean delete(Long id) throws Exception {
+    LogFile logFile = getLogFileContaining(id);
+    if (logFile == null)
+      throw new Exception("unknown id: " + id);
+    return logFile.delete(id);
+  }
+
+  public Long getPreviousId(Long id) {
+    LogFile logFile = getLogFileContaining(id);
+    if (logFile == null)
+      return null;
+    int pos = logFiles.indexOf(logFile);
+    Long previousId = logFile.getPreviousId(id);
+    if (previousId == null && pos > 0) {
+      logFile = logFiles.get(pos - 1);
+      previousId = logFile.getPreviousId(id);
+    }
+    return previousId;
+  }
+
+  public long getMinId() {
+    if (logFiles.size() > 0) {
+      return logFiles.get(0).getMinId();
+    }
+    return -1;
+  }
+
+  public LogFile createNewLogFile() throws IOException {
+    long id = logIdSequence.getAndIncrement();
+    String fileName = createLogFileName(id);
+    LogFile logFile = new LogFile(id, fileName, logDirectory);
+    lock.writeLock().lock();
+    try {
+      logFiles.add(logFile);
+      return logFile;
+    } finally {
+      lock.writeLock().unlock();
+    }
+  }
+
+  private static String createLogFileName(long value) {
+    DecimalFormat decimalFormat = (DecimalFormat) DecimalFormat.getInstance();
+    decimalFormat.applyPattern("00000000");
+    return "log" + decimalFormat.format(value) + ".bin";
+  }
+
+  private static long getLogFileNumber(String fileName) {
+    String numberString = fileName.substring(3, fileName.lastIndexOf('.'));
+    return Long.parseLong(numberString);
+  }
+
+  private LogFile getLogFileContaining(Long id) {
+    lock.readLock().lock();
+    try {
+      for (LogFile logFile : logFiles) {
+        if (logFile.containsId(id)) {
+          return logFile;
+        }
+      }
+      return null;
+    } finally {
+      lock.readLock().unlock();
+    }
+  }
+
+  public boolean contains(Long id) {
+    lock.readLock().lock();
+    try {
+      for (LogFile logFile : logFiles) {
+        if (logFile.containsId(id)) {
+          return true;
+        }
+      }
+      return false;
+    } finally {
+      lock.readLock().unlock();
+    }
+  }
+
+  public RecordIterator getRecordIterator(Long snapshotId) throws IOException {
+    return new RecordIterator(snapshotId);
+  }
+
+  public class RecordIterator {
+    private Iterator<LogFile> logFileIterator;
+    private LogFile.RecordIterator currentRecordIterator;
+
+    public RecordIterator(Long snapshotId) throws IOException {
+      logFileIterator = logFiles.iterator();
+      while (logFileIterator.hasNext()) {
+        LogFile logFile = logFileIterator.next();
+        if (snapshotId == null || logFile.containsId(snapshotId)) {
+          currentRecordIterator = logFile.getRecordIterator(snapshotId);
+        }
+      }
+    }
+
+    public void close() throws IOException {
+      if (currentRecordIterator != null)
+        currentRecordIterator.close();
+    }
+
+    public Record next() throws IOException {
+      if (currentRecordIterator != null) {
+        return currentRecordIterator.next();
+      } else {
+        return null;
+      }
+    }
+
+    public boolean hasNext() throws IOException {
+      if (currentRecordIterator == null)
+        return false;
+      if (currentRecordIterator.hasNext()) {
+        return true;
+      } else {
+        currentRecordIterator.close();
+        if (logFileIterator.hasNext()) {
+          LogFile logFile = logFileIterator.next();
+          currentRecordIterator = logFile.getRecordIterator(null);
+          return hasNext();
+        }
+      }
+      return false;
+    }
+  }
+
+  public void commit(RecordHeader recordHeader) {
+    // TODO: implement commit
+  }
+
+  public RecordHeader writeRecord(Long id, byte[] doc, byte[] other) throws IOException {
+    LogFile logFile = getCurrentLogFile();
+    return logFile.writeRecord(id, doc, other);
+  }
+
+  public LogFile getCurrentLogFile() throws IOException {
+    LogFile logFile = getLast();
+    if (logFile == null) {
+      logFile = createNewLogFile();
+    }
+    return logFile;
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/log/LogFile.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/log/LogFile.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/log/LogFile.java	(revision 0)
@@ -0,0 +1,200 @@
+package org.apache.lucene.ocean.log;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import org.apache.lucene.ocean.LogDirectory;
+import org.apache.lucene.ocean.log.RawLogFile.FileStreamRecord;
+import org.apache.lucene.ocean.log.RawLogFile.StreamRecord;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.imagero.uio.RandomAccessInput;
+
+/**
+ * Log file.  Contains all record headers for loading of the actual record.
+ *
+ */
+public class LogFile implements Comparable<LogFile> {
+  final static Logger LOG = LoggerFactory.getLogger(LogFile.class);
+  private List<RecordHeader> recordHeaders; // sorted by id
+  private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
+  private RawLogFile rawLogFile;
+  private Long id;
+  
+  LogFile(Long id) {
+    this.id = id;
+  }
+  
+  public LogFile(Long id, String file, LogDirectory logDirectory) throws IOException {
+    this.id = id;
+    rawLogFile = new RawLogFile(file, logDirectory);
+    recordHeaders = rawLogFile.loadRecordHeaders();
+  }
+  
+  public int getNumRecords() {
+    return recordHeaders.size();
+  }
+  
+  public void close() throws IOException {
+    rawLogFile.close();
+  }
+  
+  public long getPreviousId(long id) {
+    long pos = getPosition(id);
+    if (pos == 0) return -1;
+    return pos-1;
+  }
+  
+  public int compareTo(LogFile other) {
+    return (id < other.id ? -1 : (id == other.id ? 0 : 1));
+  }
+
+  private void add(RecordHeader recordHeader) {
+    recordHeaders.add(recordHeader);
+  }
+
+  public long size() throws IOException {
+    return rawLogFile.size();
+  }
+
+  private int getPosition(long id) {
+    RecordHeader h = new RecordHeader();
+    h.id = id;
+    int pos = Collections.binarySearch(recordHeaders, h);
+    if (pos < 0)
+      pos = -1 - pos;
+    return pos;
+  }
+
+  public RecordHeader get(long id) {
+    if (recordHeaders.size() > 0) {
+      int pos = getPosition(id);
+      if (pos >= recordHeaders.size()) return null;
+      return recordHeaders.get(pos);
+    } else return null;
+  }
+
+  public boolean delete(long id) throws IOException {
+    lock.writeLock().lock();
+    try {
+      RecordHeader recordHeader = get(id);
+      boolean deleted = rawLogFile.delete(recordHeader);
+      if (deleted) {
+        int pos = getPosition(id);
+        recordHeaders.remove(pos);
+      }
+      return deleted;
+    } finally {
+      lock.writeLock().unlock();
+    }
+  }
+
+  public long getId() {
+    return id;
+  }
+
+  public long getMinId() {
+    if (recordHeaders.size() == 0)
+      return -1;
+    else
+      return recordHeaders.get(0).id;
+  }
+
+  public boolean containsId(Long id) {
+    if (recordHeaders.size() == 0) return false;
+    return id <= getMaxId() && id >= getMinId();
+  }
+
+  public long getMaxId() {
+    return recordHeaders.get(recordHeaders.size() - 1).id;
+  }
+
+  public List<RecordHeader> getRecordHeaders(long from, int num) {
+    int pos = getPosition(from);
+    int count = 0;
+    List<RecordHeader> recordHeaders = new ArrayList<RecordHeader>(num);
+    while (count < num) {
+      RecordHeader recordHeader = recordHeaders.get(pos);
+      recordHeaders.add(recordHeader);
+      count++;
+    }
+    return recordHeaders;
+  }
+
+  public void commit(RecordHeader recordHeader) {
+    // TODO: commit
+  }
+
+  public RecordHeader writeRecord(Long id, byte[] docBytes, byte[] otherBytes) throws IOException {
+    lock.writeLock().lock();
+    try {
+      RecordHeader recordHeader = rawLogFile.write(id, docBytes, otherBytes);
+      add(recordHeader);
+      return recordHeader;
+    } finally {
+      lock.writeLock().unlock();
+    }
+  }
+
+  public RecordIterator getRecordIterator(Long snapshotId) throws IOException {
+    RandomAccessInput input = rawLogFile.openInput();
+    return new RecordIterator(snapshotId, input);
+  }
+
+  public class RecordIterator {
+    private RandomAccessInput input;
+    int pos;
+
+    public RecordIterator(Long snapshotId, RandomAccessInput input) throws IOException {
+      this.input = input;
+      if (snapshotId == null) {
+        pos = 0;
+      } else {
+        pos = getPosition(snapshotId);
+      }
+    }
+
+    public Record next() throws IOException {
+      RecordHeader recordHeader = recordHeaders.get(pos);
+      if (LOG.isDebugEnabled()) LOG.debug("recordHeader: "+recordHeader);
+      pos++;
+      return getRecord(recordHeader);
+    }
+
+    private Record getRecord(RecordHeader recordHeader) throws IOException {
+      FileStreamRecord fileStreamRecord = rawLogFile.readRecordData(recordHeader, input);
+      Record record = new Record(recordHeader.id, fileStreamRecord);
+      return record;
+    }
+
+    public boolean hasNext() {
+      return pos < recordHeaders.size();
+    }
+
+    public void close() throws IOException {
+      input.close();
+    }
+  }
+
+  public static class Record {
+    private long id;
+    private StreamRecord streamRecord;
+
+    public Record(long id, StreamRecord streamRecord) {
+      this.id = id;
+      this.streamRecord = streamRecord;
+    }
+
+    public Long getId() {
+      return id;
+    }
+
+    public StreamRecord getStreamRecord() {
+      return streamRecord;
+    }
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/log/LogFileManager.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/log/LogFileManager.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/log/LogFileManager.java	(revision 0)
@@ -0,0 +1,266 @@
+package org.apache.lucene.ocean.log;
+
+import java.io.IOException;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.lucene.ocean.LogDirectory;
+import org.apache.lucene.ocean.log.LogFile.Record;
+import org.apache.lucene.ocean.util.LongSequence;
+
+/**
+ * Manages the log files. There is one active log file at a time that updates
+ * are written to. When the active log file reaches the MAX_FILE_SIZE a new
+ * active log file is created.
+ * 
+ */
+// TODO: delete log files that are no longer needed
+public class LogFileManager {
+  public static Logger log = Logger.getLogger(LogFileManager.class.getName());
+  public static final long MAX_FILE_SIZE = 1024 * 1024 * 64;
+  private List<LogFile> logFiles = new ArrayList<LogFile>();
+  private LongSequence logIdSequence = new LongSequence(1, 1);
+  private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
+  private ScheduledExecutorService logFileCheckTimer;
+  private LogDirectory logDirectory;
+
+  // TODO: load existing max snapshot id
+  public LogFileManager(LogDirectory logDirectory) throws IOException {
+    this.logDirectory = logDirectory;
+    String[] list = logDirectory.list();
+    for (String file : list) {
+      long id = getLogFileNumber(file);
+      LogFile logFile = new LogFile(id, file, logDirectory);
+      logFiles.add(logFile);
+    }
+    Collections.sort(logFiles);
+    if (logFiles.size() > 0) {
+      long last = logFiles.get(logFiles.size() - 1).getId();
+      long next = last + 1;
+      logIdSequence.set(next);
+    }
+    logFileCheckTimer = Executors.newSingleThreadScheduledExecutor();
+    logFileCheckTimer.scheduleWithFixedDelay(new LogFileSizeCheck(), 1000, 10 * 1000, TimeUnit.MILLISECONDS);
+  }
+
+  public int getNumRecords() {
+    int num = 0;
+    for (LogFile logFile : logFiles) {
+      num += logFile.getNumRecords();
+    }
+    return num;
+  }
+
+  public void close() throws IOException {
+    for (LogFile logFile : logFiles) {
+      logFile.close();
+    }
+  }
+
+  private int getPosition(long id) {
+    LogFile lf = new LogFile(id);
+    int pos = Collections.binarySearch(logFiles, lf);
+    if (pos < 0)
+      pos = -1 - pos;
+    return pos;
+  }
+
+  public long getMinSnapshotId() {
+    if (logFiles.size() > 0) {
+      LogFile logFile = logFiles.get(0);
+      return logFile.getMinId();
+    } else {
+      return -1;
+    }
+  }
+
+  public long getMaxSnapshotId() {
+    if (logFiles.size() > 0) {
+      LogFile logFile = logFiles.get(logFiles.size() - 1);
+      return logFile.getMaxId();
+    } else {
+      return -1;
+    }
+  }
+
+  public long getMaxId() {
+    if (logFiles.size() == 0)
+      return -1;
+    return logFiles.get(logFiles.size() - 1).getMaxId();
+  }
+
+  private LogFile getLast() {
+    if (logFiles.size() == 0)
+      return null;
+    return logFiles.get(logFiles.size() - 1);
+  }
+
+  public class LogFileSizeCheck implements Runnable {
+    public void run() {
+      try {
+        LogFile logFile = getLast();
+        if (logFile != null) {
+          if (logFile.size() >= MAX_FILE_SIZE) {
+            LogFile newLogFile = createNewLogFile();
+            logFiles.add(newLogFile);
+          }
+        }
+      } catch (IOException ioException) {
+        log.log(Level.SEVERE, "", ioException);
+      }
+    }
+  }
+
+  public boolean delete(Long id) throws Exception {
+    LogFile logFile = getLogFileContaining(id);
+    if (logFile == null)
+      throw new Exception("unknown id: " + id);
+    return logFile.delete(id);
+  }
+
+  public Long getPreviousId(Long id) {
+    LogFile logFile = getLogFileContaining(id);
+    if (logFile == null)
+      return null;
+    int pos = logFiles.indexOf(logFile);
+    Long previousId = logFile.getPreviousId(id);
+    if (previousId == null && pos > 0) {
+      logFile = logFiles.get(pos - 1);
+      previousId = logFile.getPreviousId(id);
+    }
+    return previousId;
+  }
+
+  public long getMinId() {
+    if (logFiles.size() > 0) {
+      return logFiles.get(0).getMinId();
+    }
+    return -1;
+  }
+
+  public LogFile createNewLogFile() throws IOException {
+    long id = logIdSequence.getAndIncrement();
+    String fileName = createLogFileName(id);
+    LogFile logFile = new LogFile(id, fileName, logDirectory);
+    lock.writeLock().lock();
+    try {
+      logFiles.add(logFile);
+      return logFile;
+    } finally {
+      lock.writeLock().unlock();
+    }
+  }
+
+  private static String createLogFileName(long value) {
+    DecimalFormat decimalFormat = (DecimalFormat) DecimalFormat.getInstance();
+    decimalFormat.applyPattern("00000000");
+    return "log" + decimalFormat.format(value) + ".bin";
+  }
+
+  private static long getLogFileNumber(String fileName) {
+    String numberString = fileName.substring(3, fileName.lastIndexOf('.'));
+    return Long.parseLong(numberString);
+  }
+
+  private LogFile getLogFileContaining(Long id) {
+    lock.readLock().lock();
+    try {
+      for (LogFile logFile : logFiles) {
+        if (logFile.containsId(id)) {
+          return logFile;
+        }
+      }
+      return null;
+    } finally {
+      lock.readLock().unlock();
+    }
+  }
+
+  public boolean contains(Long id) {
+    lock.readLock().lock();
+    try {
+      for (LogFile logFile : logFiles) {
+        if (logFile.containsId(id)) {
+          return true;
+        }
+      }
+      return false;
+    } finally {
+      lock.readLock().unlock();
+    }
+  }
+
+  public RecordIterator getRecordIterator(Long snapshotId) throws IOException {
+    return new RecordIterator(snapshotId);
+  }
+
+  public class RecordIterator {
+    private Iterator<LogFile> logFileIterator;
+    private LogFile.RecordIterator currentRecordIterator;
+
+    public RecordIterator(Long snapshotId) throws IOException {
+      logFileIterator = logFiles.iterator();
+      while (logFileIterator.hasNext()) {
+        LogFile logFile = logFileIterator.next();
+        if (snapshotId == null || logFile.containsId(snapshotId)) {
+          currentRecordIterator = logFile.getRecordIterator(snapshotId);
+        }
+      }
+    }
+
+    public void close() throws IOException {
+      if (currentRecordIterator != null)
+        currentRecordIterator.close();
+    }
+
+    public Record next() throws IOException {
+      if (currentRecordIterator != null) {
+        return currentRecordIterator.next();
+      } else {
+        return null;
+      }
+    }
+
+    public boolean hasNext() throws IOException {
+      if (currentRecordIterator == null)
+        return false;
+      if (currentRecordIterator.hasNext()) {
+        return true;
+      } else {
+        currentRecordIterator.close();
+        if (logFileIterator.hasNext()) {
+          LogFile logFile = logFileIterator.next();
+          currentRecordIterator = logFile.getRecordIterator(null);
+          return hasNext();
+        }
+      }
+      return false;
+    }
+  }
+
+  public void commit(RecordHeader recordHeader) {
+    // TODO: implement commit
+  }
+
+  public RecordHeader writeRecord(Long id, byte[] doc, byte[] other) throws IOException {
+    LogFile logFile = getCurrentLogFile();
+    return logFile.writeRecord(id, doc, other);
+  }
+
+  public LogFile getCurrentLogFile() throws IOException {
+    LogFile logFile = getLast();
+    if (logFile == null) {
+      logFile = createNewLogFile();
+    }
+    return logFile;
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/log/RawLogFile.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/log/RawLogFile.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/log/RawLogFile.java	(revision 0)
@@ -0,0 +1,230 @@
+package org.apache.lucene.ocean.log;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.lucene.ocean.LogDirectory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.imagero.uio.RandomAccessIO;
+import com.imagero.uio.RandomAccessInput;
+
+/**
+ * Performs raw file access including serializing records to and from the underlying file
+ *
+ */
+public class RawLogFile {
+  final static Logger LOG = LoggerFactory.getLogger(RawLogFile.class);
+  public static final byte DELETED = 101;
+  public static final byte OK = 9;
+  public static final byte[] HEADER = new byte[] { 'O', 'c', 'N' };
+  private long currentWritePosition = 0;
+  private ReentrantLock writeLock = new ReentrantLock();
+  private String file;
+  private RandomAccessIO output;
+  private LogDirectory logDirectory;
+
+  public RawLogFile(String file, LogDirectory logDirectory) throws IOException {
+    this.file = file;
+    this.logDirectory = logDirectory;
+    output = logDirectory.getOutput(file, false);
+  }
+
+  public RandomAccessInput openInput() throws IOException {
+    return logDirectory.openInput(file);
+  }
+
+  public long size() throws IOException {
+    return output.length();
+  }
+
+  public static interface StreamRecord {
+    public StreamData getDocuments();
+
+    public StreamData getOther();
+  }
+
+  public static interface StreamData {
+    public byte[] getBytes() throws IOException;
+
+    public int getLength();
+  }
+
+  public static class FileStreamRecord implements StreamRecord {
+    private RecordHeader recordHeader;
+    private RandomAccessInput input;
+
+    public FileStreamRecord(RecordHeader recordHeader, RandomAccessInput input) {
+      this.recordHeader = recordHeader;
+      this.input = input;
+    }
+
+    public FileStreamData getDocuments() {
+      if (recordHeader.docsLength == 0) return null;
+      return new FileStreamData(recordHeader.docsLength, recordHeader.docsPosition, input);
+    }
+
+    public FileStreamData getOther() {
+      if (recordHeader.otherLength == 0) return null;
+      return new FileStreamData(recordHeader.otherLength, recordHeader.docsPosition + recordHeader.docsLength, input);
+    }
+  }
+
+  public static class FileStreamData implements StreamData {
+    private int length;
+    private long position;
+    private RandomAccessInput input;
+
+    public FileStreamData(int length, long position, RandomAccessInput input) {
+      this.length = length;
+      this.position = position;
+      this.input = input;
+    }
+
+    public byte[] getBytes() throws IOException {
+      input.seek(position);
+      byte[] bytes = new byte[length];
+      input.readFully(bytes);
+      return bytes;
+    }
+
+    public int getLength() {
+      return length;
+    }
+  }
+
+  public FileStreamRecord readRecordData(RecordHeader recordHeader, RandomAccessInput input) {
+    return new FileStreamRecord(recordHeader, input);
+  }
+
+  public List<RecordHeader> loadRecordHeaders() throws IOException {
+    RandomAccessInput input = openInput();
+    if (input.length() > 0) {
+      LoadRecordHeaders loadRecordHeaders = new LoadRecordHeaders(input);
+      return loadRecordHeaders.getRecordHeaders();
+    } else {
+      return new ArrayList<RecordHeader>();
+    }
+  }
+
+  public boolean delete(RecordHeader recordHeader) throws IOException {
+    long lastPosition = -1;
+    writeLock.lock();
+    try {
+      lastPosition = output.getFilePointer();
+      output.seek(recordHeader.headerPosition);
+      byte[] header = new byte[3];
+      output.readFully(header, 0, header.length);
+      if (!Arrays.equals(HEADER, header)) {
+        throw new IOException("no header");
+      }
+      byte first = (byte) output.readByte();
+      if (first == OK) {
+        int nextHeaderPosition = output.readInt();
+        long id = output.readLong();
+        assert id == recordHeader.id;
+        output.seek(recordHeader.headerPosition + 4);
+        output.writeByte(DELETED);
+        output.flush();
+        return true;
+      } else {
+        // already deleted
+        return false;
+      }
+    } finally {
+      if (lastPosition != -1) output.seek(lastPosition);
+      writeLock.unlock();
+    }
+  }
+
+  public class LoadRecordHeaders {
+    private long currentReadPosition = 0;
+    private List<RecordHeader> recordHeaders = new ArrayList<RecordHeader>();
+
+    public LoadRecordHeaders(RandomAccessInput input) throws IOException {
+      try {
+        while (true) {
+          input.seek(currentReadPosition);
+          RecordHeader recordHeader = new RecordHeader();
+          byte[] header = new byte[3];
+          recordHeader.headerPosition = (int) input.getFilePointer();
+          input.readFully(header, 0, header.length);
+          if (!Arrays.equals(HEADER, header)) {
+            // TODO: scan to next header if there is one
+            LOG.error("header incorrect");
+            while (true) {
+              input.readFully(header, 0, header.length);
+              if (Arrays.equals(HEADER, header)) {
+                break;
+              }
+            }
+            // continue;
+          }
+          byte status = input.readByte();
+          recordHeader.id = input.readLong();
+          if (status == DELETED) {
+            // TODO: skip it
+            if (LOG.isDebugEnabled()) LOG.debug("record "+recordHeader.id+" status deleted");
+          }
+          recordHeader.docsLength = input.readInt();
+          recordHeader.otherLength = input.readInt();
+          recordHeader.docsPosition = input.getFilePointer();
+          if (status != DELETED)
+            recordHeaders.add(recordHeader);
+          currentReadPosition = input.getFilePointer() + recordHeader.docsLength + recordHeader.otherLength;
+        }
+      } catch (EOFException eofException) {
+        // at end of file
+      } finally {
+        if (input != null)
+          input.close();
+      }
+    }
+
+    public List<RecordHeader> getRecordHeaders() {
+      return recordHeaders;
+    }
+  }
+
+  // TODO: add read to footer to insure the record was fully written
+  // TODO: handle write failure cleanly
+  public RecordHeader write(Long id, byte[] docBytes, byte[] otherBytes) throws IOException {
+    writeLock.lock();
+    try {
+      int documentsLength = 0;
+      if (docBytes != null) documentsLength = docBytes.length;
+      int otherLength = 0;
+      if (otherBytes != null) otherLength = otherBytes.length;
+      RecordHeader recordHeader = new RecordHeader();
+      recordHeader.id = id;
+      recordHeader.headerPosition = (int) currentWritePosition;
+      recordHeader.docsLength = documentsLength;
+      recordHeader.otherLength = otherLength;
+
+      output.seek(currentWritePosition);
+      output.write(HEADER);
+      output.writeByte(OK);
+      output.writeLong(id);
+      output.writeInt(recordHeader.docsLength);
+      output.writeInt(recordHeader.otherLength);
+      recordHeader.docsPosition = output.getFilePointer();
+      if (docBytes != null) output.write(docBytes);
+      if (otherBytes != null) output.write(otherBytes);
+      output.flush();
+      currentWritePosition = output.getFilePointer();
+      return recordHeader;
+    } finally {
+      writeLock.unlock();
+    }
+  }
+
+  public void close() throws IOException {
+    output.close();
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/log/RecordHeader.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/log/RecordHeader.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/log/RecordHeader.java	(revision 0)
@@ -0,0 +1,23 @@
+package org.apache.lucene.ocean.log;
+
+import org.apache.commons.lang.builder.ReflectionToStringBuilder;
+
+/**
+ * Log record header
+ *
+ */
+public class RecordHeader implements Comparable<RecordHeader> {
+  public long id;
+  public int headerPosition;
+  public int docsLength;
+  public int otherLength;
+  public long docsPosition;
+
+  public int compareTo(RecordHeader other) {
+    return (id < other.id ? -1 : (id == other.id ? 0 : 1));
+  }
+
+  public String toString() {
+    return ReflectionToStringBuilder.toString(this);
+  }
+}
\ No newline at end of file
Index: ocean/src/org/apache/lucene/ocean/log/TransactionLog.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/log/TransactionLog.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/log/TransactionLog.java	(revision 0)
@@ -0,0 +1,134 @@
+package org.apache.lucene.ocean.log;
+
+import java.io.IOException;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.commons.lang.SerializationUtils;
+import org.apache.lucene.ocean.Deletes;
+import org.apache.lucene.ocean.Documents;
+import org.apache.lucene.ocean.LogDirectory;
+import org.apache.lucene.ocean.Batch.MasterBatch;
+import org.apache.lucene.ocean.Batch.SlaveBatch;
+import org.apache.lucene.ocean.log.LogFile.Record;
+import org.apache.lucene.ocean.log.LogFileManager.RecordIterator;
+import org.apache.lucene.ocean.log.RawLogFile.StreamData;
+import org.apache.lucene.ocean.util.ByteBufferPool;
+import org.apache.lucene.ocean.util.LongSequence;
+import org.apache.lucene.store.RAMDirectory;
+
+/**
+ * Serializes transactions known internally as batches to an underlying log
+ * file. Provides an iterator over the batches.
+ * 
+ */
+public class TransactionLog {
+  private ByteBufferPool byteBufferPool = new ByteBufferPool(50 * 1024, 5, 5);
+  LogFileManager logFileManager;
+  private ReentrantLock writeLock = new ReentrantLock();
+  private LogDirectory logDirectory;
+  private LongSequence snapshotIdSequence;
+
+  public TransactionLog(LogDirectory logDirectory) throws IOException {
+    this.logDirectory = logDirectory;
+    logFileManager = new LogFileManager(logDirectory);
+    long maxId = logFileManager.getMaxId();
+    if (maxId == -1) maxId = 0;
+    snapshotIdSequence = new LongSequence(maxId+1, 1);
+  }
+  
+  public void close() throws IOException {
+    logFileManager.close();
+  }
+
+  public void writeMasterBatch(final Long id, final Long previousId, final MasterBatch masterBatch) throws Exception {
+    byte[] docBytes = null;
+    byte[] otherBytes = null;
+    if (masterBatch.hasRAMDirectory()) {
+      docBytes = SerializationUtils.serialize(masterBatch.getRamDirectory());
+    } else if (masterBatch.hasDocuments()) {
+      Documents documents = masterBatch.getDocuments();
+      docBytes = SerializationUtils.serialize(documents);
+    }
+    if (masterBatch.hasDeletes()) {
+      Deletes deletes = masterBatch.getDeletes();
+      otherBytes = SerializationUtils.serialize(deletes);
+    }
+    writeLock.lock();
+    try {
+      logFileManager.writeRecord(id, docBytes, otherBytes);
+    } finally {
+      writeLock.unlock();
+    }
+  }
+  
+  public int getNumRecords() {
+    return logFileManager.getNumRecords();
+  }
+  
+  public long getNextId() {
+    return snapshotIdSequence.getAndIncrement();
+  }
+  
+  public long getMinId() {
+    return logFileManager.getMinId();
+  }
+  
+  public long getMaxId() {
+    return logFileManager.getMaxId();
+  }
+
+  public Long getPreviousId(long id) {
+    return logFileManager.getPreviousId(id);
+  }
+
+  public SlaveBatchIterator getSlaveBatchIterator(Long snapshotId) throws Exception {
+    return new SlaveBatchIterator(snapshotId);
+  }
+
+  public class SlaveBatchIterator {
+    RecordIterator recordIterator;
+
+    public SlaveBatchIterator(Long snapshotId) throws IOException {
+      recordIterator = logFileManager.getRecordIterator(snapshotId);
+    }
+
+    public boolean hasNext() throws IOException {
+      return recordIterator.hasNext();
+    }
+
+    public SlaveBatch next(boolean loadDocuments, boolean loadOther) throws Exception {
+      Record record = recordIterator.next();
+      Documents documents = null;
+      RAMDirectory ramDirectory = null;
+      Deletes deletes = null;
+      if (loadDocuments) {
+        StreamData docData = record.getStreamRecord().getDocuments();
+        if (docData != null) {
+          byte[] docBytes = docData.getBytes();
+          Object object = SerializationUtils.deserialize(docBytes);
+          if (object instanceof RAMDirectory) {
+            ramDirectory = (RAMDirectory) object;
+          } else {
+            documents = (Documents) object;
+          }
+        }
+      }
+      if (loadOther) {
+        StreamData otherData = record.getStreamRecord().getOther();
+        if (otherData != null) {
+          byte[] otherBytes = otherData.getBytes();
+          deletes = (Deletes) SerializationUtils.deserialize(otherBytes);
+        }
+      }
+      if (ramDirectory != null) {
+        return new SlaveBatch(record.getId(), ramDirectory, deletes);
+      } else {
+        return new SlaveBatch(record.getId(), documents, deletes);
+      }
+    }
+
+    public void close() throws IOException {
+      recordIterator.close();
+    }
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/LogDirectory.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/LogDirectory.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/LogDirectory.java	(revision 0)
@@ -0,0 +1,22 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+
+import com.imagero.uio.RandomAccessIO;
+import com.imagero.uio.RandomAccessInput;
+
+public abstract class LogDirectory {
+  public abstract String[] list() throws IOException;
+
+  public abstract boolean fileExists(String name) throws IOException;
+
+  public abstract long fileModified(String name) throws IOException;
+
+  public abstract void deleteFile(String name) throws IOException;
+
+  public abstract long fileLength(String name) throws IOException;
+  
+  public abstract RandomAccessInput openInput(String name) throws IOException;
+  
+  public abstract RandomAccessIO getOutput(String name, boolean overwrite) throws IOException;
+}
Index: ocean/src/org/apache/lucene/ocean/OceanConsole.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/OceanConsole.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/OceanConsole.java	(revision 0)
@@ -0,0 +1,11 @@
+package org.apache.lucene.ocean;
+
+/**
+ * Display indexes
+ * Display index snapshots
+ * Run merge
+ *
+ */
+public class OceanConsole {
+
+}
Index: ocean/src/org/apache/lucene/ocean/OceanInstantiatedIndexReader.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/OceanInstantiatedIndexReader.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/OceanInstantiatedIndexReader.java	(revision 0)
@@ -0,0 +1,31 @@
+package org.apache.lucene.ocean;
+
+import java.util.Set;
+
+import org.apache.lucene.store.instantiated.InstantiatedIndex;
+import org.apache.lucene.store.instantiated.InstantiatedIndexReader;
+
+/**
+ * Simulates a multiple version IndexReader with InstantiatedIndexReader by
+ * having documents over the set maxDoc be deleted.  
+ *
+ */
+public class OceanInstantiatedIndexReader extends InstantiatedIndexReader {
+  private int maxDoc;
+  private Set<Integer> deletedDocs;
+  
+  public OceanInstantiatedIndexReader(int maxDoc, InstantiatedIndex index, Set<Integer> deletedDocs) {
+    super(index);
+    this.maxDoc = maxDoc;
+  }
+  
+  public boolean isDeleted(int n) {
+    if (n > maxDoc) return true;
+    if (deletedDocs != null && deletedDocs.contains(n)) return true;
+    return false;
+  }
+  
+  public boolean hasDeletions() {
+    return true;
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/OceanSearcher.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/OceanSearcher.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/OceanSearcher.java	(revision 0)
@@ -0,0 +1,18 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+
+import org.apache.lucene.search.MultiSearcher;
+
+public class OceanSearcher extends MultiSearcher {
+  Snapshot snapshot;
+  
+  public OceanSearcher(Snapshot snapshot) throws IOException {
+    super(snapshot.getSearchers());
+    this.snapshot = snapshot;
+  }
+  
+  public Snapshot getSnapshot() {
+    return snapshot;
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/RAMDirectoryMap.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/RAMDirectoryMap.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/RAMDirectoryMap.java	(revision 0)
@@ -0,0 +1,51 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.RAMDirectory;
+
+public class RAMDirectoryMap extends DirectoryMap {
+  private Map<String,RAMDirectory> map = new HashMap<String,RAMDirectory>();
+  private RAMLogDirectory directory;
+  private RAMLogDirectory logDirectory;
+  
+  public RAMDirectoryMap() {
+    directory = new RAMLogDirectory();
+    logDirectory = new RAMLogDirectory();
+  }
+  
+  public LogDirectory getLogDirectory() {
+    return logDirectory;
+  }
+  
+  public Directory create(String name) throws IOException {
+    RAMDirectory dir = new RAMDirectory();
+    map.put(name, dir);
+    return dir;
+  }
+  
+  public LogDirectory getDirectory() {
+    return directory;
+  }
+  
+  public void delete(String name) throws IOException {
+    map.remove(name);
+  }
+
+  public Directory get(String name) throws IOException {
+    return map.get(name);
+  }
+
+  public String[] list() throws IOException {
+    int x = 0;
+    String[] array = new String[map.size()];
+    for (String string : map.keySet()) {
+      array[x] = string;
+      x++;
+    }
+    return array;
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/RamDirectorySerializer.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/RamDirectorySerializer.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/RamDirectorySerializer.java	(revision 0)
@@ -0,0 +1,54 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+
+import org.apache.lucene.store.IndexInput;
+import org.apache.lucene.store.IndexOutput;
+import org.apache.lucene.store.RAMDirectory;
+
+public class RamDirectorySerializer {
+	static final int BUFFER_SIZE = 16384;
+	
+  public RamDirectorySerializer() {
+  }
+  
+  public static RAMDirectory deserialize(IndexInput input) throws IOException {
+  	int numFiles = input.readVInt();
+  	RAMDirectory ramDirectory = new RAMDirectory();
+  	byte[] buffer = new byte[BUFFER_SIZE];
+  	for (int x=0; x < numFiles; x++) {
+  		String file = input.readString();
+  		int length = input.readVInt();
+  		IndexOutput indexOutput = ramDirectory.createOutput(file);
+  		int readCount = 0;
+      while (readCount < length) {
+        int toRead = readCount + BUFFER_SIZE > length ? (int)(length - readCount) : BUFFER_SIZE;
+        input.readBytes(buffer, 0, toRead);//readFully(buffer, 0, toRead);
+        indexOutput.writeBytes(buffer, toRead);
+        readCount += toRead;
+      }
+      indexOutput.close();
+  	}
+  	return ramDirectory;
+  }
+  
+  public static void serialize(RAMDirectory ramDirectory, IndexOutput output) throws IOException {
+  	String[] files = ramDirectory.list();
+  	output.writeVInt(files.length);
+  	byte[] buffer = new byte[BUFFER_SIZE];
+  	for (String file : files) {
+  		int length = (int)ramDirectory.fileLength(file);
+  		output.writeString(file);
+  		output.writeVInt(length);
+  		IndexInput indexInput = ramDirectory.openInput(file);
+  		int readCount = 0;
+      while (readCount < length) {
+        int toRead = readCount + BUFFER_SIZE > length ? (int)(length - readCount) : BUFFER_SIZE;
+        indexInput.readBytes(buffer, 0, toRead);
+        output.writeBytes(buffer, toRead);//write(buffer, 0, toRead);
+        readCount += toRead;
+      }
+      indexInput.close();
+  	}
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/RamIndex.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/RamIndex.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/RamIndex.java	(revision 0)
@@ -0,0 +1,110 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.KeepOnlyLastCommitDeletionPolicy;
+import org.apache.lucene.index.SerialMergeScheduler;
+import org.apache.lucene.ocean.WriteableMemoryIndex.MemoryIndexSnapshot;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.RAMDirectory;
+
+public class RamIndex extends DirectoryIndex {
+	private RAMDirectory ramDirectory;
+	private Long maxSnapshotId;
+	private Long maxDocumentId;
+  
+	public RamIndex(IndexID id, List<? extends IndexSnapshot> indexSnapshots, TransactionSystem system) throws Exception {
+		super(id, system);
+		ramDirectory = new RAMDirectory();
+		IndexReader[] indexReaders = getIndexReaders(indexSnapshots);
+		RAMDirectory ramDirectory = new RAMDirectory();
+		IndexWriter indexWriter = new IndexWriter(ramDirectory, false, system.getDefaultAnalyzer(), true);
+		indexWriter.setMergeScheduler(new SerialMergeScheduler());
+		indexWriter.setUseCompoundFile(true);
+		indexWriter.addIndexes(indexReaders);
+		indexWriter.close();
+		maxSnapshotId = getMaxSnapshotId(indexSnapshots);
+		maxDocumentId = getMaxDocumentId(indexSnapshots);
+	}
+	
+	// TODO: add timestamp so ramindex can be removed from indices
+	public RamIndex(IndexID id, Long snapshotId, List<Deletes> deletesList, RAMDirectory ramDirectory, TransactionSystem system) throws Exception {
+		super(id, system);
+		this.ramDirectory = ramDirectory;
+		initialIndexReader = IndexReader.open(ramDirectory, indexDeletionPolicy);
+		createNewSnapshot(snapshotId, initialIndexReader);
+		if (deletesList != null) {
+			for (Deletes deletes : deletesList) {
+				applyDeletes(true, deletes, null, initialIndexReader);
+			}
+		}
+	}
+  
+	// converts memoryIndexSnapshot into ramindexsnapshot
+	public RamIndex(IndexID id, MemoryIndexSnapshot memoryIndexSnapshot) throws Exception, IOException {
+		super(id, memoryIndexSnapshot.getIndex().getSystem());
+		this.maxSnapshotId = memoryIndexSnapshot.getMaxSnapshotId();
+		this.maxDocumentId = memoryIndexSnapshot.getMaxDocumentId();
+		ramDirectory = new RAMDirectory();
+		Analyzer defaultAnalyzer = memoryIndexSnapshot.getIndex().getSystem().getDefaultAnalyzer();
+		IndexWriter indexWriter = new IndexWriter(ramDirectory, false, defaultAnalyzer, true, new KeepOnlyLastCommitDeletionPolicy());
+		indexWriter.addIndexes(new IndexReader[] {memoryIndexSnapshot.getIndexReader()});
+		indexWriter.close();
+		initialIndexReader = IndexReader.open(ramDirectory, indexDeletionPolicy);
+		List<IndexSnapshot> indexSnapshots = new ArrayList<IndexSnapshot>(1);
+		indexSnapshots.add(memoryIndexSnapshot);
+		createNewSnapshot(memoryIndexSnapshot.getSnapshotId(), initialIndexReader);
+	}
+  
+	public RamIndexSnapshot commitIndex(Transaction transaction) throws IndexException, InterruptedException, IOException {
+		try {
+			transaction.ready(this);
+			if (transaction.go()) {
+				Long snapshotID = transaction.getId();
+				RamIndexSnapshot indexSnapshot = createNewSnapshot(snapshotID, initialIndexReader);
+				return indexSnapshot;
+			} else {
+				// if commit fails this snapshot and ramindex won't make it
+				return null;
+			}
+		} catch (Throwable throwable) {
+			LOG.error("", throwable);
+			transaction.failed(this, throwable);
+			return null;
+		}
+	}
+	
+	protected RamIndexSnapshot createNewSnapshot(Long snapshotId, IndexReader newIndexReader) throws IOException {
+		RamIndexSnapshot ramIndexSnapshot = new RamIndexSnapshot(snapshotId, newIndexReader);
+		registerSnapshot(ramIndexSnapshot);
+		return ramIndexSnapshot;
+	}
+
+	public class RamIndexSnapshot extends DirectoryIndexSnapshot {
+		public RamIndexSnapshot(Long snapshotId, IndexReader indexReader) {
+			super(snapshotId, indexReader);
+		}
+		
+		public String toString() {
+		  return "RamIndexSnapshot index: "+RamIndex.this.getId()+" snapshotid: "+snapshotId;
+		}
+		
+		//public Long getMaxSnapshotId() throws IOException {
+		//	return maxSnapshotId;
+		//}
+
+		//public Long getMaxDocumentId() throws IOException {
+		//	return maxDocumentId;
+		//}
+	}
+
+	public Directory getDirectory() {
+		return ramDirectory;
+	}
+}
Index: ocean/src/org/apache/lucene/ocean/RAMLogDirectory.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/RAMLogDirectory.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/RAMLogDirectory.java	(revision 0)
@@ -0,0 +1,66 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.imagero.uio.RandomAccessIO;
+import com.imagero.uio.RandomAccessInput;
+import com.imagero.uio.bio.ByteArrayRandomAccessIO;
+
+public class RAMLogDirectory extends LogDirectory {
+  private Map<String,RAMLogFile> map = new HashMap<String,RAMLogFile>();
+  
+  public static class RAMLogFile {
+    ByteArrayRandomAccessIO randomAccess;
+    long modified;
+    
+    public RAMLogFile() {
+      randomAccess = new ByteArrayRandomAccessIO(1024);
+      modified = System.currentTimeMillis();
+    }
+  }
+  
+  public String[] list() throws IOException {
+    int x = 0;
+    String[] array = new String[map.size()];
+    for (String string : map.keySet()) {
+      array[x] = string;
+      x++;
+    }
+    return array;
+  }
+
+  public boolean fileExists(String name) throws IOException {
+    return map.containsKey(name);
+  }
+
+  public long fileModified(String name) throws IOException {
+    RAMLogFile ramLogFile = map.get(name);
+    return ramLogFile.modified;
+  }
+
+  public void deleteFile(String name) throws IOException {
+    map.remove(name);
+  }
+
+  public long fileLength(String name) throws IOException {
+    RAMLogFile ramLogFile = map.get(name);
+    return ramLogFile.randomAccess.length();
+  }
+  
+  public RandomAccessIO getOutput(String name, boolean overwrite) throws IOException {
+    RAMLogFile ramLogFile = map.get(name);
+    if (ramLogFile == null || overwrite) {
+      ramLogFile = new RAMLogFile();
+      map.put(name, ramLogFile);
+    }
+    ramLogFile.randomAccess.seek(0);
+    return ramLogFile.randomAccess;
+  }
+  
+  public RandomAccessInput openInput(String name) throws IOException {
+    RAMLogFile ramLogFile = map.get(name);
+    return ramLogFile.randomAccess.createInputChild(0, 0, ramLogFile.randomAccess.getByteOrder(), false);
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/Snapshot.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/Snapshot.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/Snapshot.java	(revision 0)
@@ -0,0 +1,280 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.TreeMap;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.ocean.DiskIndex.DiskIndexSnapshot;
+import org.apache.lucene.ocean.Index.IndexSnapshot;
+import org.apache.lucene.ocean.RamIndex.RamIndexSnapshot;
+import org.apache.lucene.ocean.SnapshotInfo.IndexInfo;
+import org.apache.lucene.ocean.WriteableMemoryIndex.MemoryIndexSnapshot;
+import org.apache.lucene.ocean.util.SortedList;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.MultiSearcher;
+import org.apache.lucene.search.Searcher;
+
+public class Snapshot implements Comparable<Snapshot> {
+  private BigDecimal id;
+  private SortedList<IndexID,IndexSnapshot> indexSnapshotMap;
+  private MemoryIndexSnapshot writeableSnapshot;
+  private IndexReader indexReader;
+  private int maxDoc;
+  private int[] starts;
+  private TransactionSystem system;
+  private long timestamp;
+
+  public Snapshot(Long snapshotId, int minorVersion, MemoryIndexSnapshot writeableSnapshot, List<IndexSnapshot> indexSnapshots,
+      TransactionSystem system, long timestamp) throws IOException {
+    id = toId(snapshotId, minorVersion);
+    this.writeableSnapshot = writeableSnapshot;
+    this.system = system;
+    this.timestamp = timestamp;
+    assert snapshotIdsMatch(indexSnapshots);
+    indexSnapshotMap = new SortedList<IndexID,IndexSnapshot>();
+    IndexReader[] readerArray = new IndexReader[indexSnapshots.size()];
+    int x = 0;
+    for (IndexSnapshot indexSnapshot : indexSnapshots) {
+      indexSnapshotMap.put(indexSnapshot.getIndex().getId(), indexSnapshot);
+      readerArray[x] = indexSnapshot.getIndexReader();
+      x++;
+    }
+    IndexSnapshot[] indexSnapshotsArray = indexSnapshotMap.values().toArray(new IndexSnapshot[0]);
+    // build starts array
+    int[] starts = new int[indexSnapshotsArray.length + 1];
+    for (int i = 0; i < indexSnapshotsArray.length; i++) {
+      starts[i] = maxDoc;
+      maxDoc += indexSnapshotsArray[i].maxDoc(); // compute maxDocs
+    }
+    starts[indexSnapshotsArray.length] = maxDoc;
+  }
+
+  public long getTimestamp() {
+    return timestamp;
+  }
+
+  public int compareTo(Snapshot other) {
+    return id.compareTo(other.id);
+  }
+
+  public Searcher getSearcher() throws IOException {
+    MultiSearcher multiSearcher = new MultiSearcher(getSearchers());
+    return multiSearcher;
+  }
+
+  public Searcher[] getSearchers() {
+    IndexSnapshot[] indexSnapshots = (IndexSnapshot[]) indexSnapshotMap.values().toArray(new IndexSnapshot[0]);
+    Searcher[] searchers = new Searcher[indexSnapshotMap.size()];
+    for (int x = 0; x < indexSnapshots.length; x++) {
+      searchers[x] = new IndexSearcher(indexSnapshots[x].getIndexReader());
+    }
+    return searchers;
+  }
+
+  public IndexReader getIndexReader() {
+    return indexReader;
+  }
+
+  public int maxDoc() {
+    return maxDoc;
+  }
+
+  public int[] getStarts() {
+    return starts;
+  }
+
+  public List<RamIndexSnapshot> getRamIndexSnapshots() {
+    List<RamIndexSnapshot> ramIndexSnapshots = new ArrayList<RamIndexSnapshot>();
+    for (IndexSnapshot indexSnapshot : indexSnapshotMap.values()) {
+      if (indexSnapshot instanceof RamIndexSnapshot) {
+        ramIndexSnapshots.add((RamIndexSnapshot) indexSnapshot);
+      }
+    }
+    return ramIndexSnapshots;
+  }
+
+  public IndexReader[] getIndexReaders() {
+    IndexReader[] indexReaders = new IndexReader[indexSnapshotMap.size()];
+    int i = 0;
+    for (IndexSnapshot indexSnapshot : indexSnapshotMap.values()) {
+      indexReaders[i] = indexSnapshot.getIndexReader();
+      i++;
+    }
+    return indexReaders;
+  }
+
+  public int getMaxDoc() {
+    int maxDoc = 0;
+    for (IndexSnapshot indexSnapshot : indexSnapshotMap.values()) {
+      maxDoc += indexSnapshot.getIndexReader().maxDoc();
+    }
+    return maxDoc;
+  }
+
+  public int getMinorVersion() {
+    return getMinorVersion(id);
+  }
+
+  public static void main(String[] args) {
+    BigDecimal id = toId(210l, 1);
+    String string = formatId(id);
+    System.out.println(string);
+  }
+
+  public static BigDecimal toId(Long snapshotId, int minorVersion) {
+    StringBuilder builder = new StringBuilder();
+    builder.append(snapshotId);
+    builder.append(".");
+    if (10 > minorVersion)
+      builder.append("0");
+    builder.append(minorVersion);
+    BigDecimal value = new BigDecimal(builder.toString());
+    return value;
+  }
+
+  public static int getMinorVersion(BigDecimal value) {
+    value = value.subtract(new BigDecimal(value.longValue()));
+    BigDecimal decimal = value.scaleByPowerOfTen(2);
+    return decimal.intValue();
+  }
+
+  public static boolean snapshotIdsMatch(Collection<IndexSnapshot> indexSnapshots) {
+    Long current = null;
+    for (IndexSnapshot indexSnapshot : indexSnapshots) {
+      if (current == null) {
+        current = indexSnapshot.getSnapshotId();
+      } else if (!current.equals(indexSnapshot.getSnapshotId())) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  public Snapshot(BigDecimal id, MemoryIndexSnapshot writeableSnapshot, Collection<IndexSnapshot> indexSnapshots, TransactionSystem system,
+      long timestamp) throws IOException {
+    this.id = id;
+    this.writeableSnapshot = writeableSnapshot;
+    this.timestamp = timestamp;
+    assert snapshotIdsMatch(indexSnapshots);
+    indexSnapshotMap = new SortedList<IndexID,IndexSnapshot>();
+    for (IndexSnapshot indexSnapshot : indexSnapshots) {
+      indexSnapshotMap.put(indexSnapshot.getIndex().getId(), indexSnapshot);
+    }
+  }
+
+  public SnapshotInfo getSnapshotInfo() throws IOException {
+    SnapshotInfo snapshotInfo = new SnapshotInfo(id);
+    for (IndexSnapshot indexSnapshot : indexSnapshotMap.values()) {
+      Index index = indexSnapshot.getIndex();
+      String type = null;
+      Long segmentGeneration = null;
+      if (index instanceof DiskIndex) {
+        segmentGeneration = indexSnapshot.getIndexReader().getIndexCommit().getGeneration();
+        type = "disk";
+      } else if (index instanceof WriteableMemoryIndex)
+        type = "memory";
+      else if (index instanceof RamIndex)
+        type = "ram";
+      IndexInfo indexInfo = new IndexInfo(index.getId().id, segmentGeneration, type, indexSnapshot.maxDoc(), indexSnapshot.deletedDoc(), indexSnapshot.getMaxDocumentId(), indexSnapshot.getMaxSnapshotId());
+      snapshotInfo.add(indexInfo);
+    }
+    return snapshotInfo;
+  }
+
+  public static String formatId(BigDecimal id) {
+    DecimalFormat format = new DecimalFormat("##0.00");
+    return format.format(id);
+  }
+
+  public static String getFileName(BigDecimal id) {
+    String string = formatId(id);
+    String replaced = string.replace('.', '_');
+    return "snapshot_" + replaced + ".xml";
+  }
+
+  /**
+   * Create minor snapshot (meaning a merged snapshot with no real index
+   * changes) reusing the existing writeableSnapshot.
+   * 
+   * @param removeIndexIds
+   * @param newIndexSnapshot
+   * @return
+   * @throws IOException
+   */
+  public Snapshot createMinor(List<IndexID> removeIndexIds, IndexSnapshot newIndexSnapshot) throws IOException {
+    return createMinor(removeIndexIds, writeableSnapshot, newIndexSnapshot);
+  }
+
+  public Snapshot createMinor(List<IndexID> removeIndexIds, MemoryIndexSnapshot writeableSnapshot, IndexSnapshot newIndexSnapshot)
+      throws IOException {
+    HashMap<IndexID,IndexSnapshot> mapCopy = new HashMap<IndexID,IndexSnapshot>(indexSnapshotMap);
+    for (IndexID indexid : removeIndexIds) {
+      mapCopy.remove(indexid);
+    }
+    IndexID newIndexId = newIndexSnapshot.getIndex().getId();
+    assert !mapCopy.containsKey(newIndexId);
+    mapCopy.put(newIndexId, newIndexSnapshot);
+    int minorVersion = getMinorVersion();
+    Long snapshotId = getSnapshotId();
+    int newMinorVersion = minorVersion + 1;
+    System.out.println("snapshotId: " + snapshotId + " newMinorVersion: " + newMinorVersion);
+    // BigDecimal newId = toId(snapshotId, minorVersion);
+    Snapshot newSnapshot = new Snapshot(snapshotId, newMinorVersion, writeableSnapshot, new ArrayList(mapCopy.values()), system, System
+        .currentTimeMillis());
+    return newSnapshot;
+  }
+
+  public List<DiskIndex> getDiskIndices() {
+    List<DiskIndex> diskIndices = new ArrayList<DiskIndex>();
+    for (IndexSnapshot indexSnapshot : indexSnapshotMap.values()) {
+      indexSnapshot.getIndex();
+    }
+    return diskIndices;
+  }
+
+  public List<Index> getDeleteOnlyIndices() {
+    HashMap<IndexID,IndexSnapshot> mapCopy = new HashMap<IndexID,IndexSnapshot>(indexSnapshotMap);
+    mapCopy.remove(writeableSnapshot.getIndex().getId());
+    List<Index> indices = new ArrayList<Index>();
+    for (IndexSnapshot indexSnapshot : mapCopy.values()) {
+      indices.add(indexSnapshot.getIndex());
+    }
+    return indices;
+  }
+
+  public MemoryIndexSnapshot getWriteableSnapshot() {
+    return writeableSnapshot;
+  }
+
+  public boolean containsIndex(long indexid) {
+    return indexSnapshotMap.containsKey(indexid);
+  }
+
+  public List<DiskIndexSnapshot> getDiskIndexSnapshots() {
+    List<DiskIndexSnapshot> diskIndexSnapshots = new ArrayList<DiskIndexSnapshot>();
+    for (IndexSnapshot indexSnapshot : indexSnapshotMap.values()) {
+      if (indexSnapshot instanceof DiskIndexSnapshot) {
+        diskIndexSnapshots.add((DiskIndexSnapshot) indexSnapshot);
+      }
+    }
+    return diskIndexSnapshots;
+  }
+
+  public List<IndexSnapshot> getIndexSnapshots() {
+    return new ArrayList(indexSnapshotMap.values());
+  }
+
+  public Long getSnapshotId() {
+    return id.longValue();
+  }
+
+  public BigDecimal getId() {
+    return id;
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/SnapshotInfo.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/SnapshotInfo.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/SnapshotInfo.java	(revision 0)
@@ -0,0 +1,151 @@
+package org.apache.lucene.ocean;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.lucene.ocean.util.CElement;
+import org.apache.lucene.ocean.util.XMLUtil;
+import org.jdom.Element;
+
+import com.imagero.uio.RandomAccessIO;
+
+public class SnapshotInfo implements CElement, Comparable<SnapshotInfo> {
+  private BigDecimal id;
+  private Map<IndexID,IndexInfo> indexInfos;
+
+  public SnapshotInfo(BigDecimal id) {
+    assert id != null;
+    this.id = id;
+    indexInfos = new HashMap<IndexID,IndexInfo>();
+  }
+  
+  public IndexInfo getIndexInfo(IndexID id) {
+    return indexInfos.get(id);
+  }
+  
+  public int compareTo(SnapshotInfo other) {
+    return id.compareTo(other.id);
+  }
+  
+  public void add(IndexInfo indexInfo) {
+    indexInfos.put(indexInfo.getIndexID(), indexInfo);
+  }
+
+  public SnapshotInfo(Element element) {
+    indexInfos = new HashMap<IndexID,IndexInfo>();
+    id = new BigDecimal(element.getAttributeValue("id"));
+    for (Element indexElement : XMLUtil.getChildren("index", element)) {
+      IndexInfo indexInfo = new IndexInfo(indexElement);
+      indexInfos.put(indexInfo.getIndexID(), indexInfo);
+    }
+  }
+
+  public Long getSnapshotId() {
+    return id.longValue();
+  }
+
+  public BigDecimal getId() {
+    return id;
+  }
+
+  public Collection<IndexInfo> getIndexInfos() {
+    return indexInfos.values();
+  }
+
+  public SnapshotInfo(BigDecimal id, Map<IndexID,IndexInfo> indexInfos) {
+    this.id = id;
+    this.indexInfos = indexInfos;
+  }
+
+  public static class IndexInfo implements CElement {
+    private Long id;
+    private Long segmentGeneration;
+    private String type;
+    private int maxDoc;
+    private int deletedDoc;
+    private Long maxDocumentId;
+    private Long maxSnapshotId;
+
+    public IndexInfo(Long id, Long segmentGeneration, String type, int maxDoc, int deletedDoc, Long maxDocumentId, Long maxSnapshotId) {
+      this.id = id;
+      this.segmentGeneration = segmentGeneration;
+      this.type = type;
+      this.maxDoc = maxDoc;
+      this.deletedDoc = deletedDoc;
+      this.maxDocumentId = maxDocumentId;
+      this.maxSnapshotId = maxSnapshotId;
+    }
+    
+    public IndexID getIndexID() {
+      return new IndexID(id, type);
+    }
+    
+    public IndexInfo(Element element) {
+      id = XMLUtil.getAttributeLong("id", element);
+      segmentGeneration = XMLUtil.getAttributeLong("segmentGeneration", element);
+      type = XMLUtil.getAttributeString("type", element);
+      maxDoc = XMLUtil.getAttributeInteger("maxDoc", element);
+      deletedDoc = XMLUtil.getAttributeInteger("deletedDoc", element);
+      maxDocumentId = XMLUtil.getAttributeLong("maxDocumentId", element);
+      maxSnapshotId = XMLUtil.getAttributeLong("maxSnapshotId", element);
+    }
+    
+    public Long getMaxSnapshotId() {
+      return maxSnapshotId;
+    }
+    
+    public Long getMaxDocumentId() {
+      return maxDocumentId;
+    }
+    
+    public int getDeletedDoc() {
+      return deletedDoc;
+    }
+    
+    public Long getSegmentGeneration() {
+      return segmentGeneration;
+    }
+    
+    public int getMaxDoc() {
+      return maxDoc;
+    }
+    
+    public Long getId() {
+      return id;
+    }
+
+    public String getType() {
+      return type;
+    }
+
+    public Element toElement() {
+      Element element = new Element("index");
+      XMLUtil.setAttribute("id", id, element);
+      XMLUtil.setAttribute("segmentGeneration", segmentGeneration, element);
+      XMLUtil.setAttribute("type", type, element);
+      XMLUtil.setAttribute("maxDoc", maxDoc, element);
+      XMLUtil.setAttribute("deletedDoc", deletedDoc, element);
+      XMLUtil.setAttribute("maxDocumentId", maxDocumentId, element);
+      XMLUtil.setAttribute("maxSnapshotId", maxSnapshotId, element);
+      return element;
+    }
+  }
+
+  public void writeTo(RandomAccessIO output) throws Exception {
+    Element element = toElement();
+    String xml = XMLUtil.outputElement(element);
+    byte[] bytes = xml.getBytes("UTF-8");
+    output.write(bytes);
+  }
+
+  public Element toElement() {
+    Element element = new Element("snapshot");
+    XMLUtil.setAttribute("id", id, element);
+    for (IndexInfo indexInfo : indexInfos.values()) {
+      element.addContent(indexInfo.toElement());
+    }
+    return element;
+  }
+}
\ No newline at end of file
Index: ocean/src/org/apache/lucene/ocean/Snapshots.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/Snapshots.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/Snapshots.java	(revision 0)
@@ -0,0 +1,209 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.lucene.ocean.util.Util;
+import org.apache.lucene.ocean.util.XMLUtil;
+import org.jdom.Element;
+
+import com.imagero.uio.RandomAccessIO;
+
+public class Snapshots {
+  private List<Snapshot> list = new ArrayList<Snapshot>();
+  private TransactionSystem system;
+  private ReentrantLock writeLock = new ReentrantLock();
+
+  public Snapshots(TransactionSystem system) {
+    this.system = system;
+  }
+
+  public void remove(int max, long durationMillis) {
+    writeLock.lock();
+    try {
+      if (list.size() > max) {
+        long now = System.currentTimeMillis();
+        int numToCheck = list.size() - max;
+        Iterator<Snapshot> iterator = list.iterator();
+        for (int x = 0; x < numToCheck; x++) {
+          Snapshot snapshot = iterator.next();
+          if ((snapshot.getTimestamp() + durationMillis) > now) {
+            iterator.remove();
+          }
+        }
+      }
+    } finally {
+      writeLock.unlock();
+    }
+  }
+
+  /**
+   * Loads the ids from the file names rather than loading each xml file.
+   * 
+   * @param directory
+   * @return
+   * @throws Exception
+   */
+  public static List<BigDecimal> loadSnapshotInfoIds(LogDirectory directory) throws Exception {
+    List<BigDecimal> list = new ArrayList<BigDecimal>();
+    for (String file : directory.list()) {
+      if (directory.fileLength(file) > 0) {
+        String str = "snapshot_";
+        if (file.startsWith(str)) {
+          String main = file.substring(str.length(), file.lastIndexOf('.'));
+          String[] split = StringUtils.split(main, "_");
+          if (split.length > 1) {
+            String replace = main.replace('_', '.');
+            //System.out.println("replace: "+replace);
+            list.add(new BigDecimal(replace));
+          } else {
+            Long snapshotId = new Long(split[0]);
+            list.add(new BigDecimal(snapshotId));
+          }
+        }
+      }
+    }
+    Collections.sort(list);
+    return list;
+  }
+
+  public static List<SnapshotInfo> loadSnapshotInfos(LogDirectory directory) throws Exception {
+    List<SnapshotInfo> snapshotInfos = new ArrayList<SnapshotInfo>();
+    for (String file : directory.list()) {
+      if (directory.fileLength(file) > 0) {
+        String str = "snapshot_";
+        if (file.startsWith(str)) {
+          String main = file.substring(str.length(), file.lastIndexOf('.'));
+          String[] split = StringUtils.split(main, "_");
+          Long snapshotId = new Long(split[0]);
+          Integer version = new Integer(0);
+          if (split.length > 1)
+            version = new Integer(split[1]);
+          String xml = Util.getString(file, directory);
+          Element element = XMLUtil.parseElement(xml);
+          snapshotInfos.add(new SnapshotInfo(element));
+          // sorted.add(new BigDecimal(snapshotId + "." + version));
+        }
+      }
+    }
+    Collections.sort(snapshotInfos);
+    return snapshotInfos;
+  }
+
+  // TODO: load max id using loadSnapshotInfoIds, then load max snapshotinfo
+  public static SnapshotInfo loadMaxSnapshotInfo(LogDirectory directory) throws Exception {
+    List<SnapshotInfo> list = loadSnapshotInfos(directory);
+    if (list.size() == 0)
+      return null;
+    return Util.max(list);
+    // TreeSet<BigDecimal> sortedSet = new TreeSet<BigDecimal>();
+    /**
+     * List<BigDecimal> sorted = new ArrayList<BigDecimal>(); for (String file :
+     * directory.list()) { if (directory.fileLength(file) > 0) { String str =
+     * "snapshot_"; if (file.startsWith(str)) { String main =
+     * file.substring(str.length(), file.lastIndexOf('.')); String[] split =
+     * StringUtils.split(main, "_"); Long snapshotId = new Long(split[0]);
+     * Integer version = new Integer(0); if (split.length > 1) version = new
+     * Integer(split[1]); sorted.add(new BigDecimal(snapshotId + "." +
+     * version)); } } } BigDecimal maxId = null; if (sorted.size() > 0) maxId =
+     * Collections.max(sorted); if (maxId == null) return null; String fileName =
+     * Snapshot.getFileName(maxId); String xml = Util.getString(fileName,
+     * directory); Element element = XMLUtil.parseElement(xml); return new
+     * SnapshotInfo(element);
+     */
+  }
+
+  public Snapshot get(long snapshotId) {
+    List<Snapshot> snapshots = getForSnapshot(snapshotId);
+    return Util.max(snapshots);
+  }
+
+  public List<Snapshot> getForSnapshot(long snapshotId) {
+    List<Snapshot> inrange = new ArrayList<Snapshot>();
+    for (Snapshot snapshot : list) {
+      long l = snapshot.getId().toBigInteger().longValue();
+      if (l == snapshotId) {
+        inrange.add(snapshot);
+      }
+    }
+    return inrange;
+  }
+
+  public boolean contains(BigDecimal id) {
+    for (Snapshot s : list) {
+      if (s.getId().compareTo(id) == 0)
+        return true;
+    }
+    return false;
+  }
+
+  public boolean contains(Long snapshotId) {
+    return get(snapshotId) != null;
+  }
+
+  public boolean containsIndex(long indexid) {
+    for (Snapshot snapshot : list) {
+      if (snapshot.containsIndex(indexid))
+        return true;
+    }
+    return false;
+  }
+
+  private void remove(Snapshot snapshot) throws IOException {
+    Iterator<Snapshot> iterator = list.iterator();
+    while (iterator.hasNext()) {
+      Snapshot s = iterator.next();
+      if (s.getId().equals(snapshot.getId())) {
+        iterator.remove();
+        String file = Snapshot.getFileName(snapshot.getId());
+        system.directoryMap.getDirectory().deleteFile(file);
+      }
+    }
+
+  }
+
+  public Snapshot getLatestSnapshot() {
+    if (list.size() == 0)
+      return null;
+    return list.get(list.size() - 1);
+  }
+
+  void add(Snapshot snapshot, boolean createFile) throws Exception {
+    writeLock.lock();
+    try {
+      if (createFile) {
+        addCreateFile(snapshot);
+      } else {
+        list.add(snapshot);
+      }
+    } finally {
+      writeLock.unlock();
+    }
+  }
+
+  private void addCreateFile(Snapshot snapshot) throws Exception {
+    BigDecimal id = snapshot.getId();
+    SnapshotInfo snapshotInfo = snapshot.getSnapshotInfo();
+    String fileName = Snapshot.getFileName(id);
+    LogDirectory directory = system.directoryMap.getDirectory();
+    if (directory.fileExists(fileName)) {
+      throw new IOException("fileName: " + fileName + " already exists");
+    }
+    RandomAccessIO output = directory.getOutput(fileName, true);
+    snapshotInfo.writeTo(output);
+    list.add(snapshot);
+    output.flush();
+    // remove previous versions for this id
+    // SortedMap<BigDecimal,Snapshot> headMap = snapshotMap.headMap(id);
+    // for (Snapshot removeSnapshot : headMap.values()) {
+    // remove(removeSnapshot);
+    // }
+    Collections.sort(list);
+  }
+}
\ No newline at end of file
Index: ocean/src/org/apache/lucene/ocean/Transaction.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/Transaction.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/Transaction.java	(revision 0)
@@ -0,0 +1,272 @@
+package org.apache.lucene.ocean;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.commons.lang.exception.ExceptionUtils;
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.ocean.Batch.MasterBatch;
+import org.apache.lucene.ocean.Index.IndexSnapshot;
+import org.apache.lucene.ocean.log.TransactionLog;
+import org.apache.lucene.store.RAMDirectory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Coordinates a multithreaded transaction commit between multiple indexes.  
+ * Utilizes java.util.concurrent.CountDownLatch for synchronization between the indexes as each 
+ * index operation is performed in it's own thread.
+ *
+ */
+// TODO: add timeout to transaction
+public class Transaction {
+  final static Logger LOG = LoggerFactory.getLogger(Transaction.class);
+  private Batch batch;
+  private CountDownLatch latch;
+  private List<Failure> failures = new ArrayList<Failure>();
+  private List<DeletesResult> deletesResults = new ArrayList<DeletesResult>();
+  private List<IndexSnapshot> newIndexSnapshots = new ArrayList<IndexSnapshot>();
+  private ReentrantLock lock = new ReentrantLock();
+  private CountDownLatch goLatch = new CountDownLatch(1);
+  private Long id;
+  private Long previousId;
+  private TransactionLog transactionLog;
+  private CommitResult commitResult;
+  private TransactionSystem system;
+
+  public Transaction(Long id, Long previousId, Batch batch, WriteableMemoryIndex writeableIndex, List<Index> nonWriteableIndices,
+      ExecutorService commitThreadPool, TransactionSystem system) throws Exception {
+    this.id = id;
+    this.previousId = previousId;
+    this.batch = batch;
+    this.transactionLog = system.getTransactionLog();
+    List<Callable> tasks = new ArrayList<Callable>();
+    Deletes deletes = batch.getDeletes();
+    if (batch.hasDeletes()) {
+      for (Index index : nonWriteableIndices) {
+        tasks.add(new DeletesTask(deletes, index, this));
+      }
+    } else {
+      for (Index index : nonWriteableIndices) {
+        tasks.add(new NothingTask(index, this));
+      }
+    }
+    int numDocsAdded = 0;
+    // handle changes to writeable index, or if a ram directory create a ram index
+    if (batch.hasRAMDirectory()) {
+      tasks.add(new AddRamIndexDocumentsTask(batch.getRamDirectory()));
+    } else if (batch.hasDocuments()) {
+      Documents documents = batch.getDocuments();
+      Analyzer analyzer = batch.getAnalyzer();
+      tasks.add(new AddWriteableMemoryDocumentsTask(documents, analyzer, deletes, writeableIndex));
+      numDocsAdded += documents.size();
+    } else {
+      tasks.add(new DeletesTask(deletes, writeableIndex, this));
+    }
+    latch = new CountDownLatch(tasks.size());
+    List<Future> futures = new ArrayList<Future>(tasks.size());
+    for (Callable callable : tasks) {
+      futures.add(commitThreadPool.submit(callable));
+    }
+    latch.await();
+    goLatch.countDown();
+    // need rollback here for failures during commit
+    for (Future future : futures) {
+      try {
+        future.get();
+      } catch (ExecutionException executionException) {
+        Throwable cause = executionException.getCause();
+        LOG.info(cause.getMessage());
+      }
+    }
+    if (failures.size() == 0) {
+      commitResult = new CommitResult(id, deletesResults, numDocsAdded, writeableIndex.getId());
+    } else {
+      // rollback indexes
+      LOG.info("rolling back snapshot: "+id);
+      writeableIndex.rollback(id);
+      for (Index index : nonWriteableIndices) {
+        index.rollback(id);
+      }
+      throw new Exception("transaction failed " + failures);
+    }
+  }
+  // TODO: no snapshots added
+  public List<IndexSnapshot> getNewIndexSnapshots() {
+    return newIndexSnapshots;
+  }
+
+  public Long getPreviousId() {
+    return previousId;
+  }
+
+  public CommitResult getCommitResult() {
+    assert commitResult != null; // should have thrown exception before this
+    // point
+    return commitResult;
+  }
+
+  public Long getId() {
+    return id;
+  }
+
+  public abstract static class Failure extends Exception {
+    private String string;
+    
+    public Failure(Throwable throwable) {
+      super(throwable);
+      string = ExceptionUtils.getFullStackTrace(throwable);
+    }
+    
+    public String toString() {
+      return string;
+    }
+  }
+
+  public static class LogFailure extends Failure {
+    public LogFailure(Throwable throwable) {
+      super(throwable);
+    }
+  }
+
+  public static class IndexFailure extends Failure {
+    Index index;
+
+    public IndexFailure(Index index, Throwable throwable) {
+      super(throwable);
+      this.index = index;
+    }
+  }
+  
+  public static class NothingTask implements Callable {
+    private Index index;
+    private Transaction transaction;
+    
+    public NothingTask(Index index, Transaction transaction) {
+      this.index = index;
+      this.transaction = transaction;
+    }
+    
+    public Object call() throws Exception {
+      index.commitNothing(transaction);
+      return null;
+    }
+  }
+  
+  public static class DeletesTask implements Callable<DeletesResult> {
+    private Index index;
+    private Deletes deletes;
+    private Transaction transaction;
+
+    public DeletesTask(Deletes deletes, Index index, Transaction transaction) {
+      this.deletes = deletes;
+      this.index = index;
+      this.transaction = transaction;
+    }
+
+    public DeletesResult call() throws Exception {
+      DeletesResult deletesResult = index.commitDeletes(deletes, transaction);
+      transaction.addDeletesResult(deletesResult);
+      return deletesResult;
+    }
+  }
+
+  public class AddRamIndexDocumentsTask implements Callable<DeletesResult> {
+    private RAMDirectory ramDirectory;
+
+    public AddRamIndexDocumentsTask(RAMDirectory ramDirectory) {
+      this.ramDirectory = ramDirectory;
+    }
+
+    public DeletesResult call() throws Exception {
+      // TODO: create new ramindex
+      long indexIdNum = system.getNextRamIndexId();
+      IndexID indexId = new IndexID(indexIdNum, "ram");
+      Analyzer analyzer = batch.getAnalyzer();
+      RamIndex ramIndex = new RamIndex(indexId, id, null, ramDirectory, system);
+      IndexSnapshot indexSnapshot = ramIndex.commitIndex(Transaction.this);
+      DeletesResult deletesResult = new DeletesResult(indexId);
+      addDeletesResult(deletesResult);
+      return deletesResult;
+    }
+  }
+
+  public class AddWriteableMemoryDocumentsTask implements Callable<DeletesResult> {
+    private Documents documents;
+    private Analyzer analyzer;
+    private Deletes deletes;
+    private WriteableMemoryIndex writeableIndex;
+
+    public AddWriteableMemoryDocumentsTask(Documents documents, Analyzer analyzer, Deletes deletes, WriteableMemoryIndex writeableIndex) {
+      this.documents = documents;
+      this.analyzer = analyzer;
+      this.deletes = deletes;
+      this.writeableIndex = writeableIndex;
+    }
+
+    public DeletesResult call() throws Exception {
+      DeletesResult deletesResult = writeableIndex.commitChanges(documents, deletes, analyzer, Transaction.this);
+      addDeletesResult(deletesResult);
+      return deletesResult;
+    }
+  }
+
+  void addDeletesResult(DeletesResult deletesResult) {
+    assert deletesResult != null;
+    deletesResults.add(deletesResult);
+  }
+
+  void failed(Index index, Throwable throwable) {
+    failures.add(new IndexFailure(index, throwable));
+    latch.countDown();
+  }
+
+  void ready(Index index) {
+    latch.countDown();
+  }
+
+  public boolean go() throws InterruptedException {
+    lock.lock();
+    try {
+      goLatch.await();
+      if (failures.size() == 0) {
+        try {
+          if (batch instanceof MasterBatch) {
+            MasterBatch masterBatch = (MasterBatch) batch;
+            if (masterBatch.hasDeletes()) {
+              int numDocIds = 0;
+              for (DeletesResult deletesResult : deletesResults) {
+                numDocIds += deletesResult.getDocIds().size();
+              }
+              long[] docIds = new long[numDocIds];
+              int x = 0;
+              for (DeletesResult deletesResult : deletesResults) {
+                for (Long docId : deletesResult.getDocIds()) {
+                  docIds[x] = docId;
+                  x++;
+                }
+              }
+              masterBatch.getDeletes().setDocIds(docIds);
+            }
+            transactionLog.writeMasterBatch(id, previousId, masterBatch);
+          }
+        } catch (Throwable throwable) {
+          LOG.error("", throwable);
+          failures.add(new LogFailure(throwable));
+          return false;
+        }
+        return true;
+      } else {
+        return false;
+      }
+    } finally {
+      lock.unlock();
+    }
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/TransactionSystem.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/TransactionSystem.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/TransactionSystem.java	(revision 0)
@@ -0,0 +1,662 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.ocean.Batch.MasterBatch;
+import org.apache.lucene.ocean.Batch.SlaveBatch;
+import org.apache.lucene.ocean.DiskIndex.DiskIndexSnapshot;
+import org.apache.lucene.ocean.Index.IndexException;
+import org.apache.lucene.ocean.Index.IndexSnapshot;
+import org.apache.lucene.ocean.RamIndex.RamIndexSnapshot;
+import org.apache.lucene.ocean.SnapshotInfo.IndexInfo;
+import org.apache.lucene.ocean.WriteableMemoryIndex.MemoryIndexSnapshot;
+import org.apache.lucene.ocean.log.TransactionLog;
+import org.apache.lucene.ocean.log.TransactionLog.SlaveBatchIterator;
+import org.apache.lucene.ocean.util.Constants;
+import org.apache.lucene.ocean.util.LongSequence;
+import org.apache.lucene.ocean.util.Util;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.RAMDirectory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Main class for search transaction system.
+ * 
+ * Indexes on disk are immutable, they can only be deleted from or merged
+ * periodically. Merges occur in the background. There is always one active
+ * WriteableMemoryIndex that new documents are written to.
+ * 
+ * A snapshot corresponds to a transaction. Each transaction creates a new
+ * snapshot. Snapshot ids have both major and minor version represented as a
+ * decimal. The major represents the transaction. The minor increments with
+ * index merges. Transaction data is known as a batch. There is a MasterBatch
+ * and SlaveBatch. A MasterBatch is what is created the initial call to
+ * TransactionSystem such as addDocument. A SlaveBatch is what is loaded from
+ * the transactionlog during a recovery.
+ * 
+ * IndexWriter like methods such as addDocument, updateDocument are provided.
+ * Also commitTransaction is provided complete transaction access.
+ * 
+ * A _documentid field is added to each document. This is an internal number for
+ * tracking a document and allows the transaction log system to be recovered
+ * properly. During recovery a delete will use the _documentid rather than the
+ * actual query or term to insure the exact documents are deleted at the point
+ * in time the transaction occurred.
+ * 
+ * 
+ */
+// TODO: need test case of maybeMergeDiskIndices
+// TODO: create recovery system if disk index creation fails
+// TODO: add .index suffix to index directory names
+// TODO: custom efficient document serializer
+// TODO: add writeVLong writeVInt to LogDirectory output
+// TODO: not sure how to handle Document fields with a TokenStream
+public class TransactionSystem {
+  final static Logger LOG = LoggerFactory.getLogger(TransactionSystem.class);
+  public static final int DEFAULT_MEMORY_INDEX_MAX_DOCS = 50;
+  public static final int DEFAULT_MAYBE_MERGE_DOC_CHANGES = 2000;
+  public static final int DEFAULT_MAX_RAM_INDEXES_SIZE = 1024 * 1024 * 30;
+  private ExecutorService commitThreadPool;
+  private ExecutorService mergeThreadPool;
+  private TransactionLog transactionLog;
+  private Indexes indexes = new Indexes();
+  private ReentrantLock commitLock = new ReentrantLock();
+  Snapshots snapshots;
+  private ReentrantLock mergeIndexesLock = new ReentrantLock();
+  private int docChangesSinceLastMerge = 0;
+  private Analyzer defaultAnalyzer;
+  private int serverNumber = 0;
+  private LongSequence documentSequence;
+  private LongSequence diskIndexSequence;
+  private LongSequence ramIndexSequence;
+  private int memoryIndexMaxDocs = DEFAULT_MEMORY_INDEX_MAX_DOCS;
+  private int maybeMergeDocChanges = DEFAULT_MAYBE_MERGE_DOC_CHANGES;
+  private int maxRamIndexesSize = DEFAULT_MAX_RAM_INDEXES_SIZE;
+  private int maxDocsIndexes = -1;
+  private int maxSnapshots = 5;
+  private long snapshotExpiration = 20 * 1000;
+  DirectoryMap directoryMap;
+  private ArrayBlockingQueue<Runnable> mergeQueue;
+
+  public TransactionSystem(TransactionLog transactionLog, Analyzer defaultAnalyzer, DirectoryMap directoryMap) throws Exception {
+    this(transactionLog, defaultAnalyzer, directoryMap, DEFAULT_MAYBE_MERGE_DOC_CHANGES, -1, DEFAULT_MEMORY_INDEX_MAX_DOCS);
+  }
+
+  public TransactionSystem(TransactionLog transactionLog, Analyzer defaultAnalyzer, DirectoryMap directoryMap, int maybeMergeDocChanges,
+      int maxDocsIndexes, int memoryIndexMaxDocs) throws Exception {
+    this.transactionLog = transactionLog;
+    this.defaultAnalyzer = defaultAnalyzer;
+    this.directoryMap = directoryMap;
+    this.maybeMergeDocChanges = maybeMergeDocChanges;
+    this.maxDocsIndexes = maxDocsIndexes;
+    this.memoryIndexMaxDocs = memoryIndexMaxDocs;
+    mergeQueue = new ArrayBlockingQueue<Runnable>(2);
+    mergeThreadPool = new ThreadPoolExecutor(1, 1, 1000 * 2, TimeUnit.MILLISECONDS, mergeQueue);
+    commitThreadPool = Executors.newFixedThreadPool(5);
+    snapshots = new Snapshots(this);
+    if (LOG.isInfoEnabled())
+      LOG.info("TransactionSystem");
+    load();
+  }
+
+  public void setMaybeMergeDocChanges(int maybeMergeDocChanges) {
+    this.maybeMergeDocChanges = maybeMergeDocChanges;
+  }
+
+  public void setMaxDocsIndexes(int maxDocsIndexes) {
+    this.maxDocsIndexes = maxDocsIndexes;
+  }
+
+  public void close() throws IOException {
+    if (LOG.isInfoEnabled())
+      LOG.info("close");
+    mergeThreadPool.shutdown();
+    commitThreadPool.shutdown();
+    transactionLog.close();
+    for (Index index : indexes.getIndexes()) {
+      index.close();
+    }
+
+  }
+
+  public OceanSearcher getSearcher() throws IOException {
+    Snapshot snapshot = snapshots.getLatestSnapshot();
+    return new OceanSearcher(snapshot);
+  }
+
+  public CommitResult deleteDocument(Term term) throws Exception {
+    List<Term> dterms = new ArrayList<Term>(1);
+    dterms.add(term);
+    return commitTransaction(null, null, dterms, null);
+  }
+
+  public CommitResult updateDocument(Term term, Document document) throws Exception {
+    return updateDocument(term, document, defaultAnalyzer);
+  }
+
+  public CommitResult updateDocument(Term term, Document document, Analyzer analyzer) throws Exception {
+    List<Document> list = new ArrayList<Document>(1);
+    list.add(document);
+    List<Term> dterms = new ArrayList<Term>(1);
+    dterms.add(term);
+    return commitTransaction(list, analyzer, dterms, null);
+  }
+
+  public CommitResult addDocument(Document document) throws Exception {
+    return addDocument(document, defaultAnalyzer);
+  }
+
+  public CommitResult addDocument(Document document, Analyzer analyzer) throws Exception {
+    List<Document> list = new ArrayList<Document>(1);
+    list.add(document);
+    return commitTransaction(list, analyzer, null, null);
+  }
+
+  public CommitResult commitTransaction(List<Document> documents, Analyzer analyzer, List<Term> deleteByTerms, List<Query> deleteByQueries)
+      throws Exception {
+    MasterBatch masterBatch = new MasterBatch(this);
+    if (documents != null) masterBatch.addDocuments(new Documents(documents));
+    masterBatch.setAnalyzer(analyzer);
+    Deletes deletes = new Deletes();
+    if (deleteByTerms != null) {
+      for (Term deleteTerm : deleteByTerms) {
+        deletes.addTerm(deleteTerm);
+      }
+    }
+    if (deleteByQueries != null) {
+      for (Query query : deleteByQueries) {
+        deletes.addQuery(query);
+      }
+    }
+    masterBatch.setDeletes(deletes);
+    return commitBatch(masterBatch);
+  }
+
+  public Analyzer getDefaultAnalyzer() {
+    return defaultAnalyzer;
+  }
+
+  public long getNextRamIndexId() {
+    return ramIndexSequence.getAndIncrement();
+  }
+
+  public long getNextDiskIndexId() {
+    return diskIndexSequence.getAndIncrement();
+  }
+
+  public TransactionLog getTransactionLog() {
+    return transactionLog;
+  }
+
+  public ExecutorService getCommitThreadPool() {
+    return commitThreadPool;
+  }
+
+  public void load() throws Exception {
+    BigDecimal id;
+    Long snapshotId;
+    List<IndexSnapshot> indexSnapshots = null;
+    SnapshotInfo snapshotInfo = Snapshots.loadMaxSnapshotInfo(directoryMap.getDirectory());
+    if (LOG.isDebugEnabled())
+      LOG.debug("snapshotInfo: " + snapshotInfo);
+    long timestamp = System.currentTimeMillis();
+    if (snapshotInfo != null) {
+      id = snapshotInfo.getId();
+      snapshotId = snapshotInfo.getSnapshotId();
+      assert snapshotId == transactionLog.getMaxId();
+      loadDiskIndexes(snapshotInfo, indexes);
+      IndexID diskMaxId = indexes.getMaxId("disk");
+      if (diskMaxId != null)
+        diskIndexSequence = new LongSequence(diskMaxId.id.longValue() + 1, 1);
+      else
+        diskIndexSequence = new LongSequence(1, 1);
+      ramIndexSequence = new LongSequence(1, 1);
+      indexSnapshots = new ArrayList<IndexSnapshot>();
+      List<Long> snapshotIds = new LinkedList<Long>();
+      // TODO: what if index directory is deleted and it is still referenced
+      for (IndexInfo indexInfo : snapshotInfo.getIndexInfos()) {
+        if (indexInfo.getType().equals("disk")) {
+          DiskIndex diskIndex = (DiskIndex) indexes.get(indexInfo.getIndexID());
+          if (diskIndex != null) {
+            IndexSnapshot indexSnapshot = diskIndex.getIndexSnapshot(snapshotInfo.getSnapshotId());
+            indexSnapshots.add(indexSnapshot);
+            snapshotIds.add(indexSnapshot.getMaxSnapshotId());
+          }
+        }
+      }
+      Long maxDiskIndexSnapshotId = Util.max(snapshotIds);
+      System.out.println("maxDiskIndexSnapshotId: " + maxDiskIndexSnapshotId);
+      if (maxDiskIndexSnapshotId != null) {
+        maxDiskIndexSnapshotId = new Long(maxDiskIndexSnapshotId.longValue() + 1);
+      }
+      List<RamIndexSnapshot> ramIndexSnapshots = runTransactionsNotInIndex(maxDiskIndexSnapshotId);
+      System.out.println("ramIndexSnapshots: " + ramIndexSnapshots);
+      // TODO: verify all snapshots have same id
+      indexSnapshots.addAll(ramIndexSnapshots);
+      List<Long> documentIds = new ArrayList<Long>(indexSnapshots.size());
+      for (IndexSnapshot indexSnapshot : indexSnapshots) {
+        documentIds.add(indexSnapshot.getMaxDocumentId());
+      }
+      Long maxDocumentId = Util.max(documentIds);
+      if (maxDocumentId != null) {
+        Long documentSequenceId = Util.getNextServerSequence(maxDocumentId, serverNumber);
+        documentSequence = new LongSequence(documentSequenceId, 100);
+      } else {
+        documentSequence = new LongSequence(serverNumber, 100);
+      }
+    } else {
+      snapshotId = new Long(0);
+      id = new BigDecimal(snapshotId.toString());
+      documentSequence = new LongSequence(serverNumber, 100);
+      diskIndexSequence = new LongSequence(1, 1);
+      ramIndexSequence = new LongSequence(1, 1);
+    }
+    WriteableMemoryIndex writeableMemoryIndex = newWriteableMemoryIndex();
+    MemoryIndexSnapshot writeableSnapshot = writeableMemoryIndex.setSnapshot(snapshotId);
+    if (indexSnapshots == null) {
+      indexSnapshots = new ArrayList<IndexSnapshot>();
+      indexSnapshots.add(writeableSnapshot);
+    }
+    Snapshot snapshot = new Snapshot(id, writeableSnapshot, indexSnapshots, this, timestamp);
+    snapshots.add(snapshot, false);
+    deleteUnreferencedSnapshots();
+    new MaybeMergeIndices().run();
+  }
+
+  /**
+   * Delete snapshotinfo if no longer referenced in Snapshots
+   * 
+   * @throws Exception
+   */
+  private void deleteUnreferencedSnapshots() throws Exception {
+    snapshots.remove(maxSnapshots, snapshotExpiration);
+    LogDirectory directory = directoryMap.getDirectory();
+    List<BigDecimal> ids = Snapshots.loadSnapshotInfoIds(directory);
+    for (BigDecimal id : ids) {
+      if (!snapshots.contains(id)) {
+        // not referenced, delete it
+        String fileName = Snapshot.getFileName(id);
+        System.out.println("deleteFile: " + fileName + " id: " + Snapshot.formatId(id));
+        try {
+          directory.deleteFile(fileName);
+          if (LOG.isDebugEnabled())
+            LOG.debug("deleteFile: " + fileName);
+        } catch (Exception exception) {
+          LOG.error(exception.getMessage());
+        }
+      }
+    }
+  }
+
+  public Indexes getIndices() {
+    return indexes;
+  }
+
+  public Snapshots getSnapshots() {
+    return snapshots;
+  }
+
+  /**
+   * Runs the transactions from the transaction log that are not already in
+   * Lucene indices
+   * 
+   * @param startSnapshotId
+   * @return loaded ram snapshots
+   * @throws Exception
+   * @throws CategoryException
+   * @throws IOException
+   */
+  private List<RamIndexSnapshot> runTransactionsNotInIndex(Long startSnapshotId) throws Exception, IOException {
+    LOG.info("startSnapshotId: " + startSnapshotId);
+    SlaveBatchIterator iterator = transactionLog.getSlaveBatchIterator(startSnapshotId);
+    if (!iterator.hasNext())
+      return new ArrayList<RamIndexSnapshot>();
+    try {
+      long indexIdNum = ramIndexSequence.getAndIncrement();
+      IndexID indexId = new IndexID(indexIdNum, "ram");
+      RAMDirectory ramDirectory = new RAMDirectory();
+      ExecutorService threadPool = getCommitThreadPool();
+      IndexCreator indexCreator = new IndexCreator(ramDirectory, Long.MAX_VALUE, 4, defaultAnalyzer, threadPool);
+      BlockingQueue<IndexCreator.Add> addQueue = new ArrayBlockingQueue<IndexCreator.Add>(4000, true);
+      List<Deletes> deletesList = new ArrayList<Deletes>(); // deletes are
+      // recorded and run
+      // against all of the
+      // snapshots at the
+      // end
+      indexCreator.start(addQueue);
+      List<RAMDirectory> ramDirectories = new ArrayList<RAMDirectory>();
+      int docCount = 0;
+      while (iterator.hasNext()) {
+        SlaveBatch slaveBatch = iterator.next(true, true);
+        Analyzer analyzer = slaveBatch.getAnalyzer();
+        if (slaveBatch.hasDocuments()) {
+          Documents documents = slaveBatch.getDocuments();
+          for (Document document : documents) {
+            addQueue.add(new IndexCreator.Add(document));
+            docCount++;
+          }
+        } else if (slaveBatch.hasRAMDirectory()) {
+          ramDirectories.add(slaveBatch.getRamDirectory());
+        }
+        if (slaveBatch.hasDeletes()) {
+          deletesList.add(slaveBatch.getDeletes());
+        }
+      }
+      LOG.info("docCount: " + docCount);
+      indexCreator.create();
+      ramDirectories.add(ramDirectory);
+      Long snapshotId = transactionLog.getMaxId();
+      List<RamIndexSnapshot> indexSnapshots = new ArrayList<RamIndexSnapshot>(ramDirectories.size());
+      for (RAMDirectory rd : ramDirectories) {
+        RamIndex ramIndex = new RamIndex(indexId, snapshotId, deletesList, rd, this);
+        indexes.add(ramIndex);
+        RamIndexSnapshot indexSnapshot = (RamIndexSnapshot) ramIndex.getIndexSnapshot(snapshotId);
+        assert indexSnapshot != null;
+        indexSnapshots.add(indexSnapshot);
+      }
+      // TODO: run maybe merge here
+      return indexSnapshots;
+    } finally {
+      if (iterator != null)
+        iterator.close();
+    }
+  }
+
+  private void loadDiskIndexes(SnapshotInfo snapshotInfo, Indexes indices) throws Exception, IOException {
+    for (String name : directoryMap.list()) {
+      try {
+        Directory directory = directoryMap.get(name);
+        Long indexIdNum = new Long(name);
+        IndexID indexId = new IndexID(indexIdNum, "disk");
+        try {
+          IndexInfo indexInfo = snapshotInfo.getIndexInfo(indexId);
+          if (indexInfo != null) {
+            Long snapshotId = snapshotInfo.getSnapshotId();
+            Long segmentGeneration = indexInfo.getSegmentGeneration();
+            DiskIndex diskIndex = new DiskIndex(indexId, directory, snapshotId, segmentGeneration, this);
+            indices.add(diskIndex);
+          } else {
+            LOG.info("index no longer referenced deleting: " + name);
+            // directoryMap.delete(name);
+          }
+        } catch (IndexException indexException) {
+          LOG.error("index not ready, deleting: " + name, indexException);
+          // directoryMap.delete(name);
+        } catch (IOException ioException) {
+          LOG.error("index not ready, deleting: " + name, ioException);
+          // directoryMap.delete(name);
+        }
+      } catch (Exception exception) {
+        LOG.error("", exception);
+        // if exception simply skip over the index
+      }
+    }
+  }
+
+  public MasterBatch createMasterBatch() throws Exception {
+    return new MasterBatch(this);
+  }
+
+  public class MaybeMergeIndices implements Runnable {
+    public MaybeMergeIndices() {
+    }
+
+    public void run() {
+      if (LOG.isDebugEnabled())
+        LOG.debug("MaybeMergeIndices");
+      mergeIndexesLock.lock();
+      try {
+        Snapshot snapshot = snapshots.getLatestSnapshot();
+        maybeMergeWriteable(snapshot);
+        snapshot = snapshots.getLatestSnapshot();
+        maybeMergeRamIndexes(snapshot);
+        snapshot = snapshots.getLatestSnapshot();
+        maybeMergeDiskIndexes(snapshot);
+      } catch (Throwable throwable) {
+        LOG.error("", throwable);
+      } finally {
+        mergeIndexesLock.unlock();
+      }
+    }
+
+    /**
+     * If the existing ram indexes are above maxRamIndexesSize, then they are
+     * merged and a new disk index is created from them. Or if the number of
+     * documents exceeds maxDocsIndexesSize.
+     * 
+     * @param snapshot
+     * @throws Exception
+     */
+    private void maybeMergeRamIndexes(Snapshot snapshot) throws Exception {
+      long size = 0;
+      int numDocs = 0;
+      List<RamIndexSnapshot> ramIndexSnapshots = snapshot.getRamIndexSnapshots();
+      for (RamIndexSnapshot ramIndexSnapshot : ramIndexSnapshots) {
+        RamIndex ramIndex = (RamIndex) ramIndexSnapshot.getIndex();
+        size += ramIndex.getSize();
+        numDocs += ramIndexSnapshot.getIndexReader().maxDoc();
+      }
+      // if merging based on number of docs
+      if (maxDocsIndexes > 0 && numDocs > maxDocsIndexes) {
+        if (LOG.isDebugEnabled())
+          LOG.debug("executeMerge because numDocs: " + numDocs + " more than maxDocsIndexes: " + maxDocsIndexes);
+        executeMerge(ramIndexSnapshots, snapshot);
+      } else if (size > maxRamIndexesSize) {
+        // merging based on size of ram indexes
+        executeMerge(ramIndexSnapshots, snapshot);
+      }
+    }
+
+    private void maybeMergeDiskIndexes(Snapshot snapshot) throws Exception {
+      Long snapshotId = snapshot.getSnapshotId();
+      List<IndexSnapshot> indexSnapshotsToMerge = new ArrayList<IndexSnapshot>();
+      for (DiskIndex diskIndex : snapshot.getDiskIndices()) {
+        DiskIndexSnapshot indexSnapshot = (DiskIndexSnapshot) diskIndex.getIndexSnapshot(snapshotId);
+        // TODO: create config attribute for percentage of deleted docs
+        if (diskIndex.hasTooManyDeletedDocs(0.3)) {
+          indexSnapshotsToMerge.add(indexSnapshot);
+        }
+      }
+      if (indexSnapshotsToMerge.size() > 0) {
+        executeMerge(indexSnapshotsToMerge, snapshot);
+      }
+    }
+
+    /**
+     * converts current memorywriteableindex to a ramindex
+     * 
+     * @param snapshot
+     * @throws Exception
+     */
+    private void maybeMergeWriteable(Snapshot snapshot) throws Exception {
+      MemoryIndexSnapshot writeableIndexSnapshot = snapshot.getWriteableSnapshot();
+      int maxDoc = writeableIndexSnapshot.getIndexReader().maxDoc();
+      if (maxDoc >= memoryIndexMaxDocs) {
+        if (LOG.isInfoEnabled())
+          LOG.info("merge writeable");
+        commitLock.lock();
+        try {
+          long indexIdNum = ramIndexSequence.getAndIncrement();
+          IndexID indexId = new IndexID(indexIdNum, "memory");
+          RamIndex ramIndex = new RamIndex(indexId, writeableIndexSnapshot);
+          indexes.add(ramIndex);
+          Snapshot currentSnapshot = snapshots.getLatestSnapshot();
+          List<IndexID> removeIndexIds = new ArrayList<IndexID>();
+          removeIndexIds.add(writeableIndexSnapshot.getIndex().getId());
+          
+          // create new WriteableMemoryIndex for the new snapshot because the one that was there
+          // has been converted to a RamIndex
+          WriteableMemoryIndex newWriteableMemoryIndex = newWriteableMemoryIndex();
+          MemoryIndexSnapshot newMemoryIndexSnapshot = newWriteableMemoryIndex.setSnapshot(snapshot.getSnapshotId());
+          Snapshot newSnapshot = currentSnapshot.createMinor(removeIndexIds, newMemoryIndexSnapshot, ramIndex.getLatestIndexSnapshot());
+          snapshots.add(newSnapshot, true);
+          if (LOG.isInfoEnabled())
+            LOG.info("merge writeable completed");
+        } finally {
+          commitLock.unlock();
+        }
+      }
+    }
+
+    /**
+     * Takes snapshots and makes a DiskIndex.
+     * 
+     * @param indexSnapshots
+     * @param snapshot
+     * @throws Exception
+     */
+    private void executeMerge(List<? extends IndexSnapshot> indexSnapshots, Snapshot snapshot) throws Exception {
+      if (indexSnapshots.size() == 0)
+        return;
+      Long snapshotId = snapshot.getSnapshotId();
+      Long indexIdNum = diskIndexSequence.getAndIncrement();
+      IndexID indexId = new IndexID(indexIdNum, "disk");
+      Directory directory = directoryMap.create(indexIdNum.toString());
+      // initial creation happens outside of commitlock because it is the most
+      // time consuming
+      // the deletes occur inside the commitlock as they are fast
+      DiskIndex newDiskIndex = new DiskIndex(indexId, directory, indexSnapshots, TransactionSystem.this);
+      indexes.add(newDiskIndex);
+      commitLock.lock();
+      try {
+        // TODO: probably can just save deletes from the batches
+        List<SlaveBatch> deleteOnlySlaveBatches = new ArrayList<SlaveBatch>();
+        Snapshot currentSnapshot = snapshots.getLatestSnapshot();
+        Long latestSnapshotId = currentSnapshot.getSnapshotId();
+        if (!snapshotId.equals(latestSnapshotId)) {
+          SlaveBatchIterator iterator = transactionLog.getSlaveBatchIterator(snapshotId);
+          while (iterator.hasNext()) {
+            SlaveBatch slaveBatch = iterator.next(false, true);
+            deleteOnlySlaveBatches.add(slaveBatch);
+          }
+        }
+        IndexSnapshot newIndexSnapshot = newDiskIndex.initialize(latestSnapshotId, deleteOnlySlaveBatches, TransactionSystem.this);
+        List<IndexID> removeIndexIds = new ArrayList<IndexID>();
+        for (IndexSnapshot indexSnapshot : indexSnapshots) {
+          Index index = indexSnapshot.getIndex();
+          removeIndexIds.add(index.getId());
+        }
+        Snapshot newSnapshot = currentSnapshot.createMinor(removeIndexIds, newIndexSnapshot);
+        snapshots.add(newSnapshot, true);
+      } finally {
+        commitLock.unlock();
+      }
+    }
+  }
+
+  /**
+   * Commits a batch to the transaction log
+   * 
+   * @param batch
+   * @return CommitResult
+   * @throws Exception
+   * @throws IOException
+   */
+  CommitResult commitBatch(Batch batch) throws Exception, IOException {
+    batch.close();
+    commitLock.lock();
+    try {
+      Long snapshotId = null;
+      if (batch instanceof SlaveBatch) {
+        snapshotId = ((SlaveBatch) batch).getId();
+      } else {
+        MasterBatch masterBatch = (MasterBatch) batch;
+        snapshotId = transactionLog.getNextId();
+        if (batch.hasDocuments()) {
+          Documents documents = batch.getDocuments();
+          for (Document document : documents) {
+            Long documentId = documentSequence.getAndIncrement();
+            Util.setValue(Constants.DOCUMENTID, documentId, document);
+            Util.setValue(Constants.SNAPSHOTID, snapshotId, document);
+          }
+          if (documents.size() >= memoryIndexMaxDocs) {
+            RAMDirectory ramDirectory = createRamDirectory(documents, batch.getAnalyzer());
+            masterBatch.setRAMDirectory(ramDirectory);
+          }
+        }
+      }
+      ExecutorService threadPool = getCommitThreadPool();
+      Snapshot currentSnapshot = snapshots.getLatestSnapshot();
+      MemoryIndexSnapshot writeableIndexSnapshot = currentSnapshot.getWriteableSnapshot();
+      WriteableMemoryIndex writeableMemoryIndex = (WriteableMemoryIndex) writeableIndexSnapshot.getIndex();
+      List<Index> nonWriteableIndices = currentSnapshot.getDeleteOnlyIndices();
+      Transaction transaction = null;
+      CommitResult commitResult = null;
+      try {
+        Long previousId = transactionLog.getPreviousId(snapshotId);
+        transaction = new Transaction(snapshotId, previousId, batch, writeableMemoryIndex, nonWriteableIndices, threadPool, this);
+        commitResult = transaction.getCommitResult();
+      } catch (Exception exception) {
+        LOG.error("transaction failed");
+        throw new Exception("transaction failed", exception);
+      }
+      List<IndexSnapshot> indexSnapshots = new ArrayList<IndexSnapshot>(nonWriteableIndices.size() + 1);
+      for (Index index : nonWriteableIndices) {
+        indexSnapshots.add(index.getIndexSnapshot(snapshotId));
+      }
+      for (IndexSnapshot newIndexSnapshot : transaction.getNewIndexSnapshots()) {
+        indexes.add(newIndexSnapshot.getIndex());
+        indexSnapshots.add(newIndexSnapshot);
+      }
+      assert snapshotId == transaction.getId();
+      MemoryIndexSnapshot newWriteableSnapshot = writeableMemoryIndex.getIndexSnapshot(snapshotId);
+      assert newWriteableSnapshot != null;
+      indexSnapshots.add(newWriteableSnapshot);
+
+      Snapshot newSnapshot = new Snapshot(snapshotId, 0, newWriteableSnapshot, indexSnapshots, this, System.currentTimeMillis());
+      snapshots.add(newSnapshot, true);
+      docChangesSinceLastMerge += commitResult.getNumDocChanges();
+      int writeableMaxDoc = writeableMemoryIndex.getLatestIndexSnapshot().getIndexReader().maxDoc();
+      if (docChangesSinceLastMerge > maybeMergeDocChanges || writeableMaxDoc >= memoryIndexMaxDocs) {
+        System.out.println("docChangesSinceLastMerge: " + docChangesSinceLastMerge + " maybeMergeDocChanges: " + maybeMergeDocChanges);
+        System.out.println("writeableMaxDoc: " + writeableMaxDoc + " memoryIndexMaxDocs: " + memoryIndexMaxDocs);
+        if (mergeQueue.size() == 0) {
+          mergeThreadPool.submit(new MaybeMergeIndices());
+          docChangesSinceLastMerge = 0;
+        }
+      }
+      deleteUnreferencedSnapshots();
+      // TODO: reset document sequence
+      return commitResult;
+    } finally {
+      commitLock.unlock();
+    }
+  }
+
+  RAMDirectory createRamDirectory(Documents documents, Analyzer analyzer) throws Exception {
+    RAMDirectory ramDirectory = new RAMDirectory();
+    ExecutorService threadPool = getCommitThreadPool();
+    IndexCreator indexCreator = new IndexCreator(ramDirectory, Long.MAX_VALUE, 4, analyzer, threadPool);
+    BlockingQueue<IndexCreator.Add> addQueue = new ArrayBlockingQueue<IndexCreator.Add>(1000, true);
+    indexCreator.start(addQueue);
+    for (Document document : documents) {
+      addQueue.add(new IndexCreator.Add(document));
+    }
+    indexCreator.create();
+    return ramDirectory;
+  }
+
+  private WriteableMemoryIndex newWriteableMemoryIndex() throws Exception {
+    Long indexIdNum = ramIndexSequence.getAndIncrement();
+    IndexID indexId = new IndexID(indexIdNum, "memory");
+    WriteableMemoryIndex writeableMemoryIndex = new WriteableMemoryIndex(indexId, this);
+    indexes.add(writeableMemoryIndex);
+    return writeableMemoryIndex;
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/util/ByteArrayOutputStream.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/util/ByteArrayOutputStream.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/util/ByteArrayOutputStream.java	(revision 0)
@@ -0,0 +1,185 @@
+package org.apache.lucene.ocean.util;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.lucene.ocean.util.ByteBufferPool.ByteBuffer;
+import org.apache.lucene.ocean.util.ByteBufferPool.ByteBuffers;
+
+
+public class ByteArrayOutputStream extends OutputStream {
+	private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+
+	private List<ByteBuffer> buffers = new ArrayList<ByteBuffer>();
+	/** The index of the current buffer. */
+	private int currentBufferIndex;
+	/** The total count of bytes in all the filled buffers. */
+	private int filledBufferSum;
+	/** The current buffer. */
+	private ByteBuffer currentBuffer;
+	/** The total count of bytes written. */
+	private int count;
+	private ByteBufferPool byteBufferPool;
+
+	/**
+	 * Creates a new byte array output stream. The buffer capacity is initially
+	 * 1024 bytes, though its size increases if necessary.
+	 */
+	public ByteArrayOutputStream(ByteBufferPool byteBufferPool) {
+		this.byteBufferPool = byteBufferPool;
+	}
+  
+	public ByteBuffers getByteBuffers() {
+		return new ByteBuffers(buffers, size());
+	}
+	
+	/**
+	 * Return the appropriate <code>byte[]</code> buffer specified by index.
+	 * 
+	 * @param index
+	 *          the index of the buffer required
+	 * @return the buffer
+	 */
+	private ByteBuffer getBuffer(int index) {
+		return (ByteBuffer) buffers.get(index);
+	}
+
+	/**
+	 * Makes a new buffer available either by allocating a new one or re-cycling
+	 * an existing one.
+	 * 
+	 * @param newcount
+	 *          the size of the buffer if one is created
+	 */
+	private void needNewBuffer(int newcount) {
+		if (currentBufferIndex < buffers.size() - 1) {
+			// Recycling old buffer
+			filledBufferSum += currentBuffer.getBytes().length;
+
+			currentBufferIndex++;
+			currentBuffer = getBuffer(currentBufferIndex);
+		} else {
+			// Creating new buffer
+			int newBufferSize = 1024 * 16;
+			/**
+			 * if (currentBuffer == null) { newBufferSize = newcount; filledBufferSum =
+			 * 0; } else { newBufferSize = Math.max(currentBuffer.getBytes().length <<
+			 * 1, newcount - filledBufferSum); filledBufferSum +=
+			 * currentBuffer.getBytes().length; }
+			 */
+			currentBufferIndex++;
+			currentBuffer = byteBufferPool.get(newBufferSize);
+			buffers.add(currentBuffer);
+		}
+	}
+
+	/**
+	 * @see java.io.OutputStream#write(byte[], int, int)
+	 */
+	public void write(byte[] b, int off, int len) {
+		if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0)) {
+			throw new IndexOutOfBoundsException();
+		} else if (len == 0) {
+			return;
+		}
+		int newcount = count + len;
+		int remaining = len;
+		int inBufferPos = count - filledBufferSum;
+		while (remaining > 0) {
+			int part = Math.min(remaining, currentBuffer.getBytes().length - inBufferPos);
+			System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part);
+			remaining -= part;
+			if (remaining > 0) {
+				needNewBuffer(newcount);
+				inBufferPos = 0;
+			}
+		}
+		count = newcount;
+	}
+
+	/**
+	 * @see java.io.OutputStream#write(int)
+	 */
+	public void write(int b) {
+		int inBufferPos = count - filledBufferSum;
+		if (inBufferPos == currentBuffer.getBytes().length) {
+			needNewBuffer(count + 1);
+			inBufferPos = 0;
+		}
+		currentBuffer.getBytes()[inBufferPos] = (byte) b;
+		count++;
+	}
+
+	/**
+	 * @see java.io.ByteArrayOutputStream#size()
+	 */
+	public int size() {
+		return count;
+	}
+
+	/**
+	 * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in
+	 * this class can be called after the stream has been closed without
+	 * generating an <tt>IOException</tt>.
+	 * 
+	 * @throws IOException
+	 *           never (this method should not declare this exception but it has
+	 *           to now due to backwards compatability)
+	 */
+	public void close() {
+		for (ByteBuffer byteBuffer : buffers) {
+			byteBuffer.finished();
+		}
+	}
+
+	/**
+	 * @see java.io.ByteArrayOutputStream#reset()
+	 */
+	public void reset() {
+		close();
+		count = 0;
+		filledBufferSum = 0;
+		currentBufferIndex = 0;
+		currentBuffer = getBuffer(currentBufferIndex);
+	}
+
+	/**
+	 * Writes the entire contents of this byte stream to the specified output
+	 * stream.
+	 * 
+	 * @param out
+	 *          the output stream to write to
+	 * @throws IOException
+	 *           if an I/O error occurs, such as if the stream is closed
+	 * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
+	 */
+	public void writeTo(OutputStream out) throws IOException {
+		int remaining = count;
+		for (int i = 0; i < buffers.size(); i++) {
+			byte[] buf = getBuffer(i).getBytes();
+			int c = Math.min(buf.length, remaining);
+			out.write(buf, 0, c);
+			remaining -= c;
+			if (remaining == 0) {
+				break;
+			}
+		}
+	}
+
+	/**
+	 * Gets the curent contents of this byte stream as a byte array. The result is
+	 * independent of this stream.
+	 * 
+	 * @return the current contents of this output stream, as a byte array
+	 * @see java.io.ByteArrayOutputStream#toByteArray()
+	 * 
+	 * public synchronized byte[] toByteArray() { int remaining = count; if
+	 * (remaining == 0) { return EMPTY_BYTE_ARRAY; } byte newbuf[] = new
+	 * byte[remaining]; int pos = 0; for (int i = 0; i < buffers.size(); i++) {
+	 * byte[] buf = getBuffer(i); int c = Math.min(buf.length, remaining);
+	 * System.arraycopy(buf, 0, newbuf, pos, c); pos += c; remaining -= c; if
+	 * (remaining == 0) { break; } } return newbuf; }
+	 */
+}
Index: ocean/src/org/apache/lucene/ocean/util/ByteBufferPool.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/util/ByteBufferPool.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/util/ByteBufferPool.java	(revision 0)
@@ -0,0 +1,181 @@
+package org.apache.lucene.ocean.util;
+
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class ByteBufferPool {
+  private ReentrantLock lock = new ReentrantLock();
+	private TreeSet<ByteBuffer> available = new TreeSet<ByteBuffer>();
+	private Set<ByteBuffer> inuse = new HashSet<ByteBuffer>();
+	private int maxCount;
+  
+	public ByteBufferPool(int initialSize, int count, int maxCount) {
+		this.maxCount = maxCount;
+		for (int x=0; x < count; x++) {
+			get(initialSize);
+		}
+	}
+	
+	public static class ByteBuffers {
+		private List<ByteBuffer> byteBuffers;
+		private int length;
+		
+		public ByteBuffers(RandomAccessFile randomAccessFile, int bufferSize, int size, ByteBufferPool bufferPool) throws IOException {
+			this.length = size;
+			int numRead = 0;
+			byteBuffers = new ArrayList<ByteBuffer>();
+			while (true) {
+				if (numRead >= size) break;
+				ByteBuffer byteBuffer = bufferPool.get(bufferSize);
+				int len = byteBuffer.getBytes().length;
+				if ( (len + numRead) > size) {
+					len = size - numRead;
+				}
+				int n = randomAccessFile.read(byteBuffer.getBytes(), 0, len);
+				if (n == -1) break;
+				byteBuffers.add(byteBuffer);
+				numRead += n;
+			}
+		}
+		
+		public ByteBuffers(InputStream inputStream, int bufferSize, int size, ByteBufferPool bufferPool) throws IOException {
+			this.length = size;
+			int numRead = 0;
+			byteBuffers = new ArrayList<ByteBuffer>();
+			while (true) {
+				if (numRead >= size) break;
+				ByteBuffer byteBuffer = bufferPool.get(bufferSize);
+				int len = byteBuffer.getBytes().length;
+				if ( (len + numRead) > size) {
+					len = size - numRead;
+				}
+				int n = inputStream.read(byteBuffer.getBytes(), 0, len);
+				if (n == -1) break;
+				byteBuffers.add(byteBuffer);
+				numRead += n;
+			}
+		}
+		
+		public ByteBuffers(ByteBuffer byteBuffer, int length) {
+			byteBuffers = new ArrayList<ByteBuffer>(1);
+			byteBuffers.add(byteBuffer);
+			this.length = length;
+		}
+		
+		public ByteBuffers(List<ByteBuffer> byteBuffers, int length) {
+			this.byteBuffers = byteBuffers;
+			this.length = length;
+		}
+    
+		public InputStream getInputStream() throws IOException {
+			return new ByteArrayInputStream(this);
+		}
+		
+		public void writeTo(DataOutput out) throws IOException {
+			int remaining = length;
+			for (int i = 0; i < byteBuffers.size(); i++) {
+				byte[] buf = byteBuffers.get(i).getBytes();
+				int c = Math.min(buf.length, remaining);
+				out.write(buf, 0, c);
+				remaining -= c;
+				if (remaining == 0) {
+					break;
+				}
+			}
+		}
+		
+		public void finished() {
+			for (ByteBuffer byteBuffer : byteBuffers) {
+				byteBuffer.finished();
+			}
+		}
+		
+		public List<ByteBuffer> getByteBuffers() {
+			return byteBuffers;
+		}
+
+		public int getLength() {
+			return length;
+		}
+	}
+	
+	public static class ByteBuffer implements Comparable<ByteBuffer> {
+		private byte[] bytes;
+		private ByteBufferPool byteBufferPool;
+		
+		public ByteBuffer(int size, ByteBufferPool byteBufferPool) {
+			this.bytes = new byte[size];
+			this.byteBufferPool = byteBufferPool;
+		}
+		
+		public void finished() {
+			byteBufferPool.finished(this);
+		}
+		
+		public int compareTo(ByteBuffer other) {
+			return new Integer(size()).compareTo(other.size());
+		}
+		
+		public int size() {
+			return bytes.length;
+		}
+		
+		public byte[] getBytes() {
+			return bytes;
+		}
+	}
+	
+	private void finished(ByteBuffer byteBuffer) {
+		lock.lock();
+		try {
+			inuse.remove(byteBuffer);
+			available.add(byteBuffer);
+		} finally {
+			lock.unlock();
+		}
+	}
+	
+	private void checkCount() {
+		lock.lock();
+		try {
+			int dif = available.size() - maxCount;
+			if (dif > 0) {
+				int count = 0;
+				Iterator<ByteBuffer> iterator = available.iterator();
+				while (iterator.hasNext() && count < dif) {
+					iterator.next();
+					iterator.remove();
+					count++;
+				}
+			}
+		} finally {
+			lock.unlock();
+		}
+	}
+	
+	public ByteBuffer get(int size) {
+		lock.lock();
+		try {
+			ByteBuffer byteBuffer = null;
+			if (available.size() > 0) available.last();
+			if (byteBuffer == null || size > byteBuffer.size()) {
+				byteBuffer = new ByteBuffer(size, this);
+			} else {
+				available.remove(byteBuffer);
+			}
+			inuse.add(byteBuffer);
+			return byteBuffer;
+		} finally {
+			lock.unlock();
+		}
+	}
+}
Index: ocean/src/org/apache/lucene/ocean/util/DocumentSerializer.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/util/DocumentSerializer.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/util/DocumentSerializer.java	(revision 0)
@@ -0,0 +1,24 @@
+package org.apache.lucene.ocean.util;
+
+import java.io.IOException;
+
+import org.apache.commons.lang.SerializationUtils;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.store.IndexInput;
+import org.apache.lucene.store.IndexOutput;
+
+public class DocumentSerializer {
+  
+  public static void write(Document document, IndexOutput output) throws IOException {
+    byte[] bytes = SerializationUtils.serialize(document);
+    output.writeVInt(bytes.length);
+    output.writeBytes(bytes, bytes.length);
+  }
+  
+  public static Document read(IndexInput input) throws IOException {
+    int length = input.readVInt();
+    byte[] bytes = new byte[length];
+    input.readBytes(bytes, 0, length);
+    return (Document)SerializationUtils.deserialize(bytes);
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/util/LongSequence.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/util/LongSequence.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/util/LongSequence.java	(revision 0)
@@ -0,0 +1,43 @@
+package org.apache.lucene.ocean.util;
+
+import java.util.concurrent.locks.ReentrantLock;
+
+public class LongSequence {
+	private long value;
+	private int increment;
+	private ReentrantLock lock = new ReentrantLock();
+
+	public LongSequence(long value, int increment) {
+		this.value = value;
+		this.increment = increment;
+	}
+	
+	public long get() {
+		lock.lock();
+		try {
+			return value;
+		} finally {
+			lock.unlock();
+		}
+	}
+	
+	public void set(long i) {
+		lock.lock();
+		try {
+			value = i;
+		} finally {
+			lock.unlock();
+		}
+	}
+	
+	public long getAndIncrement() {
+		lock.lock();
+		try {
+			long v = value;
+			value += increment;
+			return v;
+		} finally {
+			lock.unlock();
+		}
+	}
+}
Index: ocean/src/org/apache/lucene/ocean/util/FastInputStream.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/util/FastInputStream.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/util/FastInputStream.java	(revision 0)
@@ -0,0 +1,215 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.ocean.util;
+
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+
+/** Single threaded buffered InputStream
+ *  Internal Solr use only, subject to change.
+ */
+public class FastInputStream extends InputStream implements DataInput {
+  private final InputStream in;
+  private final byte[] buf;
+  private int pos;
+  private int end;
+
+  public FastInputStream(InputStream in) {
+  // use default BUFSIZE of BufferedOutputStream so if we wrap that
+  // it won't cause double buffering.
+    this(in, new byte[8192], 0, 0);
+  }
+
+  public FastInputStream(InputStream in, byte[] tempBuffer, int start, int end) {
+    this.in = in;
+    this.buf = tempBuffer;
+    this.pos = start;
+    this.end = end;
+  }
+
+
+  public static FastInputStream wrap(InputStream in) {
+    return (in instanceof FastInputStream) ? (FastInputStream)in : new FastInputStream(in);
+  }
+
+  @Override
+  public int read() throws IOException {
+    if (pos >= end) {
+      refill();
+      if (pos >= end) return -1;
+    }
+    return buf[pos++] & 0xff;     
+  }
+
+  public int readUnsignedByte() throws IOException {
+    if (pos >= end) {
+      refill();
+      if (pos >= end) throw new EOFException();
+    }
+    return buf[pos++] & 0xff;
+  }
+
+  public void refill() throws IOException {
+    // this will set end to -1 at EOF
+    end = in.read(buf, 0, buf.length);
+    pos = 0;
+  }
+
+  @Override
+  public int available() throws IOException {
+    return end - pos;
+  }
+
+  @Override
+  public int read(byte b[], int off, int len) throws IOException {
+    int r=0;  // number of bytes read
+    // first read from our buffer;
+    if (end-pos > 0) {
+      r = Math.min(end-pos, len);
+      System.arraycopy(buf, pos, b, off, r);      
+      pos += r;
+    }
+
+    if (r == len) return r;
+
+    // amount left to read is >= buffer size
+    if (len-r >= buf.length) {
+      int ret = in.read(b, off+r, len-r);
+      if (ret==-1) return r==0 ? -1 : r;
+      r += ret;
+      return r;
+    }
+
+    refill();
+
+    // first read from our buffer;
+    if (end-pos > 0) {
+      int toRead = Math.min(end-pos, len-r);
+      System.arraycopy(buf, pos, b, off+r, toRead);
+      pos += toRead;
+      r += toRead;
+      return r;
+    }
+    
+    return -1;
+  }
+
+  @Override
+  public void close() throws IOException {
+    in.close();
+  }
+
+  public void readFully(byte b[]) throws IOException {
+    readFully(b, 0, b.length);
+  }
+
+  public void readFully(byte b[], int off, int len) throws IOException {
+    while (len>0) {
+      int ret = read(b, off, len);
+      if (ret==-1) {
+        throw new EOFException();
+      }
+      off += ret;
+      len -= ret;
+    }
+  }
+
+  public int skipBytes(int n) throws IOException {
+    if (end-pos >= n) {
+      pos += n;
+      return n;
+    }
+
+    if (end-pos<0) return -1;
+    
+    int r = end-pos;
+    pos = end;
+
+    while (r < n) {
+      refill();
+      if (end-pos <= 0) return r;
+      int toRead = Math.min(end-pos, n-r);
+      r += toRead;
+      pos += toRead;
+    }
+
+    return r;
+  }
+
+  public boolean readBoolean() throws IOException {
+    return readByte()==1;
+  }
+
+  public byte readByte() throws IOException {
+    if (pos >= end) {
+      refill();
+      if (pos >= end) throw new EOFException();
+    }
+    return buf[pos++];
+  }
+
+
+  public short readShort() throws IOException {
+    return (short)((readUnsignedByte() << 8) | readUnsignedByte());
+  }
+
+  public int readUnsignedShort() throws IOException {
+    return (readUnsignedByte() << 8) | readUnsignedByte();
+  }
+
+  public char readChar() throws IOException {
+    return (char)((readUnsignedByte() << 8) | readUnsignedByte());
+  }
+
+  public int readInt() throws IOException {
+    return  ((readUnsignedByte() << 24)
+            |(readUnsignedByte() << 16)
+            |(readUnsignedByte() << 8)
+            | readUnsignedByte());
+  }
+
+  public long readLong() throws IOException {
+    return  (((long)readUnsignedByte()) << 56)
+            | (((long)readUnsignedByte()) << 48)
+            | (((long)readUnsignedByte()) << 40)
+            | (((long)readUnsignedByte()) << 32)
+            | (((long)readUnsignedByte()) << 24)
+            | (readUnsignedByte() << 16)
+            | (readUnsignedByte() << 8)
+            | (readUnsignedByte());
+  }
+
+  public float readFloat() throws IOException {
+    return Float.intBitsToFloat(readInt());    
+  }
+
+  public double readDouble() throws IOException {
+    return Double.longBitsToDouble(readLong());    
+  }
+
+  public String readLine() throws IOException {
+    return new DataInputStream(this).readLine();
+  }
+
+  public String readUTF() throws IOException {
+    return new DataInputStream(this).readUTF();
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/util/CElement.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/util/CElement.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/util/CElement.java	(revision 0)
@@ -0,0 +1,11 @@
+package org.apache.lucene.ocean.util;
+
+import org.jdom.Element;
+
+/**
+ *
+ * @author jasonr
+ */
+public interface CElement {
+  public Element toElement() throws Exception;
+}
Index: ocean/src/org/apache/lucene/ocean/util/Constants.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/util/Constants.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/util/Constants.java	(revision 0)
@@ -0,0 +1,7 @@
+package org.apache.lucene.ocean.util;
+
+public interface Constants {
+  //public static final String ID = "_id".intern();
+  public static final String DOCUMENTID = "_documentid".intern();
+  public static final String SNAPSHOTID = "_snapshotid".intern();
+}
Index: ocean/src/org/apache/lucene/ocean/util/SortedList.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/util/SortedList.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/util/SortedList.java	(revision 0)
@@ -0,0 +1,321 @@
+package org.apache.lucene.ocean.util;
+
+import java.util.AbstractCollection;
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.ObjectUtils;
+
+public class SortedList<K extends Comparable<K>,V> implements Map<K,V> {
+  private List<SortedEntry<K,V>> entries = new ArrayList<SortedEntry<K,V>>();
+  private transient Set<Map.Entry<K,V>> entrySet = null;
+  transient volatile Set<K>        keySet = null;
+  transient volatile Collection<V> values = null;
+  
+  private Map.Entry<K,V> removeMapping(Map.Entry<K,V> toRemove) {
+    int pos = Collections.binarySearch(entries, new SortedEntry<K,V>((K) toRemove.getKey()));
+    if (pos >= 0) {
+      SortedEntry<K,V> entry = entries.get(pos);
+      if (entry.value.equals(toRemove.getValue())) {
+        entries.remove(pos);
+        return entry;
+      }
+    }
+    return null;
+  }
+  
+  public static class SortedEntry<K extends Comparable<K>,V> implements Map.Entry<K,V>, Comparable<SortedEntry<K,V>> {
+    private K key;
+    private V value;
+
+    private SortedEntry(K key, V value) {
+      this.key = key;
+      this.value = value;
+    }
+
+    public K setKey(K key) {
+      K oldKey = this.key;
+      this.key = key;
+      return oldKey;
+    }
+
+    public V setValue(V value) {
+      V oldValue = this.value;
+      this.value = value;
+      return oldValue;
+    }
+
+    public K getKey() {
+      return key;
+    }
+
+    public V getValue() {
+      return value;
+    }
+
+    private SortedEntry(K key) {
+      this.key = key;
+    }
+
+    public int compareTo(SortedEntry<K,V> other) {
+      return key.compareTo(other.key);
+    }
+  }
+
+  public void putAll(Map<? extends K,? extends V> m) {
+    for (Iterator<? extends Map.Entry<? extends K,? extends V>> i = m.entrySet().iterator(); i.hasNext();) {
+      Map.Entry<? extends K,? extends V> e = i.next();
+      put(e.getKey(), e.getValue());
+    }
+  }
+  /**
+  public Set<K> keySet() {
+    Set<K> set = new HashSet<K>(entries.size());
+    for (SortedEntry<K,V> entry : entries) {
+      set.add(entry.key);
+    }
+    return set;
+  }
+  **/
+  // public Set<Map.Entry<K,V>> entrySet() {
+  // Set<Map.Entry<K,V>> set = new HashSet<Map.Entry<K,V>>(entries);
+  // return set;
+  // }
+
+  public boolean isEmpty() {
+    return entries.size() == 0;
+  }
+
+  public void clear() {
+    entries.clear();
+  }
+
+  public int size() {
+    return entries.size();
+  }
+
+  private int getPos(K key) {
+    int pos = Collections.binarySearch(entries, new SortedEntry<K,V>(key));
+    if (pos < 0)
+      pos = -1 - pos;
+    return pos;
+  }
+
+  public boolean containsValue(Object value) {
+    for (SortedEntry<K,V> entry : entries) {
+      boolean b = ObjectUtils.equals(value, entry.value);
+      if (b)
+        return true;
+    }
+    return false;
+  }
+
+  public boolean containsKey(Object key) {
+    int pos = Collections.binarySearch(entries, new SortedEntry<K,V>((K) key));
+    return pos >= 0;
+  }
+
+  public K lastKey() {
+    if (entries.size() == 0)
+      return null;
+    return entries.get(entries.size() - 1).key;
+  }
+
+  public V lastValue() {
+    if (entries.size() == 0)
+      return null;
+    return entries.get(entries.size() - 1).value;
+  }
+
+  public V put(K key, V value) {
+    V oldValue = null;
+    int pos = Collections.binarySearch(entries, new SortedEntry<K,V>(key));
+    if (pos >= 0) {
+      oldValue = entries.get(pos).value;
+    }
+    if (pos < 0)
+      pos = -1 - pos;
+
+    entries.add(pos, new SortedEntry<K,V>(key, value));
+    return oldValue;
+  }
+
+  private SortedEntry<K,V> getEntry(Object key) {
+    int pos = Collections.binarySearch(entries, new SortedEntry<K,V>((K) key));
+    if (pos >= 0) {
+      return entries.get(pos);
+    } else {
+      return null;
+    }
+  }
+
+  public V get(Object key) {
+    int pos = Collections.binarySearch(entries, new SortedEntry<K,V>((K) key));
+    if (pos >= 0) {
+      return entries.get(pos).value;
+    } else {
+      return null;
+    }
+  }
+
+  public V remove(Object key) {
+    int pos = Collections.binarySearch(entries, new SortedEntry<K,V>((K) key));
+    if (pos >= 0) {
+      SortedEntry<K,V> entry = entries.get(pos);
+      entries.remove(pos);
+      return entry.value;
+    }
+    return null;
+  }
+
+  public void add(K key, V value) {
+    int pos = getPos(key);
+    entries.add(pos, new SortedEntry<K,V>(key, value));
+  }
+
+  private abstract class SortedListIterator<E> implements Iterator<E> {
+    private Iterator<SortedEntry<K,V>> iterator;
+    
+    public SortedListIterator() {
+      iterator = entries.iterator();
+    }
+    
+    protected Map.Entry<K,V> nextEntry() {
+      return iterator.next();
+    }
+    
+    public void remove() {
+      iterator.remove();
+    }
+    
+    public boolean hasNext() {
+      return iterator.hasNext();
+    }
+  }
+
+  private class ValueIterator extends SortedListIterator<V> {
+    public V next() {
+      return nextEntry().getValue();
+    }
+  }
+
+  private class KeyIterator extends SortedListIterator<K> {
+    public K next() {
+      return nextEntry().getKey();
+    }
+  }
+
+  private class EntryIterator extends SortedListIterator<Map.Entry<K,V>> {
+    public Map.Entry<K,V> next() {
+      return nextEntry();
+    }
+  }
+
+  // Subclass overrides these to alter behavior of views' iterator() method
+  Iterator<K> newKeyIterator() {
+    return new KeyIterator();
+  }
+
+  Iterator<V> newValueIterator() {
+    return new ValueIterator();
+  }
+
+  Iterator<Map.Entry<K,V>> newEntryIterator() {
+    return new EntryIterator();
+  }
+
+  public Set<K> keySet() {
+    Set<K> ks = keySet;
+    return (ks != null ? ks : (keySet = new KeySet()));
+  }
+
+  private class KeySet extends AbstractSet<K> {
+    public Iterator<K> iterator() {
+      return newKeyIterator();
+    }
+
+    public int size() {
+      return entries.size();
+    }
+
+    public boolean contains(Object o) {
+      return containsKey(o);
+    }
+
+    public boolean remove(Object o) {
+      return SortedList.this.remove(o) != null;
+    }
+
+    public void clear() {
+      SortedList.this.clear();
+    }
+  }
+
+  public Collection<V> values() {
+    Collection<V> vs = values;
+    return (vs != null ? vs : (values = new Values()));
+  }
+
+  private class Values extends AbstractCollection<V> {
+    public Iterator<V> iterator() {
+      return newValueIterator();
+    }
+
+    public int size() {
+      return SortedList.this.size();
+    }
+
+    public boolean contains(Object o) {
+      return containsValue(o);
+    }
+
+    public void clear() {
+      SortedList.this.clear();
+    }
+  }
+
+  public Set<Map.Entry<K,V>> entrySet() {
+    Set<Map.Entry<K,V>> es = entrySet;
+    return (es != null ? es : (entrySet = (Set<Map.Entry<K,V>>) (Set) new EntrySet()));
+  }
+
+  private class EntrySet extends AbstractSet/* <Map.Entry<K,V>> */{
+    public Iterator/* <Map.Entry<K,V>> */iterator() {
+      return newEntryIterator();
+    }
+
+    public boolean contains(Object o) {
+      if (!(o instanceof Map.Entry))
+        return false;
+      Map.Entry<K,V> e = (Map.Entry<K,V>) o;
+      SortedEntry<K,V> candidate = getEntry(e.getKey());
+      return candidate != null && candidate.equals(e);
+    }
+
+    public boolean remove(Object o) {
+      return removeMapping((Map.Entry<K,V>)o) != null;
+    }
+
+    public int size() {
+      return SortedList.this.size();
+    }
+
+    public void clear() {
+      SortedList.this.clear();
+    }
+  }
+
+  /**
+   * public Collection<V> values() { List<V> values = new ArrayList<V>(entries.size());
+   * for (SortedEntry<K,V> entry : entries) { values.add(entry.value); } return
+   * values; }
+   */
+}
Index: ocean/src/org/apache/lucene/ocean/util/Util.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/util/Util.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/util/Util.java	(revision 0)
@@ -0,0 +1,444 @@
+package org.apache.lucene.ocean.util;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.io.Reader;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.text.DateFormat;
+import java.text.DecimalFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TimeZone;
+import java.util.TreeSet;
+import java.util.regex.Pattern;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NameNotFoundException;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.index.TermDocs;
+import org.apache.lucene.ocean.LogDirectory;
+import org.apache.lucene.search.ExtendedFieldCache;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.IndexInput;
+import org.apache.lucene.store.IndexOutput;
+
+import com.imagero.uio.RandomAccessIO;
+import com.imagero.uio.RandomAccessInput;
+
+public class Util {
+  public static TimeZone UTC = TimeZone.getTimeZone("UTC");
+  public final static Pattern splitPattern = Pattern.compile(",| ");
+  private static ThreadLocalDateFormat dateFormatThreadLocal = new ThreadLocalDateFormat();
+
+  public static String formatSnapshotId(BigDecimal id) {
+    DecimalFormat format = new DecimalFormat("##0.00");
+    return format.format(id);
+  }
+
+  /**
+   * Handles size 0 collections returns null
+   */
+  public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) {
+    if (coll.size() == 0)
+      return null;
+    return Collections.max(coll);
+  }
+
+  public static void touchFile(String file, Directory directory) throws IOException {
+    IndexOutput output = directory.createOutput(file);
+    output.close();
+  }
+
+  public static String getString(String file, LogDirectory directory) throws IOException {
+    try {
+      RandomAccessInput input = directory.openInput(file);
+      if (input.length() == 0)
+        return null;
+      byte[] bytes = new byte[(int) input.length()];
+      input.read(bytes);
+      return new String(bytes, "UTF-8");
+    } catch (Throwable ioException) {
+      IOException newIOException = new IOException("file: "+file);
+      newIOException.initCause(ioException);
+      throw newIOException;
+    }
+  }
+
+  public static void save(String string, String file, LogDirectory directory) throws IOException {
+    byte[] bytes = string.getBytes("UTF-8");
+    RandomAccessIO output = directory.getOutput(file, true);
+    output.write(bytes);
+    output.flush();
+  }
+
+  public static String getString(String file, Directory directory) throws IOException {
+    IndexInput input = directory.openInput(file);
+    byte[] bytes = new byte[(int) input.length()];
+    input.readBytes(bytes, 0, bytes.length);
+    return new String(bytes, "UTF-8");
+  }
+
+  public static void save(String string, String file, Directory directory) throws IOException {
+    byte[] bytes = string.getBytes("UTF-8");
+    IndexOutput output = directory.createOutput(file);
+    output.writeBytes(bytes, bytes.length);
+    output.flush();
+    output.close();
+  }
+
+  public static void copy(IndexInput input, IndexOutput output, byte[] buffer) throws IOException {
+    long len = input.length();
+    long readCount = 0;
+    while (readCount < len) {
+      int toRead = readCount + buffer.length > len ? (int) (len - readCount) : buffer.length;
+      input.readBytes(buffer, 0, toRead);
+      output.writeBytes(buffer, toRead);
+      readCount += toRead;
+    }
+  }
+
+  public static <K,V> V getLastValue(SortedMap<K,V> map) {
+    if (map.size() == 0)
+      return null;
+    return map.get(map.lastKey());
+  }
+
+  public static void setValue(String name, long value, Document document) {
+    String encoded = longToEncoded(value);
+    document.add(new Field(name, encoded, Field.Store.YES, Field.Index.UN_TOKENIZED));
+  }
+
+  public static boolean mkdir(File dir) {
+    if (!dir.exists()) {
+      return dir.mkdirs();
+    }
+    return false;
+  }
+
+  public static String longToEncoded(long value) {
+    return long2sortableStr(value);
+  }
+
+  public static long longFromEncoded(String string) {
+    return SortableStr2long(string, 0, 5);
+  }
+
+  public static String long2sortableStr(long val) {
+    char[] arr = new char[5];
+    long2sortableStr(val, arr, 0);
+    return new String(arr, 0, 5);
+  }
+
+  // uses binary representation of an int to build a string of
+  // chars that will sort correctly. Only char ranges
+  // less than 0xd800 will be used to avoid UCS-16 surrogates.
+  // we can use the lowest 15 bits of a char, (or a mask of 0x7fff)
+  public static int long2sortableStr(long val, char[] out, int offset) {
+    val += Long.MIN_VALUE;
+    out[offset++] = (char) (val >>> 60);
+    out[offset++] = (char) (val >>> 45 & 0x7fff);
+    out[offset++] = (char) (val >>> 30 & 0x7fff);
+    out[offset++] = (char) (val >>> 15 & 0x7fff);
+    out[offset] = (char) (val & 0x7fff);
+    return 5;
+  }
+
+  public static long SortableStr2long(String sval, int offset, int len) {
+    long val = (long) (sval.charAt(offset++)) << 60;
+    val |= ((long) sval.charAt(offset++)) << 45;
+    val |= ((long) sval.charAt(offset++)) << 30;
+    val |= sval.charAt(offset++) << 15;
+    val |= sval.charAt(offset);
+    val -= Long.MIN_VALUE;
+    return val;
+  }
+
+  public static int getDoc(String fieldName, long value, IndexReader indexReader) throws IOException {
+    String encoded = longToEncoded(value);
+    return getTermDoc(new Term(fieldName, encoded), indexReader);
+  }
+
+  public static int getTermDoc(Term term, IndexReader indexReader) throws IOException {
+    TermDocs docs = indexReader.termDocs(term);
+    try {
+      if (docs.next()) {
+        return docs.doc();
+      }
+    } finally {
+      docs.close();
+    }
+    return -1;
+  }
+
+  public static List<Integer> getTermDocs(Term term, IndexReader indexReader) throws IOException {
+    List<Integer> list = new ArrayList<Integer>();
+    TermDocs docs = indexReader.termDocs(term);
+    try {
+      while (docs.next()) {
+        list.add(docs.doc());
+      }
+    } finally {
+      docs.close();
+    }
+    return list;
+  }
+
+  public static long getSize(Directory directory) throws IOException {
+    long total = 0;
+    for (String file : directory.list()) {
+      total += directory.fileLength(file);
+    }
+    return total;
+  }
+
+  public static void copy(InputStream is, RandomAccessFile ras, byte[] buf) throws IOException {
+    int numRead;
+    while ((numRead = is.read(buf)) >= 0) {
+      ras.write(buf, 0, numRead);
+    }
+  }
+
+  public static File getDirectory(File root, String path) {
+    File file = new File(root, path);
+    if (!file.exists()) {
+      file.mkdirs();
+    }
+    return file;
+  }
+
+  public static DateFormat getThreadLocalDateFormat() {
+    return dateFormatThreadLocal.get();
+  }
+
+  public static String formatDate(Date date) {
+    DateFormat dateFormat = getThreadLocalDateFormat();
+    return dateFormat.format(date);
+  }
+
+  public static Date parseDate(String string) throws ParseException {
+    DateFormat dateFormat = getThreadLocalDateFormat();
+    return dateFormat.parse(string);
+  }
+
+  private static class ThreadLocalDateFormat extends ThreadLocal<DateFormat> {
+    DateFormat proto;
+
+    public ThreadLocalDateFormat() {
+      super();
+      SimpleDateFormat tmp = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
+      tmp.setTimeZone(UTC);
+      proto = tmp;
+    }
+
+    protected DateFormat initialValue() {
+      return (DateFormat) proto.clone();
+    }
+  }
+
+  public static long[] getFieldCacheLong(String field, IndexReader indexReader) throws IOException {
+    return ExtendedFieldCache.EXT_DEFAULT.getLongs(indexReader, field);
+  }
+
+  public static boolean isTrue(String string) {
+    return StringUtils.equalsIgnoreCase("true", string);
+  }
+
+  public static Object construct(Object parameter, Class clazz) throws Exception {
+    Constructor constructor = clazz.getDeclaredConstructor(parameter.getClass());
+    try {
+      return constructor.newInstance(parameter);
+    } catch (InvocationTargetException invocationTargetException) {
+      Throwable cause = invocationTargetException.getCause();
+      if (cause instanceof Exception)
+        throw (Exception) cause;
+      else
+        throw invocationTargetException;
+    }
+  }
+
+  public static Long parseLong(String value) {
+    if (StringUtils.isBlank(value)) {
+      return null;
+    }
+    try {
+      return new Long(value);
+    } catch (Throwable ex) {
+      return null;
+    }
+  }
+
+  public static URL parseURL(String string) throws MalformedURLException {
+    if (StringUtils.isBlank(string)) {
+      return null;
+    }
+    return new URL(string);
+  }
+
+  public static List<String> readLines(Reader reader) throws Exception {
+    return IOUtils.readLines(reader);
+  }
+
+  public static IOException asIOException(String message, Throwable throwable) throws IOException {
+    IOException ioException = new IOException(message);
+    ioException.initCause(throwable);
+    return ioException;
+  }
+
+  public static IOException asIOException(Throwable throwable) {
+    if (throwable instanceof IOException) {
+      return (IOException) throwable;
+    }
+    IOException ioException = new IOException(throwable.getMessage());
+    ioException.initCause(throwable);
+    return ioException;
+  }
+
+  public static void copy(InputStream is, OutputStream os, byte[] buf) throws IOException {
+    int numRead;
+    while ((numRead = is.read(buf)) >= 0) {
+      os.write(buf, 0, numRead);
+    }
+  }
+
+  public static int[] toIntArray(List<Integer> list) {
+    int size = list.size();
+    int[] array = new int[size];
+    int x = 0;
+    for (int i : list) {
+      array[x] = i;
+      x++;
+    }
+    return array;
+  }
+
+  public static List<URL> loadUrls(File file) throws IOException {
+    List<String> lines = IOUtils.readLines(new FileReader(file));
+    List<URL> urls = new ArrayList<URL>();
+    for (String line : lines) {
+      urls.add(new URL(line));
+    }
+    return urls;
+  }
+
+  /**
+   * public static HttpParameters toHttpParameters(HttpServletRequest request) {
+   * try { if (StringUtils.equalsIgnoreCase("post", request.getMethod())) {
+   * HttpParameters parameters = new HttpParameters(); URL url = new
+   * URL(request.getRequestURL().toString()); CGIParser cgiParser = new
+   * CGIParser(url.toString(), "UTF-8"); for (String name :
+   * cgiParser.getParameterNameList()) { for (String value :
+   * cgiParser.getParameterValues(name)) { parameters.add(name, value); } }
+   * return parameters; } } catch (Exception exception) { throw new
+   * RuntimeException(exception); } if (StringUtils.equalsIgnoreCase("get",
+   * request.getMethod())) { HttpParameters parameters = new HttpParameters();
+   * Enumeration paramEnum = request.getParameterNames(); while
+   * (paramEnum.hasMoreElements()) { String name = (String)
+   * paramEnum.nextElement(); String[] array = request.getParameterValues(name);
+   * if (array != null && array.length > 0) { for (String value : array) {
+   * parameters.add(name, value); } } } return parameters; } throw new
+   * RuntimeException("unknown http method " + request.getMethod()); }
+   */
+  public static Long getNextServerSequence(Long value, int serverNumber) {
+    if (value == null) {
+      return new Long(serverNumber);
+    }
+    Long i = null;
+    if (value > 99) {
+      String string = value.toString();
+      String substring = string.substring(0, string.length() - 2);
+      i = new Long(substring + "00");
+    } else {
+      i = new Long(0);
+    }
+    long v = i + serverNumber;
+    return v + 100;
+  }
+
+  public static int getServerNumber(BigInteger id) {
+    String string = id.toString();
+    String substring = string.substring(string.length() - 2, string.length());
+    return Integer.parseInt(substring);
+  }
+
+  public static SortedSet<String> splitToSortedSet(String string) {
+    if (StringUtils.isBlank(string)) {
+      return null;
+    }
+    String[] array = splitPattern.split(string.trim(), 0);
+    TreeSet<String> sortedSet = new TreeSet<String>();
+    for (int x = 0; x < array.length; x++) {
+      sortedSet.add(array[x]);
+    }
+    return sortedSet;
+  }
+
+  public static File getAppServerHome() {
+    return new File(System.getProperty("catalina.home"));
+  }
+
+  public static File getHomeDirectory(String name, File defaultDirectory) throws Exception {
+    String value = System.getenv(name);
+    if (value != null)
+      return new File(value);
+    value = System.getProperty(name);
+    if (value != null)
+      return new File(value);
+    Context context = (Context) new InitialContext().lookup("java:comp/env");
+    try {
+      String string = (String) context.lookup(name);
+      if (StringUtils.isNotBlank(value))
+        return new File(value);
+    } catch (NameNotFoundException nameNotFoundException) {
+    }
+    defaultDirectory.mkdirs();
+    return defaultDirectory;
+  }
+
+  public static Object getFirst(List list) {
+    Iterator iterator = list.iterator();
+    if (iterator.hasNext()) {
+      return iterator.next();
+    } else {
+      return null;
+    }
+  }
+
+  public static Map<String,String> toMapExcept(org.jdom.Element element, String exceptName) {
+    Map<String,String> map = new HashMap<String,String>();
+    for (Object object : element.getAttributes()) {
+      org.jdom.Attribute attribute = (org.jdom.Attribute) object;
+      String name = attribute.getName();
+      if (!StringUtils.equals(name, exceptName)) {
+        map.put(name, attribute.getValue());
+      }
+    }
+    return map;
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/util/ByteArrayInputStream.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/util/ByteArrayInputStream.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/util/ByteArrayInputStream.java	(revision 0)
@@ -0,0 +1,97 @@
+package org.apache.lucene.ocean.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.lucene.ocean.util.ByteBufferPool.ByteBuffer;
+import org.apache.lucene.ocean.util.ByteBufferPool.ByteBuffers;
+
+public class ByteArrayInputStream extends InputStream {
+	private List<ByteBuffer> byteBuffers;
+  private long length;
+
+  private ByteBuffer currentBuffer;
+  private int currentBufferIndex;
+  
+  private int bufferPosition;
+  private long bufferStart;
+  private int bufferLength;
+  private int bufferSize;
+  
+  public ByteArrayInputStream(ByteBuffers byteBuffers) throws IOException {
+  	this.length = byteBuffers.getLength();
+  	int numRead = 0;
+  	this.byteBuffers = byteBuffers.getByteBuffers();
+    currentBufferIndex = -1;
+    currentBuffer = null;
+  }
+  
+  public ByteArrayInputStream(int bufferSize, InputStream input, int length, ByteBufferPool byteBufferPool) throws IOException {
+  	this.length = length;
+  	int numRead = 0;
+  	byteBuffers = new ArrayList<ByteBuffer>();
+		while (true) {
+			ByteBuffer byteBuffer = byteBufferPool.get(bufferSize);
+			int n = input.read(byteBuffer.getBytes());
+			if (n == -1) {
+				byteBuffer.finished();
+				break;
+			}
+			numRead += n;
+			byteBuffers.add(byteBuffer);
+		}
+		if (length != numRead) {
+			throw new IOException("num read different than length");
+		}
+    currentBufferIndex = -1;
+    currentBuffer = null;
+  }
+
+  public void close() {
+    for (ByteBuffer byteBuffer : byteBuffers) {
+    	byteBuffer.finished();
+    }
+  }
+
+  public long length() {
+    return length;
+  }
+
+  public int read() throws IOException {
+    if (bufferPosition >= bufferLength) {
+      currentBufferIndex++;
+      switchCurrentBuffer();
+    }
+    return currentBuffer.getBytes()[bufferPosition++];
+  }
+
+  public void readBytes(byte[] b, int offset, int len) throws IOException {
+    while (len > 0) {
+      if (bufferPosition >= bufferLength) {
+        currentBufferIndex++;
+        switchCurrentBuffer();
+      }
+      int remainInBuffer = bufferLength - bufferPosition;
+      int bytesToCopy = len < remainInBuffer ? len : remainInBuffer;
+      System.arraycopy(currentBuffer, bufferPosition, b, offset, bytesToCopy);
+      offset += bytesToCopy;
+      len -= bytesToCopy;
+      bufferPosition += bytesToCopy;
+    }
+  }
+
+  private final void switchCurrentBuffer() throws IOException {
+    if (currentBufferIndex >= byteBuffers.size()) {
+      // end of file reached, no more buffers left
+      throw new IOException("Read past EOF");
+    } else {
+      currentBuffer = byteBuffers.get(currentBufferIndex);
+      bufferPosition = 0;
+      bufferStart = (long) bufferSize * (long) currentBufferIndex;
+      long buflen = length - bufferStart;
+      bufferLength = buflen > bufferSize ? bufferSize : (int) buflen;
+    }
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/util/XMLUtil.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/util/XMLUtil.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/util/XMLUtil.java	(revision 0)
@@ -0,0 +1,435 @@
+package org.apache.lucene.ocean.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.io.Writer;
+import java.lang.reflect.Field;
+import java.net.URL;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.logging.Logger;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.BooleanUtils;
+import org.apache.commons.lang.StringUtils;
+import org.jdom.Attribute;
+import org.jdom.Document;
+import org.jdom.Element;
+import org.jdom.Namespace;
+import org.jdom.Verifier;
+import org.jdom.input.SAXBuilder;
+import org.jdom.output.Format;
+import org.jdom.output.XMLOutputter;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+
+/**
+ * 
+ * @author Jason Rutherglen
+ */
+public class XMLUtil {
+	public static Logger log = Logger.getLogger(XMLUtil.class.getName());
+
+	public XMLUtil() {
+	}
+
+	public static Collection<Attribute> getAttributes(Element element) {
+		return (Collection<Attribute>)element.getAttributes();
+	}
+	
+	public static String printAndDetach(Element element) throws Exception {
+		List<Element> list = element.cloneContent();
+		Element root = null;
+		if (list.size() == 1) {
+			root = list.get(0);
+		} else {
+			root = new Element("root");
+			root.addContent(list);
+		}
+		return outputElement(root);
+	}
+
+	public static Object getProperValue(Field field, String string) throws Exception {
+		if (!field.getType().isAssignableFrom(String.class)) {
+			if (field.getType().isAssignableFrom(URL.class)) {
+				return new URL(string);
+			} else if (field.getType().isAssignableFrom(Date.class)) {
+				return Util.parseDate(string);
+			} else if (field.getType().isAssignableFrom(File.class)) {
+				return new File(string);
+			} else if (field.getType().isAssignableFrom(Long.class)) {
+				return new Long(string);
+			} else if (field.getType().isAssignableFrom(Double.class)) {
+				return new Double(string);
+			}
+			throw new Exception("unknown type " + field.getType().getName());
+		} else
+			return string;
+	}
+
+	public static void addAll(List<Element> children, Element parent) {
+		for (Element element : children) {
+			parent.addContent(element);
+		}
+	}
+
+	public static void printList(String rootName, List list, PrintWriter writer) throws Exception {
+		Format format = Format.getPrettyFormat();
+		format.setLineSeparator("\n");
+		XMLOutputter outputter = new XMLOutputter(format);
+		Element root = new Element(rootName);
+		for (Object object : list) {
+			if (object instanceof CElement) {
+				CElement cElement = (CElement) object;
+				root.addContent(cElement.toElement());
+			}
+		}
+		Document document = new Document();
+		document.addContent(root);
+		outputter.output(document, writer);
+	}
+
+	public static List<Element> getChildren(String xml) throws Exception {
+		return getChildren(parseElement(xml));
+	}
+
+	public static List<Element> getChildren(String name, Element element) {
+		List<Element> elements = (List<Element>) element.getChildren(name);
+		if (element == null)
+			return new ArrayList<Element>(0);
+		else
+			return elements;
+	}
+
+	public static List<Element> getChildren(Element element) {
+		List<Element> elements = (List<Element>) element.getChildren();
+		if (element == null)
+			return new ArrayList<Element>(0);
+		else
+			return elements;
+	}
+
+	public static Element getFirstChild(Element element) {
+		return (Element) Util.getFirst(element.getChildren());
+	}
+
+	public static void setAttribute(String name, Object value, Element element) {
+		if (value == null) {
+			return;
+		}
+		if (value instanceof Date) {
+			value = Util.formatDate((Date) value);
+		}
+		element.setAttribute(name, value.toString());
+	}
+
+	public static java.net.URL getChildURL(String name, Element element) {
+		String childText = element.getChildText(name);
+		try {
+			return new java.net.URL(childText);
+		} catch (Throwable th) {
+		}
+		return null;
+	}
+
+	public static Element getChild(String name, Element root) {
+		return root.getChild(name);
+	}
+
+	public static java.math.BigInteger getChildBigInteger(String name, Element element) {
+		String childText = element.getChildText(name);
+		try {
+			return new java.math.BigInteger(childText);
+		} catch (Throwable throwable) {
+		}
+		return null;
+	}
+
+	public static Long getChildLong(String name, Element element) throws NumberFormatException {
+		String childText = element.getChildText(name);
+		return new Long(childText);
+	}
+
+	public static String getAttributeString(String name, Element element) {
+		String text = element.getAttributeValue(name);
+		return text;
+	}
+  
+	public static Integer getAttributeInteger(String name, Element element) throws NumberFormatException {
+		String text = element.getAttributeValue(name);
+		if (text == null || text.equals("")) {
+			return null;
+		}
+		return new Integer(text);
+	}
+	
+	public static Float getAttributeFloat(String name, Element element) throws NumberFormatException {
+    String text = element.getAttributeValue(name);
+    if (text == null || text.equals("")) {
+      return null;
+    }
+    return new Float(text);
+  }
+	
+	public static Long getAttributeLong(String name, Element element) throws NumberFormatException {
+		String text = element.getAttributeValue(name);
+		if (text == null || text.equals("")) {
+			return null;
+		}
+		return new Long(text);
+	}
+
+	public static Date getAttributeDate(String name, Element element) throws ParseException {
+		String text = element.getAttributeValue(name);
+		if (text == null || text.equals("")) {
+			return null;
+		}
+		return Util.parseDate(text);
+	}
+
+	public static Boolean getChildBoolean(String name, Element element) {
+		String text = element.getChildText(name);
+		if (StringUtils.isBlank(text)) {
+			return null;
+		}
+		return BooleanUtils.toBooleanObject(text);
+	}
+
+	public static boolean getAttributeBooleanPrimitive(String name, Element element) {
+		String text = element.getAttributeValue(name);
+		if (StringUtils.isBlank(text)) {
+			return false;
+		}
+		return BooleanUtils.toBooleanObject(text);
+	}
+
+	public static Boolean getAttributeBoolean(String name, Element element) {
+		String text = element.getAttributeValue(name);
+		if (StringUtils.isBlank(text)) {
+			return null;
+		}
+		return BooleanUtils.toBooleanObject(text);
+	}
+
+	public static Date getChildDate(String name, Element element) throws ParseException {
+		String text = element.getChildText(name);
+		if (text == null || text.equals("")) {
+			return null;
+		}
+		return parseDate(text);
+	}
+
+	public static Date parseDate(String dateStr) throws ParseException {
+		if (org.apache.commons.lang.StringUtils.isEmpty(dateStr)) {
+			return null;
+		}
+		return Util.parseDate(dateStr);
+	}
+
+	public static String formatDate(Date date) {
+		if (date == null) {
+			return "";
+		}
+		return Util.formatDate(date);
+	}
+
+	public static String getChildText(String name, Element element) {
+		return element.getChildText(name);
+	}
+
+	public static Integer getChildInteger(String name, Element element) {
+		try {
+			String text = element.getChildText(name);
+			return new Integer(text);
+		} catch (Throwable ex) {
+		}
+		return null;
+	}
+
+	public static Double getChildDouble(String name, Element element) throws NumberFormatException {
+		try {
+			String text = element.getChildText(name);
+			return new Double(text);
+		} catch (Throwable ex) {
+		}
+		return null;
+	}
+
+	public static void outputElement(Element element, Writer writer) throws Exception {
+		XMLOutputter xmlOut = new XMLOutputter(Format.getPrettyFormat());
+		xmlOut.output(new Document(element), writer);
+	}
+
+	public static String outputElement(Element element) throws Exception {
+		XMLOutputter xmlOut = new XMLOutputter(Format.getPrettyFormat());
+		String xmlString = xmlOut.outputString(new Document(element));
+		return xmlString;
+	}
+
+	public static String outputElementMinimal(Element element) throws Exception {
+		Format format = Format.getCompactFormat();
+		format.setOmitDeclaration(true);
+		XMLOutputter xmlOut = new XMLOutputter(format);
+		String xmlString = xmlOut.outputString(new Document(element));
+		return xmlString;
+	}
+
+	public static String outputElementOmitDeclaration(Element element) {
+		Format format = Format.getPrettyFormat();
+		format.setOmitDeclaration(true);
+		XMLOutputter xmlOut = new XMLOutputter(format);
+		String xmlString = xmlOut.outputString(new Document(element));
+		return xmlString;
+	}
+
+	public static String outputDocument(Document document) throws Exception {
+		XMLOutputter xmlOut = new XMLOutputter(Format.getPrettyFormat());
+		String xmlString = xmlOut.outputString(document);
+		return xmlString;
+	}
+
+	public static String removeInvalidXMLChars(String value) {
+		StringBuffer buffer = new StringBuffer();
+		char[] array = value.toCharArray();
+		for (int x = 0; x < array.length; x++) {
+			if (Verifier.isXMLCharacter(array[x])) {
+				buffer.append(array[x]);
+			}
+		}
+		return buffer.toString();
+	}
+
+	public static Element createTextElement(String name, Object object, Element parentElement) {
+		if (object == null) {
+			return null;
+		}
+
+		String text = null;
+		if (object instanceof Date) {
+			Date date = (Date) object;
+
+			text = formatDate(date);
+		} else {
+			text = object.toString();
+		}
+		Element element = new Element(name);
+		// XMLOutputter outputter = new XMLOutputter();
+		// text = outputter.escapeElementEntities(text);
+		// text = removeInvalidXMLChars(text);
+		element.setText(text);
+
+		parentElement.addContent(element);
+
+		return element;
+	}
+
+	public static Element createTextElement(String name, Object object, Namespace namespace, Element parentElement) {
+		if (object == null) {
+			return null;
+		}
+
+		String text = null;
+		if (object instanceof File) {
+			text = ((File) object).getAbsolutePath();
+		} else {
+			text = object.toString();
+		}
+		Element element = new Element(name, namespace);
+		XMLOutputter outputter = new XMLOutputter();
+		text = outputter.escapeElementEntities(text);
+		text = removeInvalidXMLChars(text);
+		element.setText(text);
+
+		parentElement.addContent(element);
+
+		return element;
+	}
+
+	public static void saveXML(Element element, File file) throws IOException {
+		Document document = new Document();
+		document.addContent(element);
+		saveXML(document, file);
+	}
+
+	public static void saveXML(Document document, File file) throws IOException {
+		File parentDir = file.getParentFile();
+		if (!parentDir.exists()) {
+			parentDir.mkdirs();
+		}
+		Format format = Format.getPrettyFormat();
+		format.setLineSeparator("\n");
+		XMLOutputter outputter = new XMLOutputter(format);
+		String channelXMLStr = outputter.outputString(document);
+		FileUtils.writeStringToFile(file, channelXMLStr, "UTF-8");
+	}
+
+	/**
+	 * public static XmlPullParser parseDocumentSAX(InputStream input) throws
+	 * Exception { XmlPullParserFactory factory =
+	 * XmlPullParserFactory.newInstance("org.xmlpull.mxp1.MXParserFactory", null);
+	 * factory.setNamespaceAware(false); factory.setValidating(false);
+	 * XmlPullParser xpp = factory.newPullParser(); xpp.setInput(input, "UTF-8");
+	 * return xpp; }
+	 */
+	public static File getChildFile(String name, Element element) throws Exception {
+		String path = element.getChildTextTrim(name);
+		if (StringUtils.isBlank(path)) {
+			return null;
+		}
+		return new File(path);
+	}
+
+	public static Document parseDocument(File file) throws XMLException, IOException {
+		String xml = FileUtils.readFileToString(file, "UTF-8");
+		return parseDocument(xml);
+	}
+
+	public static Element parseElement(File file) throws XMLException, IOException {
+		if (!file.exists()) {
+			return null;
+		}
+		String xml = FileUtils.readFileToString(file, "UTF-8");
+		return parseElement(xml);
+	}
+
+	public static Element parseElement(String xml) throws XMLException {
+		Document document = parseDocument(xml);
+		return document.getRootElement();
+	}
+  
+	public static class XMLException extends Exception {
+		public XMLException(String message, Throwable throwable) {
+			super(message, throwable);
+		}
+	}
+	
+	public static Document parseDocument(String xml) throws XMLException {
+		if (StringUtils.isBlank(xml))
+			throw new IllegalArgumentException("xml blank"); 
+		EntityResolver RESOLVER = new EmptyEntityResolver();
+		SAXBuilder saxBuilder = new SAXBuilder(false);
+		saxBuilder.setEntityResolver(RESOLVER);
+		try {
+			Document document = saxBuilder.build(new StringReader(xml));
+			return document;
+		} catch (Exception exception) {
+			throw new XMLException(xml, exception);
+		}
+	}
+
+	public static class EmptyEntityResolver implements EntityResolver {
+		public InputSource resolveEntity(String publicId, String systemId) {
+			InputSource EMPTY_INPUTSOURCE = new InputSource(new ByteArrayInputStream(new byte[0]));
+			return EMPTY_INPUTSOURCE;
+			// if (systemId != null && systemId.endsWith(".dtd")) return
+			// EMPTY_INPUTSOURCE;
+			// return null;
+		}
+	}
+}
Index: ocean/src/org/apache/lucene/ocean/util/ByteArrayInputStream.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/util/ByteArrayInputStream.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/util/ByteArrayInputStream.java	(revision 0)
@@ -0,0 +1,97 @@
+package org.apache.lucene.ocean.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.lucene.ocean.util.ByteBufferPool.ByteBuffer;
+import org.apache.lucene.ocean.util.ByteBufferPool.ByteBuffers;
+
+public class ByteArrayInputStream extends InputStream {
+	private List<ByteBuffer> byteBuffers;
+  private long length;
+
+  private ByteBuffer currentBuffer;
+  private int currentBufferIndex;
+  
+  private int bufferPosition;
+  private long bufferStart;
+  private int bufferLength;
+  private int bufferSize;
+  
+  public ByteArrayInputStream(ByteBuffers byteBuffers) throws IOException {
+  	this.length = byteBuffers.getLength();
+  	int numRead = 0;
+  	this.byteBuffers = byteBuffers.getByteBuffers();
+    currentBufferIndex = -1;
+    currentBuffer = null;
+  }
+  
+  public ByteArrayInputStream(int bufferSize, InputStream input, int length, ByteBufferPool byteBufferPool) throws IOException {
+  	this.length = length;
+  	int numRead = 0;
+  	byteBuffers = new ArrayList<ByteBuffer>();
+		while (true) {
+			ByteBuffer byteBuffer = byteBufferPool.get(bufferSize);
+			int n = input.read(byteBuffer.getBytes());
+			if (n == -1) {
+				byteBuffer.finished();
+				break;
+			}
+			numRead += n;
+			byteBuffers.add(byteBuffer);
+		}
+		if (length != numRead) {
+			throw new IOException("num read different than length");
+		}
+    currentBufferIndex = -1;
+    currentBuffer = null;
+  }
+
+  public void close() {
+    for (ByteBuffer byteBuffer : byteBuffers) {
+    	byteBuffer.finished();
+    }
+  }
+
+  public long length() {
+    return length;
+  }
+
+  public int read() throws IOException {
+    if (bufferPosition >= bufferLength) {
+      currentBufferIndex++;
+      switchCurrentBuffer();
+    }
+    return currentBuffer.getBytes()[bufferPosition++];
+  }
+
+  public void readBytes(byte[] b, int offset, int len) throws IOException {
+    while (len > 0) {
+      if (bufferPosition >= bufferLength) {
+        currentBufferIndex++;
+        switchCurrentBuffer();
+      }
+      int remainInBuffer = bufferLength - bufferPosition;
+      int bytesToCopy = len < remainInBuffer ? len : remainInBuffer;
+      System.arraycopy(currentBuffer, bufferPosition, b, offset, bytesToCopy);
+      offset += bytesToCopy;
+      len -= bytesToCopy;
+      bufferPosition += bytesToCopy;
+    }
+  }
+
+  private final void switchCurrentBuffer() throws IOException {
+    if (currentBufferIndex >= byteBuffers.size()) {
+      // end of file reached, no more buffers left
+      throw new IOException("Read past EOF");
+    } else {
+      currentBuffer = byteBuffers.get(currentBufferIndex);
+      bufferPosition = 0;
+      bufferStart = (long) bufferSize * (long) currentBufferIndex;
+      long buflen = length - bufferStart;
+      bufferLength = buflen > bufferSize ? bufferSize : (int) buflen;
+    }
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/util/ByteArrayOutputStream.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/util/ByteArrayOutputStream.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/util/ByteArrayOutputStream.java	(revision 0)
@@ -0,0 +1,185 @@
+package org.apache.lucene.ocean.util;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.lucene.ocean.util.ByteBufferPool.ByteBuffer;
+import org.apache.lucene.ocean.util.ByteBufferPool.ByteBuffers;
+
+
+public class ByteArrayOutputStream extends OutputStream {
+	private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+
+	private List<ByteBuffer> buffers = new ArrayList<ByteBuffer>();
+	/** The index of the current buffer. */
+	private int currentBufferIndex;
+	/** The total count of bytes in all the filled buffers. */
+	private int filledBufferSum;
+	/** The current buffer. */
+	private ByteBuffer currentBuffer;
+	/** The total count of bytes written. */
+	private int count;
+	private ByteBufferPool byteBufferPool;
+
+	/**
+	 * Creates a new byte array output stream. The buffer capacity is initially
+	 * 1024 bytes, though its size increases if necessary.
+	 */
+	public ByteArrayOutputStream(ByteBufferPool byteBufferPool) {
+		this.byteBufferPool = byteBufferPool;
+	}
+  
+	public ByteBuffers getByteBuffers() {
+		return new ByteBuffers(buffers, size());
+	}
+	
+	/**
+	 * Return the appropriate <code>byte[]</code> buffer specified by index.
+	 * 
+	 * @param index
+	 *          the index of the buffer required
+	 * @return the buffer
+	 */
+	private ByteBuffer getBuffer(int index) {
+		return (ByteBuffer) buffers.get(index);
+	}
+
+	/**
+	 * Makes a new buffer available either by allocating a new one or re-cycling
+	 * an existing one.
+	 * 
+	 * @param newcount
+	 *          the size of the buffer if one is created
+	 */
+	private void needNewBuffer(int newcount) {
+		if (currentBufferIndex < buffers.size() - 1) {
+			// Recycling old buffer
+			filledBufferSum += currentBuffer.getBytes().length;
+
+			currentBufferIndex++;
+			currentBuffer = getBuffer(currentBufferIndex);
+		} else {
+			// Creating new buffer
+			int newBufferSize = 1024 * 16;
+			/**
+			 * if (currentBuffer == null) { newBufferSize = newcount; filledBufferSum =
+			 * 0; } else { newBufferSize = Math.max(currentBuffer.getBytes().length <<
+			 * 1, newcount - filledBufferSum); filledBufferSum +=
+			 * currentBuffer.getBytes().length; }
+			 */
+			currentBufferIndex++;
+			currentBuffer = byteBufferPool.get(newBufferSize);
+			buffers.add(currentBuffer);
+		}
+	}
+
+	/**
+	 * @see java.io.OutputStream#write(byte[], int, int)
+	 */
+	public void write(byte[] b, int off, int len) {
+		if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0)) {
+			throw new IndexOutOfBoundsException();
+		} else if (len == 0) {
+			return;
+		}
+		int newcount = count + len;
+		int remaining = len;
+		int inBufferPos = count - filledBufferSum;
+		while (remaining > 0) {
+			int part = Math.min(remaining, currentBuffer.getBytes().length - inBufferPos);
+			System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part);
+			remaining -= part;
+			if (remaining > 0) {
+				needNewBuffer(newcount);
+				inBufferPos = 0;
+			}
+		}
+		count = newcount;
+	}
+
+	/**
+	 * @see java.io.OutputStream#write(int)
+	 */
+	public void write(int b) {
+		int inBufferPos = count - filledBufferSum;
+		if (inBufferPos == currentBuffer.getBytes().length) {
+			needNewBuffer(count + 1);
+			inBufferPos = 0;
+		}
+		currentBuffer.getBytes()[inBufferPos] = (byte) b;
+		count++;
+	}
+
+	/**
+	 * @see java.io.ByteArrayOutputStream#size()
+	 */
+	public int size() {
+		return count;
+	}
+
+	/**
+	 * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in
+	 * this class can be called after the stream has been closed without
+	 * generating an <tt>IOException</tt>.
+	 * 
+	 * @throws IOException
+	 *           never (this method should not declare this exception but it has
+	 *           to now due to backwards compatability)
+	 */
+	public void close() {
+		for (ByteBuffer byteBuffer : buffers) {
+			byteBuffer.finished();
+		}
+	}
+
+	/**
+	 * @see java.io.ByteArrayOutputStream#reset()
+	 */
+	public void reset() {
+		close();
+		count = 0;
+		filledBufferSum = 0;
+		currentBufferIndex = 0;
+		currentBuffer = getBuffer(currentBufferIndex);
+	}
+
+	/**
+	 * Writes the entire contents of this byte stream to the specified output
+	 * stream.
+	 * 
+	 * @param out
+	 *          the output stream to write to
+	 * @throws IOException
+	 *           if an I/O error occurs, such as if the stream is closed
+	 * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
+	 */
+	public void writeTo(OutputStream out) throws IOException {
+		int remaining = count;
+		for (int i = 0; i < buffers.size(); i++) {
+			byte[] buf = getBuffer(i).getBytes();
+			int c = Math.min(buf.length, remaining);
+			out.write(buf, 0, c);
+			remaining -= c;
+			if (remaining == 0) {
+				break;
+			}
+		}
+	}
+
+	/**
+	 * Gets the curent contents of this byte stream as a byte array. The result is
+	 * independent of this stream.
+	 * 
+	 * @return the current contents of this output stream, as a byte array
+	 * @see java.io.ByteArrayOutputStream#toByteArray()
+	 * 
+	 * public synchronized byte[] toByteArray() { int remaining = count; if
+	 * (remaining == 0) { return EMPTY_BYTE_ARRAY; } byte newbuf[] = new
+	 * byte[remaining]; int pos = 0; for (int i = 0; i < buffers.size(); i++) {
+	 * byte[] buf = getBuffer(i); int c = Math.min(buf.length, remaining);
+	 * System.arraycopy(buf, 0, newbuf, pos, c); pos += c; remaining -= c; if
+	 * (remaining == 0) { break; } } return newbuf; }
+	 */
+}
Index: ocean/src/org/apache/lucene/ocean/util/ByteBufferPool.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/util/ByteBufferPool.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/util/ByteBufferPool.java	(revision 0)
@@ -0,0 +1,181 @@
+package org.apache.lucene.ocean.util;
+
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class ByteBufferPool {
+  private ReentrantLock lock = new ReentrantLock();
+	private TreeSet<ByteBuffer> available = new TreeSet<ByteBuffer>();
+	private Set<ByteBuffer> inuse = new HashSet<ByteBuffer>();
+	private int maxCount;
+  
+	public ByteBufferPool(int initialSize, int count, int maxCount) {
+		this.maxCount = maxCount;
+		for (int x=0; x < count; x++) {
+			get(initialSize);
+		}
+	}
+	
+	public static class ByteBuffers {
+		private List<ByteBuffer> byteBuffers;
+		private int length;
+		
+		public ByteBuffers(RandomAccessFile randomAccessFile, int bufferSize, int size, ByteBufferPool bufferPool) throws IOException {
+			this.length = size;
+			int numRead = 0;
+			byteBuffers = new ArrayList<ByteBuffer>();
+			while (true) {
+				if (numRead >= size) break;
+				ByteBuffer byteBuffer = bufferPool.get(bufferSize);
+				int len = byteBuffer.getBytes().length;
+				if ( (len + numRead) > size) {
+					len = size - numRead;
+				}
+				int n = randomAccessFile.read(byteBuffer.getBytes(), 0, len);
+				if (n == -1) break;
+				byteBuffers.add(byteBuffer);
+				numRead += n;
+			}
+		}
+		
+		public ByteBuffers(InputStream inputStream, int bufferSize, int size, ByteBufferPool bufferPool) throws IOException {
+			this.length = size;
+			int numRead = 0;
+			byteBuffers = new ArrayList<ByteBuffer>();
+			while (true) {
+				if (numRead >= size) break;
+				ByteBuffer byteBuffer = bufferPool.get(bufferSize);
+				int len = byteBuffer.getBytes().length;
+				if ( (len + numRead) > size) {
+					len = size - numRead;
+				}
+				int n = inputStream.read(byteBuffer.getBytes(), 0, len);
+				if (n == -1) break;
+				byteBuffers.add(byteBuffer);
+				numRead += n;
+			}
+		}
+		
+		public ByteBuffers(ByteBuffer byteBuffer, int length) {
+			byteBuffers = new ArrayList<ByteBuffer>(1);
+			byteBuffers.add(byteBuffer);
+			this.length = length;
+		}
+		
+		public ByteBuffers(List<ByteBuffer> byteBuffers, int length) {
+			this.byteBuffers = byteBuffers;
+			this.length = length;
+		}
+    
+		public InputStream getInputStream() throws IOException {
+			return new ByteArrayInputStream(this);
+		}
+		
+		public void writeTo(DataOutput out) throws IOException {
+			int remaining = length;
+			for (int i = 0; i < byteBuffers.size(); i++) {
+				byte[] buf = byteBuffers.get(i).getBytes();
+				int c = Math.min(buf.length, remaining);
+				out.write(buf, 0, c);
+				remaining -= c;
+				if (remaining == 0) {
+					break;
+				}
+			}
+		}
+		
+		public void finished() {
+			for (ByteBuffer byteBuffer : byteBuffers) {
+				byteBuffer.finished();
+			}
+		}
+		
+		public List<ByteBuffer> getByteBuffers() {
+			return byteBuffers;
+		}
+
+		public int getLength() {
+			return length;
+		}
+	}
+	
+	public static class ByteBuffer implements Comparable<ByteBuffer> {
+		private byte[] bytes;
+		private ByteBufferPool byteBufferPool;
+		
+		public ByteBuffer(int size, ByteBufferPool byteBufferPool) {
+			this.bytes = new byte[size];
+			this.byteBufferPool = byteBufferPool;
+		}
+		
+		public void finished() {
+			byteBufferPool.finished(this);
+		}
+		
+		public int compareTo(ByteBuffer other) {
+			return new Integer(size()).compareTo(other.size());
+		}
+		
+		public int size() {
+			return bytes.length;
+		}
+		
+		public byte[] getBytes() {
+			return bytes;
+		}
+	}
+	
+	private void finished(ByteBuffer byteBuffer) {
+		lock.lock();
+		try {
+			inuse.remove(byteBuffer);
+			available.add(byteBuffer);
+		} finally {
+			lock.unlock();
+		}
+	}
+	
+	private void checkCount() {
+		lock.lock();
+		try {
+			int dif = available.size() - maxCount;
+			if (dif > 0) {
+				int count = 0;
+				Iterator<ByteBuffer> iterator = available.iterator();
+				while (iterator.hasNext() && count < dif) {
+					iterator.next();
+					iterator.remove();
+					count++;
+				}
+			}
+		} finally {
+			lock.unlock();
+		}
+	}
+	
+	public ByteBuffer get(int size) {
+		lock.lock();
+		try {
+			ByteBuffer byteBuffer = null;
+			if (available.size() > 0) available.last();
+			if (byteBuffer == null || size > byteBuffer.size()) {
+				byteBuffer = new ByteBuffer(size, this);
+			} else {
+				available.remove(byteBuffer);
+			}
+			inuse.add(byteBuffer);
+			return byteBuffer;
+		} finally {
+			lock.unlock();
+		}
+	}
+}
Index: ocean/src/org/apache/lucene/ocean/util/CElement.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/util/CElement.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/util/CElement.java	(revision 0)
@@ -0,0 +1,11 @@
+package org.apache.lucene.ocean.util;
+
+import org.jdom.Element;
+
+/**
+ *
+ * @author jasonr
+ */
+public interface CElement {
+  public Element toElement() throws Exception;
+}
Index: ocean/src/org/apache/lucene/ocean/util/Constants.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/util/Constants.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/util/Constants.java	(revision 0)
@@ -0,0 +1,7 @@
+package org.apache.lucene.ocean.util;
+
+public interface Constants {
+  //public static final String ID = "_id".intern();
+  public static final String DOCUMENTID = "_documentid".intern();
+  public static final String SNAPSHOTID = "_snapshotid".intern();
+}
Index: ocean/src/org/apache/lucene/ocean/util/DocumentSerializer.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/util/DocumentSerializer.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/util/DocumentSerializer.java	(revision 0)
@@ -0,0 +1,24 @@
+package org.apache.lucene.ocean.util;
+
+import java.io.IOException;
+
+import org.apache.commons.lang.SerializationUtils;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.store.IndexInput;
+import org.apache.lucene.store.IndexOutput;
+
+public class DocumentSerializer {
+  
+  public static void write(Document document, IndexOutput output) throws IOException {
+    byte[] bytes = SerializationUtils.serialize(document);
+    output.writeVInt(bytes.length);
+    output.writeBytes(bytes, bytes.length);
+  }
+  
+  public static Document read(IndexInput input) throws IOException {
+    int length = input.readVInt();
+    byte[] bytes = new byte[length];
+    input.readBytes(bytes, 0, length);
+    return (Document)SerializationUtils.deserialize(bytes);
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/util/FastInputStream.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/util/FastInputStream.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/util/FastInputStream.java	(revision 0)
@@ -0,0 +1,215 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.ocean.util;
+
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+
+/** Single threaded buffered InputStream
+ *  Internal Solr use only, subject to change.
+ */
+public class FastInputStream extends InputStream implements DataInput {
+  private final InputStream in;
+  private final byte[] buf;
+  private int pos;
+  private int end;
+
+  public FastInputStream(InputStream in) {
+  // use default BUFSIZE of BufferedOutputStream so if we wrap that
+  // it won't cause double buffering.
+    this(in, new byte[8192], 0, 0);
+  }
+
+  public FastInputStream(InputStream in, byte[] tempBuffer, int start, int end) {
+    this.in = in;
+    this.buf = tempBuffer;
+    this.pos = start;
+    this.end = end;
+  }
+
+
+  public static FastInputStream wrap(InputStream in) {
+    return (in instanceof FastInputStream) ? (FastInputStream)in : new FastInputStream(in);
+  }
+
+  @Override
+  public int read() throws IOException {
+    if (pos >= end) {
+      refill();
+      if (pos >= end) return -1;
+    }
+    return buf[pos++] & 0xff;     
+  }
+
+  public int readUnsignedByte() throws IOException {
+    if (pos >= end) {
+      refill();
+      if (pos >= end) throw new EOFException();
+    }
+    return buf[pos++] & 0xff;
+  }
+
+  public void refill() throws IOException {
+    // this will set end to -1 at EOF
+    end = in.read(buf, 0, buf.length);
+    pos = 0;
+  }
+
+  @Override
+  public int available() throws IOException {
+    return end - pos;
+  }
+
+  @Override
+  public int read(byte b[], int off, int len) throws IOException {
+    int r=0;  // number of bytes read
+    // first read from our buffer;
+    if (end-pos > 0) {
+      r = Math.min(end-pos, len);
+      System.arraycopy(buf, pos, b, off, r);      
+      pos += r;
+    }
+
+    if (r == len) return r;
+
+    // amount left to read is >= buffer size
+    if (len-r >= buf.length) {
+      int ret = in.read(b, off+r, len-r);
+      if (ret==-1) return r==0 ? -1 : r;
+      r += ret;
+      return r;
+    }
+
+    refill();
+
+    // first read from our buffer;
+    if (end-pos > 0) {
+      int toRead = Math.min(end-pos, len-r);
+      System.arraycopy(buf, pos, b, off+r, toRead);
+      pos += toRead;
+      r += toRead;
+      return r;
+    }
+    
+    return -1;
+  }
+
+  @Override
+  public void close() throws IOException {
+    in.close();
+  }
+
+  public void readFully(byte b[]) throws IOException {
+    readFully(b, 0, b.length);
+  }
+
+  public void readFully(byte b[], int off, int len) throws IOException {
+    while (len>0) {
+      int ret = read(b, off, len);
+      if (ret==-1) {
+        throw new EOFException();
+      }
+      off += ret;
+      len -= ret;
+    }
+  }
+
+  public int skipBytes(int n) throws IOException {
+    if (end-pos >= n) {
+      pos += n;
+      return n;
+    }
+
+    if (end-pos<0) return -1;
+    
+    int r = end-pos;
+    pos = end;
+
+    while (r < n) {
+      refill();
+      if (end-pos <= 0) return r;
+      int toRead = Math.min(end-pos, n-r);
+      r += toRead;
+      pos += toRead;
+    }
+
+    return r;
+  }
+
+  public boolean readBoolean() throws IOException {
+    return readByte()==1;
+  }
+
+  public byte readByte() throws IOException {
+    if (pos >= end) {
+      refill();
+      if (pos >= end) throw new EOFException();
+    }
+    return buf[pos++];
+  }
+
+
+  public short readShort() throws IOException {
+    return (short)((readUnsignedByte() << 8) | readUnsignedByte());
+  }
+
+  public int readUnsignedShort() throws IOException {
+    return (readUnsignedByte() << 8) | readUnsignedByte();
+  }
+
+  public char readChar() throws IOException {
+    return (char)((readUnsignedByte() << 8) | readUnsignedByte());
+  }
+
+  public int readInt() throws IOException {
+    return  ((readUnsignedByte() << 24)
+            |(readUnsignedByte() << 16)
+            |(readUnsignedByte() << 8)
+            | readUnsignedByte());
+  }
+
+  public long readLong() throws IOException {
+    return  (((long)readUnsignedByte()) << 56)
+            | (((long)readUnsignedByte()) << 48)
+            | (((long)readUnsignedByte()) << 40)
+            | (((long)readUnsignedByte()) << 32)
+            | (((long)readUnsignedByte()) << 24)
+            | (readUnsignedByte() << 16)
+            | (readUnsignedByte() << 8)
+            | (readUnsignedByte());
+  }
+
+  public float readFloat() throws IOException {
+    return Float.intBitsToFloat(readInt());    
+  }
+
+  public double readDouble() throws IOException {
+    return Double.longBitsToDouble(readLong());    
+  }
+
+  public String readLine() throws IOException {
+    return new DataInputStream(this).readLine();
+  }
+
+  public String readUTF() throws IOException {
+    return new DataInputStream(this).readUTF();
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/util/LongSequence.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/util/LongSequence.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/util/LongSequence.java	(revision 0)
@@ -0,0 +1,43 @@
+package org.apache.lucene.ocean.util;
+
+import java.util.concurrent.locks.ReentrantLock;
+
+public class LongSequence {
+	private long value;
+	private int increment;
+	private ReentrantLock lock = new ReentrantLock();
+
+	public LongSequence(long value, int increment) {
+		this.value = value;
+		this.increment = increment;
+	}
+	
+	public long get() {
+		lock.lock();
+		try {
+			return value;
+		} finally {
+			lock.unlock();
+		}
+	}
+	
+	public void set(long i) {
+		lock.lock();
+		try {
+			value = i;
+		} finally {
+			lock.unlock();
+		}
+	}
+	
+	public long getAndIncrement() {
+		lock.lock();
+		try {
+			long v = value;
+			value += increment;
+			return v;
+		} finally {
+			lock.unlock();
+		}
+	}
+}
Index: ocean/src/org/apache/lucene/ocean/util/SortedList.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/util/SortedList.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/util/SortedList.java	(revision 0)
@@ -0,0 +1,321 @@
+package org.apache.lucene.ocean.util;
+
+import java.util.AbstractCollection;
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang.ObjectUtils;
+
+public class SortedList<K extends Comparable<K>,V> implements Map<K,V> {
+  private List<SortedEntry<K,V>> entries = new ArrayList<SortedEntry<K,V>>();
+  private transient Set<Map.Entry<K,V>> entrySet = null;
+  transient volatile Set<K>        keySet = null;
+  transient volatile Collection<V> values = null;
+  
+  private Map.Entry<K,V> removeMapping(Map.Entry<K,V> toRemove) {
+    int pos = Collections.binarySearch(entries, new SortedEntry<K,V>((K) toRemove.getKey()));
+    if (pos >= 0) {
+      SortedEntry<K,V> entry = entries.get(pos);
+      if (entry.value.equals(toRemove.getValue())) {
+        entries.remove(pos);
+        return entry;
+      }
+    }
+    return null;
+  }
+  
+  public static class SortedEntry<K extends Comparable<K>,V> implements Map.Entry<K,V>, Comparable<SortedEntry<K,V>> {
+    private K key;
+    private V value;
+
+    private SortedEntry(K key, V value) {
+      this.key = key;
+      this.value = value;
+    }
+
+    public K setKey(K key) {
+      K oldKey = this.key;
+      this.key = key;
+      return oldKey;
+    }
+
+    public V setValue(V value) {
+      V oldValue = this.value;
+      this.value = value;
+      return oldValue;
+    }
+
+    public K getKey() {
+      return key;
+    }
+
+    public V getValue() {
+      return value;
+    }
+
+    private SortedEntry(K key) {
+      this.key = key;
+    }
+
+    public int compareTo(SortedEntry<K,V> other) {
+      return key.compareTo(other.key);
+    }
+  }
+
+  public void putAll(Map<? extends K,? extends V> m) {
+    for (Iterator<? extends Map.Entry<? extends K,? extends V>> i = m.entrySet().iterator(); i.hasNext();) {
+      Map.Entry<? extends K,? extends V> e = i.next();
+      put(e.getKey(), e.getValue());
+    }
+  }
+  /**
+  public Set<K> keySet() {
+    Set<K> set = new HashSet<K>(entries.size());
+    for (SortedEntry<K,V> entry : entries) {
+      set.add(entry.key);
+    }
+    return set;
+  }
+  **/
+  // public Set<Map.Entry<K,V>> entrySet() {
+  // Set<Map.Entry<K,V>> set = new HashSet<Map.Entry<K,V>>(entries);
+  // return set;
+  // }
+
+  public boolean isEmpty() {
+    return entries.size() == 0;
+  }
+
+  public void clear() {
+    entries.clear();
+  }
+
+  public int size() {
+    return entries.size();
+  }
+
+  private int getPos(K key) {
+    int pos = Collections.binarySearch(entries, new SortedEntry<K,V>(key));
+    if (pos < 0)
+      pos = -1 - pos;
+    return pos;
+  }
+
+  public boolean containsValue(Object value) {
+    for (SortedEntry<K,V> entry : entries) {
+      boolean b = ObjectUtils.equals(value, entry.value);
+      if (b)
+        return true;
+    }
+    return false;
+  }
+
+  public boolean containsKey(Object key) {
+    int pos = Collections.binarySearch(entries, new SortedEntry<K,V>((K) key));
+    return pos >= 0;
+  }
+
+  public K lastKey() {
+    if (entries.size() == 0)
+      return null;
+    return entries.get(entries.size() - 1).key;
+  }
+
+  public V lastValue() {
+    if (entries.size() == 0)
+      return null;
+    return entries.get(entries.size() - 1).value;
+  }
+
+  public V put(K key, V value) {
+    V oldValue = null;
+    int pos = Collections.binarySearch(entries, new SortedEntry<K,V>(key));
+    if (pos >= 0) {
+      oldValue = entries.get(pos).value;
+    }
+    if (pos < 0)
+      pos = -1 - pos;
+
+    entries.add(pos, new SortedEntry<K,V>(key, value));
+    return oldValue;
+  }
+
+  private SortedEntry<K,V> getEntry(Object key) {
+    int pos = Collections.binarySearch(entries, new SortedEntry<K,V>((K) key));
+    if (pos >= 0) {
+      return entries.get(pos);
+    } else {
+      return null;
+    }
+  }
+
+  public V get(Object key) {
+    int pos = Collections.binarySearch(entries, new SortedEntry<K,V>((K) key));
+    if (pos >= 0) {
+      return entries.get(pos).value;
+    } else {
+      return null;
+    }
+  }
+
+  public V remove(Object key) {
+    int pos = Collections.binarySearch(entries, new SortedEntry<K,V>((K) key));
+    if (pos >= 0) {
+      SortedEntry<K,V> entry = entries.get(pos);
+      entries.remove(pos);
+      return entry.value;
+    }
+    return null;
+  }
+
+  public void add(K key, V value) {
+    int pos = getPos(key);
+    entries.add(pos, new SortedEntry<K,V>(key, value));
+  }
+
+  private abstract class SortedListIterator<E> implements Iterator<E> {
+    private Iterator<SortedEntry<K,V>> iterator;
+    
+    public SortedListIterator() {
+      iterator = entries.iterator();
+    }
+    
+    protected Map.Entry<K,V> nextEntry() {
+      return iterator.next();
+    }
+    
+    public void remove() {
+      iterator.remove();
+    }
+    
+    public boolean hasNext() {
+      return iterator.hasNext();
+    }
+  }
+
+  private class ValueIterator extends SortedListIterator<V> {
+    public V next() {
+      return nextEntry().getValue();
+    }
+  }
+
+  private class KeyIterator extends SortedListIterator<K> {
+    public K next() {
+      return nextEntry().getKey();
+    }
+  }
+
+  private class EntryIterator extends SortedListIterator<Map.Entry<K,V>> {
+    public Map.Entry<K,V> next() {
+      return nextEntry();
+    }
+  }
+
+  // Subclass overrides these to alter behavior of views' iterator() method
+  Iterator<K> newKeyIterator() {
+    return new KeyIterator();
+  }
+
+  Iterator<V> newValueIterator() {
+    return new ValueIterator();
+  }
+
+  Iterator<Map.Entry<K,V>> newEntryIterator() {
+    return new EntryIterator();
+  }
+
+  public Set<K> keySet() {
+    Set<K> ks = keySet;
+    return (ks != null ? ks : (keySet = new KeySet()));
+  }
+
+  private class KeySet extends AbstractSet<K> {
+    public Iterator<K> iterator() {
+      return newKeyIterator();
+    }
+
+    public int size() {
+      return entries.size();
+    }
+
+    public boolean contains(Object o) {
+      return containsKey(o);
+    }
+
+    public boolean remove(Object o) {
+      return SortedList.this.remove(o) != null;
+    }
+
+    public void clear() {
+      SortedList.this.clear();
+    }
+  }
+
+  public Collection<V> values() {
+    Collection<V> vs = values;
+    return (vs != null ? vs : (values = new Values()));
+  }
+
+  private class Values extends AbstractCollection<V> {
+    public Iterator<V> iterator() {
+      return newValueIterator();
+    }
+
+    public int size() {
+      return SortedList.this.size();
+    }
+
+    public boolean contains(Object o) {
+      return containsValue(o);
+    }
+
+    public void clear() {
+      SortedList.this.clear();
+    }
+  }
+
+  public Set<Map.Entry<K,V>> entrySet() {
+    Set<Map.Entry<K,V>> es = entrySet;
+    return (es != null ? es : (entrySet = (Set<Map.Entry<K,V>>) (Set) new EntrySet()));
+  }
+
+  private class EntrySet extends AbstractSet/* <Map.Entry<K,V>> */{
+    public Iterator/* <Map.Entry<K,V>> */iterator() {
+      return newEntryIterator();
+    }
+
+    public boolean contains(Object o) {
+      if (!(o instanceof Map.Entry))
+        return false;
+      Map.Entry<K,V> e = (Map.Entry<K,V>) o;
+      SortedEntry<K,V> candidate = getEntry(e.getKey());
+      return candidate != null && candidate.equals(e);
+    }
+
+    public boolean remove(Object o) {
+      return removeMapping((Map.Entry<K,V>)o) != null;
+    }
+
+    public int size() {
+      return SortedList.this.size();
+    }
+
+    public void clear() {
+      SortedList.this.clear();
+    }
+  }
+
+  /**
+   * public Collection<V> values() { List<V> values = new ArrayList<V>(entries.size());
+   * for (SortedEntry<K,V> entry : entries) { values.add(entry.value); } return
+   * values; }
+   */
+}
Index: ocean/src/org/apache/lucene/ocean/util/Util.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/util/Util.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/util/Util.java	(revision 0)
@@ -0,0 +1,444 @@
+package org.apache.lucene.ocean.util;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.io.Reader;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.text.DateFormat;
+import java.text.DecimalFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TimeZone;
+import java.util.TreeSet;
+import java.util.regex.Pattern;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NameNotFoundException;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.index.TermDocs;
+import org.apache.lucene.ocean.LogDirectory;
+import org.apache.lucene.search.ExtendedFieldCache;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.IndexInput;
+import org.apache.lucene.store.IndexOutput;
+
+import com.imagero.uio.RandomAccessIO;
+import com.imagero.uio.RandomAccessInput;
+
+public class Util {
+  public static TimeZone UTC = TimeZone.getTimeZone("UTC");
+  public final static Pattern splitPattern = Pattern.compile(",| ");
+  private static ThreadLocalDateFormat dateFormatThreadLocal = new ThreadLocalDateFormat();
+
+  public static String formatSnapshotId(BigDecimal id) {
+    DecimalFormat format = new DecimalFormat("##0.00");
+    return format.format(id);
+  }
+
+  /**
+   * Handles size 0 collections returns null
+   */
+  public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) {
+    if (coll.size() == 0)
+      return null;
+    return Collections.max(coll);
+  }
+
+  public static void touchFile(String file, Directory directory) throws IOException {
+    IndexOutput output = directory.createOutput(file);
+    output.close();
+  }
+
+  public static String getString(String file, LogDirectory directory) throws IOException {
+    try {
+      RandomAccessInput input = directory.openInput(file);
+      if (input.length() == 0)
+        return null;
+      byte[] bytes = new byte[(int) input.length()];
+      input.read(bytes);
+      return new String(bytes, "UTF-8");
+    } catch (Throwable ioException) {
+      IOException newIOException = new IOException("file: "+file);
+      newIOException.initCause(ioException);
+      throw newIOException;
+    }
+  }
+
+  public static void save(String string, String file, LogDirectory directory) throws IOException {
+    byte[] bytes = string.getBytes("UTF-8");
+    RandomAccessIO output = directory.getOutput(file, true);
+    output.write(bytes);
+    output.flush();
+  }
+
+  public static String getString(String file, Directory directory) throws IOException {
+    IndexInput input = directory.openInput(file);
+    byte[] bytes = new byte[(int) input.length()];
+    input.readBytes(bytes, 0, bytes.length);
+    return new String(bytes, "UTF-8");
+  }
+
+  public static void save(String string, String file, Directory directory) throws IOException {
+    byte[] bytes = string.getBytes("UTF-8");
+    IndexOutput output = directory.createOutput(file);
+    output.writeBytes(bytes, bytes.length);
+    output.flush();
+    output.close();
+  }
+
+  public static void copy(IndexInput input, IndexOutput output, byte[] buffer) throws IOException {
+    long len = input.length();
+    long readCount = 0;
+    while (readCount < len) {
+      int toRead = readCount + buffer.length > len ? (int) (len - readCount) : buffer.length;
+      input.readBytes(buffer, 0, toRead);
+      output.writeBytes(buffer, toRead);
+      readCount += toRead;
+    }
+  }
+
+  public static <K,V> V getLastValue(SortedMap<K,V> map) {
+    if (map.size() == 0)
+      return null;
+    return map.get(map.lastKey());
+  }
+
+  public static void setValue(String name, long value, Document document) {
+    String encoded = longToEncoded(value);
+    document.add(new Field(name, encoded, Field.Store.YES, Field.Index.UN_TOKENIZED));
+  }
+
+  public static boolean mkdir(File dir) {
+    if (!dir.exists()) {
+      return dir.mkdirs();
+    }
+    return false;
+  }
+
+  public static String longToEncoded(long value) {
+    return long2sortableStr(value);
+  }
+
+  public static long longFromEncoded(String string) {
+    return SortableStr2long(string, 0, 5);
+  }
+
+  public static String long2sortableStr(long val) {
+    char[] arr = new char[5];
+    long2sortableStr(val, arr, 0);
+    return new String(arr, 0, 5);
+  }
+
+  // uses binary representation of an int to build a string of
+  // chars that will sort correctly. Only char ranges
+  // less than 0xd800 will be used to avoid UCS-16 surrogates.
+  // we can use the lowest 15 bits of a char, (or a mask of 0x7fff)
+  public static int long2sortableStr(long val, char[] out, int offset) {
+    val += Long.MIN_VALUE;
+    out[offset++] = (char) (val >>> 60);
+    out[offset++] = (char) (val >>> 45 & 0x7fff);
+    out[offset++] = (char) (val >>> 30 & 0x7fff);
+    out[offset++] = (char) (val >>> 15 & 0x7fff);
+    out[offset] = (char) (val & 0x7fff);
+    return 5;
+  }
+
+  public static long SortableStr2long(String sval, int offset, int len) {
+    long val = (long) (sval.charAt(offset++)) << 60;
+    val |= ((long) sval.charAt(offset++)) << 45;
+    val |= ((long) sval.charAt(offset++)) << 30;
+    val |= sval.charAt(offset++) << 15;
+    val |= sval.charAt(offset);
+    val -= Long.MIN_VALUE;
+    return val;
+  }
+
+  public static int getDoc(String fieldName, long value, IndexReader indexReader) throws IOException {
+    String encoded = longToEncoded(value);
+    return getTermDoc(new Term(fieldName, encoded), indexReader);
+  }
+
+  public static int getTermDoc(Term term, IndexReader indexReader) throws IOException {
+    TermDocs docs = indexReader.termDocs(term);
+    try {
+      if (docs.next()) {
+        return docs.doc();
+      }
+    } finally {
+      docs.close();
+    }
+    return -1;
+  }
+
+  public static List<Integer> getTermDocs(Term term, IndexReader indexReader) throws IOException {
+    List<Integer> list = new ArrayList<Integer>();
+    TermDocs docs = indexReader.termDocs(term);
+    try {
+      while (docs.next()) {
+        list.add(docs.doc());
+      }
+    } finally {
+      docs.close();
+    }
+    return list;
+  }
+
+  public static long getSize(Directory directory) throws IOException {
+    long total = 0;
+    for (String file : directory.list()) {
+      total += directory.fileLength(file);
+    }
+    return total;
+  }
+
+  public static void copy(InputStream is, RandomAccessFile ras, byte[] buf) throws IOException {
+    int numRead;
+    while ((numRead = is.read(buf)) >= 0) {
+      ras.write(buf, 0, numRead);
+    }
+  }
+
+  public static File getDirectory(File root, String path) {
+    File file = new File(root, path);
+    if (!file.exists()) {
+      file.mkdirs();
+    }
+    return file;
+  }
+
+  public static DateFormat getThreadLocalDateFormat() {
+    return dateFormatThreadLocal.get();
+  }
+
+  public static String formatDate(Date date) {
+    DateFormat dateFormat = getThreadLocalDateFormat();
+    return dateFormat.format(date);
+  }
+
+  public static Date parseDate(String string) throws ParseException {
+    DateFormat dateFormat = getThreadLocalDateFormat();
+    return dateFormat.parse(string);
+  }
+
+  private static class ThreadLocalDateFormat extends ThreadLocal<DateFormat> {
+    DateFormat proto;
+
+    public ThreadLocalDateFormat() {
+      super();
+      SimpleDateFormat tmp = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
+      tmp.setTimeZone(UTC);
+      proto = tmp;
+    }
+
+    protected DateFormat initialValue() {
+      return (DateFormat) proto.clone();
+    }
+  }
+
+  public static long[] getFieldCacheLong(String field, IndexReader indexReader) throws IOException {
+    return ExtendedFieldCache.EXT_DEFAULT.getLongs(indexReader, field);
+  }
+
+  public static boolean isTrue(String string) {
+    return StringUtils.equalsIgnoreCase("true", string);
+  }
+
+  public static Object construct(Object parameter, Class clazz) throws Exception {
+    Constructor constructor = clazz.getDeclaredConstructor(parameter.getClass());
+    try {
+      return constructor.newInstance(parameter);
+    } catch (InvocationTargetException invocationTargetException) {
+      Throwable cause = invocationTargetException.getCause();
+      if (cause instanceof Exception)
+        throw (Exception) cause;
+      else
+        throw invocationTargetException;
+    }
+  }
+
+  public static Long parseLong(String value) {
+    if (StringUtils.isBlank(value)) {
+      return null;
+    }
+    try {
+      return new Long(value);
+    } catch (Throwable ex) {
+      return null;
+    }
+  }
+
+  public static URL parseURL(String string) throws MalformedURLException {
+    if (StringUtils.isBlank(string)) {
+      return null;
+    }
+    return new URL(string);
+  }
+
+  public static List<String> readLines(Reader reader) throws Exception {
+    return IOUtils.readLines(reader);
+  }
+
+  public static IOException asIOException(String message, Throwable throwable) throws IOException {
+    IOException ioException = new IOException(message);
+    ioException.initCause(throwable);
+    return ioException;
+  }
+
+  public static IOException asIOException(Throwable throwable) {
+    if (throwable instanceof IOException) {
+      return (IOException) throwable;
+    }
+    IOException ioException = new IOException(throwable.getMessage());
+    ioException.initCause(throwable);
+    return ioException;
+  }
+
+  public static void copy(InputStream is, OutputStream os, byte[] buf) throws IOException {
+    int numRead;
+    while ((numRead = is.read(buf)) >= 0) {
+      os.write(buf, 0, numRead);
+    }
+  }
+
+  public static int[] toIntArray(List<Integer> list) {
+    int size = list.size();
+    int[] array = new int[size];
+    int x = 0;
+    for (int i : list) {
+      array[x] = i;
+      x++;
+    }
+    return array;
+  }
+
+  public static List<URL> loadUrls(File file) throws IOException {
+    List<String> lines = IOUtils.readLines(new FileReader(file));
+    List<URL> urls = new ArrayList<URL>();
+    for (String line : lines) {
+      urls.add(new URL(line));
+    }
+    return urls;
+  }
+
+  /**
+   * public static HttpParameters toHttpParameters(HttpServletRequest request) {
+   * try { if (StringUtils.equalsIgnoreCase("post", request.getMethod())) {
+   * HttpParameters parameters = new HttpParameters(); URL url = new
+   * URL(request.getRequestURL().toString()); CGIParser cgiParser = new
+   * CGIParser(url.toString(), "UTF-8"); for (String name :
+   * cgiParser.getParameterNameList()) { for (String value :
+   * cgiParser.getParameterValues(name)) { parameters.add(name, value); } }
+   * return parameters; } } catch (Exception exception) { throw new
+   * RuntimeException(exception); } if (StringUtils.equalsIgnoreCase("get",
+   * request.getMethod())) { HttpParameters parameters = new HttpParameters();
+   * Enumeration paramEnum = request.getParameterNames(); while
+   * (paramEnum.hasMoreElements()) { String name = (String)
+   * paramEnum.nextElement(); String[] array = request.getParameterValues(name);
+   * if (array != null && array.length > 0) { for (String value : array) {
+   * parameters.add(name, value); } } } return parameters; } throw new
+   * RuntimeException("unknown http method " + request.getMethod()); }
+   */
+  public static Long getNextServerSequence(Long value, int serverNumber) {
+    if (value == null) {
+      return new Long(serverNumber);
+    }
+    Long i = null;
+    if (value > 99) {
+      String string = value.toString();
+      String substring = string.substring(0, string.length() - 2);
+      i = new Long(substring + "00");
+    } else {
+      i = new Long(0);
+    }
+    long v = i + serverNumber;
+    return v + 100;
+  }
+
+  public static int getServerNumber(BigInteger id) {
+    String string = id.toString();
+    String substring = string.substring(string.length() - 2, string.length());
+    return Integer.parseInt(substring);
+  }
+
+  public static SortedSet<String> splitToSortedSet(String string) {
+    if (StringUtils.isBlank(string)) {
+      return null;
+    }
+    String[] array = splitPattern.split(string.trim(), 0);
+    TreeSet<String> sortedSet = new TreeSet<String>();
+    for (int x = 0; x < array.length; x++) {
+      sortedSet.add(array[x]);
+    }
+    return sortedSet;
+  }
+
+  public static File getAppServerHome() {
+    return new File(System.getProperty("catalina.home"));
+  }
+
+  public static File getHomeDirectory(String name, File defaultDirectory) throws Exception {
+    String value = System.getenv(name);
+    if (value != null)
+      return new File(value);
+    value = System.getProperty(name);
+    if (value != null)
+      return new File(value);
+    Context context = (Context) new InitialContext().lookup("java:comp/env");
+    try {
+      String string = (String) context.lookup(name);
+      if (StringUtils.isNotBlank(value))
+        return new File(value);
+    } catch (NameNotFoundException nameNotFoundException) {
+    }
+    defaultDirectory.mkdirs();
+    return defaultDirectory;
+  }
+
+  public static Object getFirst(List list) {
+    Iterator iterator = list.iterator();
+    if (iterator.hasNext()) {
+      return iterator.next();
+    } else {
+      return null;
+    }
+  }
+
+  public static Map<String,String> toMapExcept(org.jdom.Element element, String exceptName) {
+    Map<String,String> map = new HashMap<String,String>();
+    for (Object object : element.getAttributes()) {
+      org.jdom.Attribute attribute = (org.jdom.Attribute) object;
+      String name = attribute.getName();
+      if (!StringUtils.equals(name, exceptName)) {
+        map.put(name, attribute.getValue());
+      }
+    }
+    return map;
+  }
+}
Index: ocean/src/org/apache/lucene/ocean/util/XMLUtil.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/util/XMLUtil.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/util/XMLUtil.java	(revision 0)
@@ -0,0 +1,435 @@
+package org.apache.lucene.ocean.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.io.Writer;
+import java.lang.reflect.Field;
+import java.net.URL;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.logging.Logger;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.BooleanUtils;
+import org.apache.commons.lang.StringUtils;
+import org.jdom.Attribute;
+import org.jdom.Document;
+import org.jdom.Element;
+import org.jdom.Namespace;
+import org.jdom.Verifier;
+import org.jdom.input.SAXBuilder;
+import org.jdom.output.Format;
+import org.jdom.output.XMLOutputter;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+
+/**
+ * 
+ * @author Jason Rutherglen
+ */
+public class XMLUtil {
+	public static Logger log = Logger.getLogger(XMLUtil.class.getName());
+
+	public XMLUtil() {
+	}
+
+	public static Collection<Attribute> getAttributes(Element element) {
+		return (Collection<Attribute>)element.getAttributes();
+	}
+	
+	public static String printAndDetach(Element element) throws Exception {
+		List<Element> list = element.cloneContent();
+		Element root = null;
+		if (list.size() == 1) {
+			root = list.get(0);
+		} else {
+			root = new Element("root");
+			root.addContent(list);
+		}
+		return outputElement(root);
+	}
+
+	public static Object getProperValue(Field field, String string) throws Exception {
+		if (!field.getType().isAssignableFrom(String.class)) {
+			if (field.getType().isAssignableFrom(URL.class)) {
+				return new URL(string);
+			} else if (field.getType().isAssignableFrom(Date.class)) {
+				return Util.parseDate(string);
+			} else if (field.getType().isAssignableFrom(File.class)) {
+				return new File(string);
+			} else if (field.getType().isAssignableFrom(Long.class)) {
+				return new Long(string);
+			} else if (field.getType().isAssignableFrom(Double.class)) {
+				return new Double(string);
+			}
+			throw new Exception("unknown type " + field.getType().getName());
+		} else
+			return string;
+	}
+
+	public static void addAll(List<Element> children, Element parent) {
+		for (Element element : children) {
+			parent.addContent(element);
+		}
+	}
+
+	public static void printList(String rootName, List list, PrintWriter writer) throws Exception {
+		Format format = Format.getPrettyFormat();
+		format.setLineSeparator("\n");
+		XMLOutputter outputter = new XMLOutputter(format);
+		Element root = new Element(rootName);
+		for (Object object : list) {
+			if (object instanceof CElement) {
+				CElement cElement = (CElement) object;
+				root.addContent(cElement.toElement());
+			}
+		}
+		Document document = new Document();
+		document.addContent(root);
+		outputter.output(document, writer);
+	}
+
+	public static List<Element> getChildren(String xml) throws Exception {
+		return getChildren(parseElement(xml));
+	}
+
+	public static List<Element> getChildren(String name, Element element) {
+		List<Element> elements = (List<Element>) element.getChildren(name);
+		if (element == null)
+			return new ArrayList<Element>(0);
+		else
+			return elements;
+	}
+
+	public static List<Element> getChildren(Element element) {
+		List<Element> elements = (List<Element>) element.getChildren();
+		if (element == null)
+			return new ArrayList<Element>(0);
+		else
+			return elements;
+	}
+
+	public static Element getFirstChild(Element element) {
+		return (Element) Util.getFirst(element.getChildren());
+	}
+
+	public static void setAttribute(String name, Object value, Element element) {
+		if (value == null) {
+			return;
+		}
+		if (value instanceof Date) {
+			value = Util.formatDate((Date) value);
+		}
+		element.setAttribute(name, value.toString());
+	}
+
+	public static java.net.URL getChildURL(String name, Element element) {
+		String childText = element.getChildText(name);
+		try {
+			return new java.net.URL(childText);
+		} catch (Throwable th) {
+		}
+		return null;
+	}
+
+	public static Element getChild(String name, Element root) {
+		return root.getChild(name);
+	}
+
+	public static java.math.BigInteger getChildBigInteger(String name, Element element) {
+		String childText = element.getChildText(name);
+		try {
+			return new java.math.BigInteger(childText);
+		} catch (Throwable throwable) {
+		}
+		return null;
+	}
+
+	public static Long getChildLong(String name, Element element) throws NumberFormatException {
+		String childText = element.getChildText(name);
+		return new Long(childText);
+	}
+
+	public static String getAttributeString(String name, Element element) {
+		String text = element.getAttributeValue(name);
+		return text;
+	}
+  
+	public static Integer getAttributeInteger(String name, Element element) throws NumberFormatException {
+		String text = element.getAttributeValue(name);
+		if (text == null || text.equals("")) {
+			return null;
+		}
+		return new Integer(text);
+	}
+	
+	public static Float getAttributeFloat(String name, Element element) throws NumberFormatException {
+    String text = element.getAttributeValue(name);
+    if (text == null || text.equals("")) {
+      return null;
+    }
+    return new Float(text);
+  }
+	
+	public static Long getAttributeLong(String name, Element element) throws NumberFormatException {
+		String text = element.getAttributeValue(name);
+		if (text == null || text.equals("")) {
+			return null;
+		}
+		return new Long(text);
+	}
+
+	public static Date getAttributeDate(String name, Element element) throws ParseException {
+		String text = element.getAttributeValue(name);
+		if (text == null || text.equals("")) {
+			return null;
+		}
+		return Util.parseDate(text);
+	}
+
+	public static Boolean getChildBoolean(String name, Element element) {
+		String text = element.getChildText(name);
+		if (StringUtils.isBlank(text)) {
+			return null;
+		}
+		return BooleanUtils.toBooleanObject(text);
+	}
+
+	public static boolean getAttributeBooleanPrimitive(String name, Element element) {
+		String text = element.getAttributeValue(name);
+		if (StringUtils.isBlank(text)) {
+			return false;
+		}
+		return BooleanUtils.toBooleanObject(text);
+	}
+
+	public static Boolean getAttributeBoolean(String name, Element element) {
+		String text = element.getAttributeValue(name);
+		if (StringUtils.isBlank(text)) {
+			return null;
+		}
+		return BooleanUtils.toBooleanObject(text);
+	}
+
+	public static Date getChildDate(String name, Element element) throws ParseException {
+		String text = element.getChildText(name);
+		if (text == null || text.equals("")) {
+			return null;
+		}
+		return parseDate(text);
+	}
+
+	public static Date parseDate(String dateStr) throws ParseException {
+		if (org.apache.commons.lang.StringUtils.isEmpty(dateStr)) {
+			return null;
+		}
+		return Util.parseDate(dateStr);
+	}
+
+	public static String formatDate(Date date) {
+		if (date == null) {
+			return "";
+		}
+		return Util.formatDate(date);
+	}
+
+	public static String getChildText(String name, Element element) {
+		return element.getChildText(name);
+	}
+
+	public static Integer getChildInteger(String name, Element element) {
+		try {
+			String text = element.getChildText(name);
+			return new Integer(text);
+		} catch (Throwable ex) {
+		}
+		return null;
+	}
+
+	public static Double getChildDouble(String name, Element element) throws NumberFormatException {
+		try {
+			String text = element.getChildText(name);
+			return new Double(text);
+		} catch (Throwable ex) {
+		}
+		return null;
+	}
+
+	public static void outputElement(Element element, Writer writer) throws Exception {
+		XMLOutputter xmlOut = new XMLOutputter(Format.getPrettyFormat());
+		xmlOut.output(new Document(element), writer);
+	}
+
+	public static String outputElement(Element element) throws Exception {
+		XMLOutputter xmlOut = new XMLOutputter(Format.getPrettyFormat());
+		String xmlString = xmlOut.outputString(new Document(element));
+		return xmlString;
+	}
+
+	public static String outputElementMinimal(Element element) throws Exception {
+		Format format = Format.getCompactFormat();
+		format.setOmitDeclaration(true);
+		XMLOutputter xmlOut = new XMLOutputter(format);
+		String xmlString = xmlOut.outputString(new Document(element));
+		return xmlString;
+	}
+
+	public static String outputElementOmitDeclaration(Element element) {
+		Format format = Format.getPrettyFormat();
+		format.setOmitDeclaration(true);
+		XMLOutputter xmlOut = new XMLOutputter(format);
+		String xmlString = xmlOut.outputString(new Document(element));
+		return xmlString;
+	}
+
+	public static String outputDocument(Document document) throws Exception {
+		XMLOutputter xmlOut = new XMLOutputter(Format.getPrettyFormat());
+		String xmlString = xmlOut.outputString(document);
+		return xmlString;
+	}
+
+	public static String removeInvalidXMLChars(String value) {
+		StringBuffer buffer = new StringBuffer();
+		char[] array = value.toCharArray();
+		for (int x = 0; x < array.length; x++) {
+			if (Verifier.isXMLCharacter(array[x])) {
+				buffer.append(array[x]);
+			}
+		}
+		return buffer.toString();
+	}
+
+	public static Element createTextElement(String name, Object object, Element parentElement) {
+		if (object == null) {
+			return null;
+		}
+
+		String text = null;
+		if (object instanceof Date) {
+			Date date = (Date) object;
+
+			text = formatDate(date);
+		} else {
+			text = object.toString();
+		}
+		Element element = new Element(name);
+		// XMLOutputter outputter = new XMLOutputter();
+		// text = outputter.escapeElementEntities(text);
+		// text = removeInvalidXMLChars(text);
+		element.setText(text);
+
+		parentElement.addContent(element);
+
+		return element;
+	}
+
+	public static Element createTextElement(String name, Object object, Namespace namespace, Element parentElement) {
+		if (object == null) {
+			return null;
+		}
+
+		String text = null;
+		if (object instanceof File) {
+			text = ((File) object).getAbsolutePath();
+		} else {
+			text = object.toString();
+		}
+		Element element = new Element(name, namespace);
+		XMLOutputter outputter = new XMLOutputter();
+		text = outputter.escapeElementEntities(text);
+		text = removeInvalidXMLChars(text);
+		element.setText(text);
+
+		parentElement.addContent(element);
+
+		return element;
+	}
+
+	public static void saveXML(Element element, File file) throws IOException {
+		Document document = new Document();
+		document.addContent(element);
+		saveXML(document, file);
+	}
+
+	public static void saveXML(Document document, File file) throws IOException {
+		File parentDir = file.getParentFile();
+		if (!parentDir.exists()) {
+			parentDir.mkdirs();
+		}
+		Format format = Format.getPrettyFormat();
+		format.setLineSeparator("\n");
+		XMLOutputter outputter = new XMLOutputter(format);
+		String channelXMLStr = outputter.outputString(document);
+		FileUtils.writeStringToFile(file, channelXMLStr, "UTF-8");
+	}
+
+	/**
+	 * public static XmlPullParser parseDocumentSAX(InputStream input) throws
+	 * Exception { XmlPullParserFactory factory =
+	 * XmlPullParserFactory.newInstance("org.xmlpull.mxp1.MXParserFactory", null);
+	 * factory.setNamespaceAware(false); factory.setValidating(false);
+	 * XmlPullParser xpp = factory.newPullParser(); xpp.setInput(input, "UTF-8");
+	 * return xpp; }
+	 */
+	public static File getChildFile(String name, Element element) throws Exception {
+		String path = element.getChildTextTrim(name);
+		if (StringUtils.isBlank(path)) {
+			return null;
+		}
+		return new File(path);
+	}
+
+	public static Document parseDocument(File file) throws XMLException, IOException {
+		String xml = FileUtils.readFileToString(file, "UTF-8");
+		return parseDocument(xml);
+	}
+
+	public static Element parseElement(File file) throws XMLException, IOException {
+		if (!file.exists()) {
+			return null;
+		}
+		String xml = FileUtils.readFileToString(file, "UTF-8");
+		return parseElement(xml);
+	}
+
+	public static Element parseElement(String xml) throws XMLException {
+		Document document = parseDocument(xml);
+		return document.getRootElement();
+	}
+  
+	public static class XMLException extends Exception {
+		public XMLException(String message, Throwable throwable) {
+			super(message, throwable);
+		}
+	}
+	
+	public static Document parseDocument(String xml) throws XMLException {
+		if (StringUtils.isBlank(xml))
+			throw new IllegalArgumentException("xml blank"); 
+		EntityResolver RESOLVER = new EmptyEntityResolver();
+		SAXBuilder saxBuilder = new SAXBuilder(false);
+		saxBuilder.setEntityResolver(RESOLVER);
+		try {
+			Document document = saxBuilder.build(new StringReader(xml));
+			return document;
+		} catch (Exception exception) {
+			throw new XMLException(xml, exception);
+		}
+	}
+
+	public static class EmptyEntityResolver implements EntityResolver {
+		public InputSource resolveEntity(String publicId, String systemId) {
+			InputSource EMPTY_INPUTSOURCE = new InputSource(new ByteArrayInputStream(new byte[0]));
+			return EMPTY_INPUTSOURCE;
+			// if (systemId != null && systemId.endsWith(".dtd")) return
+			// EMPTY_INPUTSOURCE;
+			// return null;
+		}
+	}
+}
Index: ocean/src/org/apache/lucene/ocean/WriteableMemoryIndex.java
===================================================================
--- ocean/src/org/apache/lucene/ocean/WriteableMemoryIndex.java	(revision 0)
+++ ocean/src/org/apache/lucene/ocean/WriteableMemoryIndex.java	(revision 0)
@@ -0,0 +1,185 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.index.FilterIndexReader;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.ocean.util.SortedList;
+import org.apache.lucene.ocean.util.Util;
+import org.apache.lucene.store.instantiated.InstantiatedIndex;
+import org.apache.lucene.store.instantiated.InstantiatedIndexReader;
+import org.apache.lucene.store.instantiated.InstantiatedIndexWriter;
+
+/**
+ * Encapsulates org.apache.lucene.store.instantiated.InstantiatedIndex for use with the
+ * Ocean system.  This is the only index that is writeable, meaning new documents may
+ * be added to.  
+ * 
+ */
+//TODO: release old MemoryIndexSnapshots from map
+public class WriteableMemoryIndex extends Index {
+	private WriteableIndexWriter indexWriter;
+	//private InstantiatedIndexReader indexReader;
+	private InstantiatedIndex instantiatedIndex;
+  private SortedList<Long,MemoryIndexSnapshot> snapshotMap = new SortedList<Long,MemoryIndexSnapshot>();
+	
+	public WriteableMemoryIndex(IndexID id, TransactionSystem system) throws IOException {
+		super(id, system);
+		instantiatedIndex = new InstantiatedIndex();
+		indexWriter = new WriteableIndexWriter(instantiatedIndex);
+		//indexReader = new InstantiatedIndexReader(instantiatedIndex);
+	}
+  
+	public boolean rollback(Long snapshotId) {
+	  return snapshotMap.remove(snapshotId) != null;
+	}
+	
+	// called by Category.runTransactionsNotInIndex
+	void addDocuments(Documents documents, Analyzer analyzer) throws IOException {
+		for (Document document : documents) {
+			indexWriter.addDocument(document, analyzer);
+		}
+	}
+
+	// called by Category.runTransactionsNotInIndex
+	MemoryIndexSnapshot setSnapshot(Long snapshotId) {//, List<Deletes> deletesList) throws Exception {
+		//int maxDoc = indexReader.maxDoc();
+		//HashSet<Integer> deletedSet = new HashSet<Integer>();
+		//if (deletesList != null) {
+		//	for (Deletes deletes : deletesList) {
+		//		applyDeletes(false, deletes, null, indexReader);
+		//	}
+		//}
+	  int maxDoc = instantiatedIndex.getDocumentsByNumber().length;
+	  HashSet<Integer> deletedSet = new HashSet<Integer>();
+		MemoryIndexSnapshot memoryIndexSnapshot = new MemoryIndexSnapshot(snapshotId, maxDoc, deletedSet);
+		snapshotMap.put(snapshotId, memoryIndexSnapshot);
+		return memoryIndexSnapshot;
+	}
+
+	public MemoryIndexSnapshot getIndexSnapshot(Long snapshotId) {
+		return snapshotMap.get(snapshotId);
+	}
+
+	public MemoryIndexSnapshot getLatestIndexSnapshot() {
+		return snapshotMap.lastValue();
+	}
+
+	public class MemoryIndexSnapshot extends IndexSnapshot {
+		private final int maxDoc;
+		private HashSet<Integer> deletedDocs;
+		private OceanInstantiatedIndexReader indexReader;
+
+		public MemoryIndexSnapshot(Long snapshotId, int maxDoc, HashSet<Integer> deletedDocs) {
+			super(snapshotId);
+			this.maxDoc = maxDoc;
+			this.deletedDocs = deletedDocs;
+			indexReader = new OceanInstantiatedIndexReader(maxDoc, instantiatedIndex, deletedDocs);
+		}
+		
+		public int deletedDoc() {
+		  return deletedDocs.size();
+		}
+		
+		public int maxDoc() {
+		  return maxDoc;
+		}
+
+		public IndexReader getIndexReader() {
+			return indexReader;
+		}
+	}
+
+	Long getLatestSnapshotId() {
+		return snapshotMap.lastKey();
+	}
+
+	private HashSet<Integer> getLatestSnapshotDeletedDocSet() {
+		MemoryIndexSnapshot memoryIndexSnapshot = snapshotMap.lastValue();
+		if (memoryIndexSnapshot == null || memoryIndexSnapshot.deletedDocs == null)
+			return null;
+		HashSet<Integer> deletedDocSet = memoryIndexSnapshot.deletedDocs;
+		return deletedDocSet;
+	}
+
+	public int getDocumentCount() {
+		return instantiatedIndex.getDocumentsByNumber().length;
+	}
+
+	public DeletesResult commitDeletes(Deletes deletes, Transaction transaction) throws Exception, InterruptedException, IOException {
+		return commitChanges(null, deletes, null, transaction);
+	}
+  
+	public void commitNothing(Transaction transaction) throws IndexException, InterruptedException, IOException {
+	  Long snapshotId = transaction.getId();
+	  MemoryIndexSnapshot latestIndexSnapshot = getLatestIndexSnapshot();
+	  int maxDoc = latestIndexSnapshot.maxDoc();
+	  HashSet<Integer> deletedDocSet = latestIndexSnapshot.deletedDocs;
+	  MemoryIndexSnapshot memoryIndexSnapshot = new MemoryIndexSnapshot(snapshotId, maxDoc, deletedDocSet);
+    snapshotMap.put(snapshotId, memoryIndexSnapshot);
+    removeOldSnapshots(snapshotMap);
+	}
+	
+	public DeletesResult commitChanges(Documents documents, Deletes deletes, Analyzer analyzer, Transaction transaction) throws InterruptedException, Exception, IOException {
+		try {
+			if (isClosed()) {
+				throw new IOException("index is closed");
+			}
+			if (isReadOnly() && documents != null && documents.size() > 0) {
+				throw new IOException("index not accepting new documents");
+			}
+			DeletesResult deletesResult = new DeletesResult(getId());
+			HashSet<Integer> deletedSet = null;
+			if (deletes != null && deletes.hasDeletes()) {
+			  HashSet<Integer> previousDeletedSet = getLatestSnapshotDeletedDocSet();
+				if (previousDeletedSet != null) {
+					deletedSet = (HashSet<Integer>) previousDeletedSet.clone();
+				} else {
+					deletedSet = new HashSet<Integer>();
+				}
+				IndexSnapshot indexSnapshot = getLatestIndexSnapshot();
+				deletesResult = applyDeletes(false, deletes, deletedSet, indexSnapshot.getIndexReader());
+			} else if (deletes == null || !deletes.hasDeletes()) { // if no deletes
+				// just use same
+				deletedSet = getLatestSnapshotDeletedDocSet();
+			}
+			if (documents != null) {
+				for (Document document : documents) {
+					indexWriter.addDocument(document, analyzer);
+				}
+			}
+			transaction.ready(this);
+			if (transaction.go()) {
+				indexWriter.commit();
+				int maxDoc = instantiatedIndex.getDocumentsByNumber().length;
+				Long snapshotId = transaction.getId();
+				MemoryIndexSnapshot memoryIndexSnapshot = new MemoryIndexSnapshot(snapshotId, maxDoc, deletedSet);
+				snapshotMap.put(snapshotId, memoryIndexSnapshot);
+				removeOldSnapshots(snapshotMap);
+				return deletesResult;
+			} else {
+				indexWriter.abort();
+				return null;
+			}
+		} catch (Throwable throwable) {
+			transaction.failed(this, throwable);
+			if (throwable instanceof Exception) {
+			  throw (Exception)throwable;
+			} else {
+			  throw new Exception(throwable);
+			}
+		}
+		//return null;
+	}
+
+	public class WriteableIndexWriter extends InstantiatedIndexWriter {
+		public WriteableIndexWriter(InstantiatedIndex index) throws IOException {
+			super(index);
+		}
+	}
+}
Index: ocean/test/org/apache/lucene/ocean/TestRandomAccessFile.java
===================================================================
--- ocean/test/org/apache/lucene/ocean/TestRandomAccessFile.java	(revision 0)
+++ ocean/test/org/apache/lucene/ocean/TestRandomAccessFile.java	(revision 0)
@@ -0,0 +1,30 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+
+import junit.framework.TestSuite;
+import junit.textui.TestRunner;
+
+import org.apache.lucene.util.LuceneTestCase;
+
+import com.imagero.uio.bio.ByteArrayRandomAccessIO;
+
+public class TestRandomAccessFile extends LuceneTestCase {
+  public static void main(String args[]) {
+    TestRunner.run(new TestSuite(TestRandomAccessFile.class));
+  }
+  
+  public void testMain() throws IOException {
+    ByteArrayRandomAccessIO random = new ByteArrayRandomAccessIO(1024);
+    byte[] bytes = "hello world".getBytes("UTF-8");
+    long filePointer = random.getFilePointer();
+    System.out.println("filePointer: "+filePointer);
+    random.writeInt(bytes.length);
+    random.write(bytes);
+    random.seek(filePointer);
+    int length = random.readInt();
+    byte[] readBytes = new byte[length];
+    random.read(readBytes);
+    System.out.println(new String(readBytes, "UTF-8"));
+  }
+}
Index: ocean/test/org/apache/lucene/ocean/TestLogRecords.java
===================================================================
--- ocean/test/org/apache/lucene/ocean/TestLogRecords.java	(revision 0)
+++ ocean/test/org/apache/lucene/ocean/TestLogRecords.java	(revision 0)
@@ -0,0 +1,55 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestSuite;
+import junit.textui.TestRunner;
+
+import org.apache.lucene.ocean.log.LogFile;
+import org.apache.lucene.ocean.log.LogFile.Record;
+import org.apache.lucene.ocean.log.LogFile.RecordIterator;
+import org.apache.lucene.util.LuceneTestCase;
+
+public class TestLogRecords extends LuceneTestCase {
+  public static void main(String args[]) {
+    TestRunner.run(new TestSuite(TestLogRecords.class));
+  }
+  
+  public void testLogRecords() throws IOException {
+    List<String> docList = new ArrayList<String>();
+    List<String> otherList = new ArrayList<String>();
+    
+    RAMDirectoryMap directoryMap = new RAMDirectoryMap();
+    LogFile logFile = new LogFile(1l, "test", directoryMap.getLogDirectory());
+    
+    for (int x=0; x < 5; x++) {
+      String docStr = "docs "+x+" ";
+      docList.add(docStr);
+      byte[] docBytes = docStr.getBytes("UTF-8");
+      System.out.println("docs.length: "+docBytes.length);
+      String otherStr = "other "+x+" ";
+      byte[] otherBytes = otherStr.getBytes("UTF-8");
+      otherList.add(otherStr);
+      logFile.writeRecord(new Long(x), docBytes, otherBytes);
+    }
+    System.out.println("min: "+logFile.getMinId());
+    System.out.println("max: "+logFile.getMaxId());
+    logFile.close();
+    logFile = new LogFile(1l, "test", directoryMap.getLogDirectory());
+    
+    RecordIterator iterator = logFile.getRecordIterator(null);
+    while (iterator.hasNext()) {
+      Record record = iterator.next();
+      
+      byte[] docBytes = record.getStreamRecord().getDocuments().getBytes();
+      String docs = new String(docBytes, "UTF-8");
+      byte[] otherBytes = record.getStreamRecord().getOther().getBytes();
+      String other = new String(otherBytes, "UTF-8");
+      System.out.println("docs: "+docs);
+      System.out.println("other: "+other);
+    }
+    iterator.close();
+  }
+}
Index: ocean/test/org/apache/lucene/ocean/TestSearch.java
===================================================================
--- ocean/test/org/apache/lucene/ocean/TestSearch.java	(revision 0)
+++ ocean/test/org/apache/lucene/ocean/TestSearch.java	(revision 0)
@@ -0,0 +1,142 @@
+package org.apache.lucene.ocean;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.Random;
+
+import junit.framework.TestSuite;
+import junit.textui.TestRunner;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.analysis.SimpleAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.ocean.log.TransactionLog;
+import org.apache.lucene.ocean.util.Constants;
+import org.apache.lucene.ocean.util.Util;
+import org.apache.lucene.queryParser.QueryParser;
+import org.apache.lucene.search.MatchAllDocsQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.util.LuceneTestCase;
+
+// TODO: skipping snapshot id 2 when adding
+// TODO: on reload missing id 7
+public class TestSearch extends LuceneTestCase {
+  List<Long> deletedDocIds = new ArrayList<Long>();
+  Random random = new Random(System.currentTimeMillis());
+  
+  /** Main for running test case by itself. */
+  public static void main(String args[]) {
+    TestRunner.run(new TestSuite(TestSearch.class));
+  }
+
+  public static TransactionSystem setupTransactionSystem() throws Exception {
+    FSDirectoryMap directoryMap = new FSDirectoryMap(new File("g:\\testocean"), "log");
+    // RAMDirectoryMap directoryMap = new RAMDirectoryMap();
+    LogDirectory logDirectory = directoryMap.getLogDirectory();
+    TransactionLog transactionLog = new TransactionLog(logDirectory);
+    System.out.println("transactionLog num: " + transactionLog.getNumRecords() + " min: " + transactionLog.getMinId() + " max: "
+        + transactionLog.getMaxId());
+    return new TransactionSystem(transactionLog, new SimpleAnalyzer(), directoryMap, 20, 5, 10);
+  }
+
+  public void testSearch() throws Exception {
+    TransactionSystem system = setupTransactionSystem();
+    StringWriter sw = new StringWriter();
+    PrintWriter pw = new PrintWriter(sw, true);
+    doTestSearch(pw, false, 10, system);
+    pw.close();
+    sw.close();
+    String multiFileOutput = sw.getBuffer().toString();
+
+    String singleFileOutput = sw.getBuffer().toString();
+    System.out.println(multiFileOutput);
+    
+    //doDeleteRandomDocuments(system);
+    //verifyDocumentsDeleted(system);
+  }
+  
+  private void verifyDocumentsDeleted(TransactionSystem system) throws Exception {
+    OceanSearcher searcher = system.getSearcher();
+    for (Long docId : deletedDocIds) {
+      String encodedId = Util.longToEncoded(docId);
+      Term term = new Term(Constants.DOCUMENTID, encodedId);
+      int totalHits = searcher.search(new TermQuery(term), null, 1000).totalHits;
+      assertEquals(totalHits, 0);
+    }
+  }
+  
+  private void doDeleteRandomDocuments(TransactionSystem system) throws Exception {
+    OceanSearcher searcher = system.getSearcher();
+    ScoreDoc[] hits = searcher.search(new MatchAllDocsQuery(), null, 5000).scoreDocs;
+    for (ScoreDoc scoreDoc : hits) {
+      Document document = searcher.doc(scoreDoc.doc);
+      String docEncoded = document.get(Constants.DOCUMENTID);
+      Long documentId = Util.longFromEncoded(docEncoded);
+      if (random.nextBoolean()) {
+        system.deleteDocument(new Term(Constants.DOCUMENTID, docEncoded));
+        deletedDocIds.add(documentId);
+      }
+    }
+    System.out.println("deletedDocIds.size: "+deletedDocIds.size());
+  }
+  
+  private void doTestSearch(PrintWriter out, boolean addDocs, int rounds, TransactionSystem system) throws Exception {
+    Analyzer analyzer = new SimpleAnalyzer();
+
+    if (addDocs) {
+      String[] docs = { "a b c d e", "a b c d e a b c d e", "a b c d e f g h i j", "a c e", "e c a", "a c e a c e", "a c e a b c" };
+      for (int x = 0; x < rounds; x++) {
+        for (int j = 0; j < docs.length; j++) {
+          Document d = new Document();
+          d.add(new Field("contents", docs[j], Field.Store.YES, Field.Index.TOKENIZED));
+          system.addDocument(d);
+        }
+      }
+    }
+
+    OceanSearcher searcher = system.getSearcher();
+
+    out.println("snapshot id: " + Util.formatSnapshotId(searcher.getSnapshot().getId()));
+
+    String[] queries = { "a b", "\"a b\"", "\"a b c\"", "a c", "\"a c\"", "\"a c e\"", "*:*" };
+    ScoreDoc[] hits = null;
+
+    QueryParser parser = new QueryParser("contents", analyzer);
+    parser.setPhraseSlop(4);
+    for (int j = 0; j < queries.length; j++) {
+      Query query = parser.parse(queries[j]);
+      out.println("Query: " + query.toString("contents"));
+
+      // DateFilter filter =
+      // new DateFilter("modified", Time(1997,0,1), Time(1998,0,1));
+      // DateFilter filter = DateFilter.Before("modified", Time(1997,00,01));
+      // System.out.println(filter);
+
+      hits = searcher.search(query, null, 1000).scoreDocs;
+
+      out.println(hits.length + " total results");
+      for (int i = 0; i < hits.length && i < 10; i++) {
+        Document d = searcher.doc(hits[i].doc);
+        String docEncoded = d.get(Constants.DOCUMENTID);
+        out.println(i + " " + hits[i].score
+        // + " " + DateField.stringToDate(d.get("modified"))
+            + " " + d.get("contents") + " " + Util.longFromEncoded(docEncoded));
+      }
+    }
+    //system.close();
+  }
+
+  static long Time(int year, int month, int day) {
+    GregorianCalendar calendar = new GregorianCalendar();
+    calendar.set(year, month, day);
+    return calendar.getTime().getTime();
+  }
+}
Index: ocean/test/org/apache/lucene/ocean/TestUpdates.java
===================================================================
--- ocean/test/org/apache/lucene/ocean/TestUpdates.java	(revision 0)
+++ ocean/test/org/apache/lucene/ocean/TestUpdates.java	(revision 0)
@@ -0,0 +1,42 @@
+package org.apache.lucene.ocean;
+
+import junit.framework.TestSuite;
+import junit.textui.TestRunner;
+
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.util.English;
+import org.apache.lucene.util.LuceneTestCase;
+
+/**
+ * Add documents, delete them, verify they are deleted. Update documents verify
+ * they have been updated.
+ * 
+ */
+public class TestUpdates extends LuceneTestCase {
+  TransactionSystem transactionSystem;
+  
+  public static void main(String args[]) {
+    TestRunner.run(new TestSuite(TestUpdates.class));
+  }
+  
+  public void testUpdateDocuments() throws Exception {
+    transactionSystem = TestSearch.setupTransactionSystem();
+    // add documents
+    int count = 2;
+    for (int i = 0; i < 200; i++) {
+      Document d = new Document();
+      d.add(new Field("id", Integer.toString(i), Field.Store.YES, Field.Index.UN_TOKENIZED));
+      d.add(new Field("contents", English.intToEnglish(i + 10 * count), Field.Store.NO, Field.Index.TOKENIZED));
+      transactionSystem.addDocument(d);
+      //transactionSystem.updateDocument(new Term("id", Integer.toString(i)), d);
+    }
+    OceanSearcher searcher = transactionSystem.getSearcher();
+    int maxDoc = searcher.maxDoc();
+    System.out.println("maxDoc: "+maxDoc);
+    assertEquals(maxDoc, 200);
+    
+    
+    transactionSystem.close();
+  }
+}
Index: ocean/test/org/apache/lucene/ocean/TestByteBufferPool.java
===================================================================
--- ocean/test/org/apache/lucene/ocean/TestByteBufferPool.java	(revision 0)
+++ ocean/test/org/apache/lucene/ocean/TestByteBufferPool.java	(revision 0)
@@ -0,0 +1,11 @@
+package org.apache.lucene.ocean;
+
+/**
+ * Write to pool, read from pool make sure results match.
+ *
+ */
+public class TestByteBufferPool {
+  public void testByteBufferPool() {
+    
+  }
+}
Index: ocean/test/org/apache/lucene/ocean/TestIndexCreator.java
===================================================================
--- ocean/test/org/apache/lucene/ocean/TestIndexCreator.java	(revision 0)
+++ ocean/test/org/apache/lucene/ocean/TestIndexCreator.java	(revision 0)
@@ -0,0 +1,73 @@
+package org.apache.lucene.ocean;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import junit.framework.TestSuite;
+import junit.textui.TestRunner;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.analysis.WhitespaceAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.MatchAllDocsQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.RAMDirectory;
+import org.apache.lucene.util.LuceneTestCase;
+
+public class TestIndexCreator extends LuceneTestCase {
+
+  /** Main for running test case by itself. */
+  public static void main(String args[]) {
+    TestRunner.run(new TestSuite(TestIndexCreator.class));
+  }
+
+  public void testMain() throws Exception {
+    Directory directory = new RAMDirectory();
+    Analyzer analyzer = new WhitespaceAnalyzer();
+    ExecutorService threadPool = Executors.newFixedThreadPool(8);
+    IndexCreator indexCreator = new IndexCreator(directory, 1 * 1024 * 1024, 4, analyzer, threadPool);
+    BlockingQueue<IndexCreator.Add> queue = new ArrayBlockingQueue<IndexCreator.Add>(4000, true);
+    indexCreator.start(queue); // start threads consuming
+    String[] docs = { "a b c d e", "a b c d e a b c d e", "a b c d e f g h i j", "a c e", "e c a", "a c e a c e", "a c e a b c" };
+    for (int x = 0; x < 10; x++) {
+      for (int j = 0; j < docs.length; j++) {
+        Document d = new Document();
+        d.add(new Field("contents", x+" "+docs[j], Field.Store.YES, Field.Index.TOKENIZED));
+        queue.add(new IndexCreator.Add(d));
+      }
+    }
+    while ( queue.peek() != null) { 
+      Thread.sleep(5);
+    }
+    System.out.println("before create");
+    indexCreator.create();
+    System.out.println("after create");
+    IndexReader indexReader = IndexReader.open(directory);
+    IndexSearcher searcher = new IndexSearcher(indexReader);
+    Query query = new MatchAllDocsQuery();
+    ScoreDoc[] hits = searcher.search(query, null, 1000).scoreDocs;
+    
+    StringWriter sw = new StringWriter();
+    PrintWriter out = new PrintWriter(sw, true);
+    out.println(hits.length + " total results");
+    for (int i = 0; i < hits.length; i++) {
+      Document d = searcher.doc(hits[i].doc);
+      out.println(i + " " + hits[i].score
+      // + " " + DateField.stringToDate(d.get("modified"))
+          + " " + d.get("contents"));
+    }
+    sw.close();
+    out.close();
+    String singleFileOutput = sw.getBuffer().toString();
+    System.out.println(singleFileOutput);
+  }
+}
Index: ocean/test/org/apache/lucene/ocean/TestByteBufferPool.java
===================================================================
--- ocean/test/org/apache/lucene/ocean/TestByteBufferPool.java	(revision 0)
+++ ocean/test/org/apache/lucene/ocean/TestByteBufferPool.java	(revision 0)
@@ -0,0 +1,11 @@
+package org.apache.lucene.ocean;
+
+/**
+ * Write to pool, read from pool make sure results match.
+ *
+ */
+public class TestByteBufferPool {
+  public void testByteBufferPool() {
+    
+  }
+}
Index: ocean/test/org/apache/lucene/ocean/TestIndexCreator.java
===================================================================
--- ocean/test/org/apache/lucene/ocean/TestIndexCreator.java	(revision 0)
+++ ocean/test/org/apache/lucene/ocean/TestIndexCreator.java	(revision 0)
@@ -0,0 +1,73 @@
+package org.apache.lucene.ocean;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import junit.framework.TestSuite;
+import junit.textui.TestRunner;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.analysis.WhitespaceAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.MatchAllDocsQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.RAMDirectory;
+import org.apache.lucene.util.LuceneTestCase;
+
+public class TestIndexCreator extends LuceneTestCase {
+
+  /** Main for running test case by itself. */
+  public static void main(String args[]) {
+    TestRunner.run(new TestSuite(TestIndexCreator.class));
+  }
+
+  public void testMain() throws Exception {
+    Directory directory = new RAMDirectory();
+    Analyzer analyzer = new WhitespaceAnalyzer();
+    ExecutorService threadPool = Executors.newFixedThreadPool(8);
+    IndexCreator indexCreator = new IndexCreator(directory, 1 * 1024 * 1024, 4, analyzer, threadPool);
+    BlockingQueue<IndexCreator.Add> queue = new ArrayBlockingQueue<IndexCreator.Add>(4000, true);
+    indexCreator.start(queue); // start threads consuming
+    String[] docs = { "a b c d e", "a b c d e a b c d e", "a b c d e f g h i j", "a c e", "e c a", "a c e a c e", "a c e a b c" };
+    for (int x = 0; x < 10; x++) {
+      for (int j = 0; j < docs.length; j++) {
+        Document d = new Document();
+        d.add(new Field("contents", x+" "+docs[j], Field.Store.YES, Field.Index.TOKENIZED));
+        queue.add(new IndexCreator.Add(d));
+      }
+    }
+    while ( queue.peek() != null) { 
+      Thread.sleep(5);
+    }
+    System.out.println("before create");
+    indexCreator.create();
+    System.out.println("after create");
+    IndexReader indexReader = IndexReader.open(directory);
+    IndexSearcher searcher = new IndexSearcher(indexReader);
+    Query query = new MatchAllDocsQuery();
+    ScoreDoc[] hits = searcher.search(query, null, 1000).scoreDocs;
+    
+    StringWriter sw = new StringWriter();
+    PrintWriter out = new PrintWriter(sw, true);
+    out.println(hits.length + " total results");
+    for (int i = 0; i < hits.length; i++) {
+      Document d = searcher.doc(hits[i].doc);
+      out.println(i + " " + hits[i].score
+      // + " " + DateField.stringToDate(d.get("modified"))
+          + " " + d.get("contents"));
+    }
+    sw.close();
+    out.close();
+    String singleFileOutput = sw.getBuffer().toString();
+    System.out.println(singleFileOutput);
+  }
+}
Index: ocean/test/org/apache/lucene/ocean/TestLogRecords.java
===================================================================
--- ocean/test/org/apache/lucene/ocean/TestLogRecords.java	(revision 0)
+++ ocean/test/org/apache/lucene/ocean/TestLogRecords.java	(revision 0)
@@ -0,0 +1,55 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestSuite;
+import junit.textui.TestRunner;
+
+import org.apache.lucene.ocean.log.LogFile;
+import org.apache.lucene.ocean.log.LogFile.Record;
+import org.apache.lucene.ocean.log.LogFile.RecordIterator;
+import org.apache.lucene.util.LuceneTestCase;
+
+public class TestLogRecords extends LuceneTestCase {
+  public static void main(String args[]) {
+    TestRunner.run(new TestSuite(TestLogRecords.class));
+  }
+  
+  public void testLogRecords() throws IOException {
+    List<String> docList = new ArrayList<String>();
+    List<String> otherList = new ArrayList<String>();
+    
+    RAMDirectoryMap directoryMap = new RAMDirectoryMap();
+    LogFile logFile = new LogFile(1l, "test", directoryMap.getLogDirectory());
+    
+    for (int x=0; x < 5; x++) {
+      String docStr = "docs "+x+" ";
+      docList.add(docStr);
+      byte[] docBytes = docStr.getBytes("UTF-8");
+      System.out.println("docs.length: "+docBytes.length);
+      String otherStr = "other "+x+" ";
+      byte[] otherBytes = otherStr.getBytes("UTF-8");
+      otherList.add(otherStr);
+      logFile.writeRecord(new Long(x), docBytes, otherBytes);
+    }
+    System.out.println("min: "+logFile.getMinId());
+    System.out.println("max: "+logFile.getMaxId());
+    logFile.close();
+    logFile = new LogFile(1l, "test", directoryMap.getLogDirectory());
+    
+    RecordIterator iterator = logFile.getRecordIterator(null);
+    while (iterator.hasNext()) {
+      Record record = iterator.next();
+      
+      byte[] docBytes = record.getStreamRecord().getDocuments().getBytes();
+      String docs = new String(docBytes, "UTF-8");
+      byte[] otherBytes = record.getStreamRecord().getOther().getBytes();
+      String other = new String(otherBytes, "UTF-8");
+      System.out.println("docs: "+docs);
+      System.out.println("other: "+other);
+    }
+    iterator.close();
+  }
+}
Index: ocean/test/org/apache/lucene/ocean/TestRandomAccessFile.java
===================================================================
--- ocean/test/org/apache/lucene/ocean/TestRandomAccessFile.java	(revision 0)
+++ ocean/test/org/apache/lucene/ocean/TestRandomAccessFile.java	(revision 0)
@@ -0,0 +1,30 @@
+package org.apache.lucene.ocean;
+
+import java.io.IOException;
+
+import junit.framework.TestSuite;
+import junit.textui.TestRunner;
+
+import org.apache.lucene.util.LuceneTestCase;
+
+import com.imagero.uio.bio.ByteArrayRandomAccessIO;
+
+public class TestRandomAccessFile extends LuceneTestCase {
+  public static void main(String args[]) {
+    TestRunner.run(new TestSuite(TestRandomAccessFile.class));
+  }
+  
+  public void testMain() throws IOException {
+    ByteArrayRandomAccessIO random = new ByteArrayRandomAccessIO(1024);
+    byte[] bytes = "hello world".getBytes("UTF-8");
+    long filePointer = random.getFilePointer();
+    System.out.println("filePointer: "+filePointer);
+    random.writeInt(bytes.length);
+    random.write(bytes);
+    random.seek(filePointer);
+    int length = random.readInt();
+    byte[] readBytes = new byte[length];
+    random.read(readBytes);
+    System.out.println(new String(readBytes, "UTF-8"));
+  }
+}
Index: ocean/test/org/apache/lucene/ocean/TestSearch.java
===================================================================
--- ocean/test/org/apache/lucene/ocean/TestSearch.java	(revision 0)
+++ ocean/test/org/apache/lucene/ocean/TestSearch.java	(revision 0)
@@ -0,0 +1,142 @@
+package org.apache.lucene.ocean;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.Random;
+
+import junit.framework.TestSuite;
+import junit.textui.TestRunner;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.analysis.SimpleAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.ocean.log.TransactionLog;
+import org.apache.lucene.ocean.util.Constants;
+import org.apache.lucene.ocean.util.Util;
+import org.apache.lucene.queryParser.QueryParser;
+import org.apache.lucene.search.MatchAllDocsQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.util.LuceneTestCase;
+
+// TODO: skipping snapshot id 2 when adding
+// TODO: on reload missing id 7
+public class TestSearch extends LuceneTestCase {
+  List<Long> deletedDocIds = new ArrayList<Long>();
+  Random random = new Random(System.currentTimeMillis());
+  
+  /** Main for running test case by itself. */
+  public static void main(String args[]) {
+    TestRunner.run(new TestSuite(TestSearch.class));
+  }
+
+  public static TransactionSystem setupTransactionSystem() throws Exception {
+    FSDirectoryMap directoryMap = new FSDirectoryMap(new File("g:\\testocean"), "log");
+    // RAMDirectoryMap directoryMap = new RAMDirectoryMap();
+    LogDirectory logDirectory = directoryMap.getLogDirectory();
+    TransactionLog transactionLog = new TransactionLog(logDirectory);
+    System.out.println("transactionLog num: " + transactionLog.getNumRecords() + " min: " + transactionLog.getMinId() + " max: "
+        + transactionLog.getMaxId());
+    return new TransactionSystem(transactionLog, new SimpleAnalyzer(), directoryMap, 20, 5, 10);
+  }
+
+  public void testSearch() throws Exception {
+    TransactionSystem system = setupTransactionSystem();
+    StringWriter sw = new StringWriter();
+    PrintWriter pw = new PrintWriter(sw, true);
+    doTestSearch(pw, false, 10, system);
+    pw.close();
+    sw.close();
+    String multiFileOutput = sw.getBuffer().toString();
+
+    String singleFileOutput = sw.getBuffer().toString();
+    System.out.println(multiFileOutput);
+    
+    //doDeleteRandomDocuments(system);
+    //verifyDocumentsDeleted(system);
+  }
+  
+  private void verifyDocumentsDeleted(TransactionSystem system) throws Exception {
+    OceanSearcher searcher = system.getSearcher();
+    for (Long docId : deletedDocIds) {
+      String encodedId = Util.longToEncoded(docId);
+      Term term = new Term(Constants.DOCUMENTID, encodedId);
+      int totalHits = searcher.search(new TermQuery(term), null, 1000).totalHits;
+      assertEquals(totalHits, 0);
+    }
+  }
+  
+  private void doDeleteRandomDocuments(TransactionSystem system) throws Exception {
+    OceanSearcher searcher = system.getSearcher();
+    ScoreDoc[] hits = searcher.search(new MatchAllDocsQuery(), null, 5000).scoreDocs;
+    for (ScoreDoc scoreDoc : hits) {
+      Document document = searcher.doc(scoreDoc.doc);
+      String docEncoded = document.get(Constants.DOCUMENTID);
+      Long documentId = Util.longFromEncoded(docEncoded);
+      if (random.nextBoolean()) {
+        system.deleteDocument(new Term(Constants.DOCUMENTID, docEncoded));
+        deletedDocIds.add(documentId);
+      }
+    }
+    System.out.println("deletedDocIds.size: "+deletedDocIds.size());
+  }
+  
+  private void doTestSearch(PrintWriter out, boolean addDocs, int rounds, TransactionSystem system) throws Exception {
+    Analyzer analyzer = new SimpleAnalyzer();
+
+    if (addDocs) {
+      String[] docs = { "a b c d e", "a b c d e a b c d e", "a b c d e f g h i j", "a c e", "e c a", "a c e a c e", "a c e a b c" };
+      for (int x = 0; x < rounds; x++) {
+        for (int j = 0; j < docs.length; j++) {
+          Document d = new Document();
+          d.add(new Field("contents", docs[j], Field.Store.YES, Field.Index.TOKENIZED));
+          system.addDocument(d);
+        }
+      }
+    }
+
+    OceanSearcher searcher = system.getSearcher();
+
+    out.println("snapshot id: " + Util.formatSnapshotId(searcher.getSnapshot().getId()));
+
+    String[] queries = { "a b", "\"a b\"", "\"a b c\"", "a c", "\"a c\"", "\"a c e\"", "*:*" };
+    ScoreDoc[] hits = null;
+
+    QueryParser parser = new QueryParser("contents", analyzer);
+    parser.setPhraseSlop(4);
+    for (int j = 0; j < queries.length; j++) {
+      Query query = parser.parse(queries[j]);
+      out.println("Query: " + query.toString("contents"));
+
+      // DateFilter filter =
+      // new DateFilter("modified", Time(1997,0,1), Time(1998,0,1));
+      // DateFilter filter = DateFilter.Before("modified", Time(1997,00,01));
+      // System.out.println(filter);
+
+      hits = searcher.search(query, null, 1000).scoreDocs;
+
+      out.println(hits.length + " total results");
+      for (int i = 0; i < hits.length && i < 10; i++) {
+        Document d = searcher.doc(hits[i].doc);
+        String docEncoded = d.get(Constants.DOCUMENTID);
+        out.println(i + " " + hits[i].score
+        // + " " + DateField.stringToDate(d.get("modified"))
+            + " " + d.get("contents") + " " + Util.longFromEncoded(docEncoded));
+      }
+    }
+    //system.close();
+  }
+
+  static long Time(int year, int month, int day) {
+    GregorianCalendar calendar = new GregorianCalendar();
+    calendar.set(year, month, day);
+    return calendar.getTime().getTime();
+  }
+}
Index: ocean/test/org/apache/lucene/ocean/TestUpdates.java
===================================================================
--- ocean/test/org/apache/lucene/ocean/TestUpdates.java	(revision 0)
+++ ocean/test/org/apache/lucene/ocean/TestUpdates.java	(revision 0)
@@ -0,0 +1,42 @@
+package org.apache.lucene.ocean;
+
+import junit.framework.TestSuite;
+import junit.textui.TestRunner;
+
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.util.English;
+import org.apache.lucene.util.LuceneTestCase;
+
+/**
+ * Add documents, delete them, verify they are deleted. Update documents verify
+ * they have been updated.
+ * 
+ */
+public class TestUpdates extends LuceneTestCase {
+  TransactionSystem transactionSystem;
+  
+  public static void main(String args[]) {
+    TestRunner.run(new TestSuite(TestUpdates.class));
+  }
+  
+  public void testUpdateDocuments() throws Exception {
+    transactionSystem = TestSearch.setupTransactionSystem();
+    // add documents
+    int count = 2;
+    for (int i = 0; i < 200; i++) {
+      Document d = new Document();
+      d.add(new Field("id", Integer.toString(i), Field.Store.YES, Field.Index.UN_TOKENIZED));
+      d.add(new Field("contents", English.intToEnglish(i + 10 * count), Field.Store.NO, Field.Index.TOKENIZED));
+      transactionSystem.addDocument(d);
+      //transactionSystem.updateDocument(new Term("id", Integer.toString(i)), d);
+    }
+    OceanSearcher searcher = transactionSystem.getSearcher();
+    int maxDoc = searcher.maxDoc();
+    System.out.println("maxDoc: "+maxDoc);
+    assertEquals(maxDoc, 200);
+    
+    
+    transactionSystem.close();
+  }
+}
