All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.dyuproject.protostuff.JsonXByteArrayInput Maven / Gradle / Ivy

The newest version!
//========================================================================
//Copyright 2022 David Yu
//------------------------------------------------------------------------
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at 
//http://www.apache.org/licenses/LICENSE-2.0
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
//========================================================================

package com.dyuproject.protostuff;

import java.io.IOException;

/**
 * An byte array based input used for reading data with json format.
 *
 * @author David Yu
 * @created Mar 24, 2022
 */
public final class JsonXByteArrayInput implements Input
{
    static final byte
            VALUE_NUM = 0,
            VALUE_NULL = 'n',
            START_OBJECT = '{',
            END_OBJECT = '}',
            START_ARRAY = '[',
            END_ARRAY = ']',
            COMMA = ',',
            COLON = ':',
            QUOTE = '"';
    
    private static final int[] intDigits = new int[127];
    private static final int[] floatDigits = new int[127];
    static final int END_OF_NUMBER = -2;
    static final int DOT_IN_NUMBER = -3;
    static final int INVALID_CHAR_FOR_NUMBER = -1;
    // for string
    private static final int[] hexDigits = new int['f' + 1];
    // for skip
    private static final byte[] breaks = new byte[256];
    
    static
    {
        for (int i = 0; i < floatDigits.length; i++)
        {
            floatDigits[i] = INVALID_CHAR_FOR_NUMBER;
            intDigits[i] = INVALID_CHAR_FOR_NUMBER;
        }
        for (int i = '0'; i <= '9'; ++i)
        {
            floatDigits[i] = (i - '0');
            intDigits[i] = (i - '0');
        }
        floatDigits[','] = END_OF_NUMBER;
        floatDigits[']'] = END_OF_NUMBER;
        floatDigits['}'] = END_OF_NUMBER;
        floatDigits[' '] = END_OF_NUMBER;
        floatDigits['.'] = DOT_IN_NUMBER;

        for (int i = 0; i < hexDigits.length; i++)
            hexDigits[i] = -1;
        
        for (int i = '0'; i <= '9'; ++i)
            hexDigits[i] = (i - '0');
        
        for (int i = 'a'; i <= 'f'; ++i)
            hexDigits[i] = ((i - 'a') + 10);
        
        for (int i = 'A'; i <= 'F'; ++i)
            hexDigits[i] = ((i - 'A') + 10);
        
        breaks[' '] = 1;
        breaks['\t'] = 1;
        breaks['\n'] = 1;
        breaks['\r'] = 1;
        breaks[','] = 2;
        breaks['}'] = 2;
        breaks[']'] = 2;
    }
    
    private byte[] buf;
    private int start, offset, limit;
    final boolean allowQuotedInt64;
    final byte alphaNumericPrefixChar;
    final char[] charBuf;
    final int charOffset, charLimit;
    final boolean charBufAsLimit;
    final int previewMaxLen;
    private byte token = 0;
    
    // resettable
    private boolean lastRepeated;
    private int lastNumber;
    
    public JsonXByteArrayInput(byte[] buf, int offset, int len, boolean allowQuotedInt64,
            byte alphaNumericPrefixChar,
            char[] charBuf, int charOffset, int charLen, boolean charBufAsLimit,
            int previewMaxLen)
    {
        this.buf = buf;
        this.start = offset;
        this.offset = offset;
        this.limit = offset + len;
        this.allowQuotedInt64 = allowQuotedInt64;
        this.alphaNumericPrefixChar = alphaNumericPrefixChar;
        this.charBuf = charBuf;
        this.charOffset = charOffset;
        this.charLimit = charOffset + charLen;
        // whether we should error when the limit is exceeded
        this.charBufAsLimit = charBufAsLimit;
        this.previewMaxLen = previewMaxLen;
    }
    
    /**
     * Returns the current offset;
     */
    int currentOffset()
    {
        return offset;
    }
    
    /**
     * Gets the last field number read.
     */
    public int getLastNumber()
    {
        return lastNumber;
    }
    
    /**
     * Returns true if the last read field was a repeated field.
     */
    public boolean isLastRepeated()
    {
        return lastRepeated;
    }
    
