JacksonAvroParserImpl.java

package com.fasterxml.jackson.dataformat.avro.deser;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Writer;

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

/**
 * Implementation class that exposes additional internal API
 * to be used as callbacks by {@link AvroReadContext} implementations.
 */
public class JacksonAvroParserImpl extends AvroParserImpl
{
    /**
     * Additionally we can combine UTF-8 decoding info into similar
     * data table.
     * Values indicate "byte length - 1"; meaning -1 is used for
     * invalid bytes, 0 for single-byte codes, 1 for 2-byte codes
     * and 2 for 3-byte codes.
     */
    public final static int[] sUtf8UnitLengths;
    static {
        int[] table = new int[256];
        for (int c = 128; c < 256; ++c) {
            int code;

            // We'll add number of bytes needed for decoding
            if ((c & 0xE0) == 0xC0) { // 2 bytes (0x0080 - 0x07FF)
                code = 1;
            } else if ((c & 0xF0) == 0xE0) { // 3 bytes (0x0800 - 0xFFFF)
                code = 2;
            } else if ((c & 0xF8) == 0xF0) {
                // 4 bytes; double-char with surrogates and all...
                code = 3;
            } else {
                // And -1 seems like a good "universal" error marker...
                code = -1;
            }
            table[c] = code;
        }
        sUtf8UnitLengths = table;
    }

    /*
    /**********************************************************
    /* Input source config
    /**********************************************************
     */

    protected InputStream _inputStream;

    /**
     * Current buffer from which data is read; generally data is read into
     * buffer from input source, but in some cases pre-loaded buffer
     * is handed to the parser.
     */
    protected byte[] _inputBuffer;

    /**
     * Flag that indicates whether the input buffer is recycable (and
     * needs to be returned to recycler once we are done) or not.
     *<p>
     * If it is not, it also means that parser can NOT modify underlying
     * buffer.
     */
    protected boolean _bufferRecyclable;

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

    public JacksonAvroParserImpl(IOContext ctxt, int parserFeatures, int avroFeatures,
            ObjectCodec codec, InputStream in)
    {
        super(ctxt, parserFeatures, avroFeatures, codec);
        _inputStream = in;
        _inputBuffer = ctxt.allocReadIOBuffer();
        _inputPtr = 0;
        _inputEnd = 0;
        _bufferRecyclable = true;
    }

    public JacksonAvroParserImpl(IOContext ctxt, int parserFeatures, int avroFeatures,
            ObjectCodec codec,
            byte[] data, int offset, int len)
    {
        super(ctxt, parserFeatures, avroFeatures, codec);
        _inputStream = null;
        _inputBuffer = data;
        _inputPtr = offset;
        _inputEnd = offset + len;
    }

    @Override
    protected void _releaseBuffers() throws IOException {
        super._releaseBuffers();
        if (_bufferRecyclable) {
            byte[] buf = _inputBuffer;
            if (buf != null) {
                _inputBuffer = null;
                _ioContext.releaseReadIOBuffer(buf);
            }
        }
    }

    /*
    /**********************************************************
    /* Abstract method impls, i/o access
    /**********************************************************
     */

    @Override
    public Object getInputSource() {
        return _inputStream;
    }

    @SuppressWarnings("deprecation")
    @Override
    protected void _closeInput() throws IOException {
        if (_inputStream != null) {
            if (_ioContext.isResourceManaged() || isEnabled(JsonParser.Feature.AUTO_CLOSE_SOURCE)) {
                _inputStream.close();
            }
            _inputStream = null;
        }
    }

    @Override
    public int releaseBuffered(OutputStream out) throws IOException
    {
        int count = _inputEnd - _inputPtr;
        if (count < 1) {
            return 0;
        }
        // let's just advance ptr to end
        int origPtr = _inputPtr;
        out.write(_inputBuffer, origPtr, count);
        return count;
    }

    /*
    /**********************************************************
    /* Abstract method impls, traversal
    /**********************************************************
     */

