/* * Copyright 1999-2101 Alibaba Group. * * 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 org.zbus.common.json.parser; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.HashSet; import java.util.Locale; import java.util.TimeZone; import org.zbus.common.json.JSON; import org.zbus.common.json.JSONException; import org.zbus.common.json.util.Base64; //这个类,为了性能优化做了很多特别处理,一切都是为了性能!!! /** * @author wenshao<szujobs@hotmail.com> */ public final class JSONScanner extends JSONLexerBase { private final String text; public JSONScanner(String input){ this(input, JSON.DEFAULT_PARSER_FEATURE); } public JSONScanner(String input, int features){ this.features = features; text = input; bp = -1; next(); if (ch == 65279) { next(); } } public final char charAt(int index) { if (index >= text.length()) { return EOI; } return text.charAt(index); } public final char next() { return ch = charAt(++bp); } public JSONScanner(char[] input, int inputLength){ this(input, inputLength, JSON.DEFAULT_PARSER_FEATURE); } public JSONScanner(char[] input, int inputLength, int features){ this(new String(input, 0, inputLength), features); } protected final void copyTo(int offset, int count, char[] dest) { text.getChars(offset, offset + count, dest, 0); } protected final static char[] typeFieldName = ("\"" + JSON.DEFAULT_TYPE_KEY + "\":\"").toCharArray(); public final int scanType(String type) { matchStat = UNKOWN; if (!charArrayCompare(text, bp, typeFieldName)) { return NOT_MATCH_NAME; } int bpLocal = this.bp + typeFieldName.length; final int typeLength = type.length(); for (int i = 0; i < typeLength; ++i) { if (type.charAt(i) != charAt(bpLocal + i)) { return NOT_MATCH; } } bpLocal += typeLength; if (charAt(bpLocal) != '"') { return NOT_MATCH; } this.ch = charAt(++bpLocal); if (ch == ',') { this.ch = charAt(++bpLocal); this.bp = bpLocal; token = JSONToken.COMMA; return VALUE; } else if (ch == '}') { ch = charAt(++bpLocal); if (ch == ',') { token = JSONToken.COMMA; this.ch = charAt(++bpLocal); } else if (ch == ']') { token = JSONToken.RBRACKET; this.ch = charAt(++bpLocal); } else if (ch == '}') { token = JSONToken.RBRACE; this.ch = charAt(++bpLocal); } else if (ch == EOI) { token = JSONToken.EOF; } else { return NOT_MATCH; } matchStat = END; } this.bp = bpLocal; return matchStat; } static final boolean charArrayCompare(String src, int offset, char[] dest) { final int destLen = dest.length; if (destLen + offset > src.length()) { return false; } for (int i = 0; i < destLen; ++i) { if (dest[i] != src.charAt(offset + i)) { return false; } } return true; } public final boolean charArrayCompare(char[] chars) { return charArrayCompare(text, bp, chars); } public final int indexOf(char ch, int startIndex) { return text.indexOf(ch, startIndex); } public final String addSymbol(int offset, int len, int hash, final SymbolTable symbolTable) { return symbolTable.addSymbol(text, offset, len, hash); } public byte[] bytesValue() { return Base64.decodeFast(text, np + 1, sp); } // public int scanField2(char[] fieldName, Object object, FieldDeserializer fieldDeserializer) { // return NOT_MATCH; // } /** * The value of a literal token, recorded as a string. For integers, leading 0x and 'l' suffixes are suppressed. */ public final String stringVal() { if (!hasSpecial) { // return new String(buf, np + 1, sp); return text.substring(np + 1, np + 1 + sp); } else { return new String(sbuf, 0, sp); } } public final String subString(int offset, int count) { return text.substring(offset, offset + count); } public final String numberString() { char chLocal = charAt(np + sp - 1); int sp = this.sp; if (chLocal == 'L' || chLocal == 'S' || chLocal == 'B' || chLocal == 'F' || chLocal == 'D') { sp--; } return text.substring(np, np + sp); // return new String(buf, np, sp); } public final int ISO8601_LEN_0 = "0000-00-00".length(); public final int ISO8601_LEN_1 = "0000-00-00T00:00:00".length(); public final int ISO8601_LEN_2 = "0000-00-00T00:00:00.000".length(); public boolean scanISO8601DateIfMatch() { return scanISO8601DateIfMatch(true); } public boolean scanISO8601DateIfMatch(boolean strict) { int rest = text.length() - bp; if ((!strict) && rest > 13) { char c0 = charAt(bp); char c1 = charAt(bp + 1); char c2 = charAt(bp + 2); char c3 = charAt(bp + 3); char c4 = charAt(bp + 4); char c5 = charAt(bp + 5); char c_r0 = charAt(bp + rest - 1); char c_r1 = charAt(bp + rest - 2); if (c0 == '/' && c1 == 'D' && c2 == 'a' && c3 == 't' && c4 == 'e' && c5 == '(' && c_r0 == '/' && c_r1 == ')') { int plusIndex = -1; for (int i = 6; i < rest; ++i) { char c = charAt(bp + i); if (c == '+') { plusIndex = i; } else if (c < '0' || c > '9') { break; } } if (plusIndex == -1) { return false; } int offset = bp + 6; String numberText = this.subString(offset, plusIndex - offset); long millis = Long.parseLong(numberText); Locale local = Locale.getDefault(); calendar = Calendar.getInstance(TimeZone.getDefault(), local); calendar.setTimeInMillis(millis); token = JSONToken.LITERAL_ISO8601_DATE; return true; } } if (rest == 8 || rest == 14 || rest == 17) { if (strict) { return false; } char y0 = charAt(bp); char y1 = charAt(bp + 1); char y2 = charAt(bp + 2); char y3 = charAt(bp + 3); char M0 = charAt(bp + 4); char M1 = charAt(bp + 5); char d0 = charAt(bp + 6); char d1 = charAt(bp + 7); if (!checkDate(y0, y1, y2, y3, M0, M1, d0, d1)) { return false; } setCalendar(y0, y1, y2, y3, M0, M1, d0, d1); int hour, minute, seconds, millis; if (rest != 8) { char h0 = charAt(bp + 8); char h1 = charAt(bp + 9); char m0 = charAt(bp + 10); char m1 = charAt(bp + 11); char s0 = charAt(bp + 12); char s1 = charAt(bp + 13); if (!checkTime(h0, h1, m0, m1, s0, s1)) { return false; } if (rest == 17) { char S0 = charAt(bp + 14); char S1 = charAt(bp + 15); char S2 = charAt(bp + 16); if (S0 < '0' || S0 > '9') { return false; } if (S1 < '0' || S1 > '9') { return false; } if (S2 < '0' || S2 > '9') { return false; } millis = digits[S0] * 100 + digits[S1] * 10 + digits[S2]; } else { millis = 0; } hour = digits[h0] * 10 + digits[h1]; minute = digits[m0] * 10 + digits[m1]; seconds = digits[s0] * 10 + digits[s1]; } else { hour = 0; minute = 0; seconds = 0; millis = 0; } calendar.set(Calendar.HOUR_OF_DAY, hour); calendar.set(Calendar.MINUTE, minute); calendar.set(Calendar.SECOND, seconds); calendar.set(Calendar.MILLISECOND, millis); token = JSONToken.LITERAL_ISO8601_DATE; return true; } if (rest < ISO8601_LEN_0) { return false; } if (charAt(bp + 4) != '-') { return false; } if (charAt(bp + 7) != '-') { return false; } char y0 = charAt(bp); char y1 = charAt(bp + 1); char y2 = charAt(bp + 2); char y3 = charAt(bp + 3); char M0 = charAt(bp + 5); char M1 = charAt(bp + 6); char d0 = charAt(bp + 8); char d1 = charAt(bp + 9); if (!checkDate(y0, y1, y2, y3, M0, M1, d0, d1)) { return false; } setCalendar(y0, y1, y2, y3, M0, M1, d0, d1); char t = charAt(bp + 10); if (t == 'T' || (t == ' ' && !strict)) { if (rest < ISO8601_LEN_1) { return false; } } else if (t == '"' || t == EOI) { calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); ch = charAt(bp += 10); token = JSONToken.LITERAL_ISO8601_DATE; return true; } else { return false; } if (charAt(bp + 13) != ':') { return false; } if (charAt(bp + 16) != ':') { return false; } char h0 = charAt(bp + 11); char h1 = charAt(bp + 12); char m0 = charAt(bp + 14); char m1 = charAt(bp + 15); char s0 = charAt(bp + 17); char s1 = charAt(bp + 18); if (!checkTime(h0, h1, m0, m1, s0, s1)) { return false; } int hour = digits[h0] * 10 + digits[h1]; int minute = digits[m0] * 10 + digits[m1]; int seconds = digits[s0] * 10 + digits[s1]; calendar.set(Calendar.HOUR_OF_DAY, hour); calendar.set(Calendar.MINUTE, minute); calendar.set(Calendar.SECOND, seconds); char dot = charAt(bp + 19); if (dot == '.') { if (rest < ISO8601_LEN_2) { return false; } } else { calendar.set(Calendar.MILLISECOND, 0); ch = charAt(bp += 19); token = JSONToken.LITERAL_ISO8601_DATE; return true; } char S0 = charAt(bp + 20); char S1 = charAt(bp + 21); char S2 = charAt(bp + 22); if (S0 < '0' || S0 > '9') { return false; } if (S1 < '0' || S1 > '9') { return false; } if (S2 < '0' || S2 > '9') { return false; } int millis = digits[S0] * 100 + digits[S1] * 10 + digits[S2]; calendar.set(Calendar.MILLISECOND, millis); ch = charAt(bp += 23); token = JSONToken.LITERAL_ISO8601_DATE; return true; } private boolean checkTime(char h0, char h1, char m0, char m1, char s0, char s1) { if (h0 == '0') { if (h1 < '0' || h1 > '9') { return false; } } else if (h0 == '1') { if (h1 < '0' || h1 > '9') { return false; } } else if (h0 == '2') { if (h1 < '0' || h1 > '4') { return false; } } else { return false; } if (m0 >= '0' && m0 <= '5') { if (m1 < '0' || m1 > '9') { return false; } } else if (m0 == '6') { if (m1 != '0') { return false; } } else { return false; } if (s0 >= '0' && s0 <= '5') { if (s1 < '0' || s1 > '9') { return false; } } else if (s0 == '6') { if (s1 != '0') { return false; } } else { return false; } return true; } private void setCalendar(char y0, char y1, char y2, char y3, char M0, char M1, char d0, char d1) { Locale local = Locale.getDefault(); calendar = Calendar.getInstance(TimeZone.getDefault(), local); int year = digits[y0] * 1000 + digits[y1] * 100 + digits[y2] * 10 + digits[y3]; int month = digits[M0] * 10 + digits[M1] - 1; int day = digits[d0] * 10 + digits[d1]; calendar.set(Calendar.YEAR, year); calendar.set(Calendar.MONTH, month); calendar.set(Calendar.DAY_OF_MONTH, day); } static boolean checkDate(char y0, char y1, char y2, char y3, char M0, char M1, int d0, int d1) { if (y0 != '1' && y0 != '2') { return false; } if (y1 < '0' || y1 > '9') { return false; } if (y2 < '0' || y2 > '9') { return false; } if (y3 < '0' || y3 > '9') { return false; } if (M0 == '0') { if (M1 < '1' || M1 > '9') { return false; } } else if (M0 == '1') { if (M1 != '0' && M1 != '1' && M1 != '2') { return false; } } else { return false; } if (d0 == '0') { if (d1 < '1' || d1 > '9') { return false; } } else if (d0 == '1' || d0 == '2') { if (d1 < '0' || d1 > '9') { return false; } } else if (d0 == '3') { if (d1 != '0' && d1 != '1') { return false; } } else { return false; } return true; } @Override public boolean isEOF() { return bp == text.length() || ch == EOI && bp + 1 == text.length(); } public int scanFieldInt(char[] fieldName) { matchStat = UNKOWN; int startPos = this.bp; char startChar = this.ch; if (!charArrayCompare(text, bp, fieldName)) { matchStat = NOT_MATCH_NAME; return 0; } int index = bp + fieldName.length; char ch = charAt(index++); int value; if (ch >= '0' && ch <= '9') { value = digits[ch]; for (;;) { ch = charAt(index++); if (ch >= '0' && ch <= '9') { value = value * 10 + digits[ch]; } else if (ch == '.') { matchStat = NOT_MATCH; return 0; } else { bp = index - 1; break; } } if (value < 0) { matchStat = NOT_MATCH; return 0; } } else { matchStat = NOT_MATCH; return 0; } if (ch == ',') { this.ch = charAt(++bp); matchStat = VALUE; token = JSONToken.COMMA; return value; } if (ch == '}') { ch = charAt(++bp); if (ch == ',') { token = JSONToken.COMMA; this.ch = charAt(++bp); } else if (ch == ']') { token = JSONToken.RBRACKET; this.ch = charAt(++bp); } else if (ch == '}') { token = JSONToken.RBRACE; this.ch = charAt(++bp); } else if (ch == EOI) { token = JSONToken.EOF; } else { this.bp = startPos; this.ch = startChar; matchStat = NOT_MATCH; return 0; } matchStat = END; } return value; } public String scanFieldString(char[] fieldName) { matchStat = UNKOWN; int startPos = this.bp; char startChar = this.ch; // final int fieldNameLength = fieldName.length; // for (int i = 0; i < fieldNameLength; ++i) { // if (fieldName[i] != buf[bp + i]) { // matchStat = NOT_MATCH_NAME; // // return stringDefaultValue(); // } // } if (!charArrayCompare(text, bp, fieldName)) { matchStat = NOT_MATCH_NAME; return stringDefaultValue(); } int index = bp + fieldName.length; char ch = charAt(index++); if (ch != '"') { matchStat = NOT_MATCH; return stringDefaultValue(); } boolean hasSpecial = false; final String strVal; { int startIndex = index; int endIndex = text.indexOf('"', startIndex); if (endIndex == -1) { throw new JSONException("unclosed str"); } String stringVal = subString(startIndex, endIndex - startIndex); for (int i = 0; i < stringVal.length(); ++i) { if (stringVal.charAt(i) == '\\') { hasSpecial = true; break; } } if (hasSpecial) { matchStat = NOT_MATCH; return stringDefaultValue(); } bp = endIndex + 1; this.ch = ch = charAt(bp); strVal = stringVal; // this.stringVal = stringVal; // int pos = endIndex + 1; // char ch = charAt(pos); // if (ch != '\'') { // this.pos = pos; // this.ch = ch; // token = LITERAL_CHARS; // return; // } } // final int start = index; // for (;;) { // ch = charAt(index++); // if (ch == '\"') { // bp = index; // this.ch = ch = charAt(bp); // strVal = text.substring(start, index - 1); // // strVal = new String(buf, start, index - start - 1); // break; // } // // if (ch == '\\') { // matchStat = NOT_MATCH; // // return stringDefaultValue(); // } // } if (ch == ',') { this.ch = charAt(++bp); matchStat = VALUE; return strVal; } else if (ch == '}') { ch = charAt(++bp); if (ch == ',') { token = JSONToken.COMMA; this.ch = charAt(++bp); } else if (ch == ']') { token = JSONToken.RBRACKET; this.ch = charAt(++bp); } else if (ch == '}') { token = JSONToken.RBRACE; this.ch = charAt(++bp); } else if (ch == EOI) { token = JSONToken.EOF; } else { this.bp = startPos; this.ch = startChar; matchStat = NOT_MATCH; return stringDefaultValue(); } matchStat = END; } else { matchStat = NOT_MATCH; return stringDefaultValue(); } return strVal; } public String scanFieldSymbol(char[] fieldName, final SymbolTable symbolTable) { matchStat = UNKOWN; if (!charArrayCompare(text, bp, fieldName)) { matchStat = NOT_MATCH_NAME; return null; } int index = bp + fieldName.length; char ch = charAt(index++); if (ch != '"') { matchStat = NOT_MATCH; return null; } String strVal; int start = index; int hash = 0; for (;;) { ch = charAt(index++); if (ch == '\"') { bp = index; this.ch = ch = charAt(bp); // strVal = text.substring(start, index - 1).intern(); strVal = symbolTable.addSymbol(text, start, index - start - 1, hash); break; } hash = 31 * hash + ch; if (ch == '\\') { matchStat = NOT_MATCH; return null; } } if (ch == ',') { this.ch = charAt(++bp); matchStat = VALUE; return strVal; } else if (ch == '}') { ch = charAt(++bp); if (ch == ',') { token = JSONToken.COMMA; this.ch = charAt(++bp); } else if (ch == ']') { token = JSONToken.RBRACKET; this.ch = charAt(++bp); } else if (ch == '}') { token = JSONToken.RBRACE; this.ch = charAt(++bp); } else if (ch == EOI) { token = JSONToken.EOF; } else { matchStat = NOT_MATCH; return null; } matchStat = END; } else { matchStat = NOT_MATCH; return null; } return strVal; } @SuppressWarnings("unchecked") public Collection<String> scanFieldStringArray(char[] fieldName, Class<?> type) { matchStat = UNKOWN; if (!charArrayCompare(text, bp, fieldName)) { matchStat = NOT_MATCH_NAME; return null; } Collection<String> list; if (type.isAssignableFrom(HashSet.class)) { list = new HashSet<String>(); } else if (type.isAssignableFrom(ArrayList.class)) { list = new ArrayList<String>(); } else { try { list = (Collection<String>) type.newInstance(); } catch (Exception e) { throw new JSONException(e.getMessage(), e); } } int index = bp + fieldName.length; char ch = charAt(index++); if (ch != '[') { matchStat = NOT_MATCH; return null; } ch = charAt(index++); for (;;) { if (ch != '"') { matchStat = NOT_MATCH; return null; } String strVal; int start = index; for (;;) { ch = charAt(index++); if (ch == '\"') { strVal = text.substring(start, index - 1); // strVal = new String(buf, start, index - start - 1); list.add(strVal); ch = charAt(index++); break; } if (ch == '\\') { matchStat = NOT_MATCH; return null; } } if (ch == ',') { ch = charAt(index++); continue; } if (ch == ']') { ch = charAt(index++); break; } matchStat = NOT_MATCH; return null; } bp = index; if (ch == ',') { this.ch = charAt(bp); matchStat = VALUE; return list; } else if (ch == '}') { ch = charAt(bp); if (ch == ',') { token = JSONToken.COMMA; this.ch = charAt(++bp); } else if (ch == ']') { token = JSONToken.RBRACKET; this.ch = charAt(++bp); } else if (ch == '}') { token = JSONToken.RBRACE; this.ch = charAt(++bp); } else if (ch == EOI) { token = JSONToken.EOF; this.ch = ch; } else { matchStat = NOT_MATCH; return null; } matchStat = END; } else { matchStat = NOT_MATCH; return null; } return list; } public long scanFieldLong(char[] fieldName) { matchStat = UNKOWN; int startPos = this.bp; char startChar = this.ch; if (!charArrayCompare(text, bp, fieldName)) { matchStat = NOT_MATCH_NAME; return 0; } int index = bp + fieldName.length; char ch = charAt(index++); long value; if (ch >= '0' && ch <= '9') { value = digits[ch]; for (;;) { ch = charAt(index++); if (ch >= '0' && ch <= '9') { value = value * 10 + digits[ch]; } else if (ch == '.') { matchStat = NOT_MATCH; return 0; } else { bp = index - 1; break; } } if (value < 0) { this.bp = startPos; this.ch = startChar; matchStat = NOT_MATCH; return 0; } } else { this.bp = startPos; this.ch = startChar; matchStat = NOT_MATCH; return 0; } if (ch == ',') { ch = charAt(++bp); matchStat = VALUE; token = JSONToken.COMMA; return value; } else if (ch == '}') { ch = charAt(++bp); if (ch == ',') { token = JSONToken.COMMA; this.ch = charAt(++bp); } else if (ch == ']') { token = JSONToken.RBRACKET; this.ch = charAt(++bp); } else if (ch == '}') { token = JSONToken.RBRACE; this.ch = charAt(++bp); } else if (ch == EOI) { token = JSONToken.EOF; } else { this.bp = startPos; this.ch = startChar; matchStat = NOT_MATCH; return 0; } matchStat = END; } else { matchStat = NOT_MATCH; return 0; } return value; } public boolean scanFieldBoolean(char[] fieldName) { matchStat = UNKOWN; if (!charArrayCompare(text, bp, fieldName)) { matchStat = NOT_MATCH_NAME; return false; } int index = bp + fieldName.length; char ch = charAt(index++); boolean value; if (ch == 't') { if (charAt(index++) != 'r') { matchStat = NOT_MATCH; return false; } if (charAt(index++) != 'u') { matchStat = NOT_MATCH; return false; } if (charAt(index++) != 'e') { matchStat = NOT_MATCH; return false; } bp = index; ch = charAt(bp); value = true; } else if (ch == 'f') { if (charAt(index++) != 'a') { matchStat = NOT_MATCH; return false; } if (charAt(index++) != 'l') { matchStat = NOT_MATCH; return false; } if (charAt(index++) != 's') { matchStat = NOT_MATCH; return false; } if (charAt(index++) != 'e') { matchStat = NOT_MATCH; return false; } bp = index; ch = charAt(bp); value = false; } else { matchStat = NOT_MATCH; return false; } if (ch == ',') { this.ch = charAt(++bp); matchStat = VALUE; token = JSONToken.COMMA; } else if (ch == '}') { ch = charAt(++bp); if (ch == ',') { token = JSONToken.COMMA; this.ch = charAt(++bp); } else if (ch == ']') { token = JSONToken.RBRACKET; this.ch = charAt(++bp); } else if (ch == '}') { token = JSONToken.RBRACE; this.ch = charAt(++bp); } else if (ch == EOI) { token = JSONToken.EOF; } else { matchStat = NOT_MATCH; return false; } matchStat = END; } else { matchStat = NOT_MATCH; return false; } return value; } protected final void arrayCopy(int srcPos, char[] dest, int destPos, int length) { text.getChars(srcPos, srcPos + length, dest, destPos); } }