    /**
     * Resets this input.
     */
    public JsonXByteArrayInput reset()
    {
        //depth = 0;
        token = 0;
        lastRepeated = false;
        lastNumber = 0;
        return this;
    }
    
    /**
     * Resets this input.
     */
    public JsonXByteArrayInput reset(int offset)
    {
        this.start = offset;
        this.offset = offset;
        return reset();
    }
    
    /**
     * Resets this input.
     */
    public JsonXByteArrayInput reset(int offset, int len)
    {
        this.start = offset;
        this.offset = offset;
        this.limit = offset + len;
        return reset();
    }
    
    /**
     * Sets the offset and limit (which effectively re-uses this input).
     */
    public JsonXByteArrayInput setBounds(int offset, int limit)
    {
        this.start = offset;
        this.offset = offset;
        this.limit = limit;
        return reset();
    }
    
    /**
     * Resets this input.
     */
    public JsonXByteArrayInput reset(byte[] buf, int offset, int len)
    {
        this.buf = buf;
        return reset(offset, len);
    }
    
    // ==================================================
    
    private static int findTokenOrEnd(byte token, int offset, int max,
            byte[] buf, int limit)
    {
        if (offset == limit)
            return limit;
        
        for (int i = 0, remaining = Math.min(limit - offset, max);
                i < remaining && token != buf[offset];
                i++, offset++);
            
        return offset;
    }
    
    private static int rfindTokenOrStart(byte token, int offset, int max, int maxCount, int diff,
            byte[] buf, final int start, char[] out, int outOffset)
    {
        out[outOffset] = 0;
        final int remaining = Math.min(offset - start, max);
        if (remaining == 0)
            return 0;
        
        int i = 0, seenCount = 0;
        for (int end = offset; i < remaining; i++, offset--)
        {
            if (token == buf[offset])
            {
                if (1 == ++seenCount)
                    out[outOffset] = (char)(end - offset + diff);
                
                if (seenCount == maxCount)
                    break;
            }
        }
        
        return i == remaining ? 0 : offset + diff;
    }
    
    private final JsonInputException reportError(String op, String msg) {
        final StringBuilder sb = new StringBuilder()
            .append(op)
            .append(':')
            .append(' ')
            .append(msg)
            .append(" at offset: ")
            .append(offset - 1)
            .append('\n');
        
        if (previewMaxLen == 0)
            return new JsonInputException(sb.toString());
        
        final int diff = 1;
        int end = findTokenOrEnd((byte)'\n', offset, 16, buf, limit);
        int start = rfindTokenOrStart((byte)'\n', offset - 1, 250, 5, diff, buf, this.start,
                charBuf, charOffset);
        int len = end - start;
        if (len > previewMaxLen)
            return new JsonInputException(sb.toString());
        
        String preview;
        try
        {
            preview = new String(buf, start, len, "UTF-8");
        }
        catch (Exception e)
        {
            preview = "";
        }
        
        if (!preview.isEmpty())
        {
            sb.append(preview).append('\n');
            len = 0xFFFF & charBuf[charOffset];
            if (len == 0)
            {
                // no newlines
                end = offset;
                while (end != ++start) sb.append('-');
            }
            else
            {
                len -= diff;
                while (0 < --len) sb.append('-');
            }
            sb.append('^');
        }
        return new JsonInputException(sb.toString());
    }
    
    private String tokenToString()
    {
        return new String(new char[]{ (char)token });
    }
    /*
    private String tokenToString(byte b)
    {
        return new String(new char[]{ (char)b });
    }
    */
    byte currentToken()
    {
        return token;
    }
    
    byte nextToken() throws IOException
    {
        int i = offset;
        byte c;
        for (;;)
        {
            c = buf[i++];
            switch (c)
            {
                case ' ':
                case '\n':
                case '\r':
                case '\t':
                    if (i == limit)
                        throw reportError("nextToken", "Missing \"}\"");
                    continue;
                default:
                    offset = i;
                    token = c;
                    return c;
            }
        }
    }
    