    // !!! TODO: optimize
    @Override
    public String nextTextValue() throws IOException {
        if (nextToken() == JsonToken.VALUE_STRING) {
            return _textBuffer.contentsAsString();
        }
        return null;
    }

    /*
    /**********************************************************
    /* Abstract method impls, text
    /**********************************************************
     */

    @Override
    public boolean hasTextCharacters() {
        if (_currToken == JsonToken.VALUE_STRING) { return true; } // usually true
        // name might be copied but...
        return false;
    }

    @Override
    public String getText() throws IOException
    {
        JsonToken t = _currToken;
        if (t == JsonToken.VALUE_STRING) {
            return _textBuffer.contentsAsString();
        }
        if (t == JsonToken.FIELD_NAME) {
            return _avroContext.getCurrentName();
        }
        if (t != null) {
            if (t.isNumeric()) {
                return getNumberValue().toString();
            }
            return _currToken.asString();
        }
        return null;
    }

    @Override // since 2.8
    public int getText(Writer writer) throws IOException
    {
        JsonToken t = _currToken;
        if (t == JsonToken.VALUE_STRING) {
            return _textBuffer.contentsToWriter(writer);
        }
        if (t == JsonToken.FIELD_NAME) {
            String n = _avroContext.getCurrentName();
            writer.write(n);
            return n.length();
        }
        if (t != null) {
            if (t.isNumeric()) {
                return _textBuffer.contentsToWriter(writer);
            }
            char[] ch = t.asCharArray();
            writer.write(ch);
            return ch.length;
        }
        return 0;
    }

    /*
    /**********************************************************
    /* Methods for AvroReadContext implementations: decoding int
    /**********************************************************
     */

    @Override
    public JsonToken decodeIntToken() throws IOException {
        _numberInt = decodeInt();
        _numTypesValid = NR_INT;
        return JsonToken.VALUE_NUMBER_INT;
    }

    @Override
    public final int decodeInt() throws IOException
    {
        int ptr = _inputPtr;
        if ((_inputEnd - ptr) < 5) {
            return _decodeIntSlow();
        }
        final byte[] buf = _inputBuffer;
        int b = buf[ptr++];
        int i = b & 0x7F;
        if (b < 0) {
            b = buf[ptr++];
            i += ((b & 0x7F) << 7);
            if (b < 0) {
                b = buf[ptr++];
                i += ((b & 0x7F) << 14);
                if (b < 0) {
                    b = buf[ptr++];
                    i += ((b & 0x7F) << 21);
                    if (b < 0) {
                        b = buf[ptr++];
                        if (b < 0) {
                            _inputPtr = ptr;
                            _reportInvalidNegative(b);
                        }
                        i += (b << 28);
                    }
                }
            }
        }
        _inputPtr = ptr;
        // and final part: Zigzag decode
        return (i >>> 1) ^ (-(i & 1));
    }

    public int _decodeIntSlow() throws IOException {
        int b = _nextByteGuaranteed();
        int i = b & 0x7F;
        if (b < 0) {
            b = _nextByteGuaranteed();
            i += ((b & 0x7F) << 7);
            if (b < 0) {
                b = _nextByteGuaranteed();
                i += ((b & 0x7F) << 14);
                if (b < 0) {
                    b = _nextByteGuaranteed();
                    i += ((b & 0x7F) << 21);
                    if (b < 0) {
                        b = _nextByteGuaranteed();
                        if (b < 0) {
                            _reportInvalidNegative(b);
                        }
                        i += (b << 28);
                    }
                }
            }
        }
        // and final part: Zigzag decode
        return (i >>> 1) ^ (-(i & 1));
    }

    @Override
    public void skipInt() throws IOException
    {
        int ptr = _inputPtr;
        if ((_inputEnd - ptr) < 5) {
            _skipIntSlow();
            return;
        }
        final byte[] buf = _inputBuffer;
        if (buf[ptr++] < 0) {
            if (buf[ptr++] < 0) {
                if (buf[ptr++] < 0) {
                    if (buf[ptr++] < 0) {
                        int b = buf[ptr++];
                        if (b < 0) {
                            _inputPtr = ptr;
                            _reportInvalidNegative(b);
                        }
                    }
                }
            }
        }
        _inputPtr = ptr;
    }

    public void _skipIntSlow() throws IOException {
        if (_nextByteGuaranteed() < 0) {
            if (_nextByteGuaranteed() < 0) {
                if (_nextByteGuaranteed() < 0) {
                    if (_nextByteGuaranteed() < 0) {
                        int b = _nextByteGuaranteed();
                        if (b < 0) {
                            _reportInvalidNegative(b);
                        }
                    }
                }
            }
        }
    }

    /*
    /**********************************************************
    /* Methods for AvroReadContext implementations: decoding long
    /**********************************************************
     */

    @Override
    public JsonToken decodeLongToken() throws IOException {
        _numberLong = decodeLong();
        _numTypesValid = NR_LONG;
        return JsonToken.VALUE_NUMBER_INT;
    }

    @Override
    public long decodeLong() throws IOException {
        int ptr = _inputPtr;
        if ((_inputEnd - ptr) < 10) {
            return _decodeLongSlow();
        }
        final byte[] buf = _inputBuffer;
        // inline handling of first 4 bytes (for 28-bits of content)
        int b = buf[ptr++];
        int i = b & 0x7F;
        if (b < 0) {
            b = buf[ptr++];
            i += ((b & 0x7F) << 7);
            if (b < 0) {
                b = buf[ptr++];
                i += ((b & 0x7F) << 14);
                if (b < 0) {
                    b = buf[ptr++];
                    i += ((b & 0x7F) << 21);
                    if (b < 0) {
                        return _decodeLong2(ptr, i);
                    }
                }
            }
        }
        _inputPtr = ptr;
        // should be ok to zigzag as int, then sign-extend
        i = (i >>> 1) ^ (-(i & 1));
        return i;
    }

    private long _decodeLong2(int ptr, long lo) throws IOException
    {
        final byte[] buf = _inputBuffer;
        // then next 28 bits (altogether 8 bytes)
        int b = buf[ptr++];
        int i = b & 0x7F;
        if (b < 0) {
            b = buf[ptr++];
            i += ((b & 0x7F) << 7);
            if (b < 0) {
                b = buf[ptr++];
                i += ((b & 0x7F) << 14);
                if (b < 0) {
                    b = buf[ptr++];
                    i += ((b & 0x7F) << 21);
                    if (b < 0) {
                        // Ok 56-bits gone... still going strong!
                        b = buf[ptr++];
                        int inner = b & 0x7F;
                        if (b < 0) {
                            b = buf[ptr++];
                            if (b < 0) {
                                _inputPtr = ptr;
                                _reportInvalidNegative(b);
                            }
                            inner |= ((b & 0x1) << 7);
                        }
                        lo |= (((long) inner) << 56);
                    }
                }
            }
        }
        _inputPtr = ptr;
        lo |= (((long) i) << 28);
        return (lo >>> 1) ^ (-(lo & 1));
    }

    public long _decodeLongSlow() throws IOException {
        int b = _nextByteGuaranteed();
        int i = b & 0x7F;
        if (b < 0) {
            b = _nextByteGuaranteed();
            i += ((b & 0x7F) << 7);
            if (b < 0) {
                b = _nextByteGuaranteed();
                i += ((b & 0x7F) << 14);
                if (b < 0) {
                    b = _nextByteGuaranteed();
                    i += ((b & 0x7F) << 21);
                    if (b < 0) {
                        return _decodeLongSlow2(i);
                    }
                }
            }
        }
        i = (i >>> 1) ^ (-(i & 1));
        return i;
    }

    private long _decodeLongSlow2(long lo) throws IOException
    {
        // then next 28 bits (altogether 8 bytes)
        int b = _nextByteGuaranteed();
        int i = b & 0x7F;
        if (b < 0) {
            i &= 0x7F;
            b = _nextByteGuaranteed();
            i += ((b & 0x7F) << 7);
            if (b < 0) {
                b = _nextByteGuaranteed();
                i += ((b & 0x7F) << 14);
                if (b < 0) {
                    b = _nextByteGuaranteed();
                    i += ((b & 0x7F) << 21);
                    if (b < 0) {
                        // Ok 56-bits gone... still going strong!
                        lo |= (((long) i) << 28);
                        b = _nextByteGuaranteed();
                        i = b & 0x7F;
                        if (b < 0) {
                            b = _nextByteGuaranteed();
                            if (b < 0) {
                                _reportInvalidNegative(b);
                            }
                            i |= (b << 7);
                        }
                        lo |= (((long) i) << 56);
                        return (lo >>> 1) ^ (-(lo & 1));
                    }
                }
            }
        }
        lo |= (((long) i) << 28);
        return (lo >>> 1) ^ (-(lo & 1));
    }

    @Override
    public void skipLong() throws IOException {
        int ptr = _inputPtr;
        if ((_inputEnd - ptr) < 10) {
            _skipLongSlow();
            return;
        }
        final byte[] buf = _inputBuffer;
        if (buf[ptr++] < 0) {
            int maxLeft = 9;
            int b;
            do {
                b = _nextByteGuaranteed();
            } while ((--maxLeft > 0) && (b < 0));
            if (b < 0) {
                _reportInvalidNegative(b);
            }
        }
        _inputPtr = ptr;
    }

    public void _skipLongSlow() throws IOException {
        if (_nextByteGuaranteed() < 0) {
            int maxLeft = 9;
            int b;
            do {
                b = _nextByteGuaranteed();
            } while ((--maxLeft > 0) && (b < 0));
            if (b < 0) {
                _reportInvalidNegative(b);
            }
        }
    }

    /*
    /**********************************************************
    /* Methods for AvroReadContext implementations: decoding float/double
    /**********************************************************
     */

    @Override
    public JsonToken decodeFloat() throws IOException {
        int ptr = _inputPtr;
        if ((_inputEnd - ptr) < 4) {
            _loadToHaveAtLeast(4);
            ptr = _inputPtr;
        }
        final byte[] buf = _inputBuffer;
        _inputPtr = ptr+4;
        int i = (buf[ptr] & 0xff) | ((buf[ptr+1] & 0xff) << 8)
                | ((buf[ptr+2] & 0xff) << 16) | (buf[ptr+3] << 24);
        _numberFloat = Float.intBitsToFloat(i);
        _numTypesValid = NR_FLOAT;
        return JsonToken.VALUE_NUMBER_FLOAT;
    }

    @Override
    public void skipFloat() throws IOException {
        _skip(4);
    }

    @Override
    public JsonToken decodeDouble() throws IOException {
        int ptr = _inputPtr;
        if ((_inputEnd - ptr) < 8) {
            _loadToHaveAtLeast(8);
            ptr = _inputPtr;
        }
        final byte[] buf = _inputBuffer;
        int i = (buf[ptr] & 0xff) | ((buf[ptr+1] & 0xff) << 8)
                | ((buf[ptr+2] & 0xff) << 16) | (buf[ptr+3] << 24);
        ptr += 4;
        int i2 = (buf[ptr] & 0xff) | ((buf[ptr+1] & 0xff) << 8)
                | ((buf[ptr+2] & 0xff) << 16) | (buf[ptr+3] << 24);

        _inputPtr = ptr+4;
        _numberDouble = Double.longBitsToDouble((((long) i) & 0xffffffffL)
                | (((long) i2) << 32));
        _numTypesValid = NR_DOUBLE;
        return JsonToken.VALUE_NUMBER_FLOAT;
    }

    @Override
    public void skipDouble() throws IOException {
        _skip(8);
    }

    /*
    /**********************************************************
    /* Methods for AvroReadContext implementations: decoding Strings
    /**********************************************************
     */

    @Override
    public JsonToken decodeStringToken() throws IOException {
        decodeString();
        return JsonToken.VALUE_STRING;
    }

