Java/Development Class/JSON

Материал из Java эксперт
Перейти к: навигация, поиск

Create Object from JSON string

   <source lang="java">

/**

* 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.
*/

import java.io.IOException; import java.io.Reader; import java.io.Writer; import java.math.BigDecimal; import java.math.BigInteger; import java.nio.CharBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; /**

* 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.
*/

/**

* @author yonik
* @version $Id$
*/

public class ObjectBuilder {

 public static Object fromJSON(String json) throws IOException {
   JSONParser p = new JSONParser(json);
   return getVal(p);
 }
 public static Object getVal(JSONParser parser) throws IOException {
   return new ObjectBuilder(parser).getVal();
 }
 final JSONParser parser;
 
 public ObjectBuilder(JSONParser parser) throws IOException {
   this.parser = parser;
   if (parser.lastEvent()==0) parser.nextEvent();
 }
 public Object getVal() throws IOException {
   int ev = parser.lastEvent();
   switch(ev) {
     case JSONParser.STRING: return getString();
     case JSONParser.LONG: return getLong();
     case JSONParser.NUMBER: return getNumber();
     case JSONParser.BIGNUMBER: return getBigNumber();
     case JSONParser.BOOLEAN: return getBoolean();
     case JSONParser.NULL: return getNull();
     case JSONParser.OBJECT_START: return getObject();
     case JSONParser.OBJECT_END: return null; // OR ERROR?
     case JSONParser.ARRAY_START: return getArray();
     case JSONParser.ARRAY_END: return  null; // OR ERROR?
     case JSONParser.EOF: return null; // OR ERROR?
     default: return null; // OR ERROR?
   }
 }
 public Object getString() throws IOException {
   return parser.getString();    
 }
 public Object getLong() throws IOException {
   return Long.valueOf(parser.getLong());    
 }
 public Object getNumber() throws IOException {
   CharArr num = parser.getNumberChars();
   String numstr = num.toString();
   double d = Double.parseDouble(numstr);
   if (!Double.isInfinite(d)) return Double.valueOf(d);
   // TODO: use more efficient constructor in Java5
   return new BigDecimal(numstr);
 }
 public Object getBigNumber() throws IOException {
   CharArr num = parser.getNumberChars();
   String numstr = num.toString();
   for(int ch; (ch=num.read())!=-1;) {
     if (ch=="." || ch=="e" || ch=="E") return new BigDecimal(numstr);
   }
   return new BigInteger(numstr);
 }
 public Object getBoolean() throws IOException {
   return parser.getBoolean();
 }
 public Object getNull() throws IOException {
   parser.getNull();
   return null;
 }
 public Object newObject() throws IOException {
   return new LinkedHashMap();
 }
 public Object getKey() throws IOException {
   return parser.getString();
 }
 public void addKeyVal(Object map, Object key, Object val) throws IOException {
   Object prev = ((Map)map).put(key,val);
   // TODO: test for repeated value?
 }
 public Object objectEnd(Object obj) {
   return obj;
 }
 public Object getObject() throws IOException {
   Object m = newObject();
   for(;;) {
     int ev = parser.nextEvent();
     if (ev==JSONParser.OBJECT_END) return objectEnd(m);
     Object key = getKey();
     ev = parser.nextEvent();      
     Object val = getVal();
     addKeyVal(m, key, val);
   }
 }
 public Object newArray() {
   return new ArrayList();
 }
 public void addArrayVal(Object arr, Object val) throws IOException {
   ((List)arr).add(val);
 }
 public Object endArray(Object arr) {
   return arr;
 }
 
