/**
* 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
*
*
* 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.
*/
/**
* @author yonik
* @version $Id$
*/
public class JSONParser {
/** Event indicating a JSON string value, including member names of objects */
/** Event indicating a JSON number value which fits into a signed 64 bit integer */
/** Event indicating a JSON number value which has a fractional part or an exponent
* and with string length <= 23 chars not including sign. This covers
* all representations of normal values for Double.toString().
*/
/** Event indicating a JSON number value that was not produced by toString of any
* Java primitive numerics such as Double or Long. It is either
* an integer outside the range of a 64 bit signed integer, or a floating
* point value with a string representation of more than 23 chars.
*/
/** Event indicating a JSON boolean */
/** Event indicating a JSON null */
/** Event indicating the start of a JSON object */
/** Event indicating the end of a JSON object */
/** Event indicating the start of a JSON array */
/** Event indicating the end of a JSON array */
/** Event indicating the end of input has been reached */
{
switch( e )
{
case STRING: return "STRING";
case LONG: return "LONG";
case NUMBER: return "NUMBER";
case BIGNUMBER: return "BIGNUMBER";
case BOOLEAN: return "BOOLEAN";
case NULL: return "NULL";
case OBJECT_START: return "OBJECT_START";
case OBJECT_END: return "OBJECT_END";
case ARRAY_START: return "ARRAY_START";
case ARRAY_END: return "ARRAY_END";
case EOF: return "EOF";
}
return "Unknown: "+e;
}
this(in, new char[8192]);
// 8192 matches the default buffer size of a BufferedReader so double
// buffering of the data is avoided.
}
}
// idea - if someone passes us a CharArrayReader, we could
// directly use that buffer as it's protected.
}
}
}
// temporary output buffer
// We need to keep some state in order to (at a minimum) know if
// we should skip ',' or ':'.
// parser states stored in the stack
// info about value that was just read (or is in the middle of being read)
private int valstate;
// push current parser state (use at start of new container)
private final void push() {
// doubling here is probably overkill, but anything that needs to double more than
// once (32 levels deep) is very atypical anyway.
}
}
// pop parser state (use at end of container)
private final void pop() {
if (--ptr<0) {
throw err("Unbalanced container");
} else {
}
}
start=0;
}
}
fill();
}
}
fill();
}
}
for (;;) {
}
}
}
}
}
// We can't tell if EOF was hit by comparing start<=end
// because the illegal char could have been the last in the buffer
// or in the stream. To deal with this, the "eof" var was introduced
else msg="JSON Parse Error";
}
}
if (start>=0) {
}
}
return context;
}
if (a>=b) return "";
}
/** Returns the long read... only significant if valstate==LONG after
* this call. firstChar should be the first numeric digit read.
*/
// We build up the number in the negative plane since it's larger (by one) than
// the positive plane.
long v = '0' - firstChar;
for (int i=0; i<22; i++) {
// TODO: is this switch faster as an if-then-else?
switch(ch) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
continue;
case '.':
return 0;
case 'e':
case 'E':
nstate=0;
return 0;
default:
// return the number, relying on nextEvent() to return an error
// for invalid chars following the number.
// the max number of digits we are reading only allows for
// a long to wrap once, so we can just check if the sign is
// what is expected to detect an overflow.
if (isNeg) {
// -0 is allowed by the spec
} else {
v=-v;
}
return v;
}
}
nstate=0;
return 0;
}
// read digits right of decimal point
while(--lim>=0) {
} else {
return NUMBER;
}
}
return BIGNUMBER;
}
// call after 'e' or 'E' has been seen to read the rest of the exponent
nstate |= HAS_EXPONENT;
}
// make sure at least one digit is read.
throw err("missing exponent number");
}
}
// continuation of readExpStart
while (--lim>=0) {
} else {
return NUMBER;
}
}
return BIGNUMBER;
}
return;
}
if (nstate != 0) {
return;
}
for(;;) {
} else if (ch=='.') {
return;
return;
} else {
return;
}
}
}
return hexdig-'0';
}
throw err("invalid hex digit");
}
// backslash has already been read when this is called
switch (getChar()) {
case '"' : return '"';
case '\\' : return '\\';
case '/' : return '/';
case 'n' : return '\n';
case 'r' : return '\r';
case 't' : return '\t';
case 'f' : return '\f';
case 'b' : return '\b';
case 'u' :
return (char)(
}
throw err("Invalid character escape in string");
}
// a dummy buffer we can use to point at other buffers
char c=0;
int i;
c = buf[i];
if (c=='"') {
return tmp;
} else if (c=='\\') {
break;
}
}
readStringChars2(out, i);
return out;
}
// middle is the pointer to the middle of a buffer to start scanning for a non-string
// character ('"' or "/"). start<=middle<end
// this should be faster for strings with fewer escapes, but probably slower for many escapes.
for (;;) {
getMore();
}
if (ch=='"') {
return;
} else if (ch=='\\') {
}
}
}
/*** alternate implelentation
// middle is the pointer to the middle of a buffer to start scanning for a non-string
// character ('"' or "/"). start<=middle<end
private void readStringChars2a(CharArr arr, int middle) throws IOException {
int ch=0;
for(;;) {
// find the next non-string char
for (; middle<end; middle++) {
ch = buf[middle];
if (ch=='"' || ch=='\\') break;
}
arr.write(buf,start,middle-start);
if (middle>=end) {
getMore();
middle=start;
} else {
start = middle+1; // set buffer pointer to correct spot
if (ch=='"') {
valstate=0;
return;
} else if (ch=='\\') {
arr.write(readEscapedChar());
if (start>=end) getMore();
middle=start;
}
}
}
}
***/
// return the next event when parser is in a neutral state (no
// map separators or array element separators to read
for(;;) {
switch (ch) {
case ' ':
case '\t': break;
case '\r':
case '\n': break; // try and keep track of linecounts?
case '"' :
return STRING;
case '{' :
push();
return OBJECT_START;
case '[':
push();
return ARRAY_START;
case '0' :
//special case '0'? If next char isn't '.' val=0
if (ch=='.') {
readNumber('0',false);
return valstate;
start--;
lval = 0;
return LONG;
} else {
throw err("Leading zeros not allowed");
}
case '1' :
case '2' :
case '3' :
case '4' :
case '5' :
case '6' :
case '7' :
case '8' :
case '9' :
return valstate;
case '-' :
return valstate;
case 't':
// TODO: test performance of this non-branching inline version.
// if ((('r'-getChar())|('u'-getChar())|('e'-getChar())) != 0) err("");
bool=true;
return BOOLEAN;
case 'f':
bool=false;
return BOOLEAN;
case 'n':
return NULL;
case -1:
return EOF;
}
}
}
}
/** Returns the next event encountered in the JSON stream, one of
* <ul>
* <li>{@link #STRING}</li>
* <li>{@link #LONG}</li>
* <li>{@link #NUMBER}</li>
* <li>{@link #BIGNUMBER}</li>
* <li>{@link #BOOLEAN}</li>
* <li>{@link #NULL}</li>
* <li>{@link #OBJECT_START}</li>
* <li>{@link #OBJECT_END}</li>
* <li>{@link #OBJECT_END}</li>
* <li>{@link #ARRAY_START}</li>
* <li>{@link #ARRAY_END}</li>
* <li>{@link #EOF}</li>
* </ul>
*/
}
}
valstate=0;
int ch; // TODO: factor out getCharNWS() to here and check speed
switch (state) {
case 0:
case DID_OBJSTART:
ch = getCharNWS();
if (ch=='}') {
pop();
return event = OBJECT_END;
}
if (ch != '"') {
throw err("Expected string");
}
state = DID_MEMNAME;
case DID_MEMNAME:
ch = getCharNWS();
if (ch!=':') {
throw err("Expected key,value separator ':'");
}
case DID_MEMVAL:
ch = getCharNWS();
if (ch=='}') {
pop();
return event = OBJECT_END;
} else if (ch!=',') {
throw err("Expected ',' or '}'");
}
ch = getCharNWS();
if (ch != '"') {
throw err("Expected string");
}
state = DID_MEMNAME;
case DID_ARRSTART:
ch = getCharNWS();
if (ch==']') {
pop();
}
case DID_ARRELEM:
ch = getCharNWS();
if (ch==']') {
pop();
} else if (ch!=',') {
throw err("Expected ',' or ']'");
}
// state = DID_ARRELEM;
}
return 0;
}
public int lastEvent() {
return event;
}
public boolean wasKey()
{
return state == DID_MEMNAME;
}
if (valstate==0) {
throw err("type mismatch");
}
valstate=0;
}
else {
throw err("type mismatch");
}
}
/** Returns the JSON string value, decoding any escaped characters. */
return getStringChars().toString();
}
/** Returns the characters of a JSON string value, decoding any escaped characters.
* <p/>The underlying buffer of the returned <code>CharArr</code> should *not* be
* modified as it may be shared with the input buffer.
* <p/>The returned <code>CharArr</code> will only be valid up until
* the next JSONParser method is called. Any required data should be
* read before that point.
*/
return readStringChars();
}
/** Reads a JSON string into the output, decoding any escaped characters. */
}
/** Reads a number from the input stream and parses it as a long, only if
* the value will in fact fit into a signed 64 bit integer. */
return lval;
}
/** Reads a number from the input stream and parses it as a double */
}
/** Returns the characters of a JSON numeric value.
* <p/>The underlying buffer of the returned <code>CharArr</code> should *not* be
* modified as it may be shared with the input buffer.
* <p/>The returned <code>CharArr</code> will only be valid up until
* the next JSONParser method is called. Any required data should be
* read before that point.
*/
int ev=0;
valstate=0;
return out;
}
valstate=0;
return out;
} else {
}
}
/** Reads a JSON numeric value into the output. */
int ev=0;
} else {
}
valstate=0;
}
/** Reads a boolean value */
return bool;
}
/** Reads a null value */
}
/**
* @return the current nesting level, the number of parent objects or arrays.
*/
public int getLevel() {
return ptr;
}
public long getPosition()
{
}
}