    @Override
    public void decodeString() throws IOException {
        int len = decodeInt();
        if (len <= 0) {
            if (len < 0) {
                _reportError("Invalid length indicator for String: "+len);
            }
            _textBuffer.resetWithEmpty();
            return;
        }

        if (len > (_inputEnd - _inputPtr)) {
            // or if not, could we read?
            if (len >= _inputBuffer.length) {
                // If not enough space, need handling similar to chunked
                _finishLongText(len);
                return;
            }
            _loadToHaveAtLeast(len);
        }
        // offline for better optimization
        _finishShortText(len);
    }

    @Override
    public void skipString() throws IOException {
        int len = decodeInt();
        if (len <= 0) {
            if (len < 0) {
                _reportError("Invalid length indicator for String: "+len);
            }
            return;
        }
        _skip(len);
    }

    private final String _finishShortText(int len) throws IOException
    {
        char[] outBuf = _textBuffer.emptyAndGetCurrentSegment();
        if (outBuf.length < len) { // one minor complication
            outBuf = _textBuffer.expandCurrentSegment(len);
        }

        int outPtr = 0;
        int inPtr = _inputPtr;
        _inputPtr += len;
        final byte[] inputBuf = _inputBuffer;

        final int end = inPtr + len;
        // Let's actually do a tight loop for ASCII first:
        int i;
        while ((i = inputBuf[inPtr]) >= 0) {
            outBuf[outPtr++] = (char) i;
            if (++inPtr == end) {
                return _textBuffer.setCurrentAndReturn(outPtr);
            }
        }

        final int[] codes = sUtf8UnitLengths;
        do {
            i = inputBuf[inPtr++] & 0xFF;
            final int code = codes[i];
            if (code == 0) { // still optimized for ASCII
                outBuf[outPtr++] = (char) i;
                continue;
            }
            if ((inPtr + code) > end) {
                if (code < 4) {
                    throw _constructError(String.format(
                            "Malformed %d-byte UTF-8 character at the end of Unicode text block", code));
                }
            }
            switch (code) {
            case 1:
                i = ((i & 0x1F) << 6) | (inputBuf[inPtr++] & 0x3F);
                break;
            case 2:
                i = ((i & 0x0F) << 12)
                   | ((inputBuf[inPtr++] & 0x3F) << 6)
                   | (inputBuf[inPtr++] & 0x3F);
                break;
            case 3:
                i = ((i & 0x07) << 18)
                 | ((inputBuf[inPtr++] & 0x3F) << 12)
                 | ((inputBuf[inPtr++] & 0x3F) << 6)
                 | (inputBuf[inPtr++] & 0x3F);
                // note: this is the codepoint value; need to split, too
                i -= 0x10000;
                outBuf[outPtr++] = (char) (0xD800 | (i >> 10));
                i = 0xDC00 | (i & 0x3FF);
                break;
            default: // invalid
                _reportError(String.format("Invalid byte 0x2X in Unicode text block", i));
            }
            outBuf[outPtr++] = (char) i;
        } while (inPtr < end);
        return _textBuffer.setCurrentAndReturn(outPtr);
    }

    private final void _finishLongText(int len) throws IOException
    {
        char[] outBuf = _textBuffer.emptyAndGetCurrentSegment();
        int outPtr = 0;
        final int[] codes = sUtf8UnitLengths;
        int outEnd = outBuf.length;

        while (--len >= 0) {
            int c = _nextByteGuaranteed() & 0xFF;
            int code = codes[c];
            if (code == 0 && outPtr < outEnd) {
                outBuf[outPtr++] = (char) c;
                continue;
            }
            if ((len -= code) < 0) { // may need to improve error here but...
                throw _constructError("Malformed UTF-8 character at end of long (non-chunked) text segment");
            }

            switch (code) {
            case 0:
                break;
            case 1: // 2-byte UTF
                {
                    int d = _nextByteGuaranteed();
                    if ((d & 0xC0) != 0x080) {
                        _reportInvalidOther(d & 0xFF, _inputPtr);
                    }
                    c = ((c & 0x1F) << 6) | (d & 0x3F);
                }
                break;
            case 2: // 3-byte UTF
                c = _decodeUTF8_3(c);
                break;
            case 3: // 4-byte UTF
                c = _decodeUTF8_4(c);
                if (outPtr >= outBuf.length) {
                    outBuf = _textBuffer.finishCurrentSegment();
                    outPtr = 0;
                    outEnd = outBuf.length;
                }
                // Let's add first part right away:
                outBuf[outPtr++] = (char) (0xD800 | (c >> 10));
                c = 0xDC00 | (c & 0x3FF);
                // And let the other char output down below
                break;
            default:
                // Is this good enough error message?
                _reportInvalidInitial(c);
            }
            // Need more room?
            if (outPtr >= outEnd) {
                outBuf = _textBuffer.finishCurrentSegment();
                outPtr = 0;
                outEnd = outBuf.length;
            }
            // Ok, let's add char to output:
            outBuf[outPtr++] = (char) c;
        }
        _textBuffer.setCurrentLength(outPtr);
    }