 public Object getArray() throws IOException {
   Object arr = newArray();
   for(;;) {
     int ev = parser.nextEvent();
     if (ev==JSONParser.ARRAY_END) return endArray(arr);
     Object val = getVal();
     addArrayVal(arr, val);
   }
 }

} /**

* @author yonik
* @version $Id: JSONParser.java 730138 2008-12-30 14:54:53Z yonik $
*/
class JSONParser {
 /** Event indicating a JSON string value, including member names of objects */
 public static final int STRING = 1;
 /**
  * Event indicating a JSON number value which fits into a signed 64 bit
  * integer
  */
 public static final int LONG = 2;
 /**
  * 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().
  */
 public static final int NUMBER = 3;
 /**
  * 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.
  */
 public static final int BIGNUMBER = 4;
 /** Event indicating a JSON boolean */
 public static final int BOOLEAN = 5;
 /** Event indicating a JSON null */
 public static final int NULL = 6;
 /** Event indicating the start of a JSON object */
 public static final int OBJECT_START = 7;
 /** Event indicating the end of a JSON object */
 public static final int OBJECT_END = 8;
 /** Event indicating the start of a JSON array */
 public static final int ARRAY_START = 9;
 /** Event indicating the end of a JSON array */
 public static final int ARRAY_END = 10;
 /** Event indicating the end of input has been reached */
 public static final int EOF = 11;
 public static String getEventString(int e) {
   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;
 }
 private static final CharArr devNull = new NullCharArr();
 final char[] buf; // input buffer with JSON text in it
 int start; // current position in the buffer
 int end; // end position in the buffer (one past last valid index)
 final Reader in; // optional reader to obtain data from
 boolean eof = false; // true if the end of the stream was reached.
 long gpos; // global position = gpos + start
 int event; // last event read
 public JSONParser(Reader in) {
   this(in, new char[8192]);
   // 8192 matches the default buffer size of a BufferedReader so double
   // buffering of the data is avoided.
 }
 public JSONParser(Reader in, char[] buffer) {
   this.in = in;
   this.buf = buffer;
 }
 // idea - if someone passes us a CharArrayReader, we could
 // directly use that buffer as it"s protected.
 public JSONParser(char[] data, int start, int end) {
   this.in = null;
   this.buf = data;
   this.start = start;
   this.end = end;
 }
 public JSONParser(String data) {
   this(data, 0, data.length());
 }
 public JSONParser(String data, int start, int end) {
   this.in = null;
   this.start = start;
   this.end = end;
   this.buf = new char[end - start];
   data.getChars(start, end, buf, 0);
 }
 // temporary output buffer
 private final CharArr out = new CharArr(64);
 // We need to keep some state in order to (at a minimum) know if
 // we should skip "," or ":".
 private byte[] stack = new byte[16];
 private int ptr = 0; // pointer into the stack of parser states
 private byte state = 0; // current parser state
 // parser states stored in the stack
 private static final byte DID_OBJSTART = 1; // "{" just read
 private static final byte DID_ARRSTART = 2; // "[" just read
 private static final byte DID_ARRELEM = 3; // array element just read
 private static final byte DID_MEMNAME = 4; // object member name (map key)
 // just read
 private static final byte DID_MEMVAL = 5; // object member value (map val)
 // just read
 // 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() {
   if (ptr >= stack.length) {
     // doubling here is probably overkill, but anything that needs to double
     // more than
     // once (32 levels deep) is very atypical anyway.
     byte[] newstack = new byte[stack.length << 1];
     System.arraycopy(stack, 0, newstack, 0, stack.length);
     stack = newstack;
   }
   stack[ptr++] = state;
 }
 // pop parser state (use at end of container)
 private final void pop() {
   if (--ptr < 0) {
     throw err("Unbalanced container");
   } else {
     state = stack[ptr];
   }
 }
 protected void fill() throws IOException {
   if (in != null) {
     gpos += end;
     start = 0;
     int num = in.read(buf, 0, buf.length);
     end = num >= 0 ? num : 0;
   }
   if (start >= end)
     eof = true;
 }
 private void getMore() throws IOException {
   fill();
   if (start >= end) {
     throw err(null);
   }
 }
 protected int getChar() throws IOException {
   if (start >= end) {
     fill();
     if (start >= end)
       return -1;
   }
   return buf[start++];
 }
 private int getCharNWS() throws IOException {
   for (;;) {
     int ch = getChar();
     if (!(ch == " " || ch == "\t" || ch == "\n" || ch == "\r"))
       return ch;
   }
 }
 private void expect(char[] arr) throws IOException {
   for (int i = 1; i < arr.length; i++) {
     int ch = getChar();
     if (ch != arr[i]) {
       if (ch == -1)
         throw new RuntimeException("Unexpected EOF");
       throw new RuntimeException("Expected " + new String(arr));
     }
   }
 }
 private RuntimeException err(String msg) {
   // 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
   if (!eof && start > 0)
     start--; // backup one char
   String chs = "char=" + ((start >= end) ? "(EOF)" : "" + (char) buf[start]);
   String pos = "position=" + (gpos + start);
   String tot = chs + "," + pos;
   if (msg == null) {
     if (start >= end)
       msg = "Unexpected EOF";
     else
       msg = "JSON Parse Error";
   }
   return new RuntimeException(msg + ": " + tot);
 }
 private boolean bool; // boolean value read
 private long lval; // long value read
 private int nstate; // current state while reading a number
 private static final int HAS_FRACTION = 0x01; // nstate flag, "." already read
 private static final int HAS_EXPONENT = 0x02; // nstate flag, "[eE][+-]?[0-9]"
 // already read
 /**
  * Returns the long read... only significant if valstate==LONG after this
  * call. firstChar should be the first numeric digit read.
  */
 private long readNumber(int firstChar, boolean isNeg) throws IOException {
   out.unsafeWrite(firstChar); // unsafe OK since we know output is big enough
   // 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++) {
     int ch = getChar();
     // 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":
       v = v * 10 - (ch - "0");
       out.unsafeWrite(ch);
       continue;
     case ".":
       out.unsafeWrite(".");
       valstate = readFrac(out, 22 - i);
       return 0;
     case "e":
     case "E":
       out.unsafeWrite(ch);
       nstate = 0;
       valstate = readExp(out, 22 - i);
       return 0;
     default:
       // return the number, relying on nextEvent() to return an error
       // for invalid chars following the number.
       if (ch != -1)
         --start; // push back last char if not EOF
       // 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
         valstate = v <= 0 ? LONG : BIGNUMBER;
       } else {
         v = -v;
         valstate = v >= 0 ? LONG : BIGNUMBER;
       }
       return v;
     }
   }
   nstate = 0;
   valstate = BIGNUMBER;
   return 0;
 }
 // read digits right of decimal point
 private int readFrac(CharArr arr, int lim) throws IOException {
   nstate = HAS_FRACTION; // deliberate set instead of "|"
   while (--lim >= 0) {
     int ch = getChar();
     if (ch >= "0" && ch <= "9") {
       arr.write(ch);
     } else if (ch == "e" || ch == "E") {
       arr.write(ch);
       return readExp(arr, lim);
     } else {
       if (ch != -1)
         start--; // back up
       return NUMBER;
     }
   }
   return BIGNUMBER;
 }
 // call after "e" or "E" has been seen to read the rest of the exponent
 private int readExp(CharArr arr, int lim) throws IOException {
   nstate |= HAS_EXPONENT;
   int ch = getChar();
   lim--;
   if (ch == "+" || ch == "-") {
     arr.write(ch);
     ch = getChar();
     lim--;
   }
   // make sure at least one digit is read.
   if (ch < "0" || ch > "9") {
     throw err("missing exponent number");
   }
   arr.write(ch);
   return readExpDigits(arr, lim);
 }
 // continuation of readExpStart
 private int readExpDigits(CharArr arr, int lim) throws IOException {
   while (--lim >= 0) {
     int ch = getChar();
     if (ch >= "0" && ch <= "9") {
       arr.write(ch);
     } else {
       if (ch != -1)
         start--; // back up
       return NUMBER;
     }
   }
   return BIGNUMBER;
 }
 private void continueNumber(CharArr arr) throws IOException {
   if (arr != out)
     arr.write(out);
   if ((nstate & HAS_EXPONENT) != 0) {
     readExpDigits(arr, Integer.MAX_VALUE);
     return;
   }
   if (nstate != 0) {
     readFrac(arr, Integer.MAX_VALUE);
     return;
   }
   for (;;) {
     int ch = getChar();
     if (ch >= "0" && ch <= "9") {
       arr.write(ch);
     } else if (ch == ".") {
       arr.write(ch);
       readFrac(arr, Integer.MAX_VALUE);
       return;
     } else if (ch == "e" || ch == "E") {
       arr.write(ch);
       readExp(arr, Integer.MAX_VALUE);
       return;
     } else {
       if (ch != -1)
         start--;
       return;
     }
   }
 }
 private int hexval(int hexdig) {
   if (hexdig >= "0" && hexdig <= "9") {
     return hexdig - "0";
   } else if (hexdig >= "A" && hexdig <= "F") {
     return hexdig + (10 - "A");
   } else if (hexdig >= "a" && hexdig <= "f") {
     return hexdig + (10 - "a");
   }
   throw err("invalid hex digit");
 }
 // backslash has already been read when this is called
 private char readEscapedChar() throws IOException {
   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) ((hexval(getChar()) << 12) | (hexval(getChar()) << 8)
         | (hexval(getChar()) << 4) | (hexval(getChar())));
   }
   throw err("Invalid character escape in string");
 }
 // a dummy buffer we can use to point at other buffers
 private final CharArr tmp = new CharArr(null, 0, 0);
 private CharArr readStringChars() throws IOException {
   char c = 0;
   int i;
   for (i = start; i < end; i++) {
     c = buf[i];
     if (c == """) {
       tmp.set(buf, start, i); // directly use input buffer
       start = i + 1; // advance past last """
       return tmp;
     } else if (c == "\\") {
       break;
     }
   }
   out.reset();
   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.
 private void readStringChars2(CharArr arr, int middle) throws IOException {
   for (;;) {
     if (middle >= end) {
       arr.write(buf, start, middle - start);
       getMore();
       middle = start;
     }
     int ch = buf[middle++];
     if (ch == """) {
       int len = middle - start - 1;
       if (len > 0)
         arr.write(buf, start, len);
       start = middle;
       return;
     } else if (ch == "\\") {
       int len = middle - start - 1;
       if (len > 0)
         arr.write(buf, start, len);
       start = middle;
       arr.write(readEscapedChar());
       middle = start;
     }
   }
 }
 /*****************************************************************************
  * * 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
 private int next(int ch) throws IOException {
   for (;;) {
     switch (ch) {
     case " ":
     case "\t":
       break;
     case "\r":
     case "\n":
       break; // try and keep track of linecounts?
     case """:
       valstate = STRING;
       return STRING;
     case "{":
       push();
       state = DID_OBJSTART;
       return OBJECT_START;
     case "[":
       push();
       state = DID_ARRSTART;
       return ARRAY_START;
     case "0":
       out.reset();
       // special case "0"? If next char isn"t "." val=0
       ch = getChar();
       if (ch == ".") {
         start--;
         ch = "0";
         readNumber("0", false);
         return valstate;
       } else if (ch > "9" || ch < "0") {
         out.unsafeWrite("0");
         start--;
         lval = 0;
         valstate = LONG;
         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":
       out.reset();
       lval = readNumber(ch, false);
       return valstate;
     case "-":
       out.reset();
       out.unsafeWrite("-");
       ch = getChar();
       if (ch < "0" || ch > "9")
         throw err("expected digit after "-"");
       lval = readNumber(ch, true);
       return valstate;
     case "t":
       valstate = BOOLEAN;
       // TODO: test performance of this non-branching inline version.
       // if ((("r"-getChar())|("u"-getChar())|("e"-getChar())) != 0) err("");
       expect(JSONUtil.TRUE_CHARS);
       bool = true;
       return BOOLEAN;
     case "f":
       valstate = BOOLEAN;
       expect(JSONUtil.FALSE_CHARS);
       bool = false;
       return BOOLEAN;
     case "n":
       valstate = NULL;
       expect(JSONUtil.NULL_CHARS);
       return NULL;
     case -1:
       if (getLevel() > 0)
         throw new RuntimeException("Premature EOF");
       return EOF;
     default:
       throw err(null);
     }
     ch = getChar();
   }
 }
 public String toString() {
   return "start=" + start + ",end=" + end + ",state=" + state + "valstate=" + valstate;
 }
 /**
  * Returns the next event encountered in the JSON stream, one of
*
    *
  • {@link #STRING}
  • *
  • {@link #LONG}
  • *
  • {@link #NUMBER}
  • *
  • {@link #BIGNUMBER}
  • *
  • {@link #BOOLEAN}
  • *
  • {@link #NULL}
  • *
  • {@link #OBJECT_START}
  • *
  • {@link #OBJECT_END}
  • *
  • {@link #OBJECT_END}
  • *
  • {@link #ARRAY_START}
  • *
  • {@link #ARRAY_END}
  • *
  • {@link #EOF}
  • *
  */
 public int nextEvent() throws IOException {
   if (valstate == STRING) {
     readStringChars2(devNull, start);
   } else if (valstate == BIGNUMBER) {
     continueNumber(devNull);
   }
   valstate = 0;
   int ch; // TODO: factor out getCharNWS() to here and check speed
   switch (state) {
   case 0:
     return event = next(getCharNWS());
   case DID_OBJSTART:
     ch = getCharNWS();
     if (ch == "}") {
       pop();
       return event = OBJECT_END;
     }
     if (ch != """) {
       throw err("Expected string");
     }
     state = DID_MEMNAME;
     valstate = STRING;
     return event = STRING;
   case DID_MEMNAME:
     ch = getCharNWS();
     if (ch != ":") {
       throw err("Expected key,value separator ":"");
     }
     state = DID_MEMVAL; // set state first because it might be pushed...
     return event = next(getChar());
   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;
     valstate = STRING;
     return event = STRING;
   case DID_ARRSTART:
     ch = getCharNWS();
     if (ch == "]") {
       pop();
       return event = ARRAY_END;
     }
     state = DID_ARRELEM; // set state first, might be pushed...
     return event = next(ch);
   case DID_ARRELEM:
     ch = getCharNWS();
     if (ch == "]") {
       pop();
       return event = ARRAY_END;
     } else if (ch != ",") {
       throw err("Expected "," or "]"");
     }
     // state = DID_ARRELEM;
     return event = next(getChar());
   }
   return 0;
 }
 public int lastEvent() {
   return event;
 }
 public boolean wasKey() {
   return state == DID_MEMNAME;
 }
 private void goTo(int what) throws IOException {
   if (valstate == what) {
     valstate = 0;
     return;
   }
   if (valstate == 0) {
     int ev = nextEvent(); // TODO
     if (valstate != what) {
       throw err("type mismatch");
     }
     valstate = 0;
   } else {
     throw err("type mismatch");
   }
 }
 /** Returns the JSON string value, decoding any escaped characters. */
 public String getString() throws IOException {
   return getStringChars().toString();
 }
 /**
  * Returns the characters of a JSON string value, decoding any escaped
  * characters. <p/>The underlying buffer of the returned CharArr
  * should *not* be modified as it may be shared with the input buffer. <p/>The
  * returned CharArr will only be valid up until the next
  * JSONParser method is called. Any required data should be read before that
  * point.
  */
 public CharArr getStringChars() throws IOException {
   goTo(STRING);
   return readStringChars();
 }
 /** Reads a JSON string into the output, decoding any escaped characters. */
 public void getString(CharArr output) throws IOException {
   goTo(STRING);
   readStringChars2(output, start);
 }
 /**
  * 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.
  */
 public long getLong() throws IOException {
   goTo(LONG);
   return lval;
 }
 /** Reads a number from the input stream and parses it as a double */
 public double getDouble() throws IOException {
   return Double.parseDouble(getNumberChars().toString());
 }
 /**
  * Returns the characters of a JSON numeric value. <p/>The underlying buffer
  * of the returned CharArr should *not* be modified as it may
  * be shared with the input buffer. <p/>The returned CharArr
  * will only be valid up until the next JSONParser method is called. Any
  * required data should be read before that point.
  */
 public CharArr getNumberChars() throws IOException {
   int ev = 0;
   if (valstate == 0)
     ev = nextEvent();
   if (valstate == LONG || valstate == NUMBER) {
     valstate = 0;
     return out;
   } else if (valstate == BIGNUMBER) {
     continueNumber(out);
     valstate = 0;
     return out;
   } else {
     throw err("Unexpected " + ev);
   }
 }
 /** Reads a JSON numeric value into the output. */
 public void getNumberChars(CharArr output) throws IOException {
   int ev = 0;
   if (valstate == 0)
     ev = nextEvent();
   if (valstate == LONG || valstate == NUMBER)
     output.write(this.out);
   else if (valstate == BIGNUMBER) {
     continueNumber(output);
   } else {
     throw err("Unexpected " + ev);
   }
   valstate = 0;
 }
 /** Reads a boolean value */
 public boolean getBoolean() throws IOException {
   goTo(BOOLEAN);
   return bool;
 }
 /** Reads a null value */
 public void getNull() throws IOException {
   goTo(NULL);
 }
 /**
  * @return the current nesting level, the number of parent objects or arrays.
  */
 public int getLevel() {
   return ptr;
 }
 public long getPosition() {
   return gpos + start;
 }

} /**

* 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.
*/

// CharArr origins // V1.0 7/06/97 // V1.1 9/21/99 // V1.2 2/02/04 // Java5 features // V1.3 11/26/06 // Make safe for Java 1.4, work into Noggit // @author yonik // Java5 version could look like the following: // public class CharArr implements CharSequence, Appendable, Readable, Closeable // { /**

* @author yonik
* @version $Id: CharArr.java 583538 2007-10-10 16:53:02Z yonik $
*/

class CharArr implements CharSequence, Appendable {

 protected char[] buf;
 protected int start;
 protected int end;
 public CharArr() {
   this(32);
 }
 public CharArr(int size) {
   buf = new char[size];
 }
 public CharArr(char[] arr, int start, int end) {
   set(arr, start, end);
 }
 public void setStart(int start) {
   this.start = start;
 }
 public void setEnd(int end) {
   this.end = end;
 }
 public void set(char[] arr, int start, int end) {
   this.buf = arr;
   this.start = start;
   this.end = end;
 }
 public char[] getArray() {
   return buf;
 }
 public int getStart() {
   return start;
 }
 public int getEnd() {
   return end;
 }
 public int size() {
   return end - start;
 }
 public int length() {
   return size();
 }
 public int capacity() {
   return buf.length;
 }
 public char charAt(int index) {
   return buf[start + index];
 }
 public CharArr subSequence(int start, int end) {
   return new CharArr(buf, this.start + start, this.start + end);
 }
 public int read() throws IOException {
   if (start >= end)
     return -1;
   return buf[start++];
 }
 public int read(char cbuf[], int off, int len) {
   // TODO
   return 0;
 }
 public void unsafeWrite(char b) {
   buf[end++] = b;
 }
 public void unsafeWrite(int b) {
   unsafeWrite((char) b);
 }
 public void unsafeWrite(char b[], int off, int len) {
   System.arraycopy(b, off, buf, end, len);
   end += len;
 }
 protected void resize(int len) {
   char newbuf[] = new char[Math.max(buf.length << 1, len)];
   System.arraycopy(buf, start, newbuf, 0, size());
   buf = newbuf;
 }
 public void reserve(int num) {
   if (end + num > buf.length)
     resize(end + num);
 }
 public void write(char b) {
   if (end >= buf.length) {
     resize(end + 1);
   }
   unsafeWrite(b);
 }
 public final void write(int b) {
   write((char) b);
 }
 public final void write(char[] b) {
   write(b, 0, b.length);
 }
 public void write(char b[], int off, int len) {
   reserve(len);
   unsafeWrite(b, off, len);
 }
 public final void write(CharArr arr) {
   write(arr.buf, start, end - start);
 }
 public final void write(String s) {
   write(s, 0, s.length());
 }
 public void write(String s, int stringOffset, int len) {
   reserve(len);
   s.getChars(stringOffset, len, buf, end);
   end += len;
 }
 public void flush() {
 }
 public final void reset() {
   start = end = 0;
 }
 public void close() {
 }
 public char[] toCharArray() {
   char newbuf[] = new char[size()];
   System.arraycopy(buf, start, newbuf, 0, size());
   return newbuf;
 }
 public String toString() {
   return new String(buf, start, size());
 }
 public int read(CharBuffer cb) throws IOException {
   /***************************************************************************
    * int sz = size(); if (sz<=0) return -1; if (sz>0) cb.put(buf, start, sz);
    * return -1;
    **************************************************************************/
   int sz = size();
   if (sz > 0)
     cb.put(buf, start, sz);
   start = end;
   while (true) {
     fill();
     int s = size();
     if (s == 0)
       return sz == 0 ? -1 : sz;
     sz += s;
     cb.put(buf, start, s);
   }
 }
 public int fill() throws IOException {
   return 0; // or -1?
 }
 // ////////////// Appendable methods /////////////
 public final Appendable append(CharSequence csq) throws IOException {
   return append(csq, 0, csq.length());
 }
 public Appendable append(CharSequence csq, int start, int end) throws IOException {
   write(csq.subSequence(start, end).toString());
   return null;
 }
 public final Appendable append(char c) throws IOException {
   write(c);
   return this;
 }

} class NullCharArr extends CharArr {

 public NullCharArr() {
   super(new char[1], 0, 0);
 }
 public void unsafeWrite(char b) {
 }
 public void unsafeWrite(char b[], int off, int len) {
 }
 public void unsafeWrite(int b) {
 }
 public void write(char b) {
 }
 public void write(char b[], int off, int len) {
 }
 public void reserve(int num) {
 }
 protected void resize(int len) {
 }
 public Appendable append(CharSequence csq, int start, int end) throws IOException {
   return this;
 }
 public char charAt(int index) {
   return 0;
 }
 public void write(String s, int stringOffset, int len) {
 }

} // IDEA: a subclass that refills the array from a reader? class CharArrReader extends CharArr {

 protected final Reader in;
 public CharArrReader(Reader in, int size) {
   super(size);
   this.in = in;
 }
 public int read() throws IOException {
   if (start >= end)
     fill();
   return start >= end ? -1 : buf[start++];
 }
 public int read(CharBuffer cb) throws IOException {
   // empty the buffer and then read direct
   int sz = size();
   if (sz > 0)
     cb.put(buf, start, end);
   int sz2 = in.read(cb);
   if (sz2 >= 0)
     return sz + sz2;
   return sz > 0 ? sz : -1;
 }
 public int fill() throws IOException {
   if (start >= end) {
     reset();
   } else if (start > 0) {
     System.arraycopy(buf, start, buf, 0, size());
     end = size();
     start = 0;
   }
   /***************************************************************************
    * // fill fully or not??? do { int sz = in.read(buf,end,buf.length-end); if
    * (sz==-1) return; end+=sz; } while (end < buf.length);
    **************************************************************************/
   int sz = in.read(buf, end, buf.length - end);
   if (sz > 0)
     end += sz;
   return sz;
 }

} class CharArrWriter extends CharArr {

 protected Writer sink;
 @Override
 public void flush() {
   try {
     sink.write(buf, start, end - start);
   } catch (IOException e) {
     throw new RuntimeException(e);
   }
   start = end = 0;
 }
 @Override
 public void write(char b) {
   if (end >= buf.length) {
     flush();
   }
   unsafeWrite(b);
 }
 @Override
 public void write(char b[], int off, int len) {
   int space = buf.length - end;
   if (len < space) {
     unsafeWrite(b, off, len);
   } else if (len < buf.length) {
     unsafeWrite(b, off, space);
     flush();
     unsafeWrite(b, off + space, len - space);
   } else {
     flush();
     try {
       sink.write(b, off, len);
     } catch (IOException e) {
       throw new RuntimeException(e);
     }
   }
 }
 @Override
 public void write(String s, int stringOffset, int len) {
   int space = buf.length - end;
   if (len < space) {
     s.getChars(stringOffset, stringOffset + len, buf, end);
     end += len;
   } else if (len < buf.length) {
     // if the data to write is small enough, buffer it.
     s.getChars(stringOffset, stringOffset + space, buf, end);
     flush();
     s.getChars(stringOffset + space, stringOffset + len, buf, 0);
     end = len - space;
   } else {
     flush();
     // don"t buffer, just write to sink
     try {
       sink.write(s, stringOffset, len);
     } catch (IOException e) {
       throw new RuntimeException(e);
     }
   }
 }

} /**

* 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.
*/

/**

* @author yonik
* @version $Id: JSONUtil.java 666240 2008-06-10 18:00:38Z yonik $
*/

class JSONUtil {

 public static final char[] TRUE_CHARS = new char[] { "t", "r", "u", "e" };
 public static final char[] FALSE_CHARS = new char[] { "f", "a", "l", "s", "e" };
 public static final char[] NULL_CHARS = new char[] { "n", "u", "l", "l" };
 public static final char[] HEX_CHARS = new char[] { "0", "1", "2", "3", "4", "5", "6", "7", "8",
     "9", "a", "b", "c", "d", "e", "f" };
 public static final char VALUE_SEPARATOR = ",";
 public static final char NAME_SEPARATOR = ":";
 public static final char OBJECT_START = "{";
 public static final char OBJECT_END = "}";
 public static final char ARRAY_START = "[";
 public static final char ARRAY_END = "]";
 public static String toJSON(Object o) {
   CharArr out = new CharArr();
   new TextSerializer().serialize(new JSONWriter(out), o);
   return out.toString();
 }
 public static void writeNumber(long number, CharArr out) {
   out.write(Long.toString(number));
 }
 public static void writeNumber(double number, CharArr out) {
   out.write(Double.toString(number));
 }
 public static void writeString(CharArr val, CharArr out) {
   writeString(val.getArray(), val.getStart(), val.getEnd(), out);
 }
 public static void writeString(char[] val, int start, int end, CharArr out) {
   out.write(""");
   writeStringPart(val, start, end, out);
   out.write(""");
 }
 public static void writeString(CharSequence val, int start, int end, CharArr out) {
   out.write(""");
   writeStringPart(val, start, end, out);
   out.write(""");
 }
 public static void writeStringPart(char[] val, int start, int end, CharArr out) {
   for (int i = start; i < end; i++) {
     char ch = val[i];
     switch (ch) {
     case """:
     case "\\":
       out.write("\\");
       out.write(ch);
       break;
     case "\r":
       out.write("\\");
       out.write("r");
       break;
     case "\n":
       out.write("\\");
       out.write("n");
       break;
     case "\t":
       out.write("\\");
       out.write("t");
       break;
     case "\b":
       out.write("\\");
       out.write("b");
       break;
     case "\f":
       out.write("\\");
       out.write("f");
       break;
     // case "/":
     default:
       if (ch <= 0x1F) {
         unicodeEscape(ch, out);
       } else {
         out.write(ch);
       }
     }
   }
 }
 public static void writeStringPart(CharSequence chars, int start, int end, CharArr out) {
   for (int i = start; i < end; i++) {
     char ch = chars.charAt(i);
     switch (ch) {
     case """:
     case "\\":
       out.write("\\");
       out.write(ch);
       break;
     case "\r":
       out.write("\\");
       out.write("r");
       break;
     case "\n":
       out.write("\\");
       out.write("n");
       break;
     case "\t":
       out.write("\\");
       out.write("t");
       break;
     case "\b":
       out.write("\\");
       out.write("b");
       break;
     case "\f":
       out.write("\\");
       out.write("f");
       break;
     // case "/":
     default:
       if (ch <= 0x1F) {
         unicodeEscape(ch, out);
       } else {
         out.write(ch);
       }
     }
   }
 }
 public static void unicodeEscape(int ch, CharArr out) {
   out.write("\\");
   out.write("u");
   out.write(HEX_CHARS[ch >>> 12]);
   out.write(HEX_CHARS[(ch >>> 8) & 0xf]);
   out.write(HEX_CHARS[(ch >>> 4) & 0xf]);
   out.write(HEX_CHARS[ch & 0xf]);
 }
 public static void writeNull(CharArr out) {
   out.write(NULL_CHARS);
 }
 public static void writeBoolean(boolean val, CharArr out) {
   out.write(val ? TRUE_CHARS : FALSE_CHARS);
 }

} /**

* 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.
*/

class TextSerializer {

 public void serialize(TextWriter writer, Map val) {
   writer.startObject();
   boolean first = true;
   for (Map.Entry entry : (Set<Map.Entry>) val.entrySet()) {
     if (first) {
       first = false;
     } else {
       writer.writeValueSeparator();
     }
     writer.writeString(entry.getKey().toString());
     writer.writeNameSeparator();
     serialize(writer, entry.getValue());
   }
   writer.endObject();
 }
 public void serialize(TextWriter writer, Collection val) {
   writer.startArray();
   boolean first = true;
   for (Object o : val) {
     if (first) {
       first = false;
     } else {
       writer.writeValueSeparator();
     }
     serialize(writer, o);
   }
   writer.endArray();
 }
 public void serialize(TextWriter writer, Object o) {
   if (o == null) {
     writer.writeNull();
   } else if (o instanceof CharSequence) {
     writer.writeString((CharSequence) o);
   } else if (o instanceof Number) {
     if (o instanceof Integer || o instanceof Long) {
       writer.write(((Number) o).longValue());
     } else if (o instanceof Float || o instanceof Double) {
       writer.write(((Number) o).doubleValue());
     } else {
       CharArr arr = new CharArr();
       arr.write(o.toString());
       writer.writeNumber(arr);
     }
   } else if (o instanceof Map) {
     this.serialize(writer, (Map) o);
   } else if (o instanceof Collection) {
     this.serialize(writer, (Collection) o);
   } else if (o instanceof Object[]) {
     this.serialize(writer, Arrays.asList(o));
   } else {
     writer.writeString(o.toString());
   }
 }

} /**

* 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.
*/

/**

* @author yonik
* @version $Id: TextWriter.java 666240 2008-06-10 18:00:38Z yonik $
*/

abstract class TextWriter {

 public abstract void writeNull();
 public abstract void writeString(CharSequence str);
 public abstract void writeString(CharArr str);
 public abstract void writeStringStart();
 public abstract void writeStringChars(CharArr partialStr);
 public abstract void writeStringEnd();
 public abstract void write(long number);
 public abstract void write(double number);
 public abstract void write(boolean bool);
 public abstract void writeNumber(CharArr digits);
 public abstract void writePartialNumber(CharArr digits);
 public abstract void startObject();
 public abstract void endObject();
 public abstract void startArray();
 public abstract void endArray();
 public abstract void writeValueSeparator();
 public abstract void writeNameSeparator();
 // void writeNameValue(String name, Object val)?

} /**

* 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.
*/

/**

* @author yonik
* @version $Id: JSONWriter.java 666240 2008-06-10 18:00:38Z yonik $
*/

// how to couple with JSONParser to allow streaming large values from input to // output? // IDEA 1) have JSONParser.getString(JSONWriter out)? // IDEA 2) have an output CharArr that acts as a filter to escape data // IDEA: a subclass of JSONWriter could provide more state and stricter checking class JSONWriter extends TextWriter {

 int level;
 boolean doIndent;
 final CharArr out;
 JSONWriter(CharArr out) {
   this.out = out;
 }
 public void writeNull() {
   JSONUtil.writeNull(out);
 }
 public void writeString(CharSequence str) {
   JSONUtil.writeString(str, 0, str.length(), out);
 }
 public void writeString(CharArr str) {
   JSONUtil.writeString(str, out);
 }
 public void writeStringStart() {
   out.write(""");
 }
 public void writeStringChars(CharArr partialStr) {
   JSONUtil
       .writeStringPart(partialStr.getArray(), partialStr.getStart(), partialStr.getEnd(), out);
 }
 public void writeStringEnd() {
   out.write(""");
 }
 public void write(long number) {
   JSONUtil.writeNumber(number, out);
 }
 public void write(double number) {
   JSONUtil.writeNumber(number, out);
 }
 public void write(boolean bool) {
   JSONUtil.writeBoolean(bool, out);
 }
 public void writeNumber(CharArr digits) {
   out.write(digits);
 }
 public void writePartialNumber(CharArr digits) {
   out.write(digits);
 }
 public void startObject() {
   out.write("{");
   level++;
 }
 public void endObject() {
   out.write("}");
   level--;
 }
 public void startArray() {
   out.write("[");
   level++;
 }
 public void endArray() {
   out.write("]");
   level--;
 }
 public void writeValueSeparator() {
   out.write(",");
 }
 public void writeNameSeparator() {
   out.write(":");
 }

} //////////////////////// /**

* 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 org.apache.noggit; import junit.framework.TestCase; import java.io.IOException; import java.util.*; /**

* @author yonik
* @version $Id$
*/

public class TestObjectBuilder extends TestCase {

 public void test(String val, Object expected) throws IOException {
   val = val.replace("\"",""");
   JSONParser p = TestJSONParser.getParser(val);
   Object v = ObjectBuilder.getVal(p);
   String s1 = JSONUtil.toJSON(v);
   String s2 = JSONUtil.toJSON(expected);
   assertEquals(s1, s2);
   // not make sure that it round-trips correctly
   JSONParser p2 = TestJSONParser.getParser(s1);
   Object v2 = ObjectBuilder.getVal(p2);
   String s3 = JSONUtil.toJSON(v2);
   assertEquals(s1, s3);
 }
 public static List A(Object... lst) {
    return Arrays.asList(lst);
 }
 public static Map O(Object... lst) {
   LinkedHashMap map = new LinkedHashMap();
   for (int i=0; i<lst.length; i+=2) {
     map.put(lst[i].toString(), lst[i+1]);
   }
   return map;
 }
 public void testVariations(String str, Object expected) throws IOException {
   test("["+str+"]", A(expected));
   test("["+str+","+str+"]", A(expected,expected));
   test("{"foo":"+str+"}", O("foo",expected));
   test("{"foo":"+str+","bar":{"a":"+str+"},"baz":["+str+"]}",
           O("foo",expected,"bar",O("a",expected),"baz",A(expected)));
 }
 public void testBuilder() throws IOException {
   testVariations("[]", A());
   testVariations("[]", A());
   testVariations("{}", O());
   testVariations("[[]]", A(A()));
   testVariations("{"foo":{}}", O("foo",O()));
   testVariations("[false,true,1,1.4,null,"hi"]", A(false,true,1,1.4,null,"hi"));
 }

}

 </source>
   
  
 
  



JSON Parser

   <source lang="java">

/**

* 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.
*/

import java.io.IOException; import java.io.Reader; import java.io.Writer; import java.nio.CharBuffer; import java.util.Arrays; import java.util.Collection; import java.util.Map; import java.util.Set; /**

* @author yonik
* @version $Id: JSONParser.java 730138 2008-12-30 14:54:53Z yonik $
*/

public class JSONParser {

 /** Event indicating a JSON string value, including member names of objects */
 public static final int STRING = 1;
 /**
  * Event indicating a JSON number value which fits into a signed 64 bit
  * integer
  */
 public static final int LONG = 2;
 /**
  * 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().
  */
 public static final int NUMBER = 3;
 /**
  * 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.
  */
 public static final int BIGNUMBER = 4;
 /** Event indicating a JSON boolean */
 public static final int BOOLEAN = 5;
 /** Event indicating a JSON null */
 public static final int NULL = 6;
 /** Event indicating the start of a JSON object */
 public static final int OBJECT_START = 7;
 /** Event indicating the end of a JSON object */
 public static final int OBJECT_END = 8;
 /** Event indicating the start of a JSON array */
 public static final int ARRAY_START = 9;
 /** Event indicating the end of a JSON array */
 public static final int ARRAY_END = 10;
 /** Event indicating the end of input has been reached */
 public static final int EOF = 11;
 public static String getEventString(int e) {
   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;
 }
 private static final CharArr devNull = new NullCharArr();
 final char[] buf; // input buffer with JSON text in it
 int start; // current position in the buffer
 int end; // end position in the buffer (one past last valid index)
 final Reader in; // optional reader to obtain data from
 boolean eof = false; // true if the end of the stream was reached.
 long gpos; // global position = gpos + start
 int event; // last event read
 public JSONParser(Reader in) {
   this(in, new char[8192]);
   // 8192 matches the default buffer size of a BufferedReader so double
   // buffering of the data is avoided.
 }
 public JSONParser(Reader in, char[] buffer) {
   this.in = in;
   this.buf = buffer;
 }
 // idea - if someone passes us a CharArrayReader, we could
 // directly use that buffer as it"s protected.
 public JSONParser(char[] data, int start, int end) {
   this.in = null;
   this.buf = data;
   this.start = start;
   this.end = end;
 }
 public JSONParser(String data) {
   this(data, 0, data.length());
 }
 public JSONParser(String data, int start, int end) {
   this.in = null;
   this.start = start;
   this.end = end;
   this.buf = new char[end - start];
   data.getChars(start, end, buf, 0);
 }
 // temporary output buffer
 private final CharArr out = new CharArr(64);
 // We need to keep some state in order to (at a minimum) know if
 // we should skip "," or ":".
 private byte[] stack = new byte[16];
 private int ptr = 0; // pointer into the stack of parser states
 private byte state = 0; // current parser state
 // parser states stored in the stack
 private static final byte DID_OBJSTART = 1; // "{" just read
 private static final byte DID_ARRSTART = 2; // "[" just read
 private static final byte DID_ARRELEM = 3; // array element just read
 private static final byte DID_MEMNAME = 4; // object member name (map key)
 // just read
 private static final byte DID_MEMVAL = 5; // object member value (map val)
 // just read
 // 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() {
   if (ptr >= stack.length) {
     // doubling here is probably overkill, but anything that needs to double
     // more than
     // once (32 levels deep) is very atypical anyway.
     byte[] newstack = new byte[stack.length << 1];
     System.arraycopy(stack, 0, newstack, 0, stack.length);
     stack = newstack;
   }
   stack[ptr++] = state;
 }
 // pop parser state (use at end of container)
 private final void pop() {
   if (--ptr < 0) {
     throw err("Unbalanced container");
   } else {
     state = stack[ptr];
   }
 }
 protected void fill() throws IOException {
   if (in != null) {
     gpos += end;
     start = 0;
     int num = in.read(buf, 0, buf.length);
     end = num >= 0 ? num : 0;
   }
   if (start >= end)
     eof = true;
 }
 private void getMore() throws IOException {
   fill();
   if (start >= end) {
     throw err(null);
   }
 }
 protected int getChar() throws IOException {
   if (start >= end) {
     fill();
     if (start >= end)
       return -1;
   }
   return buf[start++];
 }
 private int getCharNWS() throws IOException {
   for (;;) {
     int ch = getChar();
     if (!(ch == " " || ch == "\t" || ch == "\n" || ch == "\r"))
       return ch;
   }
 }
 private void expect(char[] arr) throws IOException {
   for (int i = 1; i < arr.length; i++) {
     int ch = getChar();
     if (ch != arr[i]) {
       if (ch == -1)
         throw new RuntimeException("Unexpected EOF");
       throw new RuntimeException("Expected " + new String(arr));
     }
   }
 }
 private RuntimeException err(String msg) {
   // 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
   if (!eof && start > 0)
     start--; // backup one char
   String chs = "char=" + ((start >= end) ? "(EOF)" : "" + (char) buf[start]);
   String pos = "position=" + (gpos + start);
   String tot = chs + "," + pos;
   if (msg == null) {
     if (start >= end)
       msg = "Unexpected EOF";
     else
       msg = "JSON Parse Error";
   }
   return new RuntimeException(msg + ": " + tot);
 }
 private boolean bool; // boolean value read
 private long lval; // long value read
 private int nstate; // current state while reading a number
 private static final int HAS_FRACTION = 0x01; // nstate flag, "." already read
 private static final int HAS_EXPONENT = 0x02; // nstate flag, "[eE][+-]?[0-9]"
 // already read
 /**
  * Returns the long read... only significant if valstate==LONG after this
  * call. firstChar should be the first numeric digit read.
  */
 private long readNumber(int firstChar, boolean isNeg) throws IOException {
   out.unsafeWrite(firstChar); // unsafe OK since we know output is big enough
   // 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++) {
     int ch = getChar();
     // 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":
       v = v * 10 - (ch - "0");
       out.unsafeWrite(ch);
       continue;
     case ".":
       out.unsafeWrite(".");
       valstate = readFrac(out, 22 - i);
       return 0;
     case "e":
     case "E":
       out.unsafeWrite(ch);
       nstate = 0;
       valstate = readExp(out, 22 - i);
       return 0;
     default:
       // return the number, relying on nextEvent() to return an error
       // for invalid chars following the number.
       if (ch != -1)
         --start; // push back last char if not EOF
       // 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
         valstate = v <= 0 ? LONG : BIGNUMBER;
       } else {
         v = -v;
         valstate = v >= 0 ? LONG : BIGNUMBER;
       }
       return v;
     }
   }
   nstate = 0;
   valstate = BIGNUMBER;
   return 0;
 }
 // read digits right of decimal point
 private int readFrac(CharArr arr, int lim) throws IOException {
   nstate = HAS_FRACTION; // deliberate set instead of "|"
   while (--lim >= 0) {
     int ch = getChar();
     if (ch >= "0" && ch <= "9") {
       arr.write(ch);
     } else if (ch == "e" || ch == "E") {
       arr.write(ch);
       return readExp(arr, lim);
     } else {
       if (ch != -1)
         start--; // back up
       return NUMBER;
     }
   }
   return BIGNUMBER;
 }
 // call after "e" or "E" has been seen to read the rest of the exponent
 private int readExp(CharArr arr, int lim) throws IOException {
   nstate |= HAS_EXPONENT;
   int ch = getChar();
   lim--;
   if (ch == "+" || ch == "-") {
     arr.write(ch);
     ch = getChar();
     lim--;
   }
   // make sure at least one digit is read.
   if (ch < "0" || ch > "9") {
     throw err("missing exponent number");
   }
   arr.write(ch);
   return readExpDigits(arr, lim);
 }
 // continuation of readExpStart
 private int readExpDigits(CharArr arr, int lim) throws IOException {
   while (--lim >= 0) {
     int ch = getChar();
     if (ch >= "0" && ch <= "9") {
       arr.write(ch);
     } else {
       if (ch != -1)
         start--; // back up
       return NUMBER;
     }
   }
   return BIGNUMBER;
 }
 private void continueNumber(CharArr arr) throws IOException {
   if (arr != out)
     arr.write(out);
   if ((nstate & HAS_EXPONENT) != 0) {
     readExpDigits(arr, Integer.MAX_VALUE);
     return;
   }
   if (nstate != 0) {
     readFrac(arr, Integer.MAX_VALUE);
     return;
   }
   for (;;) {
     int ch = getChar();
     if (ch >= "0" && ch <= "9") {
       arr.write(ch);
     } else if (ch == ".") {
       arr.write(ch);
       readFrac(arr, Integer.MAX_VALUE);
       return;
     } else if (ch == "e" || ch == "E") {
       arr.write(ch);
       readExp(arr, Integer.MAX_VALUE);
       return;
     } else {
       if (ch != -1)
         start--;
       return;
     }
   }
 }
 private int hexval(int hexdig) {
   if (hexdig >= "0" && hexdig <= "9") {
     return hexdig - "0";
   } else if (hexdig >= "A" && hexdig <= "F") {
     return hexdig + (10 - "A");
   } else if (hexdig >= "a" && hexdig <= "f") {
     return hexdig + (10 - "a");
   }
   throw err("invalid hex digit");
 }
 // backslash has already been read when this is called
 private char readEscapedChar() throws IOException {
   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) ((hexval(getChar()) << 12) | (hexval(getChar()) << 8)
         | (hexval(getChar()) << 4) | (hexval(getChar())));
   }
   throw err("Invalid character escape in string");
 }
 // a dummy buffer we can use to point at other buffers
 private final CharArr tmp = new CharArr(null, 0, 0);
 private CharArr readStringChars() throws IOException {
   char c = 0;
   int i;
   for (i = start; i < end; i++) {
     c = buf[i];
     if (c == """) {
       tmp.set(buf, start, i); // directly use input buffer
       start = i + 1; // advance past last """
       return tmp;
     } else if (c == "\\") {
       break;
     }
   }
   out.reset();
   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.
 private void readStringChars2(CharArr arr, int middle) throws IOException {
   for (;;) {
     if (middle >= end) {
       arr.write(buf, start, middle - start);
       getMore();
       middle = start;
     }
     int ch = buf[middle++];
     if (ch == """) {
       int len = middle - start - 1;
       if (len > 0)
         arr.write(buf, start, len);
       start = middle;
       return;
     } else if (ch == "\\") {
       int len = middle - start - 1;
       if (len > 0)
         arr.write(buf, start, len);
       start = middle;
       arr.write(readEscapedChar());
       middle = start;
     }
   }
 }
 /*****************************************************************************
  * * 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
 private int next(int ch) throws IOException {
   for (;;) {
     switch (ch) {
     case " ":
     case "\t":
       break;
     case "\r":
     case "\n":
       break; // try and keep track of linecounts?
     case """:
       valstate = STRING;
       return STRING;
     case "{":
       push();
       state = DID_OBJSTART;
       return OBJECT_START;
     case "[":
       push();
       state = DID_ARRSTART;
       return ARRAY_START;
     case "0":
       out.reset();
       // special case "0"? If next char isn"t "." val=0
       ch = getChar();
       if (ch == ".") {
         start--;
         ch = "0";
         readNumber("0", false);
         return valstate;
       } else if (ch > "9" || ch < "0") {
         out.unsafeWrite("0");
         start--;
         lval = 0;
         valstate = LONG;
         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":
       out.reset();
       lval = readNumber(ch, false);
       return valstate;
     case "-":
       out.reset();
       out.unsafeWrite("-");
       ch = getChar();
       if (ch < "0" || ch > "9")
         throw err("expected digit after "-"");
       lval = readNumber(ch, true);
       return valstate;
     case "t":
       valstate = BOOLEAN;
       // TODO: test performance of this non-branching inline version.
       // if ((("r"-getChar())|("u"-getChar())|("e"-getChar())) != 0) err("");
       expect(JSONUtil.TRUE_CHARS);
       bool = true;
       return BOOLEAN;
     case "f":
       valstate = BOOLEAN;
       expect(JSONUtil.FALSE_CHARS);
       bool = false;
       return BOOLEAN;
     case "n":
       valstate = NULL;
       expect(JSONUtil.NULL_CHARS);
       return NULL;
     case -1:
       if (getLevel() > 0)
         throw new RuntimeException("Premature EOF");
       return EOF;
     default:
       throw err(null);
     }
     ch = getChar();
   }
 }
 public String toString() {
   return "start=" + start + ",end=" + end + ",state=" + state + "valstate=" + valstate;
 }
 /**
  * Returns the next event encountered in the JSON stream, one of
*
    *
  • {@link #STRING}
  • *
  • {@link #LONG}
  • *
  • {@link #NUMBER}
  • *
  • {@link #BIGNUMBER}
  • *
  • {@link #BOOLEAN}
  • *
  • {@link #NULL}
  • *
  • {@link #OBJECT_START}
  • *
  • {@link #OBJECT_END}
  • *
  • {@link #OBJECT_END}
  • *
  • {@link #ARRAY_START}
  • *
  • {@link #ARRAY_END}
  • *
  • {@link #EOF}
  • *
  */
 public int nextEvent() throws IOException {
   if (valstate == STRING) {
     readStringChars2(devNull, start);
   } else if (valstate == BIGNUMBER) {
     continueNumber(devNull);
   }
   valstate = 0;
   int ch; // TODO: factor out getCharNWS() to here and check speed
   switch (state) {
   case 0:
     return event = next(getCharNWS());
   case DID_OBJSTART:
     ch = getCharNWS();
     if (ch == "}") {
       pop();
       return event = OBJECT_END;
     }
     if (ch != """) {
       throw err("Expected string");
     }
     state = DID_MEMNAME;
     valstate = STRING;
     return event = STRING;
   case DID_MEMNAME:
     ch = getCharNWS();
     if (ch != ":") {
       throw err("Expected key,value separator ":"");
     }
     state = DID_MEMVAL; // set state first because it might be pushed...
     return event = next(getChar());
   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;
     valstate = STRING;
     return event = STRING;
   case DID_ARRSTART:
     ch = getCharNWS();
     if (ch == "]") {
       pop();
       return event = ARRAY_END;
     }
     state = DID_ARRELEM; // set state first, might be pushed...
     return event = next(ch);
   case DID_ARRELEM:
     ch = getCharNWS();
     if (ch == "]") {
       pop();
       return event = ARRAY_END;
     } else if (ch != ",") {
       throw err("Expected "," or "]"");
     }
     // state = DID_ARRELEM;
     return event = next(getChar());
   }
   return 0;
 }
 public int lastEvent() {
   return event;
 }
 public boolean wasKey() {
   return state == DID_MEMNAME;
 }
 private void goTo(int what) throws IOException {
   if (valstate == what) {
     valstate = 0;
     return;
   }
   if (valstate == 0) {
     int ev = nextEvent(); // TODO
     if (valstate != what) {
       throw err("type mismatch");
     }
     valstate = 0;
   } else {
     throw err("type mismatch");
   }
 }
 /** Returns the JSON string value, decoding any escaped characters. */
 public String getString() throws IOException {
   return getStringChars().toString();
 }
 /**
  * Returns the characters of a JSON string value, decoding any escaped
  * characters. <p/>The underlying buffer of the returned CharArr
  * should *not* be modified as it may be shared with the input buffer. <p/>The
  * returned CharArr will only be valid up until the next
  * JSONParser method is called. Any required data should be read before that
  * point.
  */
 public CharArr getStringChars() throws IOException {
   goTo(STRING);
   return readStringChars();
 }
 /** Reads a JSON string into the output, decoding any escaped characters. */
 public void getString(CharArr output) throws IOException {
   goTo(STRING);
   readStringChars2(output, start);
 }
 /**
  * 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.
  */
 public long getLong() throws IOException {
   goTo(LONG);
   return lval;
 }
 /** Reads a number from the input stream and parses it as a double */
 public double getDouble() throws IOException {
   return Double.parseDouble(getNumberChars().toString());
 }
 /**
  * Returns the characters of a JSON numeric value. <p/>The underlying buffer
  * of the returned CharArr should *not* be modified as it may
  * be shared with the input buffer. <p/>The returned CharArr
  * will only be valid up until the next JSONParser method is called. Any
  * required data should be read before that point.
  */
 public CharArr getNumberChars() throws IOException {
   int ev = 0;
   if (valstate == 0)
     ev = nextEvent();
   if (valstate == LONG || valstate == NUMBER) {
     valstate = 0;
     return out;
   } else if (valstate == BIGNUMBER) {
     continueNumber(out);
     valstate = 0;
     return out;
   } else {
     throw err("Unexpected " + ev);
   }
 }
 /** Reads a JSON numeric value into the output. */
 public void getNumberChars(CharArr output) throws IOException {
   int ev = 0;
   if (valstate == 0)
     ev = nextEvent();
   if (valstate == LONG || valstate == NUMBER)
     output.write(this.out);
   else if (valstate == BIGNUMBER) {
     continueNumber(output);
   } else {
     throw err("Unexpected " + ev);
   }
   valstate = 0;
 }
 /** Reads a boolean value */
 public boolean getBoolean() throws IOException {
   goTo(BOOLEAN);
   return bool;
 }
 /** Reads a null value */
 public void getNull() throws IOException {
   goTo(NULL);
 }
 /**
  * @return the current nesting level, the number of parent objects or arrays.
  */
 public int getLevel() {
   return ptr;
 }
 public long getPosition() {
   return gpos + start;
 }

} /**

* 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.
*/

// CharArr origins // V1.0 7/06/97 // V1.1 9/21/99 // V1.2 2/02/04 // Java5 features // V1.3 11/26/06 // Make safe for Java 1.4, work into Noggit // @author yonik // Java5 version could look like the following: // public class CharArr implements CharSequence, Appendable, Readable, Closeable // { /**

* @author yonik
* @version $Id: CharArr.java 583538 2007-10-10 16:53:02Z yonik $
*/

class CharArr implements CharSequence, Appendable {

 protected char[] buf;
 protected int start;
 protected int end;
 public CharArr() {
   this(32);
 }
 public CharArr(int size) {
   buf = new char[size];
 }
 public CharArr(char[] arr, int start, int end) {
   set(arr, start, end);
 }
 public void setStart(int start) {
   this.start = start;
 }
 public void setEnd(int end) {
   this.end = end;
 }
 public void set(char[] arr, int start, int end) {
   this.buf = arr;
   this.start = start;
   this.end = end;
 }
 public char[] getArray() {
   return buf;
 }
 public int getStart() {
   return start;
 }
 public int getEnd() {
   return end;
 }
 public int size() {
   return end - start;
 }
 public int length() {
   return size();
 }
 public int capacity() {
   return buf.length;
 }
 public char charAt(int index) {
   return buf[start + index];
 }
 public CharArr subSequence(int start, int end) {
   return new CharArr(buf, this.start + start, this.start + end);
 }
 public int read() throws IOException {
   if (start >= end)
     return -1;
   return buf[start++];
 }
 public int read(char cbuf[], int off, int len) {
   // TODO
   return 0;
 }
 public void unsafeWrite(char b) {
   buf[end++] = b;
 }
 public void unsafeWrite(int b) {
   unsafeWrite((char) b);
 }
 public void unsafeWrite(char b[], int off, int len) {
   System.arraycopy(b, off, buf, end, len);
   end += len;
 }
 protected void resize(int len) {
   char newbuf[] = new char[Math.max(buf.length << 1, len)];
   System.arraycopy(buf, start, newbuf, 0, size());
   buf = newbuf;
 }
 public void reserve(int num) {
   if (end + num > buf.length)
     resize(end + num);
 }
 public void write(char b) {
   if (end >= buf.length) {
     resize(end + 1);
   }
   unsafeWrite(b);
 }
 public final void write(int b) {
   write((char) b);
 }
 public final void write(char[] b) {
   write(b, 0, b.length);
 }
 public void write(char b[], int off, int len) {
   reserve(len);
   unsafeWrite(b, off, len);
 }
 public final void write(CharArr arr) {
   write(arr.buf, start, end - start);
 }
 public final void write(String s) {
   write(s, 0, s.length());
 }
 public void write(String s, int stringOffset, int len) {
   reserve(len);
   s.getChars(stringOffset, len, buf, end);
   end += len;
 }
 public void flush() {
 }
 public final void reset() {
   start = end = 0;
 }
 public void close() {
 }
 public char[] toCharArray() {
   char newbuf[] = new char[size()];
   System.arraycopy(buf, start, newbuf, 0, size());
   return newbuf;
 }
 public String toString() {
   return new String(buf, start, size());
 }
 public int read(CharBuffer cb) throws IOException {
   /***************************************************************************
    * int sz = size(); if (sz<=0) return -1; if (sz>0) cb.put(buf, start, sz);
    * return -1;
    **************************************************************************/
   int sz = size();
   if (sz > 0)
     cb.put(buf, start, sz);
   start = end;
   while (true) {
     fill();
     int s = size();
     if (s == 0)
       return sz == 0 ? -1 : sz;
     sz += s;
     cb.put(buf, start, s);
   }
 }
 public int fill() throws IOException {
   return 0; // or -1?
 }
 // ////////////// Appendable methods /////////////
 public final Appendable append(CharSequence csq) throws IOException {
   return append(csq, 0, csq.length());
 }
 public Appendable append(CharSequence csq, int start, int end) throws IOException {
   write(csq.subSequence(start, end).toString());
   return null;
 }
 public final Appendable append(char c) throws IOException {
   write(c);
   return this;
 }

} class NullCharArr extends CharArr {

 public NullCharArr() {
   super(new char[1], 0, 0);
 }
 public void unsafeWrite(char b) {
 }
 public void unsafeWrite(char b[], int off, int len) {
 }
 public void unsafeWrite(int b) {
 }
 public void write(char b) {
 }
 public void write(char b[], int off, int len) {
 }
 public void reserve(int num) {
 }
 protected void resize(int len) {
 }
 public Appendable append(CharSequence csq, int start, int end) throws IOException {
   return this;
 }
 public char charAt(int index) {
   return 0;
 }
 public void write(String s, int stringOffset, int len) {
 }

} // IDEA: a subclass that refills the array from a reader? class CharArrReader extends CharArr {

 protected final Reader in;
 public CharArrReader(Reader in, int size) {
   super(size);
   this.in = in;
 }
 public int read() throws IOException {
   if (start >= end)
     fill();
   return start >= end ? -1 : buf[start++];
 }
 public int read(CharBuffer cb) throws IOException {
   // empty the buffer and then read direct
   int sz = size();
   if (sz > 0)
     cb.put(buf, start, end);
   int sz2 = in.read(cb);
   if (sz2 >= 0)
     return sz + sz2;
   return sz > 0 ? sz : -1;
 }
 public int fill() throws IOException {
   if (start >= end) {
     reset();
   } else if (start > 0) {
     System.arraycopy(buf, start, buf, 0, size());
     end = size();
     start = 0;
   }
   /***************************************************************************
    * // fill fully or not??? do { int sz = in.read(buf,end,buf.length-end); if
    * (sz==-1) return; end+=sz; } while (end < buf.length);
    **************************************************************************/
   int sz = in.read(buf, end, buf.length - end);
   if (sz > 0)
     end += sz;
   return sz;
 }

} class CharArrWriter extends CharArr {

 protected Writer sink;
 @Override
 public void flush() {
   try {
     sink.write(buf, start, end - start);
   } catch (IOException e) {
     throw new RuntimeException(e);
   }
   start = end = 0;
 }
 @Override
 public void write(char b) {
   if (end >= buf.length) {
     flush();
   }
   unsafeWrite(b);
 }
 @Override
 public void write(char b[], int off, int len) {
   int space = buf.length - end;
   if (len < space) {
     unsafeWrite(b, off, len);
   } else if (len < buf.length) {
     unsafeWrite(b, off, space);
     flush();
     unsafeWrite(b, off + space, len - space);
   } else {
     flush();
     try {
       sink.write(b, off, len);
     } catch (IOException e) {
       throw new RuntimeException(e);
     }
   }
 }
 @Override
 public void write(String s, int stringOffset, int len) {
   int space = buf.length - end;
   if (len < space) {
     s.getChars(stringOffset, stringOffset + len, buf, end);
     end += len;
   } else if (len < buf.length) {
     // if the data to write is small enough, buffer it.
     s.getChars(stringOffset, stringOffset + space, buf, end);
     flush();
     s.getChars(stringOffset + space, stringOffset + len, buf, 0);
     end = len - space;
   } else {
     flush();
     // don"t buffer, just write to sink
     try {
       sink.write(s, stringOffset, len);
     } catch (IOException e) {
       throw new RuntimeException(e);
     }
   }
 }

} /**

* 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.
*/

/**

* @author yonik
* @version $Id: JSONUtil.java 666240 2008-06-10 18:00:38Z yonik $
*/

class JSONUtil {

 public static final char[] TRUE_CHARS = new char[] { "t", "r", "u", "e" };
 public static final char[] FALSE_CHARS = new char[] { "f", "a", "l", "s", "e" };
 public static final char[] NULL_CHARS = new char[] { "n", "u", "l", "l" };
 public static final char[] HEX_CHARS = new char[] { "0", "1", "2", "3", "4", "5", "6", "7", "8",
     "9", "a", "b", "c", "d", "e", "f" };
 public static final char VALUE_SEPARATOR = ",";
 public static final char NAME_SEPARATOR = ":";
 public static final char OBJECT_START = "{";
 public static final char OBJECT_END = "}";
 public static final char ARRAY_START = "[";
 public static final char ARRAY_END = "]";
 public static String toJSON(Object o) {
   CharArr out = new CharArr();
   new TextSerializer().serialize(new JSONWriter(out), o);
   return out.toString();
 }
 public static void writeNumber(long number, CharArr out) {
   out.write(Long.toString(number));
 }
 public static void writeNumber(double number, CharArr out) {
   out.write(Double.toString(number));
 }
 public static void writeString(CharArr val, CharArr out) {
   writeString(val.getArray(), val.getStart(), val.getEnd(), out);
 }
 public static void writeString(char[] val, int start, int end, CharArr out) {
   out.write(""");
   writeStringPart(val, start, end, out);
   out.write(""");
 }
 public static void writeString(CharSequence val, int start, int end, CharArr out) {
   out.write(""");
   writeStringPart(val, start, end, out);
   out.write(""");
 }
 public static void writeStringPart(char[] val, int start, int end, CharArr out) {
   for (int i = start; i < end; i++) {
     char ch = val[i];
     switch (ch) {
     case """:
     case "\\":
       out.write("\\");
       out.write(ch);
       break;
     case "\r":
       out.write("\\");
       out.write("r");
       break;
     case "\n":
       out.write("\\");
       out.write("n");
       break;
     case "\t":
       out.write("\\");
       out.write("t");
       break;
     case "\b":
       out.write("\\");
       out.write("b");
       break;
     case "\f":
       out.write("\\");
       out.write("f");
       break;
     // case "/":
     default:
       if (ch <= 0x1F) {
         unicodeEscape(ch, out);
       } else {
         out.write(ch);
       }
     }
   }
 }
 public static void writeStringPart(CharSequence chars, int start, int end, CharArr out) {
   for (int i = start; i < end; i++) {
     char ch = chars.charAt(i);
     switch (ch) {
     case """:
     case "\\":
       out.write("\\");
       out.write(ch);
       break;
     case "\r":
       out.write("\\");
       out.write("r");
       break;
     case "\n":
       out.write("\\");
       out.write("n");
       break;
     case "\t":
       out.write("\\");
       out.write("t");
       break;
     case "\b":
       out.write("\\");
       out.write("b");
       break;
     case "\f":
       out.write("\\");
       out.write("f");
       break;
     // case "/":
     default:
       if (ch <= 0x1F) {
         unicodeEscape(ch, out);
       } else {
         out.write(ch);
       }
     }
   }
 }
 public static void unicodeEscape(int ch, CharArr out) {
   out.write("\\");
   out.write("u");
   out.write(HEX_CHARS[ch >>> 12]);
   out.write(HEX_CHARS[(ch >>> 8) & 0xf]);
   out.write(HEX_CHARS[(ch >>> 4) & 0xf]);
   out.write(HEX_CHARS[ch & 0xf]);
 }
 public static void writeNull(CharArr out) {
   out.write(NULL_CHARS);
 }
 public static void writeBoolean(boolean val, CharArr out) {
   out.write(val ? TRUE_CHARS : FALSE_CHARS);
 }

} /**

* 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.
*/

class TextSerializer {

 public void serialize(TextWriter writer, Map val) {
   writer.startObject();
   boolean first = true;
   for (Map.Entry entry : (Set<Map.Entry>) val.entrySet()) {
     if (first) {
       first = false;
     } else {
       writer.writeValueSeparator();
     }
     writer.writeString(entry.getKey().toString());
     writer.writeNameSeparator();
     serialize(writer, entry.getValue());
   }
   writer.endObject();
 }
 public void serialize(TextWriter writer, Collection val) {
   writer.startArray();
   boolean first = true;
   for (Object o : val) {
     if (first) {
       first = false;
     } else {
       writer.writeValueSeparator();
     }
     serialize(writer, o);
   }
   writer.endArray();
 }
 public void serialize(TextWriter writer, Object o) {
   if (o == null) {
     writer.writeNull();
   } else if (o instanceof CharSequence) {
     writer.writeString((CharSequence) o);
   } else if (o instanceof Number) {
     if (o instanceof Integer || o instanceof Long) {
       writer.write(((Number) o).longValue());
     } else if (o instanceof Float || o instanceof Double) {
       writer.write(((Number) o).doubleValue());
     } else {
       CharArr arr = new CharArr();
       arr.write(o.toString());
       writer.writeNumber(arr);
     }
   } else if (o instanceof Map) {
     this.serialize(writer, (Map) o);
   } else if (o instanceof Collection) {
     this.serialize(writer, (Collection) o);
   } else if (o instanceof Object[]) {
     this.serialize(writer, Arrays.asList(o));
   } else {
     writer.writeString(o.toString());
   }
 }

} /**

* 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.
*/

/**

* @author yonik
* @version $Id: TextWriter.java 666240 2008-06-10 18:00:38Z yonik $
*/

abstract class TextWriter {

 public abstract void writeNull();
 public abstract void writeString(CharSequence str);
 public abstract void writeString(CharArr str);
 public abstract void writeStringStart();
 public abstract void writeStringChars(CharArr partialStr);
 public abstract void writeStringEnd();
 public abstract void write(long number);
 public abstract void write(double number);
 public abstract void write(boolean bool);
 public abstract void writeNumber(CharArr digits);
 public abstract void writePartialNumber(CharArr digits);
 public abstract void startObject();
 public abstract void endObject();
 public abstract void startArray();
 public abstract void endArray();
 public abstract void writeValueSeparator();
 public abstract void writeNameSeparator();
 // void writeNameValue(String name, Object val)?

} /**

* 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.
*/

/**

* @author yonik
* @version $Id: JSONWriter.java 666240 2008-06-10 18:00:38Z yonik $
*/

// how to couple with JSONParser to allow streaming large values from input to // output? // IDEA 1) have JSONParser.getString(JSONWriter out)? // IDEA 2) have an output CharArr that acts as a filter to escape data // IDEA: a subclass of JSONWriter could provide more state and stricter checking class JSONWriter extends TextWriter {

 int level;
 boolean doIndent;
 final CharArr out;
 JSONWriter(CharArr out) {
   this.out = out;
 }
 public void writeNull() {
   JSONUtil.writeNull(out);
 }
 public void writeString(CharSequence str) {
   JSONUtil.writeString(str, 0, str.length(), out);
 }
 public void writeString(CharArr str) {
   JSONUtil.writeString(str, out);
 }
 public void writeStringStart() {
   out.write(""");
 }
 public void writeStringChars(CharArr partialStr) {
   JSONUtil
       .writeStringPart(partialStr.getArray(), partialStr.getStart(), partialStr.getEnd(), out);
 }
 public void writeStringEnd() {
   out.write(""");
 }
 public void write(long number) {
   JSONUtil.writeNumber(number, out);
 }
 public void write(double number) {
   JSONUtil.writeNumber(number, out);
 }
 public void write(boolean bool) {
   JSONUtil.writeBoolean(bool, out);
 }
 public void writeNumber(CharArr digits) {
   out.write(digits);
 }
 public void writePartialNumber(CharArr digits) {
   out.write(digits);
 }
 public void startObject() {
   out.write("{");
   level++;
 }
 public void endObject() {
   out.write("}");
   level--;
 }
 public void startArray() {
   out.write("[");
   level++;
 }
 public void endArray() {
   out.write("]");
   level--;
 }
 public void writeValueSeparator() {
   out.write(",");
 }
 public void writeNameSeparator() {
   out.write(":");
 }

} //////////////////////////////////////// /**

* 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 org.apache.noggit; import junit.framework.TestCase; import java.util.Random; import java.io.StringReader; import java.io.IOException; /**

* @author yonik
* @version $Id: TestJSONParser.java 583538 2007-10-10 16:53:02Z yonik $
*/

public class TestJSONParser extends TestCase {

 public static Random r = new Random(0);
 public static JSONParser getParser(String s) {
   return getParser(s, r.nextInt(2));
 }
 
 public static JSONParser getParser(String s, int type) {
   JSONParser parser=null;
   switch (type) {
     case 0:
       // test directly using input buffer
       parser = new JSONParser(s.toCharArray(),0,s.length());
       break;
     case 1:
       // test using Reader...
       // small input buffers can help find bugs on boundary conditions
       parser = new JSONParser(new StringReader(s), new char[r.nextInt(25)+1]);
       break;
   }
   return parser;
 }
 public static byte[] events = new byte[256];
 static {
   events["{"] = JSONParser.OBJECT_START;
   events["}"] = JSONParser.OBJECT_END;
   events["["] = JSONParser.ARRAY_START;
   events["]"] = JSONParser.ARRAY_END;
   events["s"] = JSONParser.STRING;
   events["b"] = JSONParser.BOOLEAN;
   events["l"] = JSONParser.LONG;
   events["n"] = JSONParser.NUMBER;
   events["N"] = JSONParser.BIGNUMBER;
   events["0"] = JSONParser.NULL;
   events["e"] = JSONParser.EOF;
 }
 // match parser states with the expected states
 public static void parse(JSONParser p, String input, String expected) throws IOException {
   expected += "e";
   for (int i=0; i<expected.length(); i++) {
     int ev = p.nextEvent();
     int expect = events[expected.charAt(i)];
     if (ev != expect) {
       TestCase.fail("Expected " + expect + ", got " + ev
               + "\n\tINPUT=" + input
               + "\n\tEXPECTED=" + expected
               + "\n\tAT=" + i + " ("+ expected.charAt(i) + ")");
     }
   }
 }
 public static void parse(String input, String expected) throws IOException {
   input = input.replace("\"",""");
   for (int i=0; i<Integer.MAX_VALUE; i++) {
     JSONParser p = getParser(input,i);
     if (p==null) break;
     parse(p,input,expected);
   }    
 }


 public static class Num {
   public String digits;
   public Num(String digits) {
     this.digits = digits;
   }
   public String toString() { return new String("NUMBERSTRING("+digits+")"); }
   public boolean equals(Object o) {
     return (getClass()==o.getClass() && digits.equals(((Num)o).digits));
   }
 }
 public static class BigNum extends Num {
   public String toString() { return new String("BIGNUM("+digits+")"); }    
   public BigNum(String digits) { super(digits); }
 }
 // Oh, what I wouldn"t give for Java5 varargs and autoboxing
 public static Long o(int l) { return new Long(l); }
 public static Long o(long l) { return new Long(l); }
 public static Double o(double d) { return new Double(d); }
 public static Boolean o(boolean b) { return new Boolean(b); }
 public static Num n(String digits) { return new Num(digits); }
 public static Num bn(String digits) { return new BigNum(digits); }
 public static Object t = new Boolean(true);
 public static Object f = new Boolean(false);
 public static Object a = new Object(){public String toString() {return "ARRAY_START";}};
 public static Object A = new Object(){public String toString() {return "ARRAY_END";}};
 public static Object m = new Object(){public String toString() {return "OBJECT_START";}};
 public static Object M = new Object(){public String toString() {return "OBJECT_END";}};
 public static Object N = new Object(){public String toString() {return "NULL";}};
 public static Object e = new Object(){public String toString() {return "EOF";}};
 // match parser states with the expected states
 public static void parse(JSONParser p, String input, Object[] expected) throws IOException {
   for (int i=0; i<expected.length; i++) {
     int ev = p.nextEvent();
     Object exp = expected[i];
     Object got = null;
     switch(ev) {
       case JSONParser.ARRAY_START: got=a; break;
       case JSONParser.ARRAY_END: got=A; break;
       case JSONParser.OBJECT_START: got=m; break;
       case JSONParser.OBJECT_END: got=M; break;
       case JSONParser.LONG: got=o(p.getLong()); break;
       case JSONParser.NUMBER:
         if (exp instanceof Double) {
           got = o(p.getDouble());
         } else {
           got = n(p.getNumberChars().toString());
         }
         break;
       case JSONParser.BIGNUMBER: got=bn(p.getNumberChars().toString()); break;
       case JSONParser.NULL: got=N; p.getNull(); break; // optional
       case JSONParser.BOOLEAN: got=o(p.getBoolean()); break;
       case JSONParser.EOF: got=e; break;
       case JSONParser.STRING: got=p.getString(); break;
       default: got="Unexpected Event Number " + ev;
     }
     if (!(exp==got || exp.equals(got))) {
       TestCase.fail("Fail: String=""+input+"""
               + "\n\tINPUT=" + got
               + "\n\tEXPECTED=" + exp
               + "\n\tAT RULE " + i);
     }
   }
 }
 public static void parse(String input, Object[] expected) throws IOException {
   input = input.replace("\"",""");
   for (int i=0; i<Integer.MAX_VALUE; i++) {
     JSONParser p = getParser(input,i);
     if (p==null) break;
     parse(p,input,expected);
   }
 }


 public static void err(String input) throws IOException {
   try {
     JSONParser p = getParser(input);
     while (p.nextEvent() != JSONParser.EOF);
   } catch (Exception e) {
     return;
   }
   TestCase.fail("Input should failed:"" + input + """);    
 }
 public void testNull() throws IOException {
   err("[nullz]");
   parse("[null]","[0]");
   parse("{"hi":null}",new Object[]{m,"hi",N,M,e});
 }
 public void testBool() throws IOException {
   err("[True]");
   err("[False]");
   err("[TRUE]");
   err("[FALSE]");
   err("[truex]");
   err("[falsex]"); 
   parse("[false,true, false , true ]",new Object[]{a,f,t,f,t,A,e});
 }
 public void testString() throws IOException {
   // NOTE: single quotes are converted to double quotes by this
   // testsuite!
   err("["]");
   err("[",]");
   err("{"}");
   err("{",}");
   err("["\\u111"]");
   err("["\\u11"]");
   err("["\\u1"]");
   err("["\\"]");
   err("["\\ "]");
   err("["\\U1111"]");
   parse("[""]",new Object[]{a,"",A,e});
   parse("["\\\\"]",new Object[]{a,"\\",A,e});
   parse("["X\\\\"]",new Object[]{a,"X\\",A,e});
   parse("["\\\\X"]",new Object[]{a,"\\X",A,e});
   parse("["\\""]",new Object[]{a,"\"",A,e});
   String esc="\\n\\r\\tX\\b\\f\\/\\\\X\\\"";
   String exp="\n\r\tX\b\f/\\X\"";
   parse("["" + esc + ""]",new Object[]{a,exp,A,e});
   parse("["" + esc+esc+esc+esc+esc + ""]",new Object[]{a,exp+exp+exp+exp+exp,A,e});
   esc="\\u004A";
   exp="\u004A";
   parse("["" + esc + ""]",new Object[]{a,exp,A,e});
   esc="\\u0000\\u1111\\u2222\\u12AF\\u12BC\\u19DE";
   exp="\u0000\u1111\u2222\u12AF\u12BC\u19DE";
   parse("["" + esc + ""]",new Object[]{a,exp,A,e});
 }
 public void testNumbers() throws IOException {
   err("[00]");
   err("[003]");
   err("[00.3]");
   err("[1e1.1]");
   err("[+1]");
   err("[NaN]");
   err("[Infinity]");
   err("[--1]");
   String lmin    = "-9223372036854775808";
   String lminNot = "-9223372036854775809";
   String lmax    = "9223372036854775807";
   String lmaxNot = "9223372036854775808";
   String bignum="12345678987654321357975312468642099775533112244668800152637485960987654321";
   parse("[0,1,-1,543,-876]", new Object[]{a,o(0),o(1),o(-1),o(543),o(-876),A,e});
   parse("[-0]",new Object[]{a,o(0),A,e});
   parse("["+lmin +"," + lmax+"]",
         new Object[]{a,o(Long.MIN_VALUE),o(Long.MAX_VALUE),A,e});
   parse("["+bignum+"]", new Object[]{a,bn(bignum),A,e});
   parse("["+"-"+bignum+"]", new Object[]{a,bn("-"+bignum),A,e});
   parse("["+lminNot+"]",new Object[]{a,bn(lminNot),A,e});
   parse("["+lmaxNot+"]",new Object[]{a,bn(lmaxNot),A,e});
   parse("["+lminNot + "," + lmaxNot + "]",
         new Object[]{a,bn(lminNot),bn(lmaxNot),A,e});
   // bignum many digits on either side of decimal
   String t = bignum + "." + bignum;
   parse("["+t+","+"-"+t+"]", new Object[]{a,bn(t),bn("-"+t),A,e});
   err(t+".1"); // extra decimal
   err("-"+t+".1");
   // bignum exponent w/o fraction
   t = "1" + "e+" + bignum;
   parse("["+t+","+"-"+t+"]", new Object[]{a,bn(t),bn("-"+t),A,e});
   t = "1" + "E+" + bignum;
   parse("["+t+","+"-"+t+"]", new Object[]{a,bn(t),bn("-"+t),A,e});
   t = "1" + "e" + bignum;
   parse("["+t+","+"-"+t+"]", new Object[]{a,bn(t),bn("-"+t),A,e});
   t = "1" + "E" + bignum;
   parse("["+t+","+"-"+t+"]", new Object[]{a,bn(t),bn("-"+t),A,e});
   t = "1" + "e-" + bignum;
   parse("["+t+","+"-"+t+"]", new Object[]{a,bn(t),bn("-"+t),A,e});
   t = "1" + "E-" + bignum;
   parse("["+t+","+"-"+t+"]", new Object[]{a,bn(t),bn("-"+t),A,e});
   t = bignum + "e+" + bignum;
   parse("["+t+","+"-"+t+"]", new Object[]{a,bn(t),bn("-"+t),A,e});
   t = bignum + "E-" + bignum;
   parse("["+t+","+"-"+t+"]", new Object[]{a,bn(t),bn("-"+t),A,e});
   t = bignum + "e" + bignum;
   parse("["+t+","+"-"+t+"]", new Object[]{a,bn(t),bn("-"+t),A,e});
   t = bignum + "." + bignum + "e" + bignum;
   parse("["+t+","+"-"+t+"]", new Object[]{a,bn(t),bn("-"+t),A,e});
   err("[1E]");
   err("[1E-]");
   err("[1E+]");
   err("[1E+.3]");
   err("[1E+0.3]");
   err("[1E+1e+3]");
   err("["+bignum+"e"+"]");
   err("["+bignum+"e-"+"]");
   err("["+bignum+"e+"+"]");
   err("["+bignum+"."+bignum+"."+bignum+"]");
   double[] vals = new double[] {0,0.1,1.1,
           Double.MAX_VALUE,
           Double.MIN_VALUE,
           2.2250738585072014E-308, /* Double.MIN_NORMAL */
   };
   for (int i=0; i<vals.length; i++) {
     double d = vals[i];
     parse("["+d+","+-d+"]", new Object[]{a,o(d),o(-d),A,e});      
   }
   // MIN_NORMAL has the max number of digits (23), so check that
   // adding an extra digit causes BIGNUM to be returned.
   t = "2.2250738585072014E-308" + "0";
   parse("["+t+","+"-"+t+"]", new Object[]{a,bn(t),bn("-"+t),A,e});
   // check it works with a leading zero too
   t = "0.2250738585072014E-308" + "0";
   parse("["+t+","+"-"+t+"]", new Object[]{a,bn(t),bn("-"+t),A,e});
 }
 public void testArray() throws IOException {
   parse("[]","[]");
   parse("[ ]","[]");
   parse(" \r\n\t[\r\t\n ]\r\n\t ","[]");
   parse("[0]","[l]");
   parse("["0"]","[s]");
   parse("[0,"0",0.1]","[lsn]");
   parse("[[[[[]]]]]","[[[[[]]]]]");
   parse("[[[[[0]]]]]","[[[[[l]]]]]");
   err("]");
   err("[");
   err("[[]");
   err("[]]");
   err("[}");
   err("{]");
   err("["a":"b"]");
 }
 public void testObject() throws IOException {
   parse("{}","{}");
   parse("{}","{}");
   parse(" \r\n\t{\r\t\n }\r\n\t ","{}");
   parse("{"":null}","{s0}");
   err("}");
   err("[}]");
   err("{");
   err("[{]");
   err("{{}");
   err("[{{}]");
   err("{}}");;
   err("[{}}]");;
   err("{1}");
   err("[{1}]");
   err("{"a"}");
   err("{"a","b"}");
   err("{null:"b"}");
   err("{[]:"b"}");
   err("{true:"b"}");
   err("{false:"b"}");
   err("{{"a":"b"}:"c"}");
   parse("{"+"}", new Object[]{m,M,e});
   parse("{"a":"b"}", new Object[]{m,"a","b",M,e});
   parse("{"a":5}", new Object[]{m,"a",o(5),M,e});
   parse("{"a":null}", new Object[]{m,"a",N,M,e});
   parse("{"a":[]}", new Object[]{m,"a",a,A,M,e});
   parse("{"a":{"b":"c"}}", new Object[]{m,"a",m,"b","c",M,M,e});
   String big = "Now is the time for all good men to come to the aid of their country!";
   String t = big+big+big+big+big;
   parse("{""+t+"":""+t+"","a":"b"}", new Object[]{m,t,t,"a","b",M,e});
 }
 public void testAPI() throws IOException {
   JSONParser p = new JSONParser("[1,2]");
   assertEquals(JSONParser.ARRAY_START, p.nextEvent());
   // no nextEvent for the next objects...
   assertEquals(1,p.getLong());
   assertEquals(2,p.getLong());
   
 }

}

 </source>
   
  
 
  



