class RewindableReadableByteChannel implements ReadableByteChannel {

  @Override
  public synchronized int read(ByteBuffer dst) throws IOException {
    if (directRead) {
      return baseChannel.read(dst);
    }
    int bytesToReadCount = dst.remaining();
    if (bytesToReadCount == 0) {
      return 0;
    }
    if (buffer == null) {  // The first read, no cached data yet.
      if (!canRewind) {
        directRead = true;
        return baseChannel.read(dst);
      }
      buffer = ByteBuffer.allocate(bytesToReadCount);
      int baseReadResult = baseChannel.read(buffer);
      // put buffer in draining mode
      buffer.flip();
      if (baseReadResult > 0) {
        dst.put(buffer);
      }
      return baseReadResult;
    }
    // Subsequent read
    if (buffer.remaining() >= bytesToReadCount) {
      // buffer has all data needed.
      // dst.put expects buffer.remaining() <= dst.remaining(). So we have to temporarily lower
      // buffer.limit. Note that
      // buffer.position() + bytesToReadCount <= buffer.position() + buffer.remaining()
      // = buffer.position() + buffer.limit() - buffer.position() = buffer.limit().
      int limit = buffer.limit();
      buffer.limit(buffer.position() + bytesToReadCount);
      dst.put(buffer);
      buffer.limit(limit);
      if (!canRewind && !buffer.hasRemaining()) {
        buffer = null;
        directRead = true;
      }
      return bytesToReadCount;
    }
    int bytesFromBufferCount = buffer.remaining();
    int stillToReadCount = bytesToReadCount - bytesFromBufferCount;

    // buffer is in draining mode.
    int currentReadPos = buffer.position();
    int contentLimit = buffer.limit();
    // Put the buffer into into filling mode by hand. The filling should start right after the
    // current limit, and at most stillToReadCount bytes should be written.
    setBufferLimit(contentLimit + stillToReadCount);
    buffer.position(contentLimit);
    int baseReadResult = baseChannel.read(buffer);
    // Put buffer in draining mode.
    buffer.flip();
    buffer.position(currentReadPos); // restore reading position.
    dst.put(buffer);
    if (bytesFromBufferCount == 0 && baseReadResult < 0) {
      return -1;  // EOF
    }
    int bytesCount = buffer.position() - currentReadPos;
    if (!canRewind && !buffer.hasRemaining()) {
      buffer = null;
      directRead = true;
    }
    return bytesCount;
  }

  public  RewindableReadableByteChannel(ReadableByteChannel baseChannel);

}