    private byte skip() throws IOException
    {
        switch (token) {
            case '"':
                //*IterImpl.*/skipString(offset + 1);
                return skipUntilBreakToken(skipString(offset), true);
            case '-':
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                //*IterImpl.*/skipUntilBreak(offset);
                return skipUntilBreakToken(offset, true);
            case 't':
            case 'n':
                //*IterImpl.*/skipFixedBytes(iter, 3); // true or null
                return skipUntilBreakToken(offset + 3, true);
            case 'f':
                ///*IterImpl.*/skipFixedBytes(iter, 4); // false
                return skipUntilBreakToken(offset + 4, true);
            case '[':
                return skipUntilBreakToken(skipArray(offset), true);
            case '{':
                return skipUntilBreakToken(skipObject(offset), true);
            default:
                throw reportError("skip", "could not skip: " + tokenToString());
        }
    }
    
    private int skipArray(int offset) throws IOException
    {
        int level = 1;
        int i = offset;
        while (i < limit)
        {
            switch (buf[i++])
            {
                case '"': // If inside string, skip it
                    i = skipString(i);
                    break;
                case '[': // If open symbol, increase level
                    level++;
                    break;
                case ']': // If close symbol, decrease level
                    // If we have returned to the original level, we're done
                    if (0 == --level)
                        return i;
                    break;
            }
        }
        throw reportError("skipArray", "incomplete array");
    }
    
    private int skipObject(int offset) throws IOException
    {
        int level = 1;
        int i = offset;
        while (i < limit)
        {
            switch (buf[i++])
            {
                case '"': // If inside string, skip it
                    i = skipString(i);
                    break;
                case '{': // If open symbol, increase level
                    level++;
                    break;
                case '}': // If close symbol, decrease level
                    // If we have returned to the original level, we're done
                    if (0 == --level)
                        return i;
                    break;
            }
        }
        throw reportError("skipObject", "Incomplete object");
    }
    
    /**
     * Does not end with shitespace.
     * @throws IOException
     */
    private byte skipUntilBreakToken(int offset, boolean nextTokenOnComma) throws IOException
    {
        // true, false, null,   number
        while (offset < limit && 2 != breaks[0xFF & buf[offset]]) offset++;
        if (offset >= limit)
            throw reportError("skipUntilBreakToken", "Truncated");
        
        byte c = buf[offset];
        this.offset = ++offset;
        
        if (!nextTokenOnComma || COMMA != c)
            token = c;
        else if (offset != limit)
            c = nextToken();
        else
            throw reportError("skipUntilBreakToken", "Truncated");
        return c;
    }
    
    private int skipString(int start) throws IOException
    {
        int end = findStringEnd(buf, start, limit);
        if (end == -1)
            throw reportError("skipString", "Incomplete string");
        return end + 1;
    }
    
    static int findStringEnd(byte[] input, int offset, int limit)
    {
        byte c;
        boolean escaped = false;
        for (int i = offset, j = 0; i < limit; i++)
        {
            if ('"' != (c = input[i]))
            {
                escaped = c == '\\';
                continue;
            }
            
            if (!escaped)
                return i;
            
            for (j = i - 1;;)
            {
                if (j < offset || '\\' != input[j--])
                {
                    // even number of backslashes
                    // either end of buffer, or " found
                    return i;
                }
                if (j < offset || '\\' != input[j--])
                {
                    // odd number of backslashes
                    // it is \" or \\\"
                    break;
                }
            }
        }
        return -1;
    }
    
    int readNumericField() throws IOException
    {
        if (offset == limit)
            throw reportError("readField", "Truncated");
        
        if (alphaNumericPrefixChar != 0)
        {
            if (alphaNumericPrefixChar != buf[offset])
            {
                throw reportError("readField", "Invalid field prefix char: " +
                        new String(new char[]{(char)buf[offset]}));
            }
            if (limit == ++offset)
                throw reportError("readField", "Truncated");
        }
            
        final int value = parseInt(buf[offset++]);
        if (QUOTE != token)
            throw reportError("readField", "Invalid field number");
        if (COLON != nextToken() || offset == limit)
            throw reportError("readField", "Invalid field value for the field: " + value);
        for (int b, c = 0xFF & nextToken(); 0 != (b = breaks[c]); c = 0xFF & nextToken())
        {
            if (b == 2 || offset == limit)
                throw reportError("readField", "Invalid field value for the field: " + value);
        }
        return value;
    }
    
