
org.bspfsystems.simplejson.parser.JSONReader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of simple-json Show documentation
Show all versions of simple-json Show documentation
A simple library for working with JSON Objects.
/*
* This file is part of the SimpleJSON Java library.
*
* It is based on Clifton Labs' version
* (https://github.com/cliftonlabs/json-simple/), which is a fork of
* the original by Yidong Fang (https://github.com/fangyidong/json-simple/).
* Other authors contributions remain the copyright of the respective
* owner, with the major ones of this derivative listed below.
*
* Copyright 2008-2009,2012-2014,2021 Yidong Fang
* Copyright 2008-2009,2012-2014,2016-2021 Clifton Labs
* Copyright 2021 BSPF Systems, LLC
*
* 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.bspfsystems.simplejson.parser;
import java.io.IOException;
import java.io.Reader;
import java.math.BigDecimal;
final class JSONReader {
/**
* Represents the end of file.
*/
static final int END_OF_FILE = -1;
/**
* Initial size of the look-ahead buffer.
*/
private static final int BUFFER_SIZE = 16384;
// Lexical states.
static final int INITIAL = 0;
static final int STRING_BEGIN = 2;
/**
* {@code LEXSTATE[1]} is the state in the DFA for the lexical state 1.
* {@code LEXSTATE[l+1]} is the state in the DFA for the lexical state 1 at
* the beginning of a line. {@code l} is of the form {@code l = 2*k}, where
* {@code k} is a non-negative integer.
*/
private static final int[] LEXSTATE = { 0 , 0 , 1 , 1 };
/**
* Top-level table for translating characters to character classes.
*/
private static final int[] CHARACTER_MAP_TOP = JSONReader.unpackCharacterMapTop();
private static int[] unpackCharacterMapTop() {
final int[] result = new int[4352];
final String characterMapTopPacked = "\1\0\327\u0100\10\u0200\u1020\u0100";
int packedIndex = 0;
int unpackedIndex = 0;
while (packedIndex < characterMapTopPacked.length()) {
int count = characterMapTopPacked.charAt(packedIndex++);
final int value = characterMapTopPacked.charAt(packedIndex++);
do {
result[unpackedIndex++] = value;
} while (--count > 0);
}
return result;
}
/**
* Second-level tables for translating characters into character classes.
*/
private static final int[] CHARACTER_MAP_BLOCKS = JSONReader.unpackCharacterMapBlocks();
private static int[] unpackCharacterMapBlocks() {
final int[] result = new int[768];
int packedIndex = 0;
int unpackedIndex = 0;
final String characterMapBlocksPacked = "\11\0\2\1\2\0\1\1\22\0\1\1\1\0\1\2\10\0\1\3\1\4\1\5\1\6\1\7\12\10\1\11\6\0\4\12\1\13\1\12\24\0\1\14\1\15\1\16\3\0\1\17\1\20\2\12\1\21\1\22\5\0\1\23\1\0\1\24\3\0\1\25\1\26\1\27\1\30\5\0\1\31\1\0\1\32\u0182\0\u0100\33";
while (packedIndex < characterMapBlocksPacked.length()) {
int count = characterMapBlocksPacked.charAt(packedIndex++);
final int value = characterMapBlocksPacked.charAt(packedIndex++);
do {
result[unpackedIndex++] = value;
} while (--count > 0);
}
return result;
}
/**
* Translates DFA states to action switch labels.
*/
private static final int[] ACTION = JSONReader.unpackAction();
private static int[] unpackAction() {
final int[] result = new int[45];
int packedIndex = 0;
int unpackedIndex = 0;
final String packedAction = "\2\0\1\1\1\2\1\3\1\4\1\1\1\5\1\6\1\7\1\10\3\1\1\11\1\12\1\13\1\14\1\15\5\0\1\16\1\17\1\15\1\20\1\21\1\22\1\23\1\24\1\0\1\5\1\0\1\5\4\0\1\25\1\26\2\0\1\27";
while (packedIndex < packedAction.length()) {
int count = packedAction.charAt(packedIndex++);
final int value = packedAction.charAt(packedIndex++);
do {
result[unpackedIndex++] = value;
} while (--count > 0);
}
return result;
}
/**
* Translates a state to a row index in the transition table.
*/
private static final int[] ROW_MAP = JSONReader.unpackRowMap();
private static int[] unpackRowMap() {
final int[] result = new int[45];
int packedIndex = 0;
int unpackedIndex = 0;
final String packedRowMap = "\0\0\0\34\0\70\0\124\0\70\0\70\0\160\0\214\0\70\0\70\0\70\0\250\0\304\0\340\0\70\0\70\0\374\0\70\0\u0118\0\u0134\0\u0150\0\u016c\0\u0188\0\u01a4\0\70\0\70\0\70\0\70\0\70\0\70\0\70\0\70\0\u01c0\0\u01dc\0\u01f8\0\u01f8\0\u0214\0\u0230\0\u024c\0\u0268\0\70\0\70\0\u0284\0\u02a0\0\70";
while (packedIndex < packedRowMap.length()) {
final int high = packedRowMap.charAt(packedIndex++) << 16;
result[unpackedIndex++] = high | packedRowMap.charAt(packedIndex++);
}
return result;
}
/**
* The transition table of the DFA.
*/
private static final int[] TRANS = zzUnpackTrans();
private static int[] zzUnpackTrans() {
final int[] result = new int[700];
int packedIndex = 0;
int unpackedIndex = 0;
final String transPacked = "\1\3\1\4\1\5\1\3\1\6\1\7\2\3\1\10\1\11\2\3\1\12\1\3\1\13\3\3\1\14\1\3\1\15\2\3\1\16\1\3\1\17\1\20\1\0\2\21\1\22\12\21\1\23\16\21\35\0\1\4\42\0\1\10\31\0\1\24\1\0\1\10\2\0\1\25\5\0\1\25\31\0\1\26\44\0\1\27\30\0\1\30\6\0\2\21\1\0\12\21\1\0\16\21\2\0\1\31\4\0\1\32\5\0\1\33\2\0\1\34\1\0\1\35\1\0\1\36\1\37\1\0\1\40\1\41\13\0\1\42\26\0\1\43\1\0\1\43\2\0\1\44\46\0\1\45\33\0\1\46\40\0\1\47\13\0\1\50\1\0\2\50\3\0\4\50\21\0\1\42\2\0\1\25\5\0\1\25\22\0\1\44\51\0\1\47\30\0\1\51\31\0\1\52\22\0\1\53\1\0\2\53\3\0\4\53\21\0\1\54\1\0\2\54\3\0\4\54\21\0\1\55\1\0\2\55\3\0\4\55\11\0";
while (packedIndex < transPacked.length()) {
int count = transPacked.charAt(packedIndex++);
int value = transPacked.charAt(packedIndex++);
value--;
do {
result[unpackedIndex++] = value;
} while (--count > 0);
}
return result;
}
/**
* {@code Lex.ATTRIBUTE[aState]} contains the attributes of {@code aState}.
*/
private static final int[] ATTRIBUTE = zzUnpackAttribute();
private static int[] zzUnpackAttribute() {
final int[] result = new int[45];
int packedIndex = 0;
int unpackedIndex = 0;
final String attributePacked = "\2\0\1\11\1\1\2\11\2\1\3\11\3\1\2\11\1\1\1\11\1\1\5\0\10\11\1\0\1\1\1\0\1\1\4\0\2\11\2\0\1\11";
while (packedIndex < attributePacked.length()) {
int count = attributePacked.charAt(packedIndex++);
int value = attributePacked.charAt(packedIndex++);
do {
result[unpackedIndex++] = value;
} while (--count > 0);
}
return result;
}
private final Reader reader;
private int lexicalState;
private char[] buffer;
private int markedPosition;
private int currentPosition;
private int startRead;
private int endRead;
private boolean atEndOfFile;
private int finalHighSurrogate;
private long position;
private StringBuilder builder;
/**
* Constructs a new {@link JSONReader}.
*
* @param reader The {@link Reader} to read input from.
*/
JSONReader(Reader reader) {
this.reader = reader;
this.lexicalState = JSONReader.INITIAL;
this.buffer = new char[JSONReader.BUFFER_SIZE];
this.finalHighSurrogate = 0;
this.builder = new StringBuilder();
}
/**
* Gets the current position.
*
* @return The current position.
*/
long getPosition() {
return this.position;
}
/**
* Translates raw input code points to DFA table row.
*/
private int translateCharacterMap(int input) {
int offset = input & 255;
return offset == input ? JSONReader.CHARACTER_MAP_BLOCKS[offset] : JSONReader.CHARACTER_MAP_BLOCKS[JSONReader.CHARACTER_MAP_TOP[input >> 8] | offset];
}
/**
* Refills the input buffer.
*
* @return {@code false} if there was new input.
* @throws IOException If any I/O error occurs.
*/
private boolean refill() throws IOException {
if (this.startRead > 0) {
this.endRead += this.finalHighSurrogate;
this.finalHighSurrogate = 0;
System.arraycopy(this.buffer, this.startRead, this.buffer, 0, this.endRead - this.startRead);
this.endRead -= this.startRead;
this.currentPosition -= this.startRead;
this.markedPosition -= this.startRead;
this.startRead = 0;
}
if (this.currentPosition >= this.buffer.length - this.finalHighSurrogate) {
char[] newBuffer = new char[this.buffer.length * 2];
System.arraycopy(this.buffer, 0, newBuffer, 0, this.buffer.length);
this.buffer = newBuffer;
this.endRead += this.finalHighSurrogate;
this.finalHighSurrogate = 0;
}
int requested = this.buffer.length - this.endRead;
int numberRead = this.reader.read(this.buffer, this.endRead, requested);
if (numberRead == 0) {
throw new IOException("Reader returned 0 characters. See JFlex examples/zero-reader for a workaround.");
}
if (numberRead > 0) {
this.endRead += numberRead;
if (Character.isHighSurrogate(this.buffer[this.endRead - 1])) {
if (numberRead == requested) {
this.endRead--;
this.finalHighSurrogate = 1;
} else {
int c = this.reader.read();
if (c == -1) {
return true;
} else {
this.buffer[this.endRead++] = (char) c;
}
}
}
return false;
}
return true;
}
/**
* Enters a new lexical state.
*
* @param newState the new lexical state.
*/
private void begin(int newState) {
this.lexicalState = newState;
}
/**
* Gets the text matched by the current regular expression.
*
* @return The matched text.
*/
private String text() {
return new String(this.buffer, this.startRead, this.markedPosition - this.startRead);
}
/**
* Resumes scanning until the next regular expression is matched, the end of
* the input is encountered, or an I/O error occurs.
*
* @return The next token.
* @throws IOException If an I/O error occurs.
* @throws JSONException If a JSON error occurs.
*/
JSONToken read() throws IOException, JSONException {
int input;
int action;
int currentPosition;
int markedPosition;
int endRead = this.endRead;
char[] buffer = this.buffer;
int[] attribute = JSONReader.ATTRIBUTE;
while (true) {
markedPosition = this.markedPosition;
this.position += markedPosition - this.startRead;
action = -1;
currentPosition = this.currentPosition = this.startRead = markedPosition;
int state = JSONReader.LEXSTATE[this.lexicalState];
int attributes = attribute[state];
if ((attributes & 1) == 1) {
action = state;
}
forAction: {
while (true) {
if (currentPosition < endRead) {
input = Character.codePointAt(buffer, currentPosition, endRead);
currentPosition += Character.charCount(input);
} else if (this.atEndOfFile) {
input = JSONReader.END_OF_FILE;
break forAction;
} else {
this.currentPosition = currentPosition;
this.markedPosition = markedPosition;
boolean endOfFile = this.refill();
currentPosition = this.currentPosition;
markedPosition = this.markedPosition;
buffer = this.buffer;
endRead = this.endRead;
if (endOfFile) {
input = JSONReader.END_OF_FILE;
break forAction;
} else {
input = Character.codePointAt(buffer, currentPosition, endRead);
currentPosition = Character.charCount(input);
}
}
int next = JSONReader.TRANS[JSONReader.ROW_MAP[state] + this.translateCharacterMap(input)];
if (next == -1) {
break forAction;
}
state = next;
attributes = attribute[state];
if ((attributes & 1) == 1) {
action = state;
markedPosition = currentPosition;
if ((attributes & 8) == 8) {
break forAction;
}
}
}
}
this.markedPosition = markedPosition;
if (input == JSONReader.END_OF_FILE && this.startRead == this.currentPosition) {
this.atEndOfFile = true;
return null;
} else {
switch(action < 0 ? action : JSONReader.ACTION[action]) {
case 1:
throw new JSONException("Unexpected character \"" + this.buffer[this.startRead] + "\" at position " + this.position + ". Please fix the String and try to parse again.");
case 2:
case 24:
case 25:
case 26:
case 27:
case 28:
case 29:
case 30:
case 31:
case 32:
case 33:
case 34:
case 35:
case 36:
case 37:
case 38:
case 39:
case 40:
case 41:
case 42:
case 43:
case 44:
case 45:
case 46:
break;
case 3:
this.builder = new StringBuilder();
this.begin(JSONReader.STRING_BEGIN);
break;
case 4:
return new JSONToken(JSONToken.Type.COMMA, null);
case 5:
BigDecimal bigDecimalValue = new BigDecimal(this.text());
return new JSONToken(JSONToken.Type.DATUM, bigDecimalValue);
case 6:
return new JSONToken(JSONToken.Type.COLON, null);
case 7:
return new JSONToken(JSONToken.Type.LEFT_SQUARE, null);
case 8:
return new JSONToken(JSONToken.Type.RIGHT_SQUARE, null);
case 9:
return new JSONToken(JSONToken.Type.LEFT_BRACE, null);
case 10:
return new JSONToken(JSONToken.Type.RIGHT_BRACE, null);
case 11:
this.builder.append(this.text());
case 12:
this.begin(JSONReader.INITIAL);
return new JSONToken(JSONToken.Type.DATUM, this.builder.toString());
case 13:
this.builder.append('\\');
break;
case 14:
this.builder.append('\"');
break;
case 15:
this.builder.append('/');
break;
case 16:
this.builder.append('\b');
break;
case 17:
this.builder.append('\f');
break;
case 18:
this.builder.append('\n');
break;
case 19:
this.builder.append('\r');
break;
case 20:
this.builder.append('\t');
break;
case 21:
return new JSONToken(JSONToken.Type.DATUM, null);
case 22:
Boolean booleanValue = Boolean.valueOf(this.text());
return new JSONToken(JSONToken.Type.DATUM, booleanValue);
case 23:
try {
int character = Integer.parseInt(this.text().substring(2), 16);
this.builder.append((char) character);
break;
} catch (Exception e) {
throw new JSONException("An unexpected exception was caught at position " + this.position + ". Please report this to the library's maintainer, as it will need to be addressed before trying to parse again.", e);
}
default:
throw new Error("Error: could not match input");
}
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy