/* * 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 groovy.json.internal; import java.util.ArrayList; import java.util.List; /** * This works in index overlay mode or chop mode. * Chop mode reduces possibility of memory leak but causes a few more buffer copies as it chops up the buffer. * * @author Rick Hightower */ public class JsonFastParser extends JsonParserCharArray { private final boolean useValues; private final boolean chop; private final boolean lazyChop; private final boolean checkDates; public JsonFastParser() { this(true); } public JsonFastParser(boolean useValues) { this(useValues, false); } public JsonFastParser(boolean useValues, boolean chop) { this(useValues, chop, !chop); } public JsonFastParser(boolean useValues, boolean chop, boolean lazyChop) { this(useValues, chop, lazyChop, true); } public JsonFastParser(boolean useValues, boolean chop, boolean lazyChop, boolean checkDates) { this.useValues = useValues; this.chop = chop; this.lazyChop = lazyChop; this.checkDates = checkDates; } protected final Value decodeJsonObjectLazyFinalParse() { char[] array = charArray; if (__currentChar == '{') __index++; ValueMap map = useValues ? new ValueMapImpl() : new LazyValueMap(lazyChop); Value value = new ValueContainer(map); objectLoop: for (; __index < array.length; __index++) { skipWhiteSpace(); switch (__currentChar) { case '"': Value key = decodeStringOverlay(); skipWhiteSpace(); if (__currentChar != ':') { complain("expecting current character to be " + charDescription(__currentChar) + "\n"); } __index++; Value item = decodeValueOverlay(); skipWhiteSpace(); MapItemValue miv = new MapItemValue(key, item); map.add(miv); } switch (__currentChar) { case '}': __index++; break objectLoop; case ',': continue; default: complain( "expecting '}' or ',' but got current char " + charDescription(__currentChar)); } } return value; } protected Value decodeValue() { return decodeValueOverlay(); } private Value decodeValueOverlay() { skipWhiteSpace(); switch (__currentChar) { case '"': return decodeStringOverlay(); case '{': return decodeJsonObjectLazyFinalParse(); case 't': return decodeTrue() ? ValueContainer.TRUE : ValueContainer.FALSE; case 'f': return !decodeFalse() ? ValueContainer.FALSE : ValueContainer.TRUE; case 'n': return decodeNull() == null ? ValueContainer.NULL : ValueContainer.NULL; case '[': return decodeJsonArrayOverlay(); case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '0': return decodeNumberOverlay(false); case '-': return decodeNumberOverlay(true); default: complain("Unable to determine the " + "current character, it is not a string, number, array, or object"); return null; } } private Value decodeNumberOverlay(final boolean minus) { char[] array = charArray; final int startIndex = __index; int index = __index; char currentChar; boolean doubleFloat = false; boolean foundDot = false; boolean foundSign = false; boolean foundExp = false; if (minus && index + 1 < array.length) { index++; } while (true) { currentChar = array[index]; if (isNumberDigit(currentChar)) { //noop } else if (currentChar <= 32) { //white break; } else if (isDelimiter(currentChar)) { break; } else if (isDecimalChar(currentChar)) { switch (currentChar) { case DECIMAL_POINT: if (foundDot || foundExp) { complain("unexpected character " + currentChar); } foundDot = true; break; case LETTER_E: case LETTER_BIG_E: if (foundExp) { complain("unexpected character " + currentChar); } foundExp = true; break; case MINUS: case PLUS: if (foundSign || !foundExp) { complain("unexpected character " + currentChar); } if (foundExp && array[index - 1] != LETTER_E && array[index - 1] != LETTER_BIG_E) { complain("unexpected character " + currentChar); } foundSign = true; break; } doubleFloat = true; } else { complain("unexpected character " + currentChar); } index++; if (index >= array.length) break; } // Handle the case where the exponential number ends without the actual exponent if (foundExp) { char prevChar = array[index - 1]; if (prevChar == LETTER_E || prevChar == LETTER_BIG_E || prevChar == MINUS || prevChar == PLUS) { complain("unexpected character " + currentChar); } } __index = index; __currentChar = currentChar; Type type = doubleFloat ? Type.DOUBLE : Type.INTEGER; return new NumberValue(chop, type, startIndex, __index, this.charArray); } private Value decodeStringOverlay() { char[] array = charArray; int index = __index; char currentChar = charArray[index]; if (index < array.length && currentChar == '"') { index++; } final int startIndex = index; boolean encoded = hasEscapeChar(array, index, indexHolder); index = indexHolder[0]; if (encoded) { index = findEndQuote(array, index); } Value value = new CharSequenceValue(chop, Type.STRING, startIndex, index, array, encoded, checkDates); if (index < array.length) { index++; } __index = index; return value; } private Value decodeJsonArrayOverlay() { char[] array = charArray; if (__currentChar == '[') { __index++; } skipWhiteSpace(); /* the list might be empty */ if (__currentChar == ']') { __index++; return new ValueContainer(new ArrayList()); } List<Object> list; if (useValues) { list = new ArrayList<Object>(); } else { list = new ValueList(lazyChop); } Value value = new ValueContainer(list); Value item; char c; int lastIndex; boolean foundEnd = false; arrayLoop: for (; __index < array.length; __index++) { item = decodeValueOverlay(); list.add(item); c = currentChar(); switch (c) { case ',': continue; case ']': __index++; foundEnd = true; break arrayLoop; } lastIndex = __index; skipWhiteSpace(); c = currentChar(); switch (c) { case ',': continue; case ']': if (__index == lastIndex) { complain("missing ]"); } foundEnd = true; __index++; break arrayLoop; default: complain( String.format("expecting a ',' or a ']', " + " but got \nthe current character of %s " + " on array size of %s \n", charDescription(__currentChar), list.size()) ); } } if (!foundEnd) { complain("Did not find end of Json Array"); } return value; } protected final Object decodeFromChars(char[] cs) { Value value = ((Value) super.decodeFromChars(cs)); if (value.isContainer()) { return value.toValue(); } else { return value; } } }