io/src/main/java/com/stanfy/enroscar/io/PoolableBufferedInputStream.java
@Override
  public synchronized int read(final byte[] buffer, final int start, final int length) throws IOException {
    int offset = start;
    // Use local ref since buf may be invalidated by an unsynchronized
    // close()
    byte[] localBuf = buf;
    if (localBuf == null) {
      throw new IOException("Stream is closed");
    }
    // avoid int overflow
    // BEGIN android-changed
    // Exception priorities (in case of multiple errors) differ from
    // RI, but are spec-compliant.
    // made implicit null check explicit, used (offset | length) < 0
    // instead of (offset < 0) || (length < 0) to safe one operation
    if (buffer == null) {
      throw new NullPointerException("Buffer is null");
    }
    if ((offset | length) < 0 || offset > buffer.length - length) {
      throw new IndexOutOfBoundsException("Bad offsets");
    }
    // END android-changed
    if (length == 0) {
      return 0;
    }
    final InputStream localIn = in;
    if (localIn == null) {
      throw new IOException("Stream is closed");
    }

    int required;
    if (pos < count) {
      /* There are bytes available in the buffer. */
      final int copylength = count - pos >= length ? length : count - pos;
      System.arraycopy(localBuf, pos, buffer, offset, copylength);
      pos += copylength;
      if (copylength == length || localIn.available() == 0) {
        return copylength;
      }
      offset += copylength;
      required = length - copylength;
    } else {
      required = length;
    }

    while (true) {
      int read;
      /*
       * If we're not marked and the required size is greater than the
       * buffer, simply read the bytes directly bypassing the buffer.
       */
      if (markpos == -1 && required >= localBuf.length) {
        read = localIn.read(buffer, offset, required);
        if (read == -1) {
          return required == length ? -1 : length - required;
        }
      } else {
        if (fillbuf(localIn, localBuf) == -1) {
          return required == length ? -1 : length - required;
        }
        // localBuf may have been invalidated by fillbuf
        if (localBuf != buf) {
          localBuf = buf;
          if (localBuf == null) {
            throw new IOException("Stream is closed");
          }
        }

        read = count - pos >= required ? required : count - pos;
        System.arraycopy(localBuf, pos, buffer, offset, read);
        pos += read;
      }
      required -= read;
      if (required == 0) {
        return length;
      }
      if (localIn.available() == 0) {
        return length - required;
      }
      offset += read;
    }
  }
@Override
  public synchronized int read(final byte[] buffer, final int start, final int length) throws IOException {
    int offset = start;
    // Use local ref since buf may be invalidated by an unsynchronized
    // close()
    byte[] localBuf = buf;
    if (localBuf == null) {
      throw new IOException("Stream is closed");
    }
    // avoid int overflow
    // BEGIN android-changed
    // Exception priorities (in case of multiple errors) differ from
    // RI, but are spec-compliant.
    // made implicit null check explicit, used (offset | length) < 0
    // instead of (offset < 0) || (length < 0) to safe one operation
    if (buffer == null) {
      throw new NullPointerException("Buffer is null");
    }
    if ((offset | length) < 0 || offset > buffer.length - length) {
      throw new IndexOutOfBoundsException("Bad offsets");
    }
    // END android-changed
    if (length == 0) {
      return 0;
    }
    final InputStream localIn = in;
    if (localIn == null) {
      throw new IOException("Stream is closed");
    }

    int required;
    if (pos < count) {
      /* There are bytes available in the buffer. */
      final int copylength = count - pos >= length ? length : count - pos;
      System.arraycopy(localBuf, pos, buffer, offset, copylength);
      pos += copylength;
      if (copylength == length || localIn.available() == 0) {
        return copylength;
      }
      offset += copylength;
      required = length - copylength;
    } else {
      required = length;
    }

    while (true) {
      int read;
      /*
       * If we're not marked and the required size is greater than the
       * buffer, simply read the bytes directly bypassing the buffer.
       */
      if (markpos == -1 && required >= localBuf.length) {
        read = localIn.read(buffer, offset, required);
        if (read == -1) {
          return required == length ? -1 : length - required;
        }
      } else {
        if (fillbuf(localIn, localBuf) == -1) {
          return required == length ? -1 : length - required;
        }
        // localBuf may have been invalidated by fillbuf
        if (localBuf != buf) {
          localBuf = buf;
          if (localBuf == null) {
            throw new IOException("Stream is closed");
          }
        }

        read = count - pos >= required ? required : count - pos;
        System.arraycopy(localBuf, pos, buffer, offset, read);
        pos += read;
      }
      required -= read;
      if (required == 0) {
        return length;
      }
      if (localIn.available() == 0) {
        return length - required;
      }
      offset += read;
    }
  }
Reads at most {@code length} bytes from this stream and stores them in
byte array {@code buffer} starting at offset {@code offset}. Returns the
number of bytes actually read or -1 if no bytes were read and the end of
the stream was encountered. If all the buffered bytes have been used, a
mark has not been set and the requested number of bytes is larger than
the receiver's buffer size, this implementation bypasses the buffer and
simply places the results directly into {@code buffer}.

@param buffer
the byte array in which to store the bytes read.
@param start
the initial position in {@code buffer} to store the bytes read
from this stream.
@param length
the maximum number of bytes to store in {@code buffer}.
@return the number of bytes actually read or -1 if end of stream.
@throws IndexOutOfBoundsException
if {@code offset < 0} or {@code length < 0}, or if
{@code offset + length} is greater than the size of
{@code buffer}.
@throws IOException
if the stream is already closed or another IOException
occurs.