    // ==================================================

    public  void handleUnknownField(int fieldNumber, Schema schema) throws IOException
    {
        if (lastRepeated)
        {
            lastRepeated = false;
            skipUntilBreakToken(skipArray(offset - 1), true);
        }
        else
        {
            skip();
        }
    }
    
    public  int readFieldNumber(final Schema schema) throws IOException
    {
        byte c = token;
        if (lastRepeated)
        {
            if (VALUE_NULL != c)
                return lastNumber;
            
            // ignore null fields
            while (VALUE_NULL == (c = skipUntilBreakToken(offset, true)));
            
            if (END_ARRAY != c)
                return lastNumber;
            
            lastNumber = 0;
            lastRepeated = false;
        }
        
        for (int fieldNumber;;)
        {
            if (END_OBJECT == c)
                return 0;
            
            if (QUOTE != c)
            {
                throw reportError("readFieldNumber", "Expected token: $field: but was " + 
                        tokenToString() + " on message " + schema.messageFullName());
            }
            
            fieldNumber = readNumericField();
            c = token;
            if (VALUE_NULL == c)
            {
                // ignore null field
                c = skipUntilBreakToken(offset + 3, true);
                continue;
            }
            if (fieldNumber < 1)
            {
                c = skip();
                continue;
            }
            
            if (START_ARRAY != c)
            {
                lastRepeated = false;
            }
            else if (END_ARRAY != nextToken())
            {
                lastRepeated = true;
            }
            else
            {
                // ignore empty array field
                c = skipUntilBreakToken(offset, true);
                continue;
            }
            lastNumber = fieldNumber;
            return fieldNumber;
        }
    }
    
    private int parseInt(byte b) throws IOException
    {
        boolean isNeg = b == '-';
        if (isNeg)
        {
            if (offset == limit)
                throw reportError("readInt32", "Truncated");
            token = b = buf[offset++];
        }
        if (b < '0' || b > '9')
            throw reportError("readInt32", "Expected 0~9");//numberError()
        int x = '0' - b;
        if (x == 0)
        {
            if (offset == limit)
                throw reportError("readInt32", "Truncated");
            token = b = buf[offset++];
            if (b == '.')
                throw reportError("readInt32", "Invalid integer");
            if (b >= '0' && b <= '9')
                throw reportError("readInt32", "Leading zero invalid");
            return x;
        }
        int pos = offset;
        while (pos < limit && '0' <= (b = buf[pos]) && b <= '9')
        {
            if (x < -214748364 || 0 < (x = x * 10 + ('0' - b)))
                throw reportError("readInt32", "Invalid number (overflow)");
            pos++;
        }
        if (pos == limit)
            throw reportError("readInt32", "Truncated");
        
        token = b;
        offset = pos + 1;
        
        if ((b | 0x20) == 'e' || b == '.')
            throw reportError("readInt32", "Invalid integer");
        if (!isNeg)
        {
            if (x == -2147483648) throw reportError("readInt32", "Invalid integer (overflow)");
            x = -x;
        }
        return x;
    }
    
    private long parseLong(byte b) throws IOException
    {
        boolean isNeg = b == '-';
        if (isNeg)
        {
            if (offset == limit)
                throw reportError("readInt64", "Truncated");
            token = b = buf[offset++];
        }
        if (b < '0' || b > '9')
            throw reportError("readInt64", "Expected 0~9");//numberError()
        long x = '0' - b;
        if (x == 0)
        {
            if (offset == limit)
                throw reportError("readInt64", "Truncated");
            token = b = buf[offset++];
            if (b == '.')
                throw reportError("readInt64", "Invalid integer");
            if (b >= '0' && b <= '9')
                throw reportError("readInt64", "Leading zero invalid");
            return x;
        }
        int pos = offset;
        while (pos < limit && '0' <= (b = buf[pos]) && b <= '9')
        {
            if (x < -922337203685477580L || 0 < (x = x * 10 + ('0' - b)))
                throw reportError("readInt64", "Invalid integer (overflow)");
            pos++;
        }
        if (pos == limit)
            throw reportError("readInt64", "Truncated");
        
        token = b;
        offset = pos + 1;
        
        if ((b | 0x20) == 'e' || b == '.')
            throw reportError("readInt64", "Invalid integer");//numberError(pos)
        if (!isNeg)
        {
            if (x == -9223372036854775808L) throw reportError("readInt64", "Invalid integer (overflow)");
            x = -x;
        }
        return x;
    }
    
