Kaynağa Gözat

fix: nio.BufferAdapter - exception handling

fix: nio.BufferAdapter.BChannel#write 
fix: nio.BufferAdapter.BChannel#position
fix: nio.BufferAdapter.BChannel#truncate

new: nio.ChannelAdapter.CAChannel - read/write wrapper for char[]
test: nio.ChannelAdapter

fix: nio.ChannelAdapter.BChannel#write
fix: nio.ChannelAdapter.AChannel#write
fix: nio.ChannelAdapter.CSChannel#read - endianess & alignment
Ranides Atterwim 11 yıl önce
ebeveyn
işleme
4c4a896d70

+ 97 - 47
src/main/java/net/ranides/assira/nio/BufferAdapter.java

@@ -6,28 +6,25 @@
  */
 package net.ranides.assira.nio;
 
-import java.io.BufferedWriter;
+import java.io.EOFException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.io.OutputStream;
 import java.io.Reader;
-import java.io.StringWriter;
 import java.io.Writer;
 import java.nio.BufferOverflowException;
 import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
 import java.nio.ReadOnlyBufferException;
+import java.nio.channels.ClosedChannelException;
 import java.nio.channels.SeekableByteChannel;
 import java.nio.charset.Charset;
-import java.nio.charset.CharsetDecoder;
-import java.nio.charset.CoderResult;
 import net.ranides.assira.generic.ValueUtils;
 import net.ranides.assira.io.FileHelper;
 import net.ranides.assira.io.RIOException;
-import net.ranides.assira.text.TextEncoding;
 
 /**
  * 
@@ -92,12 +89,6 @@ public final class BufferAdapter {
         return new CWriter(buffer);
     }
     
-    private static void nullcheck(Object object) throws IOException  {
-        if(null == object) {
-            throw new IOException("Buffer is closed");
-        }
-    }
-    
     private static class BChannel implements SeekableByteChannel {
         
         private ByteBuffer buffer;
@@ -113,14 +104,14 @@ public final class BufferAdapter {
 
         @Override
         public void close() throws IOException {
-            nullcheck(buffer);
+            nullcheck();
             FileHelper.close(buffer);
             buffer = null;
         }
 
         @Override
         public int read(ByteBuffer dst) throws IOException {
-            nullcheck(buffer);
+            nullcheck();
             if(buffer.remaining() == 0) {
                 return -1;
             }
@@ -128,11 +119,11 @@ public final class BufferAdapter {
             int prv = buffer.limit();
             int max = buffer.position() + count;
             if( max != buffer.limit(max).limit() ) {
-                throw new IOException("ByteBufferChannel can't set Buffer limits.");
+                throw new IOException("ByteBufferChannel can't set internal Buffer limits.");
             }
             dst.put(buffer);
             if(prv != buffer.limit(prv).limit()) {
-                throw new IOException("ByteBufferChannel can't restore Buffer limits.");
+                throw new IOException("ByteBufferChannel can't restore internal Buffer limits.");
             }
             return count;
         }
@@ -140,10 +131,27 @@ public final class BufferAdapter {
         @Override
         public int write(ByteBuffer src) throws IOException {
             try {
-                nullcheck(buffer);
-                int ret = src.remaining();
-                buffer.put(src);
-                return ret;
+                nullcheck();
+                int brem = buffer.remaining();
+                if(brem == 0) {
+                    throw new EOFException();
+                }
+                int srem = src.remaining();
+                if( srem > brem) {
+                    int prv = src.limit();
+                    int max = src.position() + brem;
+                    if(max != src.limit(max).limit()) {
+                        throw new IOException("ByteBufferChannel can't set source Buffer limits.");
+                    }
+                    buffer.put(src);
+                    if(prv != src.limit(prv).limit()) {
+                        throw new IOException("ByteBufferChannel can't restore source Buffer limits.");
+                    }
+                    return brem;
+                } else {
+                    buffer.put(src);
+                    return srem;
+                }
             } catch(BufferOverflowException | ReadOnlyBufferException cause) {
                 throw new IOException(cause);
             }
@@ -151,32 +159,44 @@ public final class BufferAdapter {
 
         @Override
         public long position() throws IOException {
-            nullcheck(buffer);
+            nullcheck();
             return buffer.position();
         }
 
         @Override
         public SeekableByteChannel position(long newPosition) throws IOException {
-            nullcheck(buffer);
+            nullcheck();
             assert newPosition == (int)newPosition;
-            buffer.position((int)newPosition);
+            if( newPosition < buffer.limit() ) {
+                buffer.position((int)newPosition);
+            } else {
+                buffer.position(buffer.limit());
+            }
             return this;
         }
 
         @Override
         public long size() throws IOException {
-            nullcheck(buffer);
+            nullcheck();
             return buffer.limit();
         }
 
         @Override
         public SeekableByteChannel truncate(long size) throws IOException {
-            nullcheck(buffer);
+            nullcheck();
             assert size == (int)size;
-            buffer.limit((int)size);
+            if( size < buffer.limit()) {
+                buffer.limit((int)size);
+            }
             return this;
         }
         
+        private void nullcheck() throws IOException  {
+            if(null == buffer) {
+                throw new ClosedChannelException();
+            }
+        }
+        
     }
     
     private static class BIStream extends InputStream {
@@ -189,14 +209,14 @@ public final class BufferAdapter {
 
         @Override
         public void close() throws IOException {
-            nullcheck(buffer);
+            nullcheck();
             FileHelper.close(buffer);
             buffer = null;
         }
         
         @Override
         public int read() throws IOException {
-            nullcheck(buffer);
+            nullcheck();
             try {
                 return buffer.get();
             } catch(BufferUnderflowException cause) {
@@ -206,7 +226,7 @@ public final class BufferAdapter {
         
         @Override
         public int read(byte[] target, int offset, int len) throws IOException {
-            nullcheck(buffer);
+            nullcheck();
             try {
                 buffer.get(target, offset, len);
                 return len;
@@ -217,16 +237,22 @@ public final class BufferAdapter {
 
         @Override
         public int available() throws IOException {
-            nullcheck(buffer);
+            nullcheck();
             return buffer.remaining();
         }
 
         @Override
         public long skip(long n) throws IOException {
-            nullcheck(buffer);
+            nullcheck();
             int np = buffer.position()+(int)n;
             return np == buffer.position(np).position() ? n : 0;
         }
+        
+        private void nullcheck() throws IOException  {
+            if(null == buffer) {
+                throw new IOException("InputStream is closed");
+            }
+        }
     
     }
     
@@ -240,19 +266,19 @@ public final class BufferAdapter {
 
         @Override
         public void close() throws IOException {
-            nullcheck(buffer);
+            nullcheck();
             FileHelper.close(buffer);
             buffer = null;
         }
         
         @Override
         public void flush() throws IOException {
-            nullcheck(buffer);
+            nullcheck();
         }
         
         @Override
         public void write(int b) throws IOException {
-            nullcheck(buffer);
+            nullcheck();
             try {
                 buffer.put( (byte)b );
             } catch(BufferUnderflowException | ReadOnlyBufferException cause) {
@@ -262,7 +288,7 @@ public final class BufferAdapter {
 
         @Override
         public void write(byte[] bytes, int offset, int len) throws IOException {
-            nullcheck(buffer);
+            nullcheck();
             try {
                 buffer.put(bytes, offset, len);
             } catch(BufferUnderflowException | ReadOnlyBufferException cause) {
@@ -270,6 +296,12 @@ public final class BufferAdapter {
             }
         }
         
+        private void nullcheck() throws IOException  {
+            if(null == buffer) {
+                throw new IOException("OutputStream is closed");
+            }
+        }
+        
     }
     
     private static class CReader extends Reader {
@@ -282,14 +314,14 @@ public final class BufferAdapter {
         
         @Override
         public void close() throws IOException {
-            nullcheck(buffer);
+            nullcheck();
             FileHelper.close(buffer);
             buffer = null;
         }
         
         @Override
         public int read() throws IOException {
-            nullcheck(buffer);
+            nullcheck();
             try {
                 return buffer.get();
             } catch(BufferUnderflowException cause) {
@@ -299,7 +331,7 @@ public final class BufferAdapter {
 
         @Override
         public int read(char[] target, int offset, int len) throws IOException {
-            nullcheck(buffer);
+            nullcheck();
             try {
                 buffer.get(target, offset, len);
                 return len;
@@ -310,11 +342,17 @@ public final class BufferAdapter {
 
         @Override
         public long skip(long count) throws IOException {
-            nullcheck(buffer);
+            nullcheck();
             int np = buffer.position()+(int)count;
             return np == buffer.position(np).position() ? count : 0;
         }
         
+        private void nullcheck() throws IOException  {
+            if(null == buffer) {
+                throw new IOException("Reader is closed");
+            }
+        }
+        
     }
     
     private static class CWriter extends Writer {
@@ -332,7 +370,7 @@ public final class BufferAdapter {
 
         @Override
         public void close() throws IOException {
-            nullcheck(buffer);
+            nullcheck();
             flush();
             FileHelper.close(buffer);
             buffer = null;
@@ -341,7 +379,7 @@ public final class BufferAdapter {
         @Override
         public Writer append(char c) throws IOException {
             try {
-                nullcheck(buffer);
+                nullcheck();
                 buffer.put(c);
                 return this;
             } catch(BufferUnderflowException | ReadOnlyBufferException cause) {
@@ -357,7 +395,7 @@ public final class BufferAdapter {
         
         @Override
         public Writer append(CharSequence value, int start, int end) throws IOException {
-            nullcheck(buffer);
+            nullcheck();
             CharSequence svalue = ValueUtils.or(value, "null");
             try {
                 for(int i=start; i<end; i++) {
@@ -376,14 +414,14 @@ public final class BufferAdapter {
         
         @Override
         public void write(char[] target) throws IOException {
-            nullcheck(buffer);
+            nullcheck();
             write(target, 0, target.length);
         }
         
         @Override
         public void write(char[] target, int offset, int len) throws IOException {
             try {
-                nullcheck(buffer);
+                nullcheck();
                 buffer.put(target, offset, len);
             } catch(BufferUnderflowException | ReadOnlyBufferException cause) {
                 throw new IOException(cause);
@@ -393,7 +431,7 @@ public final class BufferAdapter {
         @Override
         public void write(String value) throws IOException {
             try {
-                nullcheck(buffer);
+                nullcheck();
                 buffer.put(value);
             } catch(BufferUnderflowException | ReadOnlyBufferException cause) {
                 throw new IOException(cause);
@@ -403,13 +441,19 @@ public final class BufferAdapter {
         @Override
         public void write(String value, int offset, int len) throws IOException {
             try {
-                nullcheck(buffer);
+                nullcheck();
                 buffer.put(value, offset, offset+len);
             } catch(BufferUnderflowException | ReadOnlyBufferException cause) {
                 throw new IOException(cause);
             }
         }
         
+        private void nullcheck() throws IOException  {
+            if(null == buffer) {
+                throw new IOException("Writer is closed");
+            }
+        }
+        
     }
     
     private static class BWriter extends Writer {
@@ -424,7 +468,7 @@ public final class BufferAdapter {
         
         @Override
         public void close() throws IOException {
-            nullcheck(buffer);
+            nullcheck();
             flush();
             FileHelper.close(buffer);
             buffer = null;
@@ -437,9 +481,15 @@ public final class BufferAdapter {
         
         @Override
         public void write(char[] cbuf, int off, int len) throws IOException {
-            nullcheck(buffer);
+            nullcheck();
             buffer.put( charset.encode(CharBuffer.wrap(cbuf, off, len)) );
         }
+        
+        private void nullcheck() throws IOException  {
+            if(null == buffer) {
+                throw new IOException("Writer is closed");
+            }
+        }
 
     }
 }

+ 169 - 18
src/main/java/net/ranides/assira/nio/ChannelAdapter.java

@@ -6,13 +6,14 @@
  */
 package net.ranides.assira.nio;
 
+import java.io.EOFException;
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 import java.nio.ReadOnlyBufferException;
 import java.nio.channels.ClosedChannelException;
 import java.nio.channels.SeekableByteChannel;
 import net.ranides.assira.collection.ByteArray;
-import net.ranides.assira.text.Strings;
 
 /**
  *
@@ -25,7 +26,7 @@ public final class ChannelAdapter {
     }
     
     public static SeekableByteChannel wrap(byte[] input) {
-        // preferred than BufferAdapter because we can write transfers to/from array
+        // preferred to BufferAdapter because we can write transfers to/from array
         // in a bit more efficient way.
         return new AChannel(input);
     }
@@ -38,14 +39,18 @@ public final class ChannelAdapter {
         return new BChannel(input);
     }
     
+    /**
+     * read only
+     * @param input
+     * @return 
+     */
     public static SeekableByteChannel wrap(CharSequence input) {
-        return new CChannel(input);
+        return new CSChannel(input);
     }
     
     public static SeekableByteChannel wrap(char[] input) {
-        // in fact, we can't optimize that, because we have to 
-        // convert everything char-by-char anyway
-        return wrap(Strings.asSequence(input));
+        // preferred to CSChannel adapter because we want to support writes
+        return new CAChannel(input);
     }
 
     
@@ -97,9 +102,9 @@ public final class ChannelAdapter {
         public int write(ByteBuffer src) throws IOException {
             nullcheck(array);
             if( position >= limit) {
-                return -1;
+                throw new EOFException();
             }
-            int ret = src.remaining();
+            int ret = Math.min(src.remaining(), limit-position);
             int max = position + ret;
             for(int i=position; i<max; i++) {
                 array.set(i, src.get());
@@ -184,9 +189,9 @@ public final class ChannelAdapter {
         public int write(ByteBuffer src) throws IOException {
             nullcheck(array);
             if( position >= limit) {
-                return -1;
+                throw new EOFException();
             }
-            int ret = src.remaining();
+            int ret = Math.min(src.remaining(), limit-position);
             src.get(array, position, ret);
             position += ret;
             return ret;
@@ -228,13 +233,13 @@ public final class ChannelAdapter {
         
     }
     
-    private static class CChannel implements SeekableByteChannel {
+    private static class CSChannel implements SeekableByteChannel {
         
         private CharSequence chars;
         private int position;
         private int limit;
 
-        public CChannel(CharSequence chars) { // NOPMD w końcu to wrapper, nie?
+        public CSChannel(CharSequence chars) { // NOPMD w końcu to wrapper, nie?
             this.chars = chars;
             this.position = 0;
             this.limit = chars.length() * 2;
@@ -259,17 +264,18 @@ public final class ChannelAdapter {
             }
             int max = Math.min(limit, position+dst.remaining());
             int ret = max - position;
-            
+            if(0 == ret) {
+                return ret;
+            }
             if(1 == position % 2) {
-                dst.put((byte) (chars.charAt(position/2)>>8) );
+                dst.put( char1(position, dst.order()) );
+                position++;
             }
             for(int i=position/2, n=max/2; i<n; i++) {
-                char c = chars.charAt(i);
-                dst.put((byte)(c >> 8));
-                dst.put((byte)c);
+                dst.putChar( chars.charAt(i) );
             }
             if(1 == max % 2) {
-                dst.put((byte) chars.charAt(max/2) );
+                dst.put( char0(max, dst.order()) );
             }
 
             position = max;
@@ -315,6 +321,151 @@ public final class ChannelAdapter {
             return this;
         }
         
+        private byte char0(int index, ByteOrder order) {
+            char c = chars.charAt(index/2);
+            return (byte)(order != ByteOrder.BIG_ENDIAN ? c : c>>8);
+        }
+
+        private byte char1(int index, ByteOrder order) {
+            char c = chars.charAt(index/2);
+            return (byte)(order == ByteOrder.BIG_ENDIAN ? c : c>>8);
+        }
+        
+    }
+    
+    private static class CAChannel implements SeekableByteChannel {
+        
+        private char[] chars;
+        private int position;
+        private int limit;
+
+        public CAChannel(char[] chars) { // NOPMD w końcu to wrapper, nie?
+            this.chars = chars;
+            this.position = 0;
+            this.limit = chars.length * 2;
+        }
+        
+        @Override
+        public boolean isOpen() {
+            return chars != null;
+        }
+
+        @Override
+        public void close() throws IOException {
+            nullcheck(chars);
+            chars = null;
+        }
+
+        @Override
+        public int read(ByteBuffer dst) throws IOException {
+            nullcheck(chars);
+            if( position >= limit) {
+                return -1;
+            }
+            int max = Math.min(limit, position+dst.remaining());
+            int ret = max - position;
+            if(0 == ret) {
+                return ret;
+            }
+            if(1 == position % 2) {
+                dst.put( char1(position, dst.order()) );
+                position++;
+            }
+            for(int i=position/2, n=max/2; i<n; i++) {
+                dst.putChar( chars[i] );
+            }
+            if(1 == max % 2) {
+                dst.put( char0(max, dst.order()) );
+            }
+
+            position = max;
+            return ret;
+        }
+        
+        @Override
+        public int write(ByteBuffer src) throws IOException {
+            nullcheck(chars);
+            if( position >= limit) {
+                throw new EOFException();
+            }
+            int ret = Math.min(src.remaining(), limit-position);
+            if(0 == ret) {
+                return ret;
+            }
+            int max = position + ret;
+            if(1 == position %2) {
+                char1s(position, src.order(), src.get());
+                position++;
+            }
+            for(int i=position/2, n=max/2; i<n; i++) {
+                chars[i] = src.getChar();
+            }
+            if(1 == max % 2) {
+                char0s(max, src.order(), src.get());
+            }
+            
+            position = max;
+            return ret;
+        }
+
+        @Override
+        public long position() throws IOException {
+            nullcheck(chars);
+            return position;
+        }
+
+        @Override
+        public SeekableByteChannel position(long newPosition) throws IOException {
+            nullcheck(chars);
+            assert newPosition == (int)newPosition;
+            position = (int)newPosition;
+            return this;
+        }
+
+        @Override
+        public long size() throws IOException {
+            nullcheck(chars);
+            return limit;
+        }
+
+        @Override
+        public SeekableByteChannel truncate(long size) throws IOException {
+            nullcheck(chars);
+            assert size == (int)size;
+            if( size > limit) {
+                return this;
+            }
+            limit = (int)size;
+            if(position > limit) {
+                position = limit;
+            }
+            return this;
+        }
+        
+        private void char0s(int index, ByteOrder order, byte value) {
+            if( order != ByteOrder.BIG_ENDIAN ) {
+                chars[index/2] = (char)((chars[index/2] & 0xFF00) | (value & 0xFF));
+            } else {
+                chars[index/2] = (char)((chars[index/2] & 0x00FF) | ((value & 0xFF)<<8));
+            }
+        }
+
+        private void char1s(int index, ByteOrder order, byte value) {
+            if( order == ByteOrder.BIG_ENDIAN ) {
+                chars[index/2] = (char)((chars[index/2] & 0xFF00) | (value & 0xFF));
+            } else {
+                chars[index/2] = (char)((chars[index/2] & 0x00FF) | ((value & 0xFF)<<8));
+            }
+        }
+        
+        private byte char0(int index, ByteOrder order) {
+            return (byte)(order != ByteOrder.BIG_ENDIAN ? chars[index/2] : chars[index/2]>>8);
+        }
+
+        private byte char1(int index, ByteOrder order) {
+            return (byte)(order == ByteOrder.BIG_ENDIAN ? chars[index/2] : chars[index/2]>>8);
+        }
+        
     }
      
 }

+ 0 - 2
src/main/java/net/ranides/assira/nio/DataAdapter.java

@@ -6,14 +6,12 @@
  */
 package net.ranides.assira.nio;
 
-import java.io.Closeable;
 import java.io.DataInput;
 import java.io.DataOutput;
 import java.io.EOFException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.io.RandomAccessFile;
 import java.io.Reader;
 import java.io.Writer;
 import net.ranides.assira.generic.ValueUtils;

+ 22 - 3
src/main/java/net/ranides/assira/nio/Streamable.java

@@ -7,8 +7,27 @@
 package net.ranides.assira.nio;
 
 /**
- *
- * Accepted function arguments:
+ * Hipotetyczna hierarchia interfejsów:
+ *      IInput - wszystko dziedziczy po tym
+ *      IOutput - wszystko dziedziczy po tym
+ *      interfejsy miałby metodę #adapter(class)
+ *      która by robiła odpowiednie wrappery na Reader/Writer/Stream/DataInput/Channel/Buffer/itd...
+ * 
+ *      ByteOutput - seek/o
+ *      ByteInput - seek/i
+ *      ByteChannel = ByteOutput + ByteInput
+ * 
+ *      CharOutput - seek/o
+ *      CharInput - seek/i
+ *      CharChannel = CharOutput + CharInput
+ * 
+ *      DataOutput - seek/o all primitives
+ *      DataInput - seek/i all primitives
+ *      DataChannel = ByteChannel + CharChannel
+ * 
+ * To nigdy raczej nie zaistnieje, ale ... pomarzyć można ;)
+ * 
+ * <h3>Accepted function arguments:</h3>
  * <table valign="top">
  *      <tr><th>input</th><th>output</th><th></th></tr>
  *      <tr>
@@ -38,7 +57,7 @@ package net.ranides.assira.nio;
  *      </tr>
  * </table>
  * 
- * Produced return values:
+ * <h3>Produced return values:</h3>
  * <table valign="top">
  *      <tr><th>input</th><th>output</th><th></th></tr>
  *      <tr>

+ 171 - 0
src/test/java/net/ranides/assira/nio/ChannelAdapterTest.java

@@ -0,0 +1,171 @@
+/*
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/assira
+ */
+package net.ranides.assira.nio;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.SeekableByteChannel;
+import net.ranides.assira.collection.ArrayUtils;
+import net.ranides.assira.collection.ByteArray;
+import net.ranides.assira.text.Strings;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author Ranides Atterwim <ranides@gmail.com>
+ */
+public class ChannelAdapterTest {
+    
+    private static final String INPUT = 
+        "00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F " +
+        "10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F " +
+        "20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F " +
+        "30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F";
+    
+    private static final String EXPECTED = 
+        "00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F " +
+        "71 72 73 74 61 62 63 64 65 66 67 68 1C 1D 1E 1F " +
+        "91 92 93 94 95 96 97 98 99 9A 2A 2B 2C 2D 41 42 " +
+        "43 44 45 46 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F";
+    
+    private static final char[] INPUT_LE = {
+        0x0100, 0x0302, 0x0504, 0x0706, 0x0908, 0x0B0A, 0x0D0C, 0x0F0E,
+        0x1110, 0x1312, 0x1514, 0x1716, 0x1918, 0x1B1A, 0x1D1C, 0x1F1E,
+        0x2120, 0x2322, 0x2524, 0x2726, 0x2928, 0x2B2A, 0x2D2C, 0x2F2E,
+        0x3130, 0x3332, 0x3534, 0x3736, 0x3938, 0x3B3A, 0x3D3C, 0x3F3E,
+    };
+        
+    private static final char[] EXPECTED_LE = {
+        0x0100, 0x0302, 0x0504, 0x0706, 0x0908, 0x0B0A, 0x0D0C, 0x0F0E,
+        0x7271, 0x7473, 0x6261, 0x6463, 0x6665, 0x6867, 0x1D1C, 0x1F1E,
+        0x9291, 0x9493, 0x9695, 0x9897, 0x9A99, 0x2B2A, 0x2D2C, 0x4241,
+        0x4443, 0x4645, 0x3534, 0x3736, 0x3938, 0x3B3A, 0x3D3C, 0x3F3E,
+    };
+    
+    private static final char[] INPUT_BE = {
+        0x0001, 0x0203, 0x0405, 0x0607, 0x0809, 0x0A0B, 0x0C0D, 0x0E0F,
+        0x1011, 0x1213, 0x1415, 0x1617, 0x1819, 0x1A1B, 0x1C1D, 0x1E1F,
+        0x2021, 0x2223, 0x2425, 0x2627, 0x2829, 0x2A2B, 0x2C2D, 0x2E2F,
+        0x3031, 0x3233, 0x3435, 0x3637, 0x3839, 0x3A3B, 0x3C3D, 0x3E3F,
+    };
+        
+    private static final char[] EXPECTED_BE = {
+        0x0001, 0x0203, 0x0405, 0x0607, 0x0809, 0x0A0B, 0x0C0D, 0x0E0F,
+        0x7172, 0x7374, 0x6162, 0x6364, 0x6566, 0x6768, 0x1C1D, 0x1E1F,
+        0x9192, 0x9394, 0x9596, 0x9798, 0x999A, 0x2A2B, 0x2C2D, 0x4142,
+        0x4344, 0x4546, 0x3435, 0x3637, 0x3839, 0x3A3B, 0x3C3D, 0x3E3F,
+    };
+
+    @Test
+    public void testArrayWrapper() throws IOException {
+        byte[] data = NIOTester.array(INPUT);
+        SeekableByteChannel channel = ChannelAdapter.wrap(data);
+        NIOTester.testRWChannel(channel);
+        assertEquals(EXPECTED, NIOTester.hex(data));
+    }
+    
+    @Test
+    public void testByteBufferWrapper() throws IOException {
+        byte[] data = NIOTester.array(INPUT);
+        ByteBuffer buffer = ByteBuffer.wrap(data);
+        
+        SeekableByteChannel channel = ChannelAdapter.wrap(buffer);
+        NIOTester.testRWChannel(channel);
+        assertEquals(EXPECTED, NIOTester.hex(data));
+    }
+    
+    @Test
+    public void testByteArrayChannel() throws IOException {
+        byte[] data = NIOTester.array(INPUT);
+        ByteArray array = new ByteArray(data);
+        
+        SeekableByteChannel channel = ChannelAdapter.wrap(array);
+        NIOTester.testRWChannel(channel);
+        assertEquals(EXPECTED, NIOTester.hex(data));
+    }
+    
+    @Test
+    public void testByteArrayChannel2() throws IOException {
+        // we are using here ByteArray with "native" endianess - that means: LITTLE
+        char[] data = ArrayUtils.arraycopy(INPUT_LE);
+        ByteArray array = new ByteArray(data);
+        
+        SeekableByteChannel channel = ChannelAdapter.wrap(array);
+        NIOTester.testRWChannel(channel);
+        assertArrayEquals(EXPECTED_LE, data);
+    }
+    
+    @Test
+    public void testSequenceChannel() throws IOException {
+        // we are using here endianess defined in ByteBuffers - that means: BIG
+        char[] data = ArrayUtils.arraycopy(INPUT_BE);
+        CharSequence sequence = Strings.asSequence(data);
+        
+        SeekableByteChannel channel = ChannelAdapter.wrap(sequence);
+        NIOTester.testROChannel(channel);
+        // it's read-only buffer, so it shouldn't change anything
+        assertArrayEquals(INPUT_BE, data); 
+    }
+    
+    @Test
+    public void testSequenceChannelEndian() throws IOException {
+        char[] data = {0x1112, 0x1314, 0x1516, 0x1718 };
+        char[] expected = ArrayUtils.arraycopy(data);
+        CharSequence sequence = Strings.asSequence(data);
+        
+        SeekableByteChannel channel = ChannelAdapter.wrap(sequence);
+        NIOTester.testEndianReads(channel);
+        assertArrayEquals(expected, data);
+    }
+    
+    @Test
+    public void testCharsChannel() throws IOException {
+        // we are using here endianess defined in ByteBuffers - that means: BIG
+        char[] data = ArrayUtils.arraycopy(INPUT_BE);
+        
+        SeekableByteChannel channel = ChannelAdapter.wrap(data);
+        NIOTester.testRWChannel(channel);
+        assertArrayEquals(EXPECTED_BE, data);
+    }
+    
+    @Test
+    public void testCharsChannelEndian() throws IOException {
+        char[] data = { 0x1112, 0x1314, 0x1516, 0x1718, 0x2021, 0x2223 };
+        
+        SeekableByteChannel channel = ChannelAdapter.wrap(data);
+        NIOTester.testEndianReads(channel);
+        
+        ByteBuffer buffer1B = NIOTester.buffer("A1 A2 A3 A4 A5 A6 A7").order(ByteOrder.BIG_ENDIAN);
+        channel.position(0);
+        channel.write(buffer1B);
+        assertArrayEquals(new char[]{0xA1A2, 0xA3A4, 0xA5A6, 0xA718, 0x2021, 0x2223}, data);
+        
+        ByteBuffer buffer1L = NIOTester.buffer("B1 B2 B3 B4 B5 B6 B7").order(ByteOrder.LITTLE_ENDIAN);
+        channel.position(0);
+        channel.write(buffer1L);
+        assertArrayEquals(new char[]{0xB2B1, 0xB4B3, 0xB6B5, 0xA7B7, 0x2021, 0x2223}, data);
+        
+        
+        ByteBuffer buffer2B = NIOTester.buffer("C1 C2 C3 C4 C5 C6 C7").order(ByteOrder.BIG_ENDIAN);
+        channel.position(1);
+        channel.write(buffer2B);
+        assertArrayEquals(new char[]{0xB2C1, 0xC2C3, 0xC4C5, 0xC6C7, 0x2021, 0x2223}, data);
+        
+        ByteBuffer buffer2L = NIOTester.buffer("D1 D2 D3 D4 D5 D6 D7").order(ByteOrder.LITTLE_ENDIAN);
+        channel.position(1);
+        channel.write(buffer2L);
+        assertArrayEquals(new char[]{0xD1C1, 0xD3D2, 0xD5D4, 0xD7D6, 0x2021, 0x2223}, data);
+        
+    }
+    
+    
+    
+    
+    
+}

+ 2 - 2
src/test/java/net/ranides/assira/nio/DataStreamerTest.java

@@ -24,9 +24,9 @@ import static org.junit.Assert.*;
  *
  * @author Ranides Atterwim <ranides@gmail.com>
  */
-public class DataStreamerTest {
+public class DataAdapterTest {
     
-    public DataStreamerTest() {
+    public DataAdapterTest() {
     }
 
     @Test

+ 233 - 0
src/test/java/net/ranides/assira/nio/NIOTester.java

@@ -0,0 +1,233 @@
+/*
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/assira
+ */
+package net.ranides.assira.nio;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.SeekableByteChannel;
+import net.ranides.assira.text.StringEncoder;
+import net.ranides.assira.text.Strings;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ *
+ * @author Ranides Atterwim <ranides@gmail.com>
+ */
+public final class NIOTester {
+    
+    private NIOTester() {
+        // utility class
+    }
+    
+    public static void testRWChannel(SeekableByteChannel channel) throws IOException {
+        testRead(channel);
+        testPosition(channel);
+        testTruncate(channel);
+        testWrite(channel);
+        testWriteEOF(channel);
+        testReadEOF(channel);
+        testCloseR(channel);
+        testCloseW(channel);
+    }
+    
+    public static void testROChannel(SeekableByteChannel channel) throws IOException {
+        testRead(channel);
+        testPosition(channel);
+        testTruncate(channel);
+        testReadEOF(channel);
+        testCloseR(channel);
+    }
+    
+    public static void testEndianReads(SeekableByteChannel channel) throws IOException {
+        ByteBuffer buffer1B = ByteBuffer.allocate(7).order(ByteOrder.BIG_ENDIAN);
+        channel.position(0);
+        channel.read(buffer1B);
+        assertEquals("11 12 13 14 15 16 17",  hex(buffer1B));
+        
+        ByteBuffer buffer1L = ByteBuffer.allocate(7).order(ByteOrder.LITTLE_ENDIAN);
+        channel.position(0);
+        channel.read(buffer1L);
+        assertEquals("12 11 14 13 16 15 18",  hex(buffer1L));
+        
+        ByteBuffer buffer2B = ByteBuffer.allocate(7).order(ByteOrder.BIG_ENDIAN);
+        channel.position(1);
+        channel.read(buffer2B);
+        assertEquals("12 13 14 15 16 17 18",  hex(buffer2B));
+        
+        ByteBuffer buffer2L = ByteBuffer.allocate(7).order(ByteOrder.LITTLE_ENDIAN);
+        channel.position(1);
+        channel.read(buffer2L);
+        assertEquals("11 14 13 16 15 18 17",  hex(buffer2L));
+    }
+    
+    private static void testRead(SeekableByteChannel channel) throws IOException {
+        ByteBuffer buffer1 = ByteBuffer.allocate(7);
+        assertEquals(7, channel.read(buffer1));
+        assertEquals("00 01 02 03 04 05 06",  hex(buffer1));
+        assertEquals(0, channel.read(buffer1));
+        
+        ByteBuffer buffer2 = ByteBuffer.allocate(8);
+        assertEquals(8, channel.read(buffer2));
+        assertEquals("07 08 09 0A 0B 0C 0D 0E",  hex(buffer2));
+
+        ByteBuffer buffer3 = ByteBuffer.allocate(10);
+        buffer3.put(new byte[]{0x51, 0x52, 0x53, 0x54});
+        assertEquals(6, channel.read(buffer3));
+        assertEquals("51 52 53 54 0F 10 11 12 13 14",  hex(buffer3));
+        
+        ByteBuffer buffer4 = ByteBuffer.allocate(10);
+        buffer4.limit(8);
+        assertEquals(8, channel.read(buffer4));
+        assertEquals("15 16 17 18 19 1A 1B 1C 00 00",  hex(buffer4));
+    }
+    
+    private static void testPosition(SeekableByteChannel channel) throws IOException {
+        assertEquals(29, channel.position());
+        assertEquals(64, channel.size());
+        assertEquals(40, channel.position(40).position());
+        assertEquals(12, channel.position(12).position());
+        assertEquals(32, channel.position(32).position());
+        
+        ByteBuffer buffer5 = ByteBuffer.allocate(8);
+        assertEquals(8, channel.read(buffer5));
+        assertEquals("20 21 22 23 24 25 26 27",  hex(buffer5));
+        
+        assertTrue( channel.position(1024).position() >= 64 );
+        ByteBuffer buffer6 = ByteBuffer.allocate(8);
+        assertEquals(-1, channel.read(buffer6));
+        
+        assertEquals(48, channel.position(48).position() );
+        assertEquals(8, channel.read(buffer6));
+        assertEquals("30 31 32 33 34 35 36 37",  hex(buffer6));
+        
+        assertEquals(60, channel.position(60).position() );
+        ByteBuffer buffer7 = ByteBuffer.allocate(16);
+        assertEquals(4, channel.read(buffer7));
+    }
+
+    private static void testTruncate(SeekableByteChannel channel) throws IOException {
+        assertEquals(64, channel.position() );
+        channel.truncate(52);
+        assertEquals(52, channel.size());
+        assertEquals(52, channel.position());
+        channel.truncate(60);
+        assertEquals(52, channel.size());
+        assertEquals(52, channel.position());
+    }
+    
+    private static void testWrite(SeekableByteChannel channel) throws IOException {
+        assertEquals(16, channel.position(16).position());
+        ByteBuffer buffer8 = buffer("71 72 73 74");
+        assertEquals(4, channel.write(buffer8));
+        
+        ByteBuffer buffer9 = buffer("55 55 61 62 63 64 65 66 67 68 55 55");
+        buffer9.get(new byte[2]);
+        buffer9.limit(10);
+        assertEquals(8, channel.write(buffer9));
+        
+        ByteBuffer buffer11 = buffer("91 92 93 94 95");
+        ByteBuffer buffer12 = buffer("96 97 98 99 9A");
+        assertEquals(32, channel.position(32).position());
+        assertEquals(5, channel.write(buffer11));
+        assertEquals(5, channel.write(buffer12));
+
+        
+        ByteBuffer buffer10 = buffer("41 42 43 44 45 46 47 48");
+        assertEquals(46, channel.position(46).position());
+        assertEquals(6, channel.write(buffer10));
+        
+        try {
+            channel.write(buffer10);
+            fail("EOFException expected");
+        } catch(EOFException cause) {
+            assertTrue(true);
+        }
+    }
+    
+    private static void testWriteEOF(SeekableByteChannel channel) throws IOException {
+        assertTrue( channel.position(1024).position() >= 52 );
+        ByteBuffer buffer1 = ByteBuffer.allocate(32);
+        try {
+            channel.write(buffer1);
+            fail("EOFException expected");
+        } catch(EOFException cause) {
+            assertTrue(true);
+        }
+        assertEquals(-1, channel.read(buffer1));
+    }
+    
+    private static void testReadEOF(SeekableByteChannel channel) throws IOException {
+        assertTrue( channel.position(1024).position() >= 52 );
+        ByteBuffer buffer1 = ByteBuffer.allocate(32);
+        assertEquals(-1, channel.read(buffer1));
+    }
+    
+    private static void testCloseR(SeekableByteChannel channel) throws IOException {
+        assertEquals(16, channel.position(16).position());
+        assertTrue(channel.isOpen());
+        channel.close();
+        assertFalse(channel.isOpen());
+        
+        try {
+            channel.close();
+            fail("ClosedChannelException expected");
+        } catch(ClosedChannelException cause) {
+            assertTrue(true);
+        }
+        
+        try {
+            ByteBuffer buffer12 = ByteBuffer.allocate(8);
+            channel.read(buffer12);
+            fail("ClosedChannelException expected");
+        } catch(ClosedChannelException cause) {
+            assertTrue(true);
+        }
+    }
+    
+    private static void testCloseW(SeekableByteChannel channel) throws IOException {
+        assertFalse( channel.isOpen() );
+        try {
+            ByteBuffer buffer11 = ByteBuffer.allocate(8);
+            channel.write(buffer11);
+            fail("ClosedChannelException expected");
+        } catch(ClosedChannelException cause) {
+            assertTrue(true);
+        }
+    }
+    
+    public static String hex(ByteBuffer buffer) {
+        return hex(buffer.array());
+    }
+    
+    public static String hex(byte[] array) {
+        return StringEncoder.bytesToHex(array," ");
+    }
+    
+    public static ByteBuffer buffer(String values) {
+        return ByteBuffer.wrap(array(values));
+    }
+    
+    public static byte[] array(String values) {
+        return StringEncoder.hexToBytes(values.replaceAll(" ", ""));
+    }
+    
+    public static void hexdump(char[] data) {
+        for(int i=0; i<data.length; i++) {
+            System.out.printf("%04X ", (int)data[i]);
+            if(7 == i%8) {
+                System.out.println();
+            }
+        }
+        System.out.println("------------");
+    }
+}