String node for JSON

   <source lang="java">

/**

*  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.
*/

// geronimo import java.util.ArrayList; import java.util.Iterator; import java.util.Hashtable; import java.util.Collections; public class StringNode implements Comparable{

   public String name = null;
   public ArrayList childs = new ArrayList();    
   public StringNode(String nm, ArrayList elements){
       name = nm;
       childs = elements;
   }
   public StringNode(String nm){
       name = nm;        
   }
   public String getName(){
       return name;
   }
   public void setName(String nm){
       name = nm;;
   }
   public ArrayList getChilds(){
       return childs;
   }
   public void setChilds(ArrayList elements){
       childs = elements;
   }
   public void addChild(Object obj){
       childs.add(obj);
   }
   public StringNode findNode(String id){
       if(id == null)return null;
       if(name != null && name.equals(id))
           return this;
       Iterator iter = childs.iterator();
       while(iter.hasNext()){
           Object obj = iter.next();
           if(obj instanceof StringNode){
               StringNode tree = ((StringNode)obj).findNode(id);
               if(tree != null)return tree;
           }                
       }
       return null;
   }
   public boolean equals(Object node){
       if(node instanceof StringNode && ((StringNode)node).name.equals(this.name))
           return true;
       return false;
   }
          
   public String toJSONObject(String prependId){
       return toJSONObject(prependId, null);
   }
   public String toJSONObject(String prependId, Hashtable htLinks){
       return toJSONObject(prependId, htLinks, false);
   }
          
   public String toJSONObject(String prependId, Hashtable htLinks, boolean level1){
       StringBuffer stb = new StringBuffer();
       if(htLinks != null){
           if(!name.startsWith("class ") && !name.startsWith("interface ") && !name.equals("Classes") && !name.equals("Interfaces") && htLinks.containsKey(name) && !level1){
               stb.append("{title:"link::");
               stb.append(htLinks.get(name));
               stb.append("",widgetId:"");
               stb.append(prependId);
               stb.append(""}");
               return stb.toString();
           }
           else {
               htLinks.put(name, prependId);
           }       
       }        
       stb.append("{title:"");
       if(name != null)
           stb.append(name);
       stb.append("",widgetId:"");
       stb.append(prependId);
       if(childs == null || childs.size() == 0){
           stb.append("",children:[]}");
       }
       else
       {
           stb.append("",children:[");
           Collections.sort(childs);
           for(int i=0;i<childs.size();i++){
               Object obj = childs.get(i);
               if(i !=0 )stb.append(",");
               if(obj instanceof StringNode)
                   stb.append(((StringNode)obj).toJSONObject(prependId+"."+i, htLinks)); 
               else
               {
                   stb.append("{title:"");
                   stb.append((String)obj);
                   stb.append("",widgetId:"");
                   stb.append(prependId+"."+i);
                   stb.append(""}");
               }
           }
           stb.append("]}");
       }
       return stb.toString();
   }
   public int compareTo(Object obj){
       if(name == null)
           return -1;
       return name.rupareTo(((StringNode)obj).getName());
   }

}

 </source>