code: @Override
    public int read(byte[] bytes, int off, int len) throws IOException
    {
      Params.notNull(bytes, "Bytes");
      if(len < 0 || off < 0 || (off + len) > bytes.length) {
        throw new IndexOutOfBoundsException(Strings.concat("Array Size=", bytes.length, ", offset=", off, ", length=", len));
      }
  
      if(len == 0) {
        return 0; // Always return 0 if len == 0
      }
  
      int bytesRead = 0;
      while(len > 0) {
        if(bytesBuffer.hasRemaining()) {
          int count = Math.min(bytesBuffer.remaining(), len);
          bytesBuffer.get(bytes, off, count);
          off += count;
          len -= count;
          bytesRead += count;
        }
        else {
          fillBytesBuffer();
          if(endOfInput && !bytesBuffer.hasRemaining()) {
            break;
          }
        }
      }
  
      return bytesRead == 0 && endOfInput ? EOF : bytesRead;
    }
docs: Reads up to len bytes of data from the input stream into an array of bytes. Returns the number of bytes read or -1
  if end of stream was reached.
  <p>
  This method simple copy {@link #bytesBuffer} into given bytes buffer. If stream buffer is empty delegates
  {@link #fillBytesBuffer()} to fill it from underlying reader.
  
  @param bytes the bytes buffer to read into,
  @param off the offset to start reading bytes into,
  @param len the number of bytes to read.
  @return the number of bytes read or <code>-1</code> if the end of the stream has been reached.
  @throws IllegalArgumentException if <code>bytes</code> argument is null.
  @throws IndexOutOfBoundsException if offset or length is negative or sum of offset and length exceeds buffer size.
  @throws IOException if read from underlying source fails.