Latin1Reader.java

package com.fasterxml.jackson.dataformat.javaprop.io;

import java.io.*;

import com.fasterxml.jackson.core.io.IOContext;

/**
 * Optimized Reader that reads ISO-8859-1 encoded content from an input stream.
 * The reason for custom implementation is that this allows recycling of
 * underlying read buffer, which is important for small content.
 */
public final class Latin1Reader extends Reader
{
    /**
     * IO context to use for returning input buffer, iff
     * buffer is to be recycled when input ends.
     */
    private final IOContext _ioContext;

    private InputStream _inputSource;

    private byte[] _inputBuffer;

    /**
     * Pointer to the next available byte (if any), iff less than
     * <code>mByteBufferEnd</code>
     */
    private int _inputPtr;

    /**
     * Pointed to the end marker, that is, position one after the last
     * valid available byte.
     */
    private int _inputEnd;

    /**
     * Total read character count; used for error reporting purposes
     */
    private int _charCount = 0;

    /*
    /**********************************************************************
    /* Life-cycle
    /**********************************************************************
     */

    /*
    public Latin1Reader(IOContext ctxt, InputStream in,
            byte[] buf, int ptr, int len)
    {
        super((in == null) ? buf : in);
        _ioContext = ctxt;
        _inputSource = in;
        _inputBuffer = buf;
        _inputPtr = ptr;
        _inputEnd = ptr+len;
    }
    */

    public Latin1Reader(byte[] buf, int ptr, int len)
    {
        super(new Object());
        _ioContext = null;
        _inputSource = null;
        _inputBuffer = buf;
        _inputPtr = ptr;
        _inputEnd = ptr+len;
    }

    public Latin1Reader(IOContext ctxt, InputStream in)
    {
        super(in);
        _ioContext = ctxt;
        _inputSource = in;
        _inputBuffer = ctxt.allocReadIOBuffer();
        _inputPtr = 0;
        _inputEnd = 0;
    }

    /*
    /**********************************************************************
    /* Extended API
    /**********************************************************************
     */

    public int getReadCharsCount() {
        return _charCount;
    }

    /*
    /**********************************************************************
    /* Reader API
    /**********************************************************************
     */

    @Override
    public void close() throws IOException
    {
        InputStream in = _inputSource;
        if (in != null) {
            _inputSource = null;
            freeBuffers();
            in.close();
        }
    }

    private char[] _tmpBuffer = null;

    // Although this method is implemented by the base class, AND it should
    // never be called by parser code, let's still implement it bit more
    // efficiently just in case
    @Override
    public int read() throws IOException
    {
        if (_tmpBuffer == null) {
            _tmpBuffer = new char[1];
        }
        if (read(_tmpBuffer, 0, 1) < 1) {
            return -1;
        }
        return _tmpBuffer[0];
    }

    @Override
    public int read(char[] cbuf) throws IOException {
        return read(cbuf, 0, cbuf.length);
    }
    
    @Override
    public int read(char[] cbuf, int start, int len) throws IOException
    {
        if (_inputBuffer == null) {
            return -1;
        }
        if (len < 1) {
            return len;
        }

        // To prevent unnecessary blocking (esp. with network streams),
        // we'll only require decoding of a single char
        int left = (_inputEnd - _inputPtr);
        if (left < 1) {
            if (!loadMore()) { // (legal) EOF?
                return -1;
            }
            left = (_inputEnd - _inputPtr);
        }
        if (left > len) {
            left = len;
        }

        final byte[] inBuf = _inputBuffer;
        int inPtr = _inputPtr;
        int outPtr = start;
        final int inEnd = inPtr + left;

        do {
            cbuf[outPtr++] = (char) inBuf[inPtr++];
        } while (inPtr < inEnd);
        _inputPtr = inPtr;
        return left;
    }
    
    /*
    /**********************************************************************
    /* Internal methods
    /**********************************************************************
     */

    /**
     * @return True, if enough bytes were read to allow decoding of at least
     *   one full character; false if EOF was encountered instead.
     */
    private boolean loadMore() throws IOException
    {
        _charCount += _inputEnd;
        _inputPtr = 0;
        _inputEnd = 0;
        if (_inputSource == null) {
            freeBuffers();
            return false;
        }
        int count = _inputSource.read(_inputBuffer, 0, _inputBuffer.length);
        if (count < 1) {
            freeBuffers();
            if (count < 0) { // -1
                return false;
            }
            // 0 count is no good; let's err out
            throw new IOException("Strange I/O stream, returned 0 bytes on read");
        }
        _inputEnd = count;
        return true;
    }

    /**
     * This method should be called along with (or instead of) normal
     * close. After calling this method, no further reads should be tried.
     * Method will try to recycle read buffers (if any).
     */
    private final void freeBuffers()
    {
        if (_ioContext != null) {
            byte[] buf = _inputBuffer;
            if (buf != null) {
                _inputBuffer = null;
                _ioContext.releaseReadIOBuffer(buf);
            }
        }
    }
}