    private final int _decodeUTF8_3(int c1) throws IOException
    {
        c1 &= 0x0F;
        int d = _nextByteGuaranteed();
        if ((d & 0xC0) != 0x080) {
            _reportInvalidOther(d & 0xFF, _inputPtr);
        }
        int c = (c1 << 6) | (d & 0x3F);
        d = _nextByteGuaranteed();
        if ((d & 0xC0) != 0x080) {
            _reportInvalidOther(d & 0xFF, _inputPtr);
        }
        c = (c << 6) | (d & 0x3F);
        return c;
    }

    private final int _decodeUTF8_4(int c) throws IOException
    {
        int d = _nextByteGuaranteed();
        if ((d & 0xC0) != 0x080) {
            _reportInvalidOther(d & 0xFF, _inputPtr);
        }
        c = ((c & 0x07) << 6) | (d & 0x3F);
        d = _nextByteGuaranteed();
        if ((d & 0xC0) != 0x080) {
            _reportInvalidOther(d & 0xFF, _inputPtr);
        }
        c = (c << 6) | (d & 0x3F);
        d = _nextByteGuaranteed();
        if ((d & 0xC0) != 0x080) {
            _reportInvalidOther(d & 0xFF, _inputPtr);
        }
        return ((c << 6) | (d & 0x3F)) - 0x10000;
    }

    private void _reportInvalidInitial(int mask) throws JsonParseException {
        _reportError("Invalid UTF-8 start byte 0x"+Integer.toHexString(mask));
    }

    private void _reportInvalidOther(int mask) throws JsonParseException {
        _reportError("Invalid UTF-8 middle byte 0x"+Integer.toHexString(mask));
    }

    private void _reportInvalidOther(int mask, int ptr) throws JsonParseException {
        _inputPtr = ptr;
        _reportInvalidOther(mask);
    }

    /*
    /**********************************************************
    /* Methods for AvroReadContext implementations: decoding Bytes
    /**********************************************************
     */

    @Override
    public JsonToken decodeBytes() throws IOException {
        int len = decodeInt();
        if (len <= 0) {
            if (len < 0) {
                _reportError("Invalid length indicator for Bytes: "+len);
            }
            _binaryValue = NO_BYTES;
        } else {
            byte[] b = new byte[len];
            // this is simple raw read, safe to use:
            _read(b, 0, len);
            _binaryValue = b;
        }
        return JsonToken.VALUE_EMBEDDED_OBJECT;
    }

    @Override
    public void skipBytes() throws IOException {
        int len = decodeInt();
        if (len <= 0) {
            if (len < 0) {
                _reportError("Invalid length indicator for Bytes: "+len);
            }
            _binaryValue = NO_BYTES;
        } else {
            _skip(len);
        }
    }

    @Override
    public JsonToken decodeFixed(int size) throws IOException {
        byte[] data = new byte[size];
        _read(data, 0, size);
        _binaryValue = data;
        return JsonToken.VALUE_EMBEDDED_OBJECT;
    }

    @Override
    public void skipFixed(int size) throws IOException {
        _skip(size);
    }

    private final void _read(byte[] target, int offset, int len) throws IOException
    {
        int ptr = _inputPtr;
        int available = _inputEnd - ptr;
        if (len <= available) { // already got it all?
            System.arraycopy(_inputBuffer, ptr, target, offset, len);
            _inputPtr = ptr + len;
            return;
        }
        // only had some, copy whatever there is
        System.arraycopy(_inputBuffer, ptr, target, offset, available);
        _inputPtr = ptr + available;
        offset += available;
        int left = len - available;
        // and rest we can read straight from input
        do {
            int count = _inputStream.read(target, offset, left);
            if (count <= 0) {
                _reportError("Needed to read "+len+" bytes, reached end-of-input after reading "+(len - left));
            }
            offset += count;
            left -= count;
        } while (left > 0);
    }

    private final void _skip(int len) throws IOException
    {
        int ptr = _inputPtr;
        int available = _inputEnd - ptr;
        int left = len - available;
        if (left <= 0) {
            _inputPtr = ptr + len;
            return;
        }
        _inputPtr = _inputEnd; // mark all used, whatever it was
        if (_inputStream != null) {
            do {
                int skipped = (int) _inputStream.skip(left);
                if (skipped < 0) {
                    break;
                }
                left -= skipped;
            } while (left > 0);
        }
        if (left > 0) {
            _reportError("Only able to skip "+(len-left)+" bytes before end-of-input (needed "+len+")");
        }
    }

    private final void _skipL(long len) throws IOException
    {
        int ptr = _inputPtr;
        int available = _inputEnd - ptr;
        long left = len - available;
        if (left <= 0L) {
            _inputPtr = ptr + (int)  len;
            return;
        }
        _inputPtr = _inputEnd; // mark all used, whatever it was
        if (_inputStream != null) {
            do {
                int skipped = (int) _inputStream.skip(left);
                if (skipped < 0) {
                    break;
                }
                left -= skipped;
            } while (left > 0L);
        }
        if (left > 0L) {
            _reportError("Only able to skip "+(len-left)+" bytes before end-of-input (needed "+len+")");
        }
    }

    /*
    /**********************************************************
    /* Methods for AvroReadContext implementations: decoding Arrays
    /**********************************************************
     */

    @Override
    public long decodeArrayStart() throws IOException {
        return _decodeChunkLength();
    }

    @Override
    public long decodeArrayNext() throws IOException {
        return _decodeChunkLength();
    }

    @Override
    public long skipArray() throws IOException {
        return _skipChunkElements();
    }

    // used for Arrays and Maps, first and other chunks
    private final long _decodeChunkLength() throws IOException {
        long result = decodeLong();
        if (result < 0) {
            skipLong(); // Consume byte-count if present
            result = -result;
        }
        return result;
    }

    private long _skipChunkElements() throws IOException {
        int result = decodeInt();
        while (result < 0) {
            long bytecount = decodeLong();
            _skipL(bytecount);
            result = decodeInt();
        }
        return result;
    }

    /*
    /**********************************************************
    /* Methods for AvroReadContext implementations: decoding Maps
    /**********************************************************
     */

    @Override
    public String decodeMapKey() throws IOException {
        decodeString();
        return _textBuffer.contentsAsString();
    }

    @Override
    public long decodeMapStart() throws IOException {
        return _decodeChunkLength();
    }

    @Override
    public long decodeMapNext() throws IOException {
        return _decodeChunkLength();
    }

    @Override
    public long skipMap() throws IOException {
        return _skipChunkElements();
    }

    /*
    /**********************************************************
    /* Methods for AvroReadContext implementations: misc
    /**********************************************************
     */

    @Override
    public JsonToken decodeBoolean() throws IOException {
        int b = _nextByteGuaranteed();
        // As per Avro default impl: only `1` recognized as true (unlike
        // "C-style" 0 == false, others true)
        return (b == 1) ? JsonToken.VALUE_TRUE : JsonToken.VALUE_FALSE;
    }

    @Override
    public void skipBoolean() throws IOException {
        _skipByteGuaranteed();
    }

    @Override
    public int decodeIndex() throws IOException {
        return (_branchIndex = decodeInt());
    }

    @Override
    public int decodeEnum() throws IOException {
        return (_enumIndex = decodeInt());
    }

    @Override
    public boolean checkInputEnd() throws IOException {
        if (_closed) {
            return true;
        }
        if (_inputPtr < _inputEnd) {
            return false;
        }
        return !_loadMore();
    }

    /*
    /**********************************************************
    /* Low-level methods: setting values from defaults
    /**********************************************************
     */

    @Override
    protected JsonToken setString(String str) throws IOException {
        _textBuffer.resetWithString(str);
        return JsonToken.VALUE_STRING;
    }

    /*
    /**********************************************************
    /* Low-level reading, other
    /**********************************************************
     */

    private final byte _nextByteGuaranteed() throws IOException
    {
        int ptr = _inputPtr;
        if (ptr < _inputEnd) {
            byte b = _inputBuffer[ptr];
            _inputPtr = ptr+1;
            return b;
        }
        return _nextByteGuaranteed2();
    }

    private final byte _nextByteGuaranteed2() throws IOException
    {
        if (!_loadMore()) { _reportInvalidEOF(); }
        return _inputBuffer[_inputPtr++];
    }

    protected final void _loadMoreGuaranteed() throws IOException {
        if (!_loadMore()) { _reportInvalidEOF(); }
    }

    private final void _skipByteGuaranteed() throws IOException
    {
        int ptr = _inputPtr;
        if (ptr < _inputEnd) {
            _inputPtr = ptr+1;
            return;
        }
        _loadMoreGuaranteed();
        _inputPtr += 1;
    }

    protected final boolean _loadMore() throws IOException
    {
        //_currInputRowStart -= _inputEnd;
        if (_inputStream != null) {
            int count = _inputStream.read(_inputBuffer, 0, _inputBuffer.length);
            _currInputProcessed += _inputEnd;
            _inputPtr = 0;
            if (count > 0) {
                _inputEnd = count;
                return true;
            }
            // important: move pointer to same as end, to keep location accurate
            _inputEnd = 0;
            // End of input
            _closeInput();
            // Should never return 0, so let's fail
            if (count == 0) {
                throw new IOException("InputStream.read() returned 0 characters when trying to read "+_inputBuffer.length+" bytes");
            }
        }
        return false;
    }

    /**
     * Helper method that will try to load at least specified number bytes in
     * input buffer, possible moving existing data around if necessary
     */
    protected final void _loadToHaveAtLeast(int minAvailable) throws IOException
    {
        // Need to move remaining data in front?
        int amount = _inputEnd - _inputPtr;
        _currInputProcessed += _inputPtr;
        if (_inputPtr > 0) {
            if (amount > 0) {
                //_currInputRowStart -= _inputPtr;
                System.arraycopy(_inputBuffer, _inputPtr, _inputBuffer, 0, amount);
                _inputEnd = amount;
            } else {
                _inputEnd = 0;
            }
        }
        _inputPtr = 0;
        // No input stream, no leading (either we are closed, or have non-stream input source)
        if (_inputStream == null) {
            _reportError("Needed to read %d bytes, reached end-of-input", minAvailable);
            return; // never gets here, but sec tools complain without
        }
        while (_inputEnd < minAvailable) {
            int count = _inputStream.read(_inputBuffer, _inputEnd, _inputBuffer.length - _inputEnd);
            if (count < 1) {
                // End of input
                _closeInput();
                // Should never return 0, so let's fail
                if (count == 0) {
                    throw new IOException("InputStream.read() returned 0 characters when trying to read "+amount+" bytes");
                }
                _reportError("Needed to read %d bytes, missed %d before end-of-input",
                        minAvailable, minAvailable);
            }
            _inputEnd += count;
        }
    }

    private void _reportInvalidNegative(int v) throws IOException
    {
        _reportError("Invalid negative byte %x at end of VInt", v);
    }
}