class ResettableFileInputStream extends ResettableInputStream implements RemoteMarkable, LengthMeasurable {

  @Override
  public synchronized int readChar() throws IOException {

    // Check whether we are in the middle of a surrogate pair,
    // in which case, return the last (low surrogate) char of the pair.
    if (hasLowSurrogate) {
      hasLowSurrogate = false;
      return lowSurrogate;
    }

    // The decoder can have issues with multi-byte characters.
    // This check ensures that there are at least maxCharWidth bytes in the buffer
    // before reaching EOF.
    if (buf.remaining() < maxCharWidth) {
      buf.clear();
      buf.flip();
      refillBuf();
    }

    int start = buf.position();
    charBuf.clear();
    charBuf.limit(1);

    boolean isEndOfInput = false;
    if (position >= fileSize) {
      isEndOfInput = true;
    }

    CoderResult res = decoder.decode(buf, charBuf, isEndOfInput);
    if (res.isMalformed() || res.isUnmappable()) {
      res.throwException();
    }

    int delta = buf.position() - start;

    charBuf.flip();

    // Found a single char
    if (charBuf.hasRemaining()) {
      char c = charBuf.get();
      incrPosition(delta, true);
      return c;
    }

    // Found nothing, but the byte buffer has not been entirely consumed.
    // This situation denotes the presence of a surrogate pair
    // that can only be decoded if we have a 2-char buffer.
    if (buf.hasRemaining()) {
      charBuf.clear();
      // increase the limit to 2
      charBuf.limit(2);
      // decode 2 chars in one pass
      res = decoder.decode(buf, charBuf, isEndOfInput);
      if (res.isMalformed() || res.isUnmappable()) {
        res.throwException();
      }
      charBuf.flip();
      // Check if we successfully decoded 2 chars
      if (charBuf.remaining() == 2) {
        char highSurrogate = charBuf.get();
        // save second (low surrogate) char for later consumption
        lowSurrogate = charBuf.get();
        // Check if we really have a surrogate pair
        if (!Character.isHighSurrogate(highSurrogate) || !Character.isLowSurrogate(lowSurrogate)) {
          // This should only happen in case of bad sequences (dangling surrogate, etc.)
          logger.warn("Decoded a pair of chars, but it does not seem to be a surrogate pair: {} {}",
                      (int)highSurrogate, (int)lowSurrogate);
        }
        hasLowSurrogate = true;
        // consider the pair as a single unit and increment position normally
        delta = buf.position() - start;
        incrPosition(delta, true);
        // return the first (high surrogate) char of the pair
        return highSurrogate;
      }
    }

    // end of file
    incrPosition(delta, false);
    return -1;

  }

  public  ResettableFileInputStream(File file, PositionTracker tracker);
  public  ResettableFileInputStream(File file, PositionTracker tracker,
      int bufSize, Charset charset, DecodeErrorPolicy decodeErrorPolicy);

}