io.github.lukehutch.fastclasspathscanner.json.JSONParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of fast-classpath-scanner Show documentation
Show all versions of fast-classpath-scanner Show documentation
Uber-fast, ultra-lightweight Java classpath scanner. Scans the classpath by parsing the classfile binary format directly rather than by using reflection.
See https://github.com/lukehutch/fast-classpath-scanner
/*
* This file is part of FastClasspathScanner.
*
* Author: Luke Hutchison
*
* Hosted at: https://github.com/lukehutch/fast-classpath-scanner
*
* --
*
* The MIT License (MIT)
*
* Copyright (c) 2018 Luke Hutchison
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
* EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
* OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.lukehutch.fastclasspathscanner.json;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
import io.github.lukehutch.fastclasspathscanner.utils.Parser;
/**
* A JSON parser, based on the PEG grammar found at:
*
* https://github.com/azatoth/PanPG/blob/master/grammars/JSON.peg
*/
class JSONParser extends Parser {
private JSONParser(final String string) throws ParseException {
super(string);
}
// -------------------------------------------------------------------------------------------------------------
/**
* Parse a quoted/escaped JSON string.
*
*
*
* String ← S? ["] ( [^ " \ U+0000-U+001F ] / Escape )* ["] S?
*
* Escape ← [\] ( [ " / \ b f n r t ] / UnicodeEscape )
*
* UnicodeEscape ← "u" [0-9A-Fa-f]{4}
*
*
*/
private CharSequence parseString() throws ParseException {
skipWhitespace();
if (peek() != '"') {
return null;
}
next();
final int startIdx = getPosition();
// Fast path
boolean hasEscape = false;
while (hasMore()) {
final char c = getc();
if (c == '\\') {
switch (getc()) {
case 'b':
case 'f':
case 'n':
case 'r':
case 't':
case '\'':
case '"':
case '/':
case '\\':
hasEscape = true;
break;
case 'u':
hasEscape = true;
advance(4);
break;
default:
throw new ParseException(this, "Invalid escape sequence: \\" + c);
}
} else if (c == '"') {
break;
}
}
final int endIdx = getPosition() - 1;
if (!hasEscape) {
return getSubsequence(startIdx, endIdx);
}
// Slow path (for strings with escape characters)
setPosition(startIdx);
final StringBuilder buf = new StringBuilder();
while (hasMore()) {
final char c = getc();
if (c == '\\') {
switch (getc()) {
case 'b':
buf.append('\b');
break;
case 'f':
buf.append('\f');
break;
case 'n':
buf.append('\n');
break;
case 'r':
buf.append('\r');
break;
case 't':
buf.append('\t');
break;
case '\'':
case '"':
case '/':
case '\\':
buf.append(c);
break;
case 'u':
int charVal = 0;
boolean charValInvalid = false;
final char h3 = getc();
if (h3 >= '0' && h3 <= '9') {
charVal |= ((h3 - '0') << 12);
} else if (h3 >= 'a' && h3 <= 'f') {
charVal |= ((h3 - 'a' + 10) << 12);
} else if (h3 >= 'A' && h3 <= 'F') {
charVal |= ((h3 - 'A' + 10) << 12);
} else {
charValInvalid = true;
}
final char h2 = getc();
if (h2 >= '0' && h2 <= '9') {
charVal |= ((h2 - '0') << 8);
} else if (h2 >= 'a' && h2 <= 'f') {
charVal |= ((h2 - 'a' + 10) << 8);
} else if (h2 >= 'A' && h2 <= 'F') {
charVal |= ((h2 - 'A' + 10) << 8);
} else {
charValInvalid = true;
}
final char h1 = getc();
if (h1 >= '0' && h1 <= '9') {
charVal |= ((h1 - '0') << 4);
} else if (h1 >= 'a' && h1 <= 'f') {
charVal |= ((h1 - 'a' + 10) << 4);
} else if (h1 >= 'A' && h1 <= 'F') {
charVal |= ((h1 - 'A' + 10) << 4);
} else {
charValInvalid = true;
}
final char h0 = getc();
if (h0 >= '0' && h0 <= '9') {
charVal |= (h0 - '0');
} else if (h0 >= 'a' && h0 <= 'f') {
charVal |= (h0 - 'a' + 10);
} else if (h0 >= 'A' && h0 <= 'F') {
charVal |= (h0 - 'A' + 10);
} else {
charValInvalid = true;
}
if (charValInvalid) {
throw new ParseException(this,
"Invalid Unicode escape sequence: \\" + c + "" + h3 + "" + h2 + "" + h1 + "" + h0);
}
buf.append((char) charVal);
break;
default:
throw new ParseException(this, "Invalid escape sequence: \\" + c);
}
} else if (c == '"') {
break;
} else {
buf.append(c);
}
}
skipWhitespace();
return buf.toString();
}
// -------------------------------------------------------------------------------------------------------------
/**
* Parses and returns Integer, Long or Double type.
*
*
*
* Number ← Minus? IntegralPart FractionalPart? ExponentPart?
*
* Minus ← "-"
*
* IntegralPart ← "0" / [1-9] [0-9]*
*
* FractionalPart ← "." [0-9]+
*
* ExponentPart ← ( "e" / "E" ) ( "+" / "-" )? [0-9]+
*
*
*/
private Object parseNumber() throws ParseException {
final int startIdx = getPosition();
if (peek() == '-') {
next();
}
final int integralStartIdx = getPosition();
for (; hasMore(); next()) {
final char c = peek();
if (c < '0' || c > '9') {
break;
}
}
final int integralEndIdx = getPosition();
final int numIntegralDigits = integralEndIdx - integralStartIdx;
if (numIntegralDigits == 0) {
throw new ParseException(this, "Expected a number");
}
final boolean hasFractionalPart = peek() == '.';
if (hasFractionalPart) {
next();
for (; hasMore(); next()) {
final char c = peek();
if (c < '0' || c > '9') {
break;
}
}
if (getPosition() - (integralEndIdx + 1) == 0) {
throw new ParseException(this, "Expected digits after decimal point");
}
}
final boolean hasExponentPart = peek() == '.';
if (hasExponentPart) {
next();
final char sign = peek();
if (sign == '-' || sign == '+') {
next();
}
final int exponentStart = getPosition();
for (; hasMore(); next()) {
final char c = peek();
if (c < '0' || c > '9') {
break;
}
}
if (getPosition() - exponentStart == 0) {
throw new ParseException(this, "Expected an exponent");
}
}
final int endIdx = getPosition();
final String numberStr = getSubstring(startIdx, endIdx).toString();
if (hasFractionalPart || hasExponentPart) {
return Double.valueOf(numberStr);
} else if (numIntegralDigits < 9) {
return Integer.valueOf(numberStr);
} else if (numIntegralDigits == 9) {
// For 9-digit numbers, could be int or long
final long longVal = Long.valueOf(numberStr);
if (longVal >= Integer.MIN_VALUE && longVal < Integer.MAX_VALUE) {
return Integer.valueOf((int) longVal);
} else {
return Long.valueOf(longVal);
}
} else {
return Long.valueOf(numberStr);
}
}
// -------------------------------------------------------------------------------------------------------------
/**
*
*
* Array ← "[" ( JSON ( "," JSON )* / S? ) "]"
*
*
*/
private JSONArray parseJSONArray() throws ParseException {
expect('[');
skipWhitespace();
if (peek() == ']') {
// Empty array
next();
return new JSONArray(Collections.emptyList());
}
final List