    public boolean readBool() throws IOException
    {
        final boolean value;
        byte c;
        switch (token)
        {
            case 'f':
                c = skipUntilBreakToken(offset + 4, true);
                value = false;
                break;
            case 't':
                c = skipUntilBreakToken(offset + 3, true);
                value = true;
                break;
            default:
                throw reportError("readBool", "Expected token: true/false but was " + tokenToString());
        }
        if (lastRepeated && c == END_ARRAY)
        {
            skipUntilBreakToken(offset, true);
            lastRepeated = false;
        }
        return value;
    }

    public double readDouble() throws IOException
    {
        final int start = offset - 1;
        byte c = skipUntilBreakToken(offset, false);
        int lastIdx = offset - 1 - 1; // exclude the break char
        // skip trailing whitespace
        while (Character.isWhitespace((char)buf[lastIdx])) lastIdx--;
        
        final String str;
        if (QUOTE != buf[start])
        {
            str = new String(buf, start, lastIdx + 1 - start);
        }
        else if (QUOTE == buf[lastIdx])
        {
            // exclude quotes
            str = new String(buf, start + 1, lastIdx - 1 - start);   
        }
        else
        {
            throw reportError("readDouble", "Invalid number/string");
        }
        final double value;
        if ("infinity".equals(str))
            value = Double.POSITIVE_INFINITY;
        else if ("-infinity".equals(str))
            value = Double.NEGATIVE_INFINITY;
        else
            value = Double.parseDouble(str);
        
        if (c == COMMA)
        {
            if (offset == limit)
                throw reportError("readDouble", "Truncated");
            c = nextToken();
        }
        
        if (lastRepeated && c == END_ARRAY)
        {
            skipUntilBreakToken(offset, true);
            lastRepeated = false;
        }
        
        return value;
    }

    public int readEnum() throws IOException
    {
        return readInt32();
    }
    
    public int readEnumIdx(EnumMapping mapping) throws IOException
    {
        final int start = offset - 1;
        byte c = skipUntilBreakToken(offset, false);
        int lastIdx = offset - 1 - 1; // exclude the break char
        // skip trailing whitespace
        while (Character.isWhitespace((char)buf[lastIdx])) lastIdx--;
        
        final Integer idx;
        if (QUOTE != buf[start])
        {
            idx = mapping.numberIdxMap.get(NumberParser.parseInt(buf, start, lastIdx + 1 - start, 10));
        }
        else if (QUOTE == buf[lastIdx])
        {
            // exclude quotes
            idx = mapping.nameIdxMap.get(new String(buf, start + 1, lastIdx - 1 - start));
        }
        else
        {
            throw reportError("readEnumIdx", "Invalid number/string");
        }
        
        if (c == COMMA)
        {
            if (offset == limit)
                throw reportError("readEnumIdx", "Truncated");
            c = nextToken();
        }
        
        if (lastRepeated && c == END_ARRAY)
        {
            skipUntilBreakToken(offset, true);
            lastRepeated = false;
        }
        
        return idx == null ? -1 : idx.intValue();
    }

    public int readFixed32() throws IOException
    {
        return readInt32();
    }

    public long readFixed64() throws IOException
    {
        return readInt64();
    }

    public float readFloat() throws IOException
    {
        return (float)readDouble();
    }

    public int readInt32() throws IOException
    {
        final int value = parseInt(token);
        byte c = token;
        if (0 == breaks[0xFF & c])
            throw reportError("readInt32", "Expected ',]}' but was " + tokenToString());
        
        if (c == COMMA)
        {
            if (offset == limit)
                throw reportError("readInt32", "Truncated");
            c = nextToken();
        }
        if (lastRepeated && c == END_ARRAY)
        {
            skipUntilBreakToken(offset, true);
            lastRepeated = false;
        }
        return value;
    }
    
