/*
* Copyright 1999-2017 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 java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.alibaba.druid.sql.parser.CharTypes;
public class JSONParser {
private String text;
private int index = 0;
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;
}
}
}