/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */

options {
    CHOICE_AMBIGUITY_CHECK=3;
    OTHER_AMBIGUITY_CHECK=2;
    ERROR_REPORTING=true;
    JAVA_UNICODE_ESCAPE=true;
    UNICODE_INPUT=true;
    IGNORE_CASE=true;
    SUPPORT_CLASS_VISIBILITY_PUBLIC=true;
    FORCE_LA_CHECK=true;
    CACHE_TOKENS=true;
    SANITY_CHECK=true;
    STATIC=false;
}

PARSER_BEGIN(JSONParser)

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.tomcat.util.json;

/**
* Basic JSON parser generated by JavaCC. It consumes the input provided through the constructor when
* {@code parseObject()}, {@code parseList()}, or {@code parse()} are called, and there is no way to directly
* reset the state.
*/
@SuppressWarnings("all") // Ignore warnings in generated code
public class JSONParser {

    protected static final org.apache.tomcat.util.res.StringManager sm = org.apache.tomcat.util.res.StringManager.getManager(JSONParser.class);

    private boolean nativeNumbers = false;

    public JSONParser(String input) {
        this(new java.io.StringReader(input));
    }

    /**
     * Parses a JSON object into a Java {@code Map}.
     */
    public java.util.LinkedHashMap<String, Object> parseObject() throws ParseException {
        java.util.LinkedHashMap<String, Object> toReturn = object(); 
        if (!ensureEOF()) {
            throw new IllegalStateException(sm.getString("parser.expectedEOF"));
        }
        return toReturn;
    }

    /**
     * Parses a JSON array into a Java {@code List}.
     */
    public java.util.ArrayList<Object> parseArray() throws ParseException {   
        java.util.ArrayList<Object> toReturn = list(); 
        if (!ensureEOF()) {
            throw new IllegalStateException(sm.getString("parser.expectedEOF"));
        }
        return toReturn;
    }

    /**
     * Parses any JSON-parseable object, returning the value.
     */
    public Object parse() throws ParseException { 
        Object toReturn = anything(); 
        if (!ensureEOF()) {
            throw new IllegalStateException(sm.getString("parser.expectedEOF"));
        }
        return toReturn;
    }

    private static String substringBefore(String str, char delim) {
        int pos = str.indexOf(delim);
        if (pos == -1) {
            return str;
        }
        return str.substring(0, pos);
    }

    public void setNativeNumbers(boolean value) {
        this.nativeNumbers = value;
    }

    public boolean getNativeNumbers() {
        return this.nativeNumbers;
    }

}

PARSER_END(JSONParser)

// Ignore comments
SKIP: {
    <C_SINGLE_COMMENT: "//" (~["\n","\r","\f"])* <EOL>>
| <C_MULTILINE_COMMENT: "/*" (~[])* "*/">
| <SH_SINGLE_COMMENT: "#" (~["\n","\r","\f"])* <EOL>>
| <WHITESPACE: " " | "\t">
| <EOL: "\n" | "\r" | "\f">
}

// Common tokens
TOKEN: {
    <COMMA: ",">
}

// Object tokens
TOKEN:{
    <BRACE_OPEN: "{">
| <BRACE_CLOSE: "}">
| <COLON: ":">
}

// Array tokens
TOKEN:{
    <BRACKET_OPEN: "[">
| <BRACKET_CLOSE: "]">
}

// Number token
TOKEN:{
    <#ZERO: "0">
| <#DIGIT_NONZERO: ["1"-"9"]>
| <#DIGIT: (<DIGIT_NONZERO> | <ZERO>) >
| <NUMBER_INTEGER:
        ("-")?
        ( (<ZERO>)+ | ( <DIGIT_NONZERO> (<DIGIT>)* ) )
    >
| <NUMBER_DECIMAL:
        ("-")?
        ( (<ZERO>)+ | ( <DIGIT_NONZERO> (<DIGIT>)* ) )
        ("."
            (<DIGIT>)+
            (
                ["e","E"]
                ("+" | "-")?
                (<DIGIT>)+
            )?
        )
    >
}

// Boolean tokens
TOKEN:{
    <TRUE: "true">
| <FALSE: "false">
}

// Null token
TOKEN:{
    <NULL: "null">
}

// String tokens
TOKEN:{
    <#QUOTE_DOUBLE: "\"">
| <#QUOTE_SINGLE: "'">
| <STRING_SINGLE_EMPTY: "''">
| <STRING_DOUBLE_EMPTY: "\"\"">
| <#STRING_SINGLE_BODY: (
        (~["'","\\","\r","\n","\f","\t"]) |
        ( "\\" ( "r" | "n" | "f" | "\\" | "/" | "'" | "b" | "t" ) )
    )+>
| <#STRING_DOUBLE_BODY: (
        (~["\"","\\","\r","\n","\f","\t"]) |
        ( "\\" ( "r" | "n" | "f" | "\\" | "/" | "\"" | "b" | "t" ) )
    )+>
| <STRING_SINGLE_NONEMPTY: <QUOTE_SINGLE> <STRING_SINGLE_BODY> <QUOTE_SINGLE>>
| <STRING_DOUBLE_NONEMPTY: <QUOTE_DOUBLE> <STRING_DOUBLE_BODY> <QUOTE_DOUBLE>>
}

// Raw symbol tokens
TOKEN:{
    <SYMBOL: (["a"-"z", "A"-"Z", "0", "1"-"9"])+ >
}


boolean ensureEOF() : {}{
    <EOF>
    { return true; }
}

Object anything() : {
    Object x;
}{
    ( x = object()
    | x = list()
    | x = value()
    )
    { return x; }
}

String objectKey() : {
    Object o;
    String key;
} {
    (
        key = string()
    | key = symbol()
    | (
        nullValue()
        { key = null; }
        )
    | (
            ( o = booleanValue() | o = number() )
            { key = o.toString(); }
        )
    )
    { return key; }
}

java.util.LinkedHashMap<String, Object> object() : {
    final java.util.LinkedHashMap<String, Object> map = new java.util.LinkedHashMap<String, Object>();
    String key;
    Object value;
}{
    <BRACE_OPEN>
    [
        key = objectKey()
        <COLON>
        value = anything()
        { map.put(key, value); }
        { key = null; value = null; }
        (
            <COMMA>
            key = objectKey()
            <COLON>
            value = anything()
            { map.put(key, value); }
            { key = null; value = null; }
        )*
    ]
    <BRACE_CLOSE>
    { return map; }
}

java.util.ArrayList<Object> list() : {
    final java.util.ArrayList<Object> list = new java.util.ArrayList<Object>();
    Object value;
}{
    <BRACKET_OPEN>
    [
        value = anything()
        { list.add(value); }
        { value = null; }
        (
            <COMMA>
            value = anything()
            { list.add(value); }
            { value = null; }
        )*
    ]
    <BRACKET_CLOSE>
    { 
        list.trimToSize();
        return list;
    }
}

Object value() : {
    Object x;
}{
    ( x = string()
    | x = number()
    | x = booleanValue()
    | x = nullValue()
    )
    { return x; }
}

Object nullValue(): {}{
    <NULL>
    { return null; }
}

Boolean booleanValue(): {
    Boolean b;
}{
    (
        (
            <TRUE>
            { b = Boolean.TRUE; }
        ) | (
            <FALSE>
            { b = Boolean.FALSE; }
        )
    )
    { return b; }
}

Number number(): {
    Token t;
}{ 
    (
        t = <NUMBER_DECIMAL>
        {
            if (nativeNumbers) {
                return new Long(t.image);
            } else {
                return new java.math.BigDecimal(t.image);
            }
        }
    ) | (
        t = <NUMBER_INTEGER>
        {
            if (nativeNumbers) {
                return new Double(t.image);
            } else {
                return new java.math.BigInteger(substringBefore(t.image, '.'));
            }
        }
    )
}

String string() : {
    String s;
}{
    ( s = doubleQuoteString()
    | s = singleQuoteString()
    )
    { return s; }
}

String doubleQuoteString() : {
}{
    (
        <STRING_DOUBLE_EMPTY>
        { return ""; }
    ) | (
        <STRING_DOUBLE_NONEMPTY>
        {
            String image = token.image;
            return image.substring(1, image.length() - 1);
        }
    )
}

String singleQuoteString() : {
}{
    (
        <STRING_SINGLE_EMPTY>
        { return ""; }
    ) | (
        <STRING_SINGLE_NONEMPTY>
        {
            String image = token.image;
            return image.substring(1, image.length() - 1);
        }
    )
}

String symbol() : {
}{
    <SYMBOL>
    { return token.image; }
}