    public long readInt64() throws IOException
    {
        if (allowQuotedInt64)
            return flexReadInt64();
        
        final long value = parseLong(token);
        byte c = token;
        if (0 == breaks[0xFF & c])
            throw reportError("readInt64", "Expected ',]}' but was " + tokenToString());
        
        if (c == COMMA)
        {
            if (offset == limit)
                throw reportError("readInt64", "Truncated");
            c = nextToken();
        }
        if (lastRepeated && c == END_ARRAY)
        {
            skipUntilBreakToken(offset, true);
            lastRepeated = false;
        }
        return value;
    }
    
    private long flexReadInt64() throws IOException
    {
        final int start = offset - 1;
        byte c = skipUntilBreakToken(offset, false);
        int lastIdx = offset - 1 - 1; // exclude the break char
        // skip trailing whitespace
        while (Character.isWhitespace((char)buf[lastIdx])) lastIdx--;
        
        final long value;
        if (QUOTE != buf[start])
        {
            value = NumberParser.parseLong(buf, start, lastIdx + 1 - start, 10);
        }
        else if (QUOTE == buf[lastIdx])
        {
            // exclude quotes
            value = Double.doubleToRawLongBits(Double.parseDouble(
                    new String(buf, start + 1, lastIdx - 1 - start)));
        }
        else
        {
            throw reportError("readLong", "Invalid number/string");
        }
        
        if (c == COMMA)
        {
            if (offset == limit)
                throw reportError("readLong", "Truncated");
            c = nextToken();
        }
        
        if (lastRepeated && c == END_ARRAY)
        {
            skipUntilBreakToken(offset, true);
            lastRepeated = false;
        }
        
        return value;
    }

    public int readSFixed32() throws IOException
    {
        return readInt32();
    }

    public long readSFixed64() throws IOException
    {
        return readInt64();
    }

    public int readSInt32() throws IOException
    {
        return readInt32();
    }

    public long readSInt64() throws IOException
    {
        return readInt64();
    }
    
    public int readUInt32() throws IOException
    {
        return readInt32();
    }

    public long readUInt64() throws IOException
    {
        return readInt64();
    }
    
    public ByteString readBytes() throws IOException
    {
        return ByteString.wrap(readByteArray());
    }
    
    public byte[] readByteArray() throws IOException
    {
        if (QUOTE != token)
            throw reportError("readByteArray", "Expected base64 string");
        
        final int start = offset;
        final int len = (offset = skipString(start)) - 1 - start; // exclude trailing quote
        token = QUOTE;
        final byte[] value = B64Code.decode(buf, start, len);
        
        byte b = skipUntilBreakToken(offset, true);
        if (lastRepeated && END_ARRAY == b)
        {
            skipUntilBreakToken(offset, true);
            lastRepeated = false;
        }
        
        return value;
    }
    
    public String readString() throws IOException
    {
        if (QUOTE != token)
            throw reportError("readString", "Expected string");
        
        final String value = parseString();
    
        byte b = skipUntilBreakToken(offset, true);
        if (lastRepeated && END_ARRAY == b)
        {
            skipUntilBreakToken(offset, true);
            lastRepeated = false;
        }
        
        return value;
    }

    public  T mergeObject(T value, final Schema schema) throws IOException
    {
        if (START_OBJECT != token)
        {
            throw reportError("mergeObject", "Expected token: { but was " + 
                    tokenToString() + " on " + lastNumber + " of message " + 
                    schema.messageFullName());
        }
        
        nextToken();
        
        final int lastNumber = this.lastNumber;
        final boolean lastRepeated = this.lastRepeated;
        
        // reset
        this.lastRepeated = false;
        
        if (value == null)
            value = schema.newMessage();
        
        schema.mergeFrom(this, value);
        
        if (END_OBJECT != token)
        {
            throw reportError("mergeObject", "Expected token: } but was " + 
                    tokenToString() + " on " + lastNumber + " of message " + 
                    schema.messageFullName());
        }
        
        if (!schema.isInitialized(value))
            throw new UninitializedMessageException(value, schema);
        
        // restore state
        this.lastNumber = lastNumber;
        this.lastRepeated = lastRepeated;
        
        if (offset == limit)
            throw reportError("readObject", "Truncated");
        
        byte c = skipUntilBreakToken(offset, true);
        if (lastRepeated && END_ARRAY == c)
        {
            skipUntilBreakToken(offset, true);
            this.lastRepeated = false;
        }
        
        return value;
    }
    
