From 00a9ee70054872712017b5a14b19aa92068c8420 Mon Sep 17 00:00:00 2001
From: Sven Gothel <sgothel@jausoft.com>
Date: Mon, 29 Sep 2014 03:57:30 +0200
Subject: Bug 1080 - Refine MappedByteBuffer*Stream impl. and API [doc], adding
 stream to stream copy as well as direct memory mapped ByteBuffer access

---
 .../jogamp/common/nio/ByteBufferInputStream.java   |  13 +-
 .../common/nio/MappedByteBufferInputStream.java    | 202 +++++++++++++++------
 .../common/nio/MappedByteBufferOutputStream.java   | 184 +++++++++++++++++--
 3 files changed, 321 insertions(+), 78 deletions(-)

(limited to 'src/java/com/jogamp/common/nio')

diff --git a/src/java/com/jogamp/common/nio/ByteBufferInputStream.java b/src/java/com/jogamp/common/nio/ByteBufferInputStream.java
index 5b6f121..b74360f 100644
--- a/src/java/com/jogamp/common/nio/ByteBufferInputStream.java
+++ b/src/java/com/jogamp/common/nio/ByteBufferInputStream.java
@@ -126,10 +126,14 @@ public class ByteBufferInputStream extends InputStream {
     public final int read(final byte[] b, final int off, final int len) {
         if (b == null) {
             throw new NullPointerException();
-        } else if (off < 0 || len < 0 || len > b.length - off) {
+        } else if( off < 0 ||
+                   len < 0 ||
+                   off > b.length ||
+                   off + len > b.length ||
+                   off + len < 0
+                 ) {
             throw new IndexOutOfBoundsException("offset "+off+", length "+len+", b.length "+b.length);
-        }
-        if ( 0 == len ) {
+        } else if ( 0 == len ) {
             return 0;
         }
         final int totalRem = buf.remaining();
@@ -152,8 +156,7 @@ public class ByteBufferInputStream extends InputStream {
             throw new NullPointerException();
         } else if (len < 0 || len > b.remaining()) {
             throw new IndexOutOfBoundsException("length "+len+", b "+b);
-        }
-        if ( 0 == len ) {
+        } else if ( 0 == len ) {
             return 0;
         }
         final int remaining = buf.remaining();
diff --git a/src/java/com/jogamp/common/nio/MappedByteBufferInputStream.java b/src/java/com/jogamp/common/nio/MappedByteBufferInputStream.java
index 67cbbbe..5f91f64 100644
--- a/src/java/com/jogamp/common/nio/MappedByteBufferInputStream.java
+++ b/src/java/com/jogamp/common/nio/MappedByteBufferInputStream.java
@@ -34,7 +34,6 @@ import java.io.RandomAccessFile;
 import java.lang.ref.WeakReference;
 import java.lang.reflect.Method;
 import java.nio.ByteBuffer;
-import java.nio.MappedByteBuffer;
 import java.nio.channels.FileChannel;
 import java.nio.channels.FileChannel.MapMode;
 import java.security.AccessController;
@@ -45,11 +44,15 @@ import jogamp.common.Debug;
 import com.jogamp.common.os.Platform;
 
 /**
- * An {@link InputStream} implementation based on an underlying {@link MappedByteBuffer}
- * supporting {@link #markSupported() mark}.
+ * An {@link InputStream} implementation based on an underlying {@link FileChannel}'s memory mapped {@link ByteBuffer},
+ * {@link #markSupported() supporting} {@link #mark(int) mark} and {@link #reset()}.
  * <p>
- * Intended to be utilized with a {@link MappedByteBuffer memory-mapped} {@link FileChannel#map(MapMode, long, long) FileChannel}
- * beyond its size limitation of {@link Integer#MAX_VALUE}.<br>
+ * Implementation allows full memory mapped {@link ByteBuffer} coverage via {@link FileChannel#map(MapMode, long, long) FileChannel}
+ * beyond its size limitation of {@link Integer#MAX_VALUE} utilizing an array of {@link ByteBuffer} slices.<br>
+ * </p>
+ * <p>
+ * Implementation further allows full random access via {@link #position()} and {@link #position(long)}
+ * and accessing the memory mapped {@link ByteBuffer} slices directly via {@link #currentSlice()} and {@link #nextSlice()}.
  * </p>
  * @since 2.3.0
  */
@@ -150,7 +153,7 @@ public class MappedByteBufferInputStream extends InputStream {
     private FileResizeOp fileResizeOp = NoFileResize;
 
     private int sliceCount;
-    ByteBuffer[] slices;
+    private ByteBuffer[] slices;
     private WeakReference<ByteBuffer>[] slices2GC;
     private long totalSize;
 
@@ -162,10 +165,10 @@ public class MappedByteBufferInputStream extends InputStream {
     private boolean hasCleaner;
     private CacheMode cmode;
 
-    int currSlice;
+    private int sliceIdx;
     private long mark;
 
-    public final void dbgDump(final String prefix, final PrintStream out) {
+    final void dbgDump(final String prefix, final PrintStream out) {
         long fcSz = 0, pos = 0, rem = 0;
         try {
             fcSz = fc.size();
@@ -184,12 +187,12 @@ public class MappedByteBufferInputStream extends InputStream {
         out.println(prefix+" refCount "+refCount+", fcSize "+fcSz+", totalSize "+totalSize);
         out.println(prefix+" position "+pos+", remaining "+rem);
         out.println(prefix+" mmode "+mmode+", cmode "+cmode+", fileResizeOp "+fileResizeOp);
-        out.println(prefix+" slice "+currSlice+" / "+sliceCount+" ("+sliceCount2+")");
+        out.println(prefix+" slice "+sliceIdx+" / "+sliceCount+" ("+sliceCount2+")");
         out.println(prefix+" sliceShift "+sliceShift+" -> "+(1L << sliceShift));
     }
 
     MappedByteBufferInputStream(final FileChannel fc, final FileChannel.MapMode mmode, final CacheMode cmode,
-                                final int sliceShift, final long totalSize, final int currSlice) throws IOException {
+                                final int sliceShift, final long totalSize, final int currSliceIdx) throws IOException {
         this.sliceShift = sliceShift;
         this.fc = fc;
         this.mmode = mmode;
@@ -207,16 +210,16 @@ public class MappedByteBufferInputStream extends InputStream {
         this.hasCleaner = false;
         this.cmode = cmode;
 
-        this.currSlice = currSlice;
+        this.sliceIdx = currSliceIdx;
         this.mark = -1;
 
-        slice(currSlice).position(0);
+        currentSlice().position(0);
     }
 
     /**
      * Creates a new instance using the given {@link FileChannel}.
      * <p>
-     * The {@link MappedByteBuffer} slices will be mapped lazily at first usage.
+     * The {@link ByteBuffer} slices will be mapped lazily at first usage.
      * </p>
      * @param fileChannel the file channel to be mapped lazily.
      * @param mmode the map mode, default is {@link FileChannel.MapMode#READ_ONLY}.
@@ -235,7 +238,7 @@ public class MappedByteBufferInputStream extends InputStream {
      * Creates a new instance using the given {@link FileChannel},
      * given mapping-mode, given cache-mode and the {@link #DEFAULT_SLICE_SHIFT}.
      * <p>
-     * The {@link MappedByteBuffer} slices will be mapped lazily at first usage.
+     * The {@link ByteBuffer} slices will be mapped lazily at first usage.
      * </p>
      * @param fileChannel the file channel to be used.
      * @param mmode the map mode, default is {@link FileChannel.MapMode#READ_ONLY}.
@@ -251,7 +254,7 @@ public class MappedByteBufferInputStream extends InputStream {
      * {@link FileChannel.MapMode#READ_ONLY read-only} mapping mode, {@link CacheMode#FLUSH_PRE_SOFT}
      * and the {@link #DEFAULT_SLICE_SHIFT}.
      * <p>
-     * The {@link MappedByteBuffer} slices will be mapped {@link FileChannel.MapMode#READ_ONLY} lazily at first usage.
+     * The {@link ByteBuffer} slices will be mapped {@link FileChannel.MapMode#READ_ONLY} lazily at first usage.
      * </p>
      * @param fileChannel the file channel to be used.
      * @throws IOException
@@ -279,12 +282,14 @@ public class MappedByteBufferInputStream extends InputStream {
                 }
                 fc.close();
                 mark = -1;
-                currSlice = -1;
+                sliceIdx = -1;
                 super.close();
             }
         }
     }
 
+    final FileChannel.MapMode getMapMode() { return mmode; }
+
     /**
      * @param fileResizeOp the new {@link FileResizeOp}.
      * @throws IllegalStateException if attempting to set the {@link FileResizeOp} to a different value than before
@@ -345,7 +350,7 @@ public class MappedByteBufferInputStream extends InputStream {
             sliceCount = 0;
             totalSize = 0;
             mark = -1;
-            currSlice = 0;
+            sliceIdx = 0;
         } else {
             final long prePosition = position();
 
@@ -353,7 +358,7 @@ public class MappedByteBufferInputStream extends InputStream {
             final int newSliceCount = (int)( ( newTotalSize + ( sliceSize - 1 ) ) / sliceSize );
             @SuppressWarnings("unchecked")
             final WeakReference<ByteBuffer>[] newSlices2GC = new WeakReference[ newSliceCount ];
-            final MappedByteBuffer[] newSlices = new MappedByteBuffer[ newSliceCount ];
+            final ByteBuffer[] newSlices = new ByteBuffer[ newSliceCount ];
             final int copySliceCount = Math.min(newSliceCount, sliceCount-1); // drop last (resize)
             if( 0 < copySliceCount ) {
                 System.arraycopy(slices2GC, 0, newSlices2GC, 0, copySliceCount);
@@ -399,46 +404,64 @@ public class MappedByteBufferInputStream extends InputStream {
     public final synchronized MappedByteBufferOutputStream getOutputStream(final FileResizeOp fileResizeOp)
             throws IllegalStateException, IOException
     {
-        if( FileChannel.MapMode.READ_ONLY == mmode ) {
-            throw new IOException("FileChannel map-mode is read-only");
-        }
         checkOpen();
-        setFileResizeOp(fileResizeOp);
+        final MappedByteBufferOutputStream res = new MappedByteBufferOutputStream(this, fileResizeOp);
         refCount++;
-        this.fileResizeOp = null != fileResizeOp ? fileResizeOp : NoFileResize;
-        return new MappedByteBufferOutputStream(this);
+        return res;
     }
 
-    final synchronized ByteBuffer slice(final int i) throws IOException {
-        if ( null != slices[i] ) {
-            return slices[i];
+    /**
+     * Return the mapped {@link ByteBuffer} slice at the current {@link #position()}.
+     * <p>
+     * Due to the nature of using sliced buffers mapping the whole region,
+     * user has to determine whether the returned buffer covers the desired region
+     * and may fetch the {@link #nextSlice()} until satisfied.<br>
+     * It is also possible to repeat this operation after reposition the stream via {@link #position(long)}
+     * or {@link #skip(long)} to a position within the next block, similar to {@link #nextSlice()}.
+     * </p>
+     * @throws IOException if a buffer slice operation failed.
+     */
+    public final synchronized ByteBuffer currentSlice() throws IOException {
+        if ( null != slices[sliceIdx] ) {
+            return slices[sliceIdx];
         } else {
             if( CacheMode.FLUSH_PRE_SOFT == cmode ) {
-                final WeakReference<ByteBuffer> ref = slices2GC[i];
+                final WeakReference<ByteBuffer> ref = slices2GC[sliceIdx];
                 if( null != ref ) {
                     final ByteBuffer mbb = ref.get();
-                    slices2GC[i] = null;
+                    slices2GC[sliceIdx] = null;
                     if( null != mbb ) {
-                        slices[i] = mbb;
+                        slices[sliceIdx] = mbb;
                         return mbb;
                     }
                 }
             }
-            final long pos = (long)i << sliceShift;
-            slices[i] = fc.map(mmode, pos, Math.min(1L << sliceShift, totalSize - pos));
-            return slices[i];
+            final long pos = (long)sliceIdx << sliceShift;
+            slices[sliceIdx] = fc.map(mmode, pos, Math.min(1L << sliceShift, totalSize - pos));
+            return slices[sliceIdx];
         }
     }
-    final synchronized boolean nextSlice() throws IOException {
-        if ( currSlice < sliceCount - 1 ) {
+
+    /**
+     * Return the <i>next</i> mapped {@link ByteBuffer} slice from the current {@link #position()},
+     * implicitly setting {@link #position(long)} to the start of the returned <i>next</i> slice,
+     * see {@link #currentSlice()}.
+     * <p>
+     * If no subsequent slice is available, {@code null} is being returned.
+     * </p>
+     * @throws IOException if a buffer slice operation failed.
+     */
+    public final synchronized ByteBuffer nextSlice() throws IOException {
+        if ( sliceIdx < sliceCount - 1 ) {
             if( CacheMode.FLUSH_NONE != cmode ) {
-                flushSlice(currSlice);
+                flushSlice(sliceIdx);
             }
-            currSlice++;
-            slice( currSlice ).position( 0 );
-            return true;
+            sliceIdx++;
+            final ByteBuffer slice = currentSlice();
+            slice.position( 0 );
+            return slice;
         } else {
-            return false;
+            return null;
         }
     }
 
@@ -573,7 +596,7 @@ public class MappedByteBufferInputStream extends InputStream {
     // @Override
     public final synchronized long position() throws IOException {
         if( 0 < refCount ) {
-            return ( (long)currSlice << sliceShift ) + slice( currSlice ).position();
+            return ( (long)sliceIdx << sliceShift ) + currentSlice().position();
         } else {
             return 0;
         }
@@ -594,9 +617,9 @@ public class MappedByteBufferInputStream extends InputStream {
         if ( totalSize < newPosition || 0 > newPosition ) {
             throw new IllegalArgumentException("new position "+newPosition+" not within [0.."+totalSize+"]");
         }
-        final int preSlice = currSlice;
+        final int preSlice = sliceIdx;
         positionImpl( newPosition );
-        if( CacheMode.FLUSH_NONE != cmode && preSlice != currSlice) {
+        if( CacheMode.FLUSH_NONE != cmode && preSlice != sliceIdx) {
             flushSlice(preSlice);
         }
         return this;
@@ -604,12 +627,12 @@ public class MappedByteBufferInputStream extends InputStream {
     private final synchronized void positionImpl( final long newPosition ) throws IOException {
         if ( totalSize == newPosition ) {
             // EOF, pos == maxPos + 1
-            currSlice = Math.max(0, sliceCount - 1); // handle zero size
-            final ByteBuffer s = slice( currSlice );
+            sliceIdx = Math.max(0, sliceCount - 1); // handle zero size
+            final ByteBuffer s = currentSlice();
             s.position( s.capacity() );
         } else {
-            currSlice = (int)( newPosition >>> sliceShift );
-            slice( currSlice ).position( (int)( newPosition - ( (long)currSlice << sliceShift ) ) );
+            sliceIdx = (int)( newPosition >>> sliceShift );
+            currentSlice().position( (int)( newPosition - ( (long)sliceIdx << sliceShift ) ) );
         }
     }
 
@@ -670,12 +693,13 @@ public class MappedByteBufferInputStream extends InputStream {
     @Override
     public final synchronized int read() throws IOException {
         checkOpen();
-        if ( ! slice( currSlice ).hasRemaining() ) {
-            if ( !nextSlice() ) {
+        ByteBuffer slice = currentSlice();
+        if ( !slice.hasRemaining() ) {
+            if ( null == ( slice = nextSlice() ) ) {
                 return -1;
             }
         }
-        return slices[ currSlice ].get() & 0xFF;
+        return slice.get() & 0xFF;
     }
 
     @Override
@@ -683,10 +707,54 @@ public class MappedByteBufferInputStream extends InputStream {
         checkOpen();
         if (b == null) {
             throw new NullPointerException();
-        } else if (off < 0 || len < 0 || len > b.length - off) {
+        } else if( off < 0 ||
+                   len < 0 ||
+                   off > b.length ||
+                   off + len > b.length ||
+                   off + len < 0
+                 ) {
             throw new IndexOutOfBoundsException("offset "+off+", length "+len+", b.length "+b.length);
+        } else if ( 0 == len ) {
+            return 0;
         }
-        if ( 0 == len ) {
+        final long totalRem = remaining();
+        if ( 0 == totalRem ) {
+            return -1;
+        }
+        final int maxLen = (int)Math.min( totalRem, len );
+        int read = 0;
+        while( read < maxLen ) {
+            ByteBuffer slice = currentSlice();
+            int currRem = slice.remaining();
+            if ( 0 == currRem ) {
+                if ( null == ( slice = nextSlice() ) ) {
+                    throw new InternalError("Unexpected EOT");
+                }
+                currRem = slice.remaining();
+            }
+            final int currLen = Math.min( maxLen - read, currRem );
+            slice.get( b, off + read, currLen );
+            read += currLen;
+        }
+        return maxLen;
+    }
+
+    /**
+     * Perform similar to {@link #read(byte[], int, int)}
+     * with {@link ByteBuffer} instead of byte array.
+     * @param b the {@link ByteBuffer} sink, data is written at current {@link ByteBuffer#position()}
+     * @param len the number of bytes to read
+     * @return the number of bytes read, -1 for EOS
+     * @throws IOException if a buffer slice operation failed or stream has been {@link #close() closed}.
+     */
+    // @Override
+    public final synchronized int read(final ByteBuffer b, final int len) throws IOException {
+        checkOpen();
+        if (b == null) {
+            throw new NullPointerException();
+        } else if (len < 0 || len > b.remaining()) {
+            throw new IndexOutOfBoundsException("length "+len+", b "+b);
+        } else if ( 0 == len ) {
             return 0;
         }
         final long totalRem = remaining();
@@ -696,15 +764,33 @@ public class MappedByteBufferInputStream extends InputStream {
         final int maxLen = (int)Math.min( totalRem, len );
         int read = 0;
         while( read < maxLen ) {
-            int currRem = slice( currSlice ).remaining();
+            ByteBuffer slice = currentSlice();
+            int currRem = slice.remaining();
             if ( 0 == currRem ) {
-                if ( !nextSlice() ) {
-                    throw new InternalError("XX");
+                if ( null == ( slice = nextSlice() ) ) {
+                    throw new InternalError("Unexpected EOT");
+                }
+                currRem = slice.remaining();
+            }
+            final int currLen = Math.min( maxLen - read, currRem );
+            if( slice.hasArray() && b.hasArray() ) {
+                System.arraycopy(slice.array(), slice.arrayOffset() + slice.position(),
+                                 b.array(), b.arrayOffset() + b.position(),
+                                 currLen);
+                slice.position( slice.position() + currLen );
+                b.position( b.position() + currLen );
+            } else if( currLen == currRem ) {
+                b.put(slice);
+            } else {
+                final int _limit = slice.limit();
+                slice.limit(currLen);
+                try {
+                    b.put(slice);
+                } finally {
+                    slice.limit(_limit);
                 }
-                currRem = slice( currSlice ).remaining();
             }
-            slices[ currSlice ].get( b, off + read, Math.min( maxLen - read, currRem ) );
-            read += Math.min( maxLen - read, currRem );
+            read += currLen;
         }
         return maxLen;
     }
diff --git a/src/java/com/jogamp/common/nio/MappedByteBufferOutputStream.java b/src/java/com/jogamp/common/nio/MappedByteBufferOutputStream.java
index 498e9f7..f84e6c2 100644
--- a/src/java/com/jogamp/common/nio/MappedByteBufferOutputStream.java
+++ b/src/java/com/jogamp/common/nio/MappedByteBufferOutputStream.java
@@ -29,12 +29,53 @@ package com.jogamp.common.nio;
 
 import java.io.IOException;
 import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileChannel.MapMode;
 
+import com.jogamp.common.nio.MappedByteBufferInputStream.CacheMode;
+import com.jogamp.common.nio.MappedByteBufferInputStream.FileResizeOp;
+
+/**
+ * An {@link OutputStream} implementation based on an underlying {@link FileChannel}'s memory mapped {@link ByteBuffer}.
+ * <p>
+ * Implementation is based on {@link MappedByteBufferInputStream}, using it as its parent instance.
+ * </p>
+ * <p>
+ * An instance maybe created via its parent {@link MappedByteBufferInputStream#getOutputStream(FileResizeOp)}
+ * or directly {@link #MappedByteBufferOutputStream(FileChannel, MapMode, CacheMode, int, FileResizeOp)}.
+ * </p>
+ * @since 2.3.0
+ */
 public class MappedByteBufferOutputStream extends OutputStream {
     private final MappedByteBufferInputStream parent;
 
-    MappedByteBufferOutputStream(final MappedByteBufferInputStream stream) throws IOException {
-        this.parent = stream;
+    MappedByteBufferOutputStream(final MappedByteBufferInputStream parent,
+                                 final FileResizeOp fileResizeOp) throws IOException {
+        if( FileChannel.MapMode.READ_ONLY == parent.getMapMode() ) {
+            throw new IOException("FileChannel map-mode is read-only");
+        }
+        this.parent = parent;
+        this.parent.setFileResizeOp(fileResizeOp);
+    }
+
+    /**
+     * Creates a new instance using the given {@link FileChannel}.
+     * <p>
+     * The {@link ByteBuffer} slices will be mapped lazily at first usage.
+     * </p>
+     * @param fileChannel the file channel to be mapped lazily.
+     * @param mmode the map mode, default is {@link FileChannel.MapMode#READ_WRITE}.
+     * @param cmode the caching mode, default is {@link MappedByteBufferInputStream.CacheMode#FLUSH_PRE_SOFT}.
+     * @param sliceShift the pow2 slice size, default is {@link MappedByteBufferInputStream#DEFAULT_SLICE_SHIFT}.
+     * @param fileResizeOp {@link MappedByteBufferInputStream.FileResizeOp} as described on {@link MappedByteBufferInputStream#setFileResizeOp(FileResizeOp)}.
+     * @throws IOException
+     */
+    public MappedByteBufferOutputStream(final FileChannel fileChannel,
+                                        final FileChannel.MapMode mmode,
+                                        final CacheMode cmode,
+                                        final int sliceShift, final FileResizeOp fileResizeOp) throws IOException {
+        this(new MappedByteBufferInputStream(fileChannel, mmode, cmode, sliceShift, fileChannel.size(), 0), fileResizeOp);
     }
 
     /**
@@ -103,16 +144,18 @@ public class MappedByteBufferOutputStream extends OutputStream {
         if ( totalRem < 1 ) { // grow if required
             parent.setLength( parent.length() + 1 );
         }
-        if ( ! parent.slice( parent.currSlice ).hasRemaining() ) {
-            if ( !parent.nextSlice() ) {
+        ByteBuffer slice = parent.currentSlice();
+        final int currRem = slice.remaining();
+        if ( 0 == currRem ) {
+            if ( null == ( slice = parent.nextSlice() ) ) {
                 if( MappedByteBufferInputStream.DEBUG ) {
-                    System.err.println("EOT write: "+parent.slices[ parent.currSlice ]);
+                    System.err.println("EOT write: "+parent.currentSlice());
                     parent.dbgDump("EOT write:", System.err);
                 }
-                throw new IOException("EOT");
+                throw new IOException("EOT"); // 'end-of-tape'
             }
         }
-        parent.slices[ parent.currSlice ].put( (byte)(b & 0xFF) );
+        slice.put( (byte)(b & 0xFF) );
     }
 
     @Override
@@ -121,8 +164,8 @@ public class MappedByteBufferOutputStream extends OutputStream {
         if (b == null) {
             throw new NullPointerException();
         } else if( off < 0 ||
-                   off > b.length ||
                    len < 0 ||
+                   off > b.length ||
                    off + len > b.length ||
                    off + len < 0
                  ) {
@@ -136,21 +179,132 @@ public class MappedByteBufferOutputStream extends OutputStream {
         }
         int written = 0;
         while( written < len ) {
-            int currRem = parent.slice( parent.currSlice ).remaining();
+            ByteBuffer slice = parent.currentSlice();
+            int currRem = slice.remaining();
             if ( 0 == currRem ) {
-                if ( !parent.nextSlice() ) {
+                if ( null == ( slice = parent.nextSlice() ) ) {
                     if( MappedByteBufferInputStream.DEBUG ) {
                         System.err.println("EOT write: offset "+off+", length "+len+", b.length "+b.length);
                         System.err.println("EOT write: written "+written+" / "+len+", currRem "+currRem);
-                        System.err.println("EOT write: "+parent.slices[ parent.currSlice ]);
+                        System.err.println("EOT write: "+parent.currentSlice());
                         parent.dbgDump("EOT write:", System.err);
                     }
-                    throw new InternalError("EOT");
+                    throw new InternalError("EOT"); // 'end-of-tape'
                 }
-                currRem = parent.slice( parent.currSlice ).remaining();
+                currRem = slice.remaining();
+            }
+            final int currLen = Math.min( len - written, currRem );
+            slice.put( b, off + written, currLen );
+            written += currLen;
+        }
+    }
+
+    /**
+     * Perform similar to {@link #write(byte[], int, int)}
+     * with {@link ByteBuffer} instead of byte array.
+     * @param b the {@link ByteBuffer} source, data is read from current {@link ByteBuffer#position()}
+     * @param len the number of bytes to write
+     * @throws IOException if a buffer slice operation failed or stream has been {@link #close() closed}.
+     */
+    // @Override
+    public final synchronized void write(final ByteBuffer b, final int len) throws IOException {
+        parent.checkOpen();
+        if (b == null) {
+            throw new NullPointerException();
+        } else if (len < 0 || len > b.remaining()) {
+            throw new IndexOutOfBoundsException("length "+len+", b "+b);
+        } else if( 0 == len ) {
+            return;
+        }
+        final long totalRem = parent.remaining();
+        if ( totalRem < len ) { // grow if required
+            parent.setLength( parent.length() + len - totalRem );
+        }
+        int written = 0;
+        while( written < len ) {
+            ByteBuffer slice = parent.currentSlice();
+            int currRem = slice.remaining();
+            if ( 0 == currRem ) {
+                if ( null == ( slice = parent.nextSlice() ) ) {
+                    if( MappedByteBufferInputStream.DEBUG ) {
+                        System.err.println("EOT write: length "+len+", b "+b);
+                        System.err.println("EOT write: written "+written+" / "+len+", currRem "+currRem);
+                        System.err.println("EOT write: "+parent.currentSlice());
+                        parent.dbgDump("EOT write:", System.err);
+                    }
+                    throw new InternalError("EOT"); // 'end-of-tape'
+                }
+                currRem = slice.remaining();
+            }
+            final int currLen = Math.min( len - written, currRem );
+
+            if( slice.hasArray() && b.hasArray() ) {
+                System.arraycopy(b.array(), b.arrayOffset() + b.position(),
+                                 slice.array(), slice.arrayOffset() + slice.position(),
+                                 currLen);
+                b.position( b.position() + currLen );
+                slice.position( slice.position() + currLen );
+            } else if( currLen == currRem ) {
+                slice.put(b);
+            } else {
+                final int _limit = b.limit();
+                b.limit(currLen);
+                try {
+                    slice.put(b);
+                } finally {
+                    b.limit(_limit);
+                }
+            }
+            written += currLen;
+        }
+    }
+
+    /**
+     * Perform similar to {@link #write(ByteBuffer, int)}
+     * with {@link MappedByteBufferInputStream} instead of byte array.
+     * <p>
+     * Method directly copies memory mapped {@link ByteBuffer}'ed data
+     * from the given input stream to this stream without extra data copy.
+     * </p>
+     * @param b the {@link ByteBuffer} source, data is read from current {@link MappedByteBufferInputStream#position()}
+     * @param len the number of bytes to write
+     * @throws IOException if a buffer slice operation failed or stream has been {@link #close() closed}.
+     */
+    // @Override
+    public final synchronized void write(final MappedByteBufferInputStream b, final long len) throws IOException {
+        parent.checkOpen();
+        if (b == null) {
+            throw new NullPointerException();
+        } else if (len < 0 || len > b.remaining()) {
+            throw new IndexOutOfBoundsException("length "+len+", b "+b);
+        } else if( 0 == len ) {
+            return;
+        }
+        final long totalRem = parent.remaining();
+        if ( totalRem < len ) { // grow if required
+            parent.setLength( parent.length() + len - totalRem );
+        }
+        long written = 0;
+        while( written < len ) {
+            ByteBuffer slice = parent.currentSlice();
+            int currRem = slice.remaining();
+            if ( 0 == currRem ) {
+                if ( null == ( slice = parent.nextSlice() ) ) {
+                    if( MappedByteBufferInputStream.DEBUG ) {
+                        System.err.println("EOT write: length "+len+", b "+b);
+                        System.err.println("EOT write: written "+written+" / "+len+", currRem "+currRem);
+                        System.err.println("EOT write: "+parent.currentSlice());
+                        parent.dbgDump("EOT write:", System.err);
+                    }
+                    throw new InternalError("EOT"); // 'end-of-tape'
+                }
+                currRem = slice.remaining();
+            }
+            final int currLen = b.read(slice, (int)Math.min( len - written, currRem ));
+            if( 0 > currLen ) {
+                throw new InternalError("Unexpected InputStream EOT"); // 'end-of-tape'
             }
-            parent.slices[ parent.currSlice ].put( b, off + written, Math.min( len - written, currRem ) );
-            written += Math.min( len - written, currRem );
+            written += currLen;
         }
     }
 }
-- 
cgit v1.2.3