content: /*
   * Copyright 1999-2018 Alibaba Group Holding Ltd.
   *
   * 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.alibaba.druid.support.json;
  
  import com.alibaba.druid.sql.parser.CharTypes;
  
  import java.util.ArrayList;
  import java.util.LinkedHashMap;
  import java.util.List;
  import java.util.Map;
  
  public class JSONParser {
      private String text;
      private int index;
      private char ch;
  
      private Token token;
      private String stringValue;
      private long longValue;
      private double doubleValue;
  
      public JSONParser(String text) {
          this.text = text;
          ch = text.charAt(0);
          nextToken();
      }
  
      public Object parse() {
          if (token == Token.LBRACE) {
              return parseMap();
          }
  
          if (token == Token.INT) {
              Object value;
              if (this.longValue >= Integer.MIN_VALUE && this.longValue <= Integer.MAX_VALUE) {
                  value = (int) this.longValue;
              } else {
                  value = this.longValue;
              }
  
              nextToken();
              return value;
          }
  
          if (token == Token.DOUBLE) {
              Object value = this.doubleValue;
              nextToken();
              return value;
          }
  
          if (token == Token.STRING) {
              Object value = this.stringValue;
              nextToken();
              return value;
          }
  
          if (token == Token.LBRACKET) {
              return parseArray();
          }
  
          if (token == Token.TRUE) {
              nextToken();
              return true;
          }
  
          if (token == Token.FALSE) {
              nextToken();
              return false;
          }
  
          if (token == Token.NULL) {
              nextToken();
              return null;
          }
  
          throw new IllegalArgumentException("illegal token : " + token);
      }
  
      public List<Object> parseArray() {
          accept(Token.LBRACKET);
          ArrayList<Object> list = new ArrayList<Object>();
  
          for (; ; ) {
              if (token == Token.RBRACKET) {
                  break;
              }
  
              if (token == Token.COMMA) {
                  nextToken();
                  continue;
              }
  
              Object item = parse();
              list.add(item);
          }
  
          accept(Token.RBRACKET);
          return list;
      }
  
      public Map<String, Object> parseMap() {
          accept(Token.LBRACE);
          Map<String, Object> map = new LinkedHashMap<String, Object>();
  
          for (; ; ) {
              if (token == Token.RBRACE) {
                  break;
              }
  
              if (token == Token.COMMA) {
                  nextToken();
                  continue;
              }
  
              String key;
              {
                  if (token != Token.STRING) {
                      throw new IllegalArgumentException("illegal json, " + token + " : " + text);
                  }
                  key = this.stringValue;
                  nextToken();
              }
  
              accept(Token.COLON);
  
              Object value = parse();
  
              map.put(key, value);
          }
  
          accept(Token.RBRACE);
          return map;
      }
  
      void accept(Token token) {
          if (this.token == token) {
              nextToken();
              return;
          }
  
          throw new IllegalArgumentException("illegal token : " + this.token + ", expect " + token);
      }
  
      final void nextToken() {
          if (index == Integer.MIN_VALUE) {
              token = Token.EOF;
              return;
          }
  
          for (; ; ) {
              if (CharTypes.isWhitespace(ch)) {
                  nextChar();
                  continue;
              }
  
              if (index >= text.length()) {
                  token = Token.EOF;
                  return;
              }
  
              break;
          }
  
          switch (ch) {
              case '{':
                  token = Token.LBRACE;
                  nextChar();
                  break;
              case '}':
                  token = Token.RBRACE;
                  nextChar();
                  break;
              case '[':
                  token = Token.LBRACKET;
                  nextChar();
                  break;
              case ']':
                  token = Token.RBRACKET;
                  nextChar();
                  break;
              case ',':
                  token = Token.COMMA;
                  nextChar();
                  break;
              case ':':
                  token = Token.COLON;
                  nextChar();
                  break;
              case '"':
                  scanString();
                  break;
              default:
                  if (isDigit(ch) || ch == '-') {
                      scanDigit();
                      return;
                  }
  
                  if (text.startsWith("null", index)) {
                      token = Token.NULL;
                      index += 3;
                      nextChar();
                      return;
                  }
  
                  if (text.startsWith("true", index)) {
                      token = Token.TRUE;
                      index += 3;
                      nextChar();
                      return;
                  }
  
                  if (text.startsWith("false", index)) {
                      token = Token.FALSE;
                      index += 4;
                      nextChar();
                      return;
                  }
                  throw new IllegalArgumentException("illegal json char : " + ch);
          }
      }
  
      private void scanDigit() {
          boolean isNegate = false;
          if (ch == '-') {
              isNegate = true;
              nextChar();
          }
  
          int dotCount = 0;
          StringBuilder digitBuf = new StringBuilder();
          for (; ; ) {
              digitBuf.append(ch);
              nextChar();
  
              if (ch == '.') {
                  dotCount++;
                  digitBuf.append('.');
                  nextChar();
                  continue;
              }
  
              if (!isDigit(ch)) {
                  break;
              }
          }
  
          if (dotCount == 0) {
              long longValue = Long.parseLong(digitBuf.toString());
              if (isNegate) {
                  longValue = -longValue;
              }
              this.longValue = longValue;
              token = Token.INT;
          } else {
              double doubleValue = Double.parseDouble(digitBuf.toString());
              if (isNegate) {
                  doubleValue = -doubleValue;
              }
              this.doubleValue = doubleValue;
              token = Token.DOUBLE;
          }
      }
  
      private void scanString() {
          nextChar();
          StringBuilder strBuf = new StringBuilder();
          for (; ; ) {
              if (index >= text.length()) {
                  throw new IllegalArgumentException("illegal string : " + strBuf);
              }
              if (ch == '"') {
                  nextChar();
                  break;
              }
  
              if (ch == '\\') {
                  nextChar();
                  if (ch == '"' || ch == '\\' || ch == '/') {
                      strBuf.append(ch);
                  } else if (ch == 'n') {
                      strBuf.append('\n');
                  } else if (ch == 'r') {
                      strBuf.append('\r');
                  } else if (ch == 'b') {
                      strBuf.append('\b');
                  } else if (ch == 'f') {
                      strBuf.append('\f');
                  } else if (ch == 't') {
                      strBuf.append('\t');
                  } else if (ch == 'u') {
                      nextChar();
                      char c1 = ch;
                      nextChar();
                      char c2 = ch;
                      nextChar();
                      char c3 = ch;
                      nextChar();
                      char c4 = ch;
                      int val = Integer.parseInt(new String(new char[]{c1, c2, c3, c4}), 16);
                      strBuf.append((char) val);
                  } else {
                      throw new IllegalArgumentException("illegal string : " + strBuf);
                  }
              } else {
                  strBuf.append(ch);
              }
              nextChar();
          }
          stringValue = strBuf.toString();
          token = Token.STRING;
      }
  
      static boolean isDigit(char ch) {
          return ch >= '0' && ch <= '9';
      }
  
      void nextChar() {
          ++index;
          if (index >= text.length()) {
              index = Integer.MIN_VALUE;
              return;
          }
  
          ch = text.charAt(index);
      }
  
      enum Token {
          INT, //
          DOUBLE, //
          STRING, //
          BOOLEAN, //
          TRUE, //
          FALSE, //
          NULL, //
          EOF, //
  
          LBRACE("{"), //
          RBRACE("}"), //
          LBRACKET("["), //
          RBRACKET("]"), //
          COMMA(","), //
          COLON(":");
  
          public final String name;
  
          Token() {
              this(null);
          }
  
          Token(String name) {
              this.name = name;
          }
      }
  }
  