groovy.json.internal.JsonParserLax Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of groovy-all Show documentation
Show all versions of groovy-all Show documentation
Groovy: A powerful, dynamic language for the JVM
/*
* 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;
/**
* @author Richard Hightower
*/
public class JsonParserLax extends JsonParserCharArray {
private final boolean useValues;
private final boolean chop;
private final boolean lazyChop;
private final boolean defaultCheckDates;
public JsonParserLax() {
this(true);
}
public JsonParserLax(boolean useValues) {
this(useValues, false);
}
public JsonParserLax(boolean useValues, boolean chop) {
this(useValues, chop, !chop);
}
public JsonParserLax(boolean useValues, boolean chop, boolean lazyChop) {
this(useValues, chop, lazyChop, true);
}
public JsonParserLax(boolean useValues, boolean chop, boolean lazyChop, boolean defaultCheckDates) {
this.useValues = useValues;
this.chop = chop;
this.lazyChop = lazyChop;
this.defaultCheckDates = defaultCheckDates;
}
private Value decodeJsonObjectLax() {
if (__currentChar == '{')
this.nextChar();
ValueMap map = useValues ? new ValueMapImpl() : new LazyValueMap(lazyChop);
Value value = new ValueContainer(map);
skipWhiteSpace();
int startIndexOfKey = __index;
Value key;
MapItemValue miv;
Value item;
done:
for (; __index < this.charArray.length; __index++) {
skipWhiteSpace();
switch (__currentChar) {
case '/': /* */ //
handleComment();
startIndexOfKey = __index;
break;
case '#':
handleBashComment();
startIndexOfKey = __index;
break;
case ':':
char startChar = charArray[startIndexOfKey];
if (startChar == ',') {
startIndexOfKey++;
}
key = extractLaxString(startIndexOfKey, __index - 1, false, false);
__index++; //skip :
item = decodeValueInternal();
skipWhiteSpace();
miv = new MapItemValue(key, item);
map.add(miv);
startIndexOfKey = __index;
if (__currentChar == '}') {
__index++;
break done;
}
break;
case '\'':
key = decodeStringSingle();
//puts ("key with quote", key);
skipWhiteSpace();
if (__currentChar != ':') {
complain("expecting current character to be ':' but got " + charDescription(__currentChar) + "\n");
}
__index++;
item = decodeValueInternal();
//puts ("key", "#" + key + "#", value);
skipWhiteSpace();
miv = new MapItemValue(key, item);
map.add(miv);
startIndexOfKey = __index;
if (__currentChar == '}') {
__index++;
break done;
}
break;
case '"':
key = decodeStringDouble();
//puts ("key with quote", key);
skipWhiteSpace();
if (__currentChar != ':') {
complain("expecting current character to be ':' but got " + charDescription(__currentChar) + "\n");
}
__index++;
item = decodeValueInternal();
//puts ("key", "#" + key + "#", value);
skipWhiteSpace();
miv = new MapItemValue(key, item);
map.add(miv);
startIndexOfKey = __index;
if (__currentChar == '}') {
__index++;
break done;
}
break;
case '}':
__index++;
break done;
}
}
return value;
}
private Value extractLaxString(int startIndexOfKey, int end, boolean encoded, boolean checkDate) {
char startChar;
startIndexLookup:
for (; startIndexOfKey < __index && startIndexOfKey < charArray.length; startIndexOfKey++) {
startChar = charArray[startIndexOfKey];
switch (startChar) {
case ' ':
case '\n':
case '\t':
continue;
default:
break startIndexLookup;
}
}
char endChar;
int endIndex = end >= charArray.length ? charArray.length - 1 : end;
endIndexLookup:
for (; endIndex >= startIndexOfKey + 1 && endIndex >= 0; endIndex--) {
endChar = charArray[endIndex];
switch (endChar) {
case ' ':
case '\n':
case '\t':
case '}':
continue;
case ',':
case ';':
continue;
case ']':
continue;
default:
break endIndexLookup;
}
}
return new CharSequenceValue(chop, Type.STRING, startIndexOfKey, endIndex + 1, this.charArray, encoded, checkDate);
}
protected final Object decodeValue() {
return this.decodeValueInternal();
}
private Value decodeValueInternal() {
Value value = null;
for (; __index < charArray.length; __index++) {
skipWhiteSpace();
switch (__currentChar) {
case '\n':
break;
case '\r':
break;
case ' ':
break;
case '\t':
break;
case '\b':
break;
case '\f':
break;
case '/': /* */ //
handleComment();
break;
case '#':
handleBashComment();
break;
case '"':
value = decodeStringDouble();
break;
case '\'':
value = decodeStringSingle();
break;
case 't':
if (isTrue()) {
return decodeTrue() == true ? ValueContainer.TRUE : ValueContainer.FALSE;
} else {
value = decodeStringLax();
}
break;
case 'f':
if (isFalse()) {
return decodeFalse() == false ? ValueContainer.FALSE : ValueContainer.TRUE;
} else {
value = decodeStringLax();
}
break;
case 'n':
if (isNull()) {
return decodeNull() == null ? ValueContainer.NULL : ValueContainer.NULL;
} else {
value = decodeStringLax();
}
break;
case '[':
value = decodeJsonArrayLax();
break;
case '{':
value = decodeJsonObjectLax();
break;
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '0':
return decodeNumberLax(false);
case '-':
return decodeNumberLax(true);
default:
value = decodeStringLax();
}
if (value != null) {
return value;
}
}
return null;
}
private void handleBashComment() {
for (; __index < charArray.length; __index++) {
__currentChar = charArray[__index];
if (__currentChar == '\n') {
__index++;
return;
}
}
}
private void handleComment() {
if (hasMore()) {
__index++;
__currentChar = charArray[__index];
switch (__currentChar) {
case '*':
for (; __index < charArray.length; __index++) {
__currentChar = charArray[__index];
if (__currentChar == '*') {
if (hasMore()) {
__index++;
__currentChar = charArray[__index];
if (__currentChar == '/') {
if (hasMore()) {
__index++;
return;
}
}
} else {
complain("missing close of comment");
}
}
}
case '/':
for (; __index < charArray.length; __index++) {
__currentChar = charArray[__index];
if (__currentChar == '\n') {
if (hasMore()) {
__index++;
return;
} else {
return;
}
}
}
}
}
}
/**
* Decodes a number from a JSON value. If at any point it is determined that
* the value is not a valid number the value is treated as a {@code String}.
*
* @param minus indicate whether the number is negative
* @return a number, or {@code String} if not a valid number
*/
protected final Value decodeNumberLax(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) { return decodeStringLax(); }
foundDot = true;
break;
case LETTER_E:
case LETTER_BIG_E:
if (foundExp) { return decodeStringLax(); }
foundExp = true;
break;
case MINUS:
case PLUS:
if (foundSign || !foundExp) { return decodeStringLax(); }
if (foundExp && array[index - 1] != LETTER_E && array[index - 1] != LETTER_BIG_E) {
return decodeStringLax();
}
foundSign = true;
break;
}
doubleFloat = true;
} else {
return decodeStringLax();
}
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) {
return decodeStringLax();
}
}
__index = index;
__currentChar = currentChar;
Type type = doubleFloat ? Type.DOUBLE : Type.INTEGER;
NumberValue value = new NumberValue(chop, type, startIndex, __index, this.charArray);
return value;
}
private boolean isNull() {
if (__index + NULL.length <= charArray.length) {
if (charArray[__index] == 'n' &&
charArray[__index + 1] == 'u' &&
charArray[__index + 2] == 'l' &&
charArray[__index + 3] == 'l') {
return true;
}
}
return false;
}
private boolean isTrue() {
if (__index + TRUE.length <= charArray.length) {
if (charArray[__index] == 't' &&
charArray[__index + 1] == 'r' &&
charArray[__index + 2] == 'u' &&
charArray[__index + 3] == 'e') {
return true;
}
}
return false;
}
private boolean isFalse() {
if (__index + FALSE.length <= charArray.length) {
if (charArray[__index] == 'f' &&
charArray[__index + 1] == 'a' &&
charArray[__index + 2] == 'l' &&
charArray[__index + 3] == 's' &&
charArray[__index + 4] == 'e') {
return true;
}
}
return false;
}
private Value decodeStringLax() {
int index = __index;
char currentChar = charArray[__index];
final int startIndex = __index;
boolean encoded = false;
char[] charArray = this.charArray;
for (; index < charArray.length; index++) {
currentChar = charArray[index];
if (isDelimiter(currentChar)) break;
else if (currentChar == '\\') break;
}
Value value = this.extractLaxString(startIndex, index, encoded, defaultCheckDates);
__index = index;
return value;
}
private Value decodeStringDouble() {
__currentChar = charArray[__index];
if (__index < charArray.length && __currentChar == '"') {
__index++;
}
final int startIndex = __index;
boolean escape = false;
boolean encoded = false;
done:
for (; __index < this.charArray.length; __index++) {
__currentChar = charArray[__index];
switch (__currentChar) {
case '"':
if (!escape) {
break done;
} else {
escape = false;
continue;
}
case '\\':
if (!escape) {
escape = true;
} else {
escape = false;
}
encoded = true;
continue;
}
escape = false;
}
Value value = new CharSequenceValue(chop, Type.STRING, startIndex, __index, this.charArray, encoded, defaultCheckDates);
if (__index < charArray.length) {
__index++;
}
return value;
}
private Value decodeStringSingle() {
__currentChar = charArray[__index];
if (__index < charArray.length && __currentChar == '\'') {
__index++;
}
final int startIndex = __index;
boolean escape = false;
boolean encoded = false;
int minusCount = 0;
int colonCount = 0;
done:
for (; __index < this.charArray.length; __index++) {
__currentChar = charArray[__index];
switch (__currentChar) {
case '\'':
if (!escape) {
break done;
} else {
escape = false;
continue;
}
case '\\':
encoded = true;
escape = true;
continue;
case '-':
minusCount++;
break;
case ':':
colonCount++;
break;
}
escape = false;
}
boolean checkDates = defaultCheckDates && !encoded && minusCount >= 2 && colonCount >= 2;
Value value = new CharSequenceValue(chop, Type.STRING, startIndex, __index, this.charArray, encoded, checkDates);
if (__index < charArray.length) {
__index++;
}
return value;
}
private Value decodeJsonArrayLax() {
if (__currentChar == '[') {
__index++;
}
skipWhiteSpace();
if (__currentChar == ']') {
__index++;
return new ValueContainer(new ArrayList());
}
List