Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
   /*
    ***** BEGIN LICENSE BLOCK *****
    * Version: CPL 1.0/GPL 2.0/LGPL 2.1
    *
    * The contents of this file are subject to the Common Public
    * License Version 1.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.eclipse.org/legal/cpl-v10.html
    *
   * Software distributed under the License is distributed on an "AS
   * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
   * implied. See the License for the specific language governing
   * rights and limitations under the License.
   *
   * Copyright (C) 2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
   * Copyright (C) 2004-2005 Thomas E Enebo <enebo@acm.org>
   * Copyright (C) 2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
   * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
   * Copyright (C) 2005 Charles O Nutter <headius@headius.com>
   * Copyright (C) 2007 Damian Steer <pldms@mac.com>
   *
   * Alternatively, the contents of this file may be used under the terms of
   * either of the GNU General Public License Version 2 or later (the "GPL"),
   * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
   * in which case the provisions of the GPL or the LGPL are applicable instead
   * of those above. If you wish to allow use of your version of this file only
   * under the terms of either the GPL or the LGPL, and not to allow others to
   * use your version of this file under the terms of the CPL, indicate your
   * decision by deleting the provisions above and replace them with the notice
   * and other provisions required by the GPL or the LGPL. If you do not delete
   * the provisions above, a recipient may use your version of this file under
   * the terms of any one of the CPL, the GPL or the LGPL.
   ***** END LICENSE BLOCK *****/
  package org.jruby.util.io;
  
  
  
  import org.jruby.Ruby;
  
This file implements a seekable IO file.
  
  public class ChannelStream implements StreamFinalizable {
  
      private static final Logger LOG = LoggerFactory.getLogger("ChannelStream");
  
      private final static boolean DEBUG = false;

    
The size of the read/write buffer allocated for this stream. This size has been scaled back from its original 16k because although the larger buffer size results in raw File.open times being rather slow (due to the cost of instantiating a relatively large buffer). We should try to find a happy medium, or potentially pool buffers, or perhaps even choose a value based on platform(??), but for now I am reducing it along with changes for the "large read" patch from JRUBY-2657.
  
      public final static int BUFSIZE = 4 * 1024;

    
The size at which a single read should turn into a chunkier bulk read. Currently, this size is about 4x a normal buffer size. This size was not really arrived at experimentally, and could potentially be increased. However, it seems like a "good size" and we should probably only adjust it if it turns out we would perform better with a larger buffer for large bulk reads.
  
      private final static int BULK_READ_SIZE = 16 * 1024;
      private final static ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);

    
A cached EOFException. Since EOFException is only used by us internally, we create a single instance to avoid stack trace generation. Comment out the initialization of this field to cause a new one each time.
  
      private static EOFException eofException = new EOFException();
  
     private volatile Ruby runtime;
     protected ModeFlags modes;
     protected boolean sync = false;
 
     protected volatile ByteBuffer buffer// r/w buffer
     protected boolean reading// are we reading or writing?
     private ChannelDescriptor descriptor;
     private boolean blocking = true;
     protected int ungotc = -1;
     private volatile boolean closedExplicitly = false;
 
     private volatile boolean eof = false;
     private volatile boolean autoclose = true;
 
     private ChannelStream(Ruby runtimeChannelDescriptor descriptorboolean autoclose) {
         this. = runtime;
         this. = descriptor;
         this. = descriptor.getOriginalModes();
          = ByteBuffer.allocate();
         .flip();
         this. = true;
         this. = autoclose;
         runtime.addInternalFinalizer(this);
     }
 
     private ChannelStream(Ruby runtimeChannelDescriptor descriptorModeFlags modesboolean autoclose) {
         this(runtimedescriptorautoclose);
         this. = modes;
     }
 
     public Ruby getRuntime() {
         return ;
     }
 
     public void checkReadable() throws IOException {
         if (!.isReadable()) throw new IOException("not opened for reading");
     }
 
     public void checkWritable() throws IOException {
         if (!.isWritable()) throw new IOException("not opened for writing");
     }
 
     public void checkPermissionsSubsetOf(ModeFlags subsetModes) {
         subsetModes.isSubsetOf();
     }
 
     public ModeFlags getModes() {
     	return ;
     }
 
     public boolean isSync() {
         return ;
     }
 
     public void setSync(boolean sync) {
         this. = sync;
     }
 
     public void setBinmode() {
         // No-op here, no binmode handling needed.
     }
     
     public boolean isBinmode() {
         return false;
     }
 
     public boolean isAutoclose() {
         return ;
     }
 
     public void setAutoclose(boolean autoclose) {
         this. = autoclose;
     }

    
Implement IO#wait as per io/wait in MRI. waits until input available or timed out and returns self, or nil when EOF reached. The default implementation loops while ready returns 0.
 
     public void waitUntilReady() throws IOExceptionInterruptedException {
         while (ready() == 0) {
             Thread.sleep(10);
         }
     }
 
     public boolean readDataBuffered() {
         return  && ( != -1 || .hasRemaining());
     }
 
     public boolean writeDataBuffered() {
         return ! && .position() > 0;
     }
 
     private final int refillBuffer() throws IOException {
         .clear();
         int n = ((ReadableByteChannel.getChannel()).read();
         .flip();
         return n;
     }
     public synchronized ByteList fgets(ByteList separatorStringthrows IOExceptionBadDescriptorException {
         checkReadable();
         ensureRead();
 
         if (separatorString == null) {
             return readall();
         }
 
         final ByteList separator = (separatorString == ) ?
              : separatorString;
 
         .checkOpen();
 
         if (feof()) {
             return null;
         }
 
         int c = read();
 
         if (c == -1) {
             return null;
         }
 
         // unread back
         .position(.position() - 1);
 
         ByteList buf = new ByteList(40);
 
         byte first = separator.getUnsafeBytes()[separator.getBegin()];
 
         LineLoop : while (true) {
             ReadLoop: while (true) {
                 byte[] bytes = .array();
                 int offset = .position();
                 int max = .limit();
 
                 // iterate over remainder of buffer until we find a match
                 for (int i = offseti < maxi++) {
                     c = bytes[i];
                     if (c == first) {
                         // terminate and advance buffer when we find our char
                         buf.append(bytesoffseti - offset);
                         if (i >= max) {
                             .clear();
                         } else {
                             .position(i + 1);
                         }
                         break ReadLoop;
                     }
                 }
 
                 // no match, append remainder of buffer and continue with next block
                 buf.append(bytesoffset.remaining());
                 int read = refillBuffer();
                 if (read == -1) break LineLoop;
             }
 
             // found a match above, check if remaining separator characters match, appending as we go
             for (int i = 0; i < separator.getRealSize(); i++) {
                 if (c == -1) {
                     break LineLoop;
                 } else if (c != separator.getUnsafeBytes()[separator.getBegin() + i]) {
                     buf.append(c);
                     continue LineLoop;
                 }
                 buf.append(c);
                 if (i < separator.getRealSize() - 1) {
                     c = read();
                 }
             }
             break;
         }
 
         if (separatorString == ) {
             while (c == separator.getUnsafeBytes()[separator.getBegin()]) {
                 c = read();
             }
             ungetc(c);
         }
 
         return buf;
     }
 
     public synchronized int getline(ByteList dstbyte terminatorthrows IOExceptionBadDescriptorException {
         checkReadable();
         ensureRead();
         .checkOpen();
 
         int totalRead = 0;
         boolean found = false;
         if ( != -1) {
             dst.append((byte);
             found =  == terminator;
              = -1;
             ++totalRead;
         }
         while (!found) {
             final byte[] bytes = .array();
             final int begin = .arrayOffset() + .position();
             final int end = begin + .remaining();
             int len = 0;
             for (int i = begini < end && !found; ++i) {
                 found = bytes[i] == terminator;
                 ++len;
             }
             if (len > 0) {
                 dst.append(len);
                 totalRead += len;
             }
             if (!found) {
                 int n = refillBuffer();
                 if (n <= 0) {
                     if (n < 0 && totalRead < 1) {
                         return -1;
                     }
                     break;
                 }
             }
         }
         return totalRead;
     }
 
     public synchronized int getline(ByteList dstbyte terminatorlong limitthrows IOExceptionBadDescriptorException {
         checkReadable();
         ensureRead();
         .checkOpen();
 
         int totalRead = 0;
         boolean found = false;
         if ( != -1) {
             dst.append((byte);
             found =  == terminator;
              = -1;
             limit--;
             ++totalRead;
         }
         while (!found) {
             final byte[] bytes = .array();
             final int begin = .arrayOffset() + .position();
             final int end = begin + .remaining();
             int len = 0;
             for (int i = begini < end && limit-- > 0 && !found; ++i) {
                 found = bytes[i] == terminator;
                 ++len;
             }
             if (limit < 1) found = true;
 
             if (len > 0) {
                 dst.append(len);
                 totalRead += len;
             }
             if (!found) {
                 int n = refillBuffer();
                 if (n <= 0) {
                     if (n < 0 && totalRead < 1) {
                         return -1;
                     }
                     break;
                 }
             }
         }
         return totalRead;
     }

    

Deprecated:
readall do busy loop for the IO which has NONBLOCK bit. You should implement the logic by yourself with fread().
 
     @Deprecated
     public synchronized ByteList readall() throws IOExceptionBadDescriptorException {
         final long fileSize = .isSeekable() && .getChannel() instanceof FileChannel
                 ? ((FileChannel.getChannel()).size() : 0;
         //
         // Check file size - special files in /proc have zero size and need to be
         // handled by the generic read path.
         //
         if (fileSize > 0) {
             ensureRead();
 
             FileChannel channel = (FileChannel).getChannel();
             final long left = fileSize - channel.position() + bufferedInputBytesRemaining();
             if (left <= 0) {
                  = true;
                 return null;
             }
 
             if (left > .) {
                 if (getRuntime() != null) {
                     throw getRuntime().newIOError("File too large");
                 } else {
                     throw new IOException("File too large");
                 }
             }
 
             ByteList result = new ByteList((intleft);
             ByteBuffer buf = ByteBuffer.wrap(result.getUnsafeBytes(),
                     result.begin(), (intleft);
 
             //
             // Copy any buffered data (including ungetc byte)
             //
             copyBufferedBytes(buf);
 
             //
             // Now read unbuffered directly from the file
             //
             while (buf.hasRemaining()) {
                 final int MAX_READ_CHUNK = 1 * 1024 * 1024;
                 //
                 // When reading into a heap buffer, the jvm allocates a temporary
                 // direct ByteBuffer of the requested size.  To avoid allocating
                 // a huge direct buffer when doing ludicrous reads (e.g. 1G or more)
                 // we split the read up into chunks of no more than 1M
                 //
                 ByteBuffer tmp = buf.duplicate();
                 if (tmp.remaining() > MAX_READ_CHUNK) {
                     tmp.limit(tmp.position() + MAX_READ_CHUNK);
                 }
                 int n = channel.read(tmp);
                 if (n <= 0) {
                     break;
                 }
                 buf.position(tmp.position());
             }
              = true;
             result.length(buf.position());
             return result;
         } else if (.isNull()) {
             return new ByteList(0);
         } else {
             checkReadable();
 
             ByteList byteList = new ByteList();
             ByteList read = fread();
 
             if (read == null) {
                  = true;
                 return byteList;
             }
 
             while (read != null) {
                 byteList.append(read);
                 read = fread();
             }
 
             return byteList;
         }
     }

    
Copies bytes from the channel buffer into a destination ByteBuffer

Parameters:
dst A ByteBuffer to place the data in.
Returns:
The number of bytes copied.
 
     private final int copyBufferedBytes(ByteBuffer dst) {
         final int bytesToCopy = dst.remaining();
 
         if ( != -1 && dst.hasRemaining()) {
             dst.put((byte);
              = -1;
         }
 
         if (.hasRemaining() && dst.hasRemaining()) {
 
             if (dst.remaining() >= .remaining()) {
                 //
                 // Copy out any buffered bytes
                 //
                 dst.put();
 
             } else {
                 //
                 // Need to clamp source (buffer) size to avoid overrun
                 //
                 ByteBuffer tmp = .duplicate();
                 tmp.limit(tmp.position() + dst.remaining());
                 dst.put(tmp);
                 .position(tmp.position());
             }
         }
 
         return bytesToCopy - dst.remaining();
     }

    
Copies bytes from the channel buffer into a destination ByteBuffer

Parameters:
dst A ByteBuffer to place the data in.
Returns:
The number of bytes copied.
 
     private final int copyBufferedBytes(byte[] dstint offint len) {
         int bytesCopied = 0;
 
         if ( != -1 && len > 0) {
             dst[off++] = (byte;
              = -1;
             ++bytesCopied;
         }
 
         final int n = Math.min(len - bytesCopied.remaining());
         .get(dstoffn);
         bytesCopied += n;
 
         return bytesCopied;
     }

    
Copies bytes from the channel buffer into a destination ByteBuffer

Parameters:
dst A ByteList to place the data in.
len The maximum number of bytes to copy.
Returns:
The number of bytes copied.
 
     private final int copyBufferedBytes(ByteList dstint len) {
         int bytesCopied = 0;
 
         dst.ensure(Math.min(lenbufferedInputBytesRemaining()));
 
         if (bytesCopied < len &&  != -1) {
             ++bytesCopied;
             dst.append((byte);
              = -1;
         }
 
         //
         // Copy out any buffered bytes
         //
         if (bytesCopied < len && .hasRemaining()) {
             int n = Math.min(.remaining(), len - bytesCopied);
             dst.append(n);
             bytesCopied += n;
         }
 
         return bytesCopied;
     }

    
Returns a count of how many bytes are available in the read buffer

Returns:
The number of bytes that can be read without reading the underlying stream.
 
     private final int bufferedInputBytesRemaining() {
         return  ? (.remaining() + ( != -1 ? 1 : 0)) : 0;
     }

    
Tests if there are bytes remaining in the read buffer.

Returns:
true if there are bytes available in the read buffer.
 
     private final boolean hasBufferedInputBytes() {
         return  && (.hasRemaining() ||  != -1);
     }

    
Returns a count of how many bytes of space is available in the write buffer.

Returns:
The number of bytes that can be written to the buffer without flushing to the underlying stream.
 
     private final int bufferedOutputSpaceRemaining() {
         return ! ? .remaining() : 0;
     }

    
Tests if there is space available in the write buffer.

Returns:
true if there are bytes available in the write buffer.
 
     private final boolean hasBufferedOutputSpace() {
         return ! && .hasRemaining();
     }

    
Closes IO handler resources.

 
     public void fclose() throws IOExceptionBadDescriptorException {
         try {
             synchronized (this) {
                  = true;
                 close(); // not closing from finalize
             }
         } finally {
             Ruby localRuntime = getRuntime();
 
             // Make sure we remove finalizers while not holding self lock,
             // otherwise there is a possibility for a deadlock!
             if (localRuntime != nulllocalRuntime.removeInternalFinalizer(this);
 
             // clear runtime so it doesn't get stuck in memory (JRUBY-2933)
              = null;
         }
     }

    
 
     private void close() throws IOExceptionBadDescriptorException {
         // finish and close ourselves
         finish(true);
     }
 
     private void finish(boolean closethrows BadDescriptorExceptionIOException {
         try {
             flushWrite();
 
             if (.info("Descriptor for fileno {} closed by stream".getFileno());
         } finally {
              = ;
 
             // clear runtime so it doesn't get stuck in memory (JRUBY-2933)
              = null;
 
             // finish descriptor
             .finish(close);
         }
     }

    
 
     public synchronized int fflush() throws IOExceptionBadDescriptorException {
         checkWritable();
         try {
             flushWrite();
         } catch (EOFException eofe) {
             return -1;
         }
         return 0;
     }

    
Flush the write buffer to the channel (if needed)

 
     private void flushWrite() throws IOExceptionBadDescriptorException {
         if ( || !.isWritable() || .position() == 0) return// Don't bother
 
         int len = .position();
         .flip();
         int n = .write();
 
         if(n != len) {
             // TODO: check the return value here
         }
         .clear();
     }

    
Flush the write buffer to the channel (if needed)

 
     private boolean flushWrite(final boolean blockthrows IOExceptionBadDescriptorException {
         if ( || !.isWritable() || .position() == 0) return false// Don't bother
         int len = .position();
         int nWritten = 0;
         .flip();
 
         // For Sockets, only write as much as will fit.
         if (.getChannel() instanceof SelectableChannel) {
             SelectableChannel selectableChannel = (SelectableChannel).getChannel();
             synchronized (selectableChannel.blockingLock()) {
                 boolean oldBlocking = selectableChannel.isBlocking();
                 try {
                     if (oldBlocking != block) {
                         selectableChannel.configureBlocking(block);
                     }
                     nWritten = .write();
                 } finally {
                     if (oldBlocking != block) {
                         selectableChannel.configureBlocking(oldBlocking);
                     }
                 }
             }
         } else {
             nWritten = .write();
         }
         if (nWritten != len) {
             .compact();
             return false;
         }
         .clear();
         return true;
     }
 
     public InputStream newInputStream() {
         InputStream in = .getBaseInputStream();
         return in == null ? new InputStreamAdapter(this) : in;
     }
 
     public OutputStream newOutputStream() {
         return new OutputStreamAdapter(this);
     }
 
     public void clearerr() {
          = false;
     }

    
 
     public boolean feof() throws IOExceptionBadDescriptorException {
         checkReadable();
 
         if () {
             return true;
         } else {
             return false;
         }
     }

    
 
     public synchronized long fgetpos() throws IOExceptionPipeExceptionInvalidValueExceptionBadDescriptorException {
         // Correct position for read / write buffering (we could invalidate, but expensive)
         if (.isSeekable()) {
             FileChannel fileChannel = (FileChannel).getChannel();
             long pos = fileChannel.position();
             // Adjust for buffered data
             if () {
                 pos -= .remaining();
                 return pos - (pos > 0 &&  != -1 ? 1 : 0);
             } else {
                 return pos + .position();
             }
         } else if (.isNull()) {
             return 0;
         } else {
             throw new PipeException();
         }
     }

    
Implementation of libc "lseek", which seeks on seekable streams, raises EPIPE if the fd is assocated with a pipe, socket, or FIFO, and doesn't do anything for other cases (like stdio).

 
     public synchronized void lseek(long offsetint typethrows IOExceptionInvalidValueExceptionPipeExceptionBadDescriptorException {
         if (.isSeekable()) {
             FileChannel fileChannel = (FileChannel).getChannel();
              = -1;
             int adj = 0;
             if () {
                 // for SEEK_CUR, need to adjust for buffered data
                 adj = .remaining();
                 .clear();
                 .flip();
             } else {
                 flushWrite();
             }
             try {
                 switch (type) {
                 case :
                     fileChannel.position(offset);
                     break;
                 case :
                     fileChannel.position(fileChannel.position() - adj + offset);
                     break;
                 case :
                     fileChannel.position(fileChannel.size() + offset);
                     break;
                 }
             } catch (IllegalArgumentException e) {
                 throw new InvalidValueException();
             } catch (IOException ioe) {
                 throw ioe;
             }
         } else if (.getChannel() instanceof SelectableChannel) {
             // TODO: It's perhaps just a coincidence that all the channels for
             // which we should raise are instanceof SelectableChannel, since
             // stdio is not...so this bothers me slightly. -CON
             throw new PipeException();
         } else {
         }
     }
 
     public synchronized void sync() throws IOExceptionBadDescriptorException {
         flushWrite();
     }

    
Ensure buffer is ready for reading, flushing remaining writes if required

 
     private void ensureRead() throws IOExceptionBadDescriptorException {
         if (return;
         flushWrite();
         .clear();
         .flip();
          = true;
     }

    
Ensure buffer is ready for reading, flushing remaining writes if required

 
     private void ensureReadNonBuffered() throws IOExceptionBadDescriptorException {
         if () {
             if (.hasRemaining()) {
                 Ruby localRuntime = getRuntime();
                 if (localRuntime != null) {
                     throw localRuntime.newIOError("sysread for buffered IO");
                 } else {
                     throw new IOException("sysread for buffered IO");
                 }
             }
         } else {
             // libc flushes writes on any read from the actual file, so we flush here
             flushWrite();
             .clear();
             .flip();
              = true;
         }
     }
 
     private void resetForWrite() throws IOException {
         if (.isSeekable()) {
             FileChannel fileChannel = (FileChannel).getChannel();
             if (.hasRemaining()) { // we have read ahead, and need to back up
                 fileChannel.position(fileChannel.position() - .remaining());
             }
         }
         // FIXME: Clearing read buffer here...is this appropriate?
         .clear();
          = false;
     }

    
Ensure buffer is ready for writing.

 
     private void ensureWrite() throws IOException {
         if (!return;
         resetForWrite();
     }
 
     public synchronized ByteList read(int numberthrows IOExceptionBadDescriptorException {
         checkReadable();
         ensureReadNonBuffered();
 
         ByteList byteList = new ByteList(number);
 
         // TODO this should entry into error handling somewhere
         int bytesRead = .read(numberbyteList);
 
         if (bytesRead == -1) {
              = true;
         }
 
         return byteList;
     }
 
     private ByteList bufferedRead(int numberthrows IOExceptionBadDescriptorException {
         checkReadable();
         ensureRead();
 
         int resultSize = 0;
 
         // 128K seems to be the minimum at which the stat+seek is faster than reallocation
         final int BULK_THRESHOLD = 128 * 1024;
         if (number >= BULK_THRESHOLD && .isSeekable() && .getChannel() instanceof FileChannel) {
             //
             // If it is a file channel, then we can pre-allocate the output buffer
             // to the total size of buffered + remaining bytes in file
             //
             FileChannel fileChannel = (FileChannel.getChannel();
             resultSize = (int) Math.min(fileChannel.size() - fileChannel.position() + bufferedInputBytesRemaining(), number);
         } else {
             //
             // Cannot discern the total read length - allocate at least enough for the buffered data
             //
             resultSize = Math.min(bufferedInputBytesRemaining(), number);
         }
 
         ByteList result = new ByteList(resultSize);
         bufferedRead(resultnumber);
         return result;
     }
 
     private int bufferedRead(ByteList dstint numberthrows IOExceptionBadDescriptorException {
 
         int bytesRead = 0;
 
         //
         // Copy what is in the buffer, if there is some buffered data
         //
         bytesRead += copyBufferedBytes(dstnumber);
 
         boolean done = false;
         //
         // Avoid double-copying for reads that are larger than the buffer size
         //
         while ((number - bytesRead) >= ) {
             //
             // limit each iteration to a max of BULK_READ_SIZE to avoid over-size allocations
             //
             final int bytesToRead = Math.min(number - bytesRead);
             final int n = .read(bytesToReaddst);
             if (n == -1) {
                  = true;
                 done = true;
                 break;
             } else if (n == 0) {
                 done = true;
                 break;
             }
             bytesRead += n;
         }
 
         //
         // Complete the request by filling the read buffer first
         //
         while (!done && bytesRead < number) {
             int read = refillBuffer();
 
             if (read == -1) {
                  = true;
                 break;
             } else if (read == 0) {
                 break;
             }
 
             // append what we read into our buffer and allow the loop to continue
             final int len = Math.min(.remaining(), number - bytesRead);
             dst.append(len);
             bytesRead += len;
         }
 
         if (bytesRead == 0 && number != 0) {
             if () {
                 throw newEOFException();
             }
         }
 
         return bytesRead;
     }
 
     private EOFException newEOFException() {
         if ( != null) {
             return ;
         } else {
             return new EOFException();
         }
     }
 
     private int bufferedRead(ByteBuffer dstboolean partialthrows IOExceptionBadDescriptorException {
         checkReadable();
         ensureRead();
 
         boolean done = false;
         int bytesRead = 0;
 
         //
         // Copy what is in the buffer, if there is some buffered data
         //
         bytesRead += copyBufferedBytes(dst);
 
         //
         // Avoid double-copying for reads that are larger than the buffer size, or
         // the destination is a direct buffer.
         //
         while ((bytesRead < 1 || !partial) && (dst.remaining() >=  || dst.isDirect())) {
             ByteBuffer tmpDst = dst;
             if (!dst.isDirect()) {
                 //
                 // We limit reads to BULK_READ_SIZED chunks to avoid NIO allocating
                 // a huge temporary native buffer, when doing reads into a heap buffer
                 // If the dst buffer is direct, then no need to limit.
                 //
                 int bytesToRead = Math.min(dst.remaining());
                 if (bytesToRead < dst.remaining()) {
                     tmpDst = dst.duplicate();
                     tmpDst.limit(tmpDst.position() + bytesToRead);
                 }
             }
             int n = .read(tmpDst);
             if (n == -1) {
                  = true;
                 done = true;
                 break;
             } else if (n == 0) {
                 done = true;
                 break;
             } else {
                 bytesRead += n;
             }
         }
        //
        // Complete the request by filling the read buffer first
        //
        while (!done && dst.hasRemaining() && (bytesRead < 1 || !partial)) {
            int read = refillBuffer();
            if (read == -1) {
                 = true;
                done = true;
                break;
            } else if (read == 0) {
                done = true;
                break;
            } else {
                // append what we read into our buffer and allow the loop to continue
                bytesRead += copyBufferedBytes(dst);
            }
        }
        if ( && bytesRead == 0 && dst.remaining() != 0) {
            throw newEOFException();
        }
        return bytesRead;
    }
    private int bufferedRead() throws IOExceptionBadDescriptorException {
        ensureRead();
        if (!.hasRemaining()) {
            int len = refillBuffer();
            if (len == -1) {
                 = true;
                return -1;
            } else if (len == 0) {
                return -1;
            }
        }
        return .get() & 0xFF;
    }

    
    private int bufferedWrite(ByteList bufthrows IOExceptionBadDescriptorException {
        checkWritable();
        ensureWrite();
        // Ruby ignores empty syswrites
        if (buf == null || buf.length() == 0) return 0;
        if (buf.length() > .capacity()) { // Doesn't fit in buffer. Write immediately.
            flushWrite(); // ensure nothing left to write
            int n = .write(ByteBuffer.wrap(buf.getUnsafeBytes(), buf.begin(), buf.length()));
            if(n != buf.length()) {
                // TODO: check the return value here
            }
        } else {
            if (buf.length() > .remaining()) flushWrite();
            .put(buf.getUnsafeBytes(), buf.begin(), buf.length());
        }
        if (isSync()) flushWrite();
        return buf.getRealSize();
    }

    
    private int bufferedWrite(ByteBuffer bufthrows IOExceptionBadDescriptorException {
        checkWritable();
        ensureWrite();
        // Ruby ignores empty syswrites
        if (buf == null || !buf.hasRemaining()) return 0;
        final int nbytes = buf.remaining();
        if (nbytes >= .capacity()) { // Doesn't fit in buffer. Write immediately.
            flushWrite(); // ensure nothing left to write
            .write(buf);
            // TODO: check the return value here
        } else {
            if (nbytes > .remaining()) flushWrite();
            .put(buf);
        }
        if (isSync()) flushWrite();
        return nbytes - buf.remaining();
    }

    
    private int bufferedWrite(int cthrows IOExceptionBadDescriptorException {
        checkWritable();
        ensureWrite();
        if (!.hasRemaining()) flushWrite();
        .put((bytec);
        if (isSync()) flushWrite();
        return 1;
    }
    public synchronized void ftruncate(long newLengththrows IOException,
        Channel ch = .getChannel();
        if (!(ch instanceof FileChannel)) {
            throw new InvalidValueException();
        }
        invalidateBuffer();
        FileChannel fileChannel = (FileChannel)ch;
        if (newLength > fileChannel.size()) {
            // truncate can't lengthen files, so we save position, seek/write, and go back
            long position = fileChannel.position();
            int difference = (int)(newLength - fileChannel.size());
            fileChannel.position(fileChannel.size());
            // FIXME: This worries me a bit, since it could allocate a lot with a large newLength
            fileChannel.write(ByteBuffer.allocate(difference));
            fileChannel.position(position);
        } else {
            fileChannel.truncate(newLength);
        }
    }

    
Invalidate buffer before a position change has occurred (e.g. seek), flushing writes if required, and correcting file position if reading

    private void invalidateBuffer() throws IOExceptionBadDescriptorException {
        if (!flushWrite();
        int posOverrun = .remaining(); // how far ahead we are when reading
        .clear();
        if () {
            .flip();
            // if the read buffer is ahead, back up
            FileChannel fileChannel = (FileChannel).getChannel();
            if (posOverrun != 0) fileChannel.position(fileChannel.position() - posOverrun);
        }
    }

    
Ensure close (especially flush) when we're finished with.
    @Override
    public void finalize() throws Throwable {
        super.finalize();
        
        if (return;
        if () {
            .info("finalize() for not explicitly closed stream");
        }
        // FIXME: I got a bunch of NPEs when I didn't check for nulls here...HOW?!
        if ( != null && .isOpen()) {
            // tidy up
            finish();
        }
    }
    public int ready() throws IOException {
        if (.getChannel() instanceof SelectableChannel) {
            int ready_stat = 0;
            java.nio.channels.Selector sel = SelectorFactory.openWithRetryFrom(null, ((SelectableChannel.getChannel()).provider());
            SelectableChannel selchan = (SelectableChannel).getChannel();
            synchronized (selchan.blockingLock()) {
                boolean is_block = selchan.isBlocking();
                try {
                    selchan.configureBlocking(false);
                    selchan.register(sel....);
                    ready_stat = sel.selectNow();
                    sel.close();
                } catch (Throwable ex) {
                } finally {
                    if (sel != null) {
                        try {
                            sel.close();
                        } catch (Exception e) {
                        }
                    }
                    selchan.configureBlocking(is_block);
                }
            }
            return ready_stat;