    public void transferByteRangeTo(Output output, boolean utf8String, int fieldNumber,
            boolean repeated) throws IOException
    {
        if (utf8String)
            output.writeString(fieldNumber, readString(), repeated);
        else
            output.writeByteArray(fieldNumber, readByteArray(), repeated);
    }
    
    public void transferEnumTo(Output output, EnumMapping mapping,
            int fieldNumber, boolean repeated) throws IOException
    {
        final int start = offset - 1;
        byte c = skipUntilBreakToken(offset, false);
        int lastIdx = offset - 1 - 1; // exclude the break char
        // skip trailing whitespace
        while (Character.isWhitespace((char)buf[lastIdx])) lastIdx--;
        
        int number = 0;
        String name = null;
        if (QUOTE != buf[start])
        {
            number = NumberParser.parseInt(buf, start, lastIdx + 1 - start, 10);
        }
        else if (QUOTE == buf[lastIdx])
        {
            // TODO could use transferByteRangeTo since this is ascii
            // exclude quotes
            name = new String(buf, start + 1, lastIdx - 1 - start);
        }
        else
        {
            throw reportError("readEnumIdx", "Invalid number/string");
        }
        
        if (c == COMMA)
        {
            if (offset == limit)
                throw reportError("readEnumIdx", "Truncated");
            c = nextToken();
        }
        
        if (lastRepeated && c == END_ARRAY)
        {
            skipUntilBreakToken(offset, true);
            lastRepeated = false;
        }
        
        Integer idx;
        if (name == null)
        {
            if (!output.isEnumsByName())
                output.writeEnum(fieldNumber, number, repeated);
            else if (null != (idx = mapping.numberIdxMap.get(number)))
                output.writeEnumFromIdx(fieldNumber, idx.intValue(), mapping, repeated);
        }
        else if (output.isEnumsByName())
        {
            output.writeString(fieldNumber, name, repeated);
        }
        else if (null != (idx = mapping.nameIdxMap.get(name)))
        {
            output.writeEnumFromIdx(fieldNumber, idx.intValue(), mapping, repeated);
        }
    }
    
    private String parseString() throws IOException
    {
        final byte[] buffer = buf;
        final char[] chars = charBuf;
		final int startIndex = offset;
        if (offset == limit)
            throw reportError("readString", "Truncated");

        byte bb;
        int ci = startIndex;
        char[] _tmp = chars;
        final int remaining = limit - offset;
        final int maxSize = charLimit - charOffset;
        int tmpSize = maxSize < remaining ? maxSize : remaining;
        int i = 0;
        while (i < tmpSize)
        {
            if ('"' == (bb = buffer[ci++]))
            {
                token = bb;
                offset = ci;
                return new String(chars, charOffset, i);
            }
            // If we encounter a backslash, which is a beginning of an escape
            // sequence
            // or a high bit was set - indicating an UTF-8 encoded multibyte
            // character,
            // there is no chance that we can decode the string without
            // instantiating
            // a temporary buffer, so quit this loop
            if ((bb ^ '\\') < 1)
                break;
            _tmp[charOffset + i] = (char)bb;
            i++;
        }
        if (i == maxSize)
            throw reportError("readString", "Maximum string buffer limit exceeded");
        /*
        if (i == _tmp.length)
        {
            final int newSize = chars.length * 2;
            if (newSize > maxStringBuffer)
            {
                throw reportError("readString", "Maximum string buffer limit exceeded");
            }
            _tmp = chars = Arrays.copyOf(chars, newSize);
        }
        */
        tmpSize = maxSize;
        int soFar = (offset = ci - 1) - startIndex;
        int bc = 0;
        while (offset < limit)
        {
            if ('"' == (bb = buffer[offset++]))
            {
                token = bb;
                return new String(chars, charOffset, soFar);
            }    
            if ('\\' == (bc = bb))
            {
                if (soFar >= tmpSize - 6)
                {
                    /*
                    final int newSize = chars.length * 2;
                    if (newSize > maxStringBuffer)
                    {
                        throw newParseErrorWith("Maximum string buffer limit exceeded",
                                maxStringBuffer);
                    }
                    _tmp = chars = Arrays.copyOf(chars, newSize);
                    tmpSize = _tmp.length;
                    */
                    throw reportError("readString", "Maximum string buffer limit exceeded");
                }
                bc = buffer[offset++];

                switch (bc)
                {
                    case 'b':
                        bc = '\b';
                        break;
                    case 't':
                        bc = '\t';
                        break;
                    case 'n':
                        bc = '\n';
                        break;
                    case 'f':
                        bc = '\f';
                        break;
                    case 'r':
                        bc = '\r';
                        break;
                    case '"':
                    case '/':
                    case '\\':
                        break;
                    case 'u':
                        bc = (hexToInt(buffer[offset++]) << 12)
                                + (hexToInt(buffer[offset++]) << 8)
                                + (hexToInt(buffer[offset++]) << 4)
                                + hexToInt(buffer[offset++]);
                        break;

                    default:
                        throw reportError("readString", "Invalid escape combination detected");//, bc);
                }
            }
            else if ((bc & 0x80) != 0)
            {
                if (soFar >= tmpSize - 4)
                {
                    /*
                    final int newSize = chars.length * 2;
                    if (newSize > maxStringBuffer)
                    {
                        throw newParseErrorWith("Maximum string buffer limit exceeded",
                                maxStringBuffer);
                    }
                    _tmp = chars = Arrays.copyOf(chars, newSize);
                    tmpSize = _tmp.length;
                    */
                    throw reportError("readString", "Maximum string buffer limit exceeded");
                }
                final int u2 = buffer[offset++];
                if ((bc & 0xE0) == 0xC0)
                {
                    bc = ((bc & 0x1F) << 6) + (u2 & 0x3F);
                }
                else
                {
                    final int u3 = buffer[offset++];
                    if ((bc & 0xF0) == 0xE0)
                    {
                        bc = ((bc & 0x0F) << 12) + ((u2 & 0x3F) << 6) + (u3 & 0x3F);
                    }
                    else
                    {
                        final int u4 = buffer[offset++];
                        if ((bc & 0xF8) == 0xF0)
                        {
                            bc = ((bc & 0x07) << 18) + ((u2 & 0x3F) << 12) + ((u3 & 0x3F) << 6)
                                    + (u4 & 0x3F);
                        }
                        else
                        {
                            // there are legal 5 & 6 byte combinations, but none
                            // are _valid_
                            throw reportError("readString", "Invalid unicode character detected");//, 0);
                        }

                        if (bc >= 0x10000)
                        {
                            // check if valid unicode
                            if (bc >= 0x110000)
                                throw reportError("readString", "Invalid unicode character detected");//, 0);

                            // split surrogates
                            final int sup = bc - 0x10000;
                            _tmp[charOffset + soFar] = (char)((sup >>> 10) + 0xd800);
                            _tmp[charOffset + soFar + 1] = (char)((sup & 0x3ff) + 0xdc00);
                            soFar += 2;
                            continue;
                        }
                    }
                }
            }
            else if (soFar >= tmpSize)
            {
                /*
                final int newSize = chars.length * 2;
                if (newSize > maxStringBuffer)
                {
                    throw reportError("readString", "Maximum string buffer limit exceeded");
                }
                _tmp = chars = Arrays.copyOf(chars, newSize);
                tmpSize = _tmp.length;
                */
                throw reportError("readString", "Maximum string buffer limit exceeded");
            }

            _tmp[charOffset + soFar] = (char)bc;
            soFar++;
        }
        throw reportError("readString", "JSON string was not closed with a double quote");
    }
    
	private int hexToInt(final byte value) throws IOException
    {
		if (value >= '0' && value <= '9') return value - 0x30;
		if (value >= 'A' && value <= 'F') return value - 0x37;
		if (value >= 'a' && value <= 'f') return value - 0x57;
		throw reportError("readString", "Could not parse unicode escape, expected a hexadecimal digit");
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy