org.glassfish.json.JsonTokenizer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of virtdata-lib-realer Show documentation
Show all versions of virtdata-lib-realer Show documentation
With inspiration from other libraries
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2012-2013 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.glassfish.json;
import org.glassfish.json.api.BufferPool;
import javax.json.JsonException;
import javax.json.stream.JsonLocation;
import javax.json.stream.JsonParser;
import javax.json.stream.JsonParsingException;
import java.io.*;
import java.math.BigDecimal;
import java.util.Arrays;
import javax.json.stream.JsonParser.Event;
/**
* JSON Tokenizer
*
* @author Jitendra Kotamraju
*/
final class JsonTokenizer implements Closeable {
// Table to look up hex ch -> value (for e.g HEX['F'] = 15, HEX['5'] = 5)
private final static int[] HEX = new int[128];
static {
Arrays.fill(HEX, -1);
for (int i='0'; i <= '9'; i++) {
HEX[i] = i-'0';
}
for (int i='A'; i <= 'F'; i++) {
HEX[i] = 10+i-'A';
}
for (int i='a'; i <= 'f'; i++) {
HEX[i] = 10+i-'a';
}
}
private final static int HEX_LENGTH = HEX.length;
private final BufferPool bufferPool;
private final Reader reader;
// Internal buffer that is used for parsing. It is also used
// for storing current string and number value token
private char[] buf;
// Indexes in buffer
//
// XXXssssssssssssXXXXXXXXXXXXXXXXXXXXXXrrrrrrrrrrrrrrXXXXXX
// ^ ^ ^ ^
// | | | |
// storeBegin storeEnd readBegin readEnd
private int readBegin;
private int readEnd;
private int storeBegin;
private int storeEnd;
// line number of the current pointer of parsing char
private long lineNo = 1;
// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
// ^
// |
// bufferOffset
//
// offset of the last \r\n or \n. will be used to calculate column number
// of a token or an error. This may be outside of the buffer.
private long lastLineOffset = 0;
// offset in the stream for the start of the buffer, will be used in
// calculating JsonLocation's stream offset, column no.
private long bufferOffset = 0;
private boolean minus;
private boolean fracOrExp;
private BigDecimal bd;
enum JsonToken {
CURLYOPEN(Event.START_OBJECT, false),
SQUAREOPEN(Event.START_ARRAY, false),
COLON(null, false),
COMMA(null, false),
STRING(Event.VALUE_STRING, true),
NUMBER(Event.VALUE_NUMBER, true),
TRUE(Event.VALUE_TRUE, true),
FALSE(Event.VALUE_FALSE, true),
NULL(Event.VALUE_NULL, true),
CURLYCLOSE(Event.END_OBJECT, false),
SQUARECLOSE(Event.END_ARRAY, false),
EOF(null, false);
private final JsonParser.Event event;
private final boolean value;
JsonToken(JsonParser.Event event, boolean value) {
this.event = event;
this.value = value;
}
JsonParser.Event getEvent() {
return event;
}
boolean isValue() {
return value;
}
}
JsonTokenizer(Reader reader, BufferPool bufferPool) {
this.reader = reader;
this.bufferPool = bufferPool;
buf = bufferPool.take();
}
private void readString() {
// when inPlace is true, no need to copy chars
boolean inPlace = true;
storeBegin = storeEnd = readBegin;
do {
// Write unescaped char block within the current buffer
if (inPlace) {
int ch;
while(readBegin < readEnd && ((ch=buf[readBegin]) >= 0x20) && ch != '\\') {
if (ch == '"') {
storeEnd = readBegin++; // ++ to consume quote char
return; // Got the entire string
}
readBegin++; // consume unescaped char
}
storeEnd = readBegin;
}
// string may be crossing buffer boundaries and may contain
// escaped characters.
int ch = read();
if (ch >= 0x20 && ch != 0x22 && ch != 0x5c) {
if (!inPlace) {
buf[storeEnd] = (char)ch;
}
storeEnd++;
continue;
}
switch (ch) {
case '\\':
inPlace = false; // Now onwards need to copy chars
unescape();
break;
case '"':
return;
default:
throw unexpectedChar(ch);
}
} while (true);
}
private void unescape() {
int ch = read();
switch (ch) {
case 'b':
buf[storeEnd++] = '\b';
break;
case 't':
buf[storeEnd++] = '\t';
break;
case 'n':
buf[storeEnd++] = '\n';
break;
case 'f':
buf[storeEnd++] = '\f';
break;
case 'r':
buf[storeEnd++] = '\r';
break;
case '"':
case '\\':
case '/':
buf[storeEnd++] = (char)ch;
break;
case 'u': {
int unicode = 0;
for (int i = 0; i < 4; i++) {
int ch3 = read();
int digit = (ch3 >= 0 && ch3 < HEX_LENGTH) ? HEX[ch3] : -1;
if (digit < 0) {
throw unexpectedChar(ch3);
}
unicode = (unicode << 4)|digit;
}
buf[storeEnd++] = (char)unicode;
break;
}
default:
throw unexpectedChar(ch);
}
}
// Reads a number char. If the char is within the buffer, directly
// reads from the buffer. Otherwise, uses read() which takes care
// of resizing, filling up the buf, adjusting the pointers
private int readNumberChar() {
if (readBegin < readEnd) {
return buf[readBegin++];
} else {
storeEnd = readBegin;
return read();
}
}
private void readNumber(int ch) {
storeBegin = storeEnd = readBegin-1;
// sign
if (ch == '-') {
this.minus = true;
ch = readNumberChar();
if (ch < '0' || ch >'9') {
throw unexpectedChar(ch);
}
}
// int
if (ch == '0') {
ch = readNumberChar();
} else {
do {
ch = readNumberChar();
} while (ch >= '0' && ch <= '9');
}
// frac
if (ch == '.') {
this.fracOrExp = true;
int count = 0;
do {
ch = readNumberChar();
count++;
} while (ch >= '0' && ch <= '9');
if (count == 1) {
throw unexpectedChar(ch);
}
}
// exp
if (ch == 'e' || ch == 'E') {
this.fracOrExp = true;
ch = readNumberChar();
if (ch == '+' || ch == '-') {
ch = readNumberChar();
}
int count;
for (count = 0; ch >= '0' && ch <= '9'; count++) {
ch = readNumberChar();
}
if (count == 0) {
throw unexpectedChar(ch);
}
}
readBegin--;
storeEnd = readBegin;
}
private void readTrue() {
int ch1 = read();
if (ch1 != 'r') {
throw expectedChar(ch1, 'r');
}
int ch2 = read();
if (ch2 != 'u') {
throw expectedChar(ch2, 'u');
}
int ch3 = read();
if (ch3 != 'e') {
throw expectedChar(ch3, 'e');
}
}
private void readFalse() {
int ch1 = read();
if (ch1 != 'a') {
throw expectedChar(ch1, 'a');
}
int ch2 = read();
if (ch2 != 'l') {
throw expectedChar(ch2, 'l');
}
int ch3 = read();
if (ch3 != 's') {
throw expectedChar(ch3, 's');
}
int ch4 = read();
if (ch4 != 'e') {
throw expectedChar(ch4, 'e');
}
}
private void readNull() {
int ch1 = read();
if (ch1 != 'u') {
throw expectedChar(ch1, 'u');
}
int ch2 = read();
if (ch2 != 'l') {
throw expectedChar(ch2, 'l');
}
int ch3 = read();
if (ch3 != 'l') {
throw expectedChar(ch3, 'l');
}
}
/*
* Could be optimized if the parser uses separate methods to match colon
* etc (that would avoid the switch statement cost in certain cases)
*/
JsonToken nextToken() {
reset();
int ch = read();
// whitespace
while (ch == 0x20 || ch == 0x09 || ch == 0x0a || ch == 0x0d) {
if (ch == '\r') {
++lineNo;
ch = read();
if (ch == '\n') {
lastLineOffset = bufferOffset+readBegin;
} else {
lastLineOffset = bufferOffset+readBegin-1;
continue;
}
} else if (ch == '\n') {
++lineNo;
lastLineOffset = bufferOffset+readBegin;
}
ch = read();
}
switch (ch) {
case '"':
readString();
return JsonToken.STRING;
case '{':
return JsonToken.CURLYOPEN;
case '[':
return JsonToken.SQUAREOPEN;
case ':':
return JsonToken.COLON;
case ',':
return JsonToken.COMMA;
case 't':
readTrue();
return JsonToken.TRUE;
case 'f':
readFalse();
return JsonToken.FALSE;
case 'n':
readNull();
return JsonToken.NULL;
case ']':
return JsonToken.SQUARECLOSE;
case '}':
return JsonToken.CURLYCLOSE;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '-':
readNumber(ch);
return JsonToken.NUMBER;
case -1:
return JsonToken.EOF;
default:
throw unexpectedChar(ch);
}
}
// Gives the location of the last char. Used for
// JsonParsingException.getLocation
JsonLocation getLastCharLocation() {
// Already read the char, so subtracting -1
return new JsonLocationImpl(lineNo, bufferOffset +readBegin-lastLineOffset, bufferOffset +readBegin-1);
}
// Gives the parser location. Used for JsonParser.getLocation
JsonLocation getLocation() {
return new JsonLocationImpl(lineNo, bufferOffset +readBegin-lastLineOffset+1, bufferOffset +readBegin);
}
private int read() {
try {
if (readBegin == readEnd) { // need to fill the buffer
int len = fillBuf();
if (len == -1) {
return -1;
}
assert len != 0;
readBegin = storeEnd;
readEnd = readBegin+len;
}
return buf[readBegin++];
} catch (IOException ioe) {
throw new JsonException(JsonMessages.TOKENIZER_IO_ERR(), ioe);
}
}
private int fillBuf() throws IOException {
if (storeEnd != 0) {
int storeLen = storeEnd-storeBegin;
if (storeLen > 0) {
// there is some store data
if (storeLen == buf.length) {
// buffer is full, double the capacity
char[] doubleBuf = Arrays.copyOf(buf, 2 * buf.length);
bufferPool.recycle(buf);
buf = doubleBuf;
} else {
// Left shift all the stored data to make space
System.arraycopy(buf, storeBegin, buf, 0, storeLen);
storeEnd = storeLen;
storeBegin = 0;
bufferOffset += readBegin-storeEnd;
}
} else {
storeBegin = storeEnd = 0;
bufferOffset += readBegin;
}
} else {
bufferOffset += readBegin;
}
// Fill the rest of the buf
return reader.read(buf, storeEnd, buf.length-storeEnd);
}
// state associated with the current token is no more valid
private void reset() {
if (storeEnd != 0) {
storeBegin = 0;
storeEnd = 0;
bd = null;
minus = false;
fracOrExp = false;
}
}
String getValue() {
return new String(buf, storeBegin, storeEnd-storeBegin);
}
BigDecimal getBigDecimal() {
if (bd == null) {
bd = new BigDecimal(buf, storeBegin, storeEnd-storeBegin);
}
return bd;
}
int getInt() {
// no need to create BigDecimal for common integer values (1-9 digits)
int storeLen = storeEnd-storeBegin;
if (!fracOrExp && (storeLen <= 9 || (minus && storeLen == 10))) {
int num = 0;
int i = minus ? 1 : 0;
for(; i < storeLen; i++) {
num = num * 10 + (buf[storeBegin+i] - '0');
}
return minus ? -num : num;
} else {
return getBigDecimal().intValue();
}
}
// returns true for common integer values (1-9 digits).
// So there are cases it will return false even though the number is int
boolean isDefinitelyInt() {
int storeLen = storeEnd-storeBegin;
return !fracOrExp && (storeLen <= 9 || (minus && storeLen == 10));
}
boolean isIntegral() {
return !fracOrExp || getBigDecimal().scale() == 0;
}
@Override
public void close() throws IOException {
reader.close();
bufferPool.recycle(buf);
}
private JsonParsingException unexpectedChar(int ch) {
JsonLocation location = getLastCharLocation();
return new JsonParsingException(
JsonMessages.TOKENIZER_UNEXPECTED_CHAR(ch, location), location);
}
private JsonParsingException expectedChar(int unexpected, char expected) {
JsonLocation location = getLastCharLocation();
return new JsonParsingException(
JsonMessages.TOKENIZER_EXPECTED_CHAR(unexpected, location, expected), location);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy