SeekableInputStreamReader.java

package de.siegmar.fastcsv.reader;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.io.Reader;
import java.nio.charset.Charset;

/// A [Reader] over a [RandomAccessFile] that supports repositioning via [#seek(long)].
///
/// A plain [InputStreamReader] (more precisely its internal `StreamDecoder`) retains undecoded
/// leftover bytes when a read ends mid-multibyte-sequence. After repositioning the file these stale
/// bytes would otherwise be prepended to the freshly read data, corrupting it. [#seek(long)]
/// therefore repositions the file *and* discards the current decoder atomically, so the two can
/// never get out of sync.
final class SeekableInputStreamReader extends Reader {

    private final RandomAccessFile raf;
    private final RandomAccessFileInputStream inputStream;
    private final Charset charset;
    private InputStreamReader delegate;

    SeekableInputStreamReader(final RandomAccessFile raf, final Charset charset) {
        this.raf = raf;
        this.charset = charset;
        inputStream = new RandomAccessFileInputStream(raf);
        delegate = new InputStreamReader(inputStream, charset);
    }

    /// Repositions the underlying file to the given byte offset and resumes decoding from there.
    ///
    /// The previous decoder is abandoned (not closed ��� that would close the shared file), dropping
    /// any retained, now-stale leftover bytes from a multibyte sequence straddling the previous read
    /// boundary.
    ///
    /// @param position the byte offset to seek to.
    /// @throws IOException if seeking fails.
    void seek(final long position) throws IOException {
        raf.seek(position);
        delegate = new InputStreamReader(inputStream, charset);
    }

    @Override
    public int read(final char[] cbuf, final int off, final int len) throws IOException {
        return delegate.read(cbuf, off, len);
    }

    @Override
    public void close() throws IOException {
        delegate.close();
    }

}