+/*
+ * $Id: JSONParser.java,v 1.1 2006/04/15 14:10:48 platform Exp $
+ * Created on 2006-4-15
+ */
+package org.json.simple.parser;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+
+/**
+ * Parser for JSON text. Please note that JSONParser is NOT thread-safe.
+ *
+ * @author FangYidong<fangyidong@yahoo.com.cn>
+ */
+public class JSONParser
+{
+ public static final int S_INIT = 0;
+
+ public static final int S_IN_FINISHED_VALUE = 1;// string,number,boolean,null,object,array
+
+ public static final int S_IN_OBJECT = 2;
+
+ public static final int S_IN_ARRAY = 3;
+
+ public static final int S_PASSED_PAIR_KEY = 4;
+
+ public static final int S_IN_PAIR_VALUE = 5;
+
+ public static final int S_END = 6;
+
+ public static final int S_IN_ERROR = -1;
+
+ private LinkedList handlerStatusStack;
+
+ private Yylex lexer = new Yylex((Reader) null);
+
+ private Yytoken token = null;
+
+ private int status = S_INIT;
+
+ private int peekStatus(LinkedList statusStack)
+ {
+ if (statusStack.size() == 0)
+ return -1;
+ Integer status = (Integer) statusStack.getFirst();
+ return status.intValue();
+ }
+
+ /**
+ * Reset the parser to the initial state without resetting the underlying
+ * reader.
+ *
+ */
+ public void reset()
+ {
+ token = null;
+ status = S_INIT;
+ handlerStatusStack = null;
+ }
+
+ /**
+ * Reset the parser to the initial state with a new character reader.
+ *
+ * @param in
+ * - The new character reader.
+ * @throws IOException
+ * @throws ParseException
+ */
+ public void reset(Reader in)
+ {
+ lexer.yyreset(in);
+ reset();
+ }
+
+ /**
+ * @return The position of the beginning of the current token.
+ */
+ public int getPosition()
+ {
+ return lexer.getPosition();
+ }
+
+ public Object parse(String s) throws ParseException
+ {
+ return parse(s, (ContainerFactory) null);
+ }
+
+ public Object parse(String s, ContainerFactory containerFactory)
+ throws ParseException
+ {
+ StringReader in = new StringReader(s);
+ try
+ {
+ return parse(in, containerFactory);
+ } catch (IOException ie)
+ {
+ /*
+ * Actually it will never happen.
+ */
+ throw new ParseException(-1,
+ ParseException.ERROR_UNEXPECTED_EXCEPTION, ie);
+ }
+ }
+
+ public Object parse(Reader in) throws IOException, ParseException
+ {
+ return parse(in, (ContainerFactory) null);
+ }
+
+ /**
+ * Parse JSON text into java object from the input source.
+ *
+ * @param in
+ * @param containerFactory
+ * - Use this factory to createyour own JSON object and JSON array
+ * containers.
+ * @return Instance of the following: org.json.simple.JSONObject,
+ * org.json.simple.JSONArray, java.lang.String, java.lang.Number,
+ * java.lang.Boolean, null
+ *
+ * @throws IOException
+ * @throws ParseException
+ */
+ public Object parse(Reader in, ContainerFactory containerFactory)
+ throws IOException, ParseException
+ {
+ reset(in);
+ LinkedList statusStack = new LinkedList();
+ LinkedList valueStack = new LinkedList();
+
+ try
+ {
+ do
+ {
+ nextToken();
+ switch (status)
+ {
+ case S_INIT:
+ switch (token.type)
+ {
+ case Yytoken.TYPE_VALUE:
+ status = S_IN_FINISHED_VALUE;
+ statusStack.addFirst(new Integer(status));
+ valueStack.addFirst(token.value);
+ break;
+ case Yytoken.TYPE_LEFT_BRACE:
+ status = S_IN_OBJECT;
+ statusStack.addFirst(new Integer(status));
+ valueStack.addFirst(createObjectContainer(containerFactory));
+ break;
+ case Yytoken.TYPE_LEFT_SQUARE:
+ status = S_IN_ARRAY;
+ statusStack.addFirst(new Integer(status));
+ valueStack.addFirst(createArrayContainer(containerFactory));
+ break;
+ default:
+ status = S_IN_ERROR;
+ }// inner switch
+ break;
+
+ case S_IN_FINISHED_VALUE:
+ if (token.type == Yytoken.TYPE_EOF)
+ return valueStack.removeFirst();
+ else
+ throw new ParseException(getPosition(),
+ ParseException.ERROR_UNEXPECTED_TOKEN, token);
+
+ case S_IN_OBJECT:
+ switch (token.type)
+ {
+ case Yytoken.TYPE_COMMA:
+ break;
+ case Yytoken.TYPE_VALUE:
+ if (token.value instanceof String)
+ {
+ String key = (String) token.value;
+ valueStack.addFirst(key);
+ status = S_PASSED_PAIR_KEY;
+ statusStack.addFirst(new Integer(status));
+ }
+ else
+ {
+ status = S_IN_ERROR;
+ }
+ break;
+ case Yytoken.TYPE_RIGHT_BRACE:
+ if (valueStack.size() > 1)
+ {
+ statusStack.removeFirst();
+ valueStack.removeFirst();
+ status = peekStatus(statusStack);
+ }
+ else
+ {
+ status = S_IN_FINISHED_VALUE;
+ }
+ break;
+ default:
+ status = S_IN_ERROR;
+ break;
+ }// inner switch
+ break;
+
+ case S_PASSED_PAIR_KEY:
+ switch (token.type)
+ {
+ case Yytoken.TYPE_COLON:
+ break;
+ case Yytoken.TYPE_VALUE:
+ statusStack.removeFirst();
+ String key = (String) valueStack.removeFirst();
+ Map parent = (Map) valueStack.getFirst();
+ parent.put(key, token.value);
+ status = peekStatus(statusStack);
+ break;
+ case Yytoken.TYPE_LEFT_SQUARE:
+ statusStack.removeFirst();
+ key = (String) valueStack.removeFirst();
+ parent = (Map) valueStack.getFirst();
+ List newArray = createArrayContainer(containerFactory);
+ parent.put(key, newArray);
+ status = S_IN_ARRAY;
+ statusStack.addFirst(new Integer(status));
+ valueStack.addFirst(newArray);
+ break;
+ case Yytoken.TYPE_LEFT_BRACE:
+ statusStack.removeFirst();
+ key = (String) valueStack.removeFirst();
+ parent = (Map) valueStack.getFirst();
+ Map newObject = createObjectContainer(containerFactory);
+ parent.put(key, newObject);
+ status = S_IN_OBJECT;
+ statusStack.addFirst(new Integer(status));
+ valueStack.addFirst(newObject);
+ break;
+ default:
+ status = S_IN_ERROR;
+ }
+ break;
+
+ case S_IN_ARRAY:
+ switch (token.type)
+ {
+ case Yytoken.TYPE_COMMA:
+ break;
+ case Yytoken.TYPE_VALUE:
+ List val = (List) valueStack.getFirst();
+ val.add(token.value);
+ break;
+ case Yytoken.TYPE_RIGHT_SQUARE:
+ if (valueStack.size() > 1)
+ {
+ statusStack.removeFirst();
+ valueStack.removeFirst();
+ status = peekStatus(statusStack);
+ }
+ else
+ {
+ status = S_IN_FINISHED_VALUE;
+ }
+ break;
+ case Yytoken.TYPE_LEFT_BRACE:
+ val = (List) valueStack.getFirst();
+ Map newObject = createObjectContainer(containerFactory);
+ val.add(newObject);
+ status = S_IN_OBJECT;
+ statusStack.addFirst(new Integer(status));
+ valueStack.addFirst(newObject);
+ break;
+ case Yytoken.TYPE_LEFT_SQUARE:
+ val = (List) valueStack.getFirst();
+ List newArray = createArrayContainer(containerFactory);
+ val.add(newArray);
+ status = S_IN_ARRAY;
+ statusStack.addFirst(new Integer(status));
+ valueStack.addFirst(newArray);
+ break;
+ default:
+ status = S_IN_ERROR;
+ }// inner switch
+ break;
+ case S_IN_ERROR:
+ throw new ParseException(getPosition(),
+ ParseException.ERROR_UNEXPECTED_TOKEN, token);
+ }// switch
+ if (status == S_IN_ERROR)
+ {
+ throw new ParseException(getPosition(),
+ ParseException.ERROR_UNEXPECTED_TOKEN, token);
+ }
+ } while (token.type != Yytoken.TYPE_EOF);
+ } catch (IOException ie)
+ {
+ throw ie;
+ }
+
+ throw new ParseException(getPosition(),
+ ParseException.ERROR_UNEXPECTED_TOKEN, token);
+ }
+
+ private void nextToken() throws ParseException, IOException
+ {
+ token = lexer.yylex();
+ if (token == null)
+ token = new Yytoken(Yytoken.TYPE_EOF, null);
+ }
+
+ private Map createObjectContainer(ContainerFactory containerFactory)
+ {
+ if (containerFactory == null)
+ return new JSONObject();
+ Map m = containerFactory.createObjectContainer();
+
+ if (m == null)
+ return new JSONObject();
+ return m;
+ }
+
+ private List createArrayContainer(ContainerFactory containerFactory)
+ {
+ if (containerFactory == null)
+ return new JSONArray();
+ List l = containerFactory.creatArrayContainer();
+
+ if (l == null)
+ return new JSONArray();
+ return l;
+ }
+
+ public void parse(String s, ContentHandler contentHandler)
+ throws ParseException
+ {
+ parse(s, contentHandler, false);
+ }
+
+ public void parse(String s, ContentHandler contentHandler,
+ boolean isResume) throws ParseException
+ {
+ StringReader in = new StringReader(s);
+ try
+ {
+ parse(in, contentHandler, isResume);
+ } catch (IOException ie)
+ {
+ /*
+ * Actually it will never happen.
+ */
+ throw new ParseException(-1,
+ ParseException.ERROR_UNEXPECTED_EXCEPTION, ie);
+ }
+ }
+
+ public void parse(Reader in, ContentHandler contentHandler)
+ throws IOException, ParseException
+ {
+ parse(in, contentHandler, false);
+ }
+
+ /**
+ * Stream processing of JSON text.
+ *
+ * @see ContentHandler
+ *
+ * @param in
+ * @param contentHandler
+ * @param isResume
+ * - Indicates if it continues previous parsing operation. If set to
+ * true, resume parsing the old stream, and parameter 'in' will be
+ * ignored. If this method is called for the first time in this
+ * instance, isResume will be ignored.
+ *
+ * @throws IOException
+ * @throws ParseException
+ */
+ public void parse(Reader in, ContentHandler contentHandler,
+ boolean isResume) throws IOException, ParseException
+ {
+ if (!isResume)
+ {
+ reset(in);
+ handlerStatusStack = new LinkedList();
+ }
+ else
+ {
+ if (handlerStatusStack == null)
+ {
+ isResume = false;
+ reset(in);
+ handlerStatusStack = new LinkedList();
+ }
+ }
+
+ LinkedList statusStack = handlerStatusStack;
+
+ try
+ {
+ do
+ {
+ switch (status)
+ {
+ case S_INIT:
+ contentHandler.startJSON();
+ nextToken();
+ switch (token.type)
+ {
+ case Yytoken.TYPE_VALUE:
+ status = S_IN_FINISHED_VALUE;
+ statusStack.addFirst(new Integer(status));
+ if (!contentHandler.primitive(token.value))
+ return;
+ break;
+ case Yytoken.TYPE_LEFT_BRACE:
+ status = S_IN_OBJECT;
+ statusStack.addFirst(new Integer(status));
+ if (!contentHandler.startObject())
+ return;
+ break;
+ case Yytoken.TYPE_LEFT_SQUARE:
+ status = S_IN_ARRAY;
+ statusStack.addFirst(new Integer(status));
+ if (!contentHandler.startArray())
+ return;
+ break;
+ default:
+ status = S_IN_ERROR;
+ }// inner switch
+ break;
+
+ case S_IN_FINISHED_VALUE:
+ nextToken();
+ if (token.type == Yytoken.TYPE_EOF)
+ {
+ contentHandler.endJSON();
+ status = S_END;
+ return;
+ }
+ else
+ {
+ status = S_IN_ERROR;
+ throw new ParseException(getPosition(),
+ ParseException.ERROR_UNEXPECTED_TOKEN, token);
+ }
+
+ case S_IN_OBJECT:
+ nextToken();
+ switch (token.type)
+ {
+ case Yytoken.TYPE_COMMA:
+ break;
+ case Yytoken.TYPE_VALUE:
+ if (token.value instanceof String)
+ {
+ String key = (String) token.value;
+ status = S_PASSED_PAIR_KEY;
+ statusStack.addFirst(new Integer(status));
+ if (!contentHandler.startObjectEntry(key))
+ return;
+ }
+ else
+ {
+ status = S_IN_ERROR;
+ }
+ break;
+ case Yytoken.TYPE_RIGHT_BRACE:
+ if (statusStack.size() > 1)
+ {
+ statusStack.removeFirst();
+ status = peekStatus(statusStack);
+ }
+ else
+ {
+ status = S_IN_FINISHED_VALUE;
+ }
+ if (!contentHandler.endObject())
+ return;
+ break;
+ default:
+ status = S_IN_ERROR;
+ break;
+ }// inner switch
+ break;
+
+ case S_PASSED_PAIR_KEY:
+ nextToken();
+ switch (token.type)
+ {
+ case Yytoken.TYPE_COLON:
+ break;
+ case Yytoken.TYPE_VALUE:
+ statusStack.removeFirst();
+ status = peekStatus(statusStack);
+ if (!contentHandler.primitive(token.value))
+ return;
+ if (!contentHandler.endObjectEntry())
+ return;
+ break;
+ case Yytoken.TYPE_LEFT_SQUARE:
+ statusStack.removeFirst();
+ statusStack.addFirst(new Integer(S_IN_PAIR_VALUE));
+ status = S_IN_ARRAY;
+ statusStack.addFirst(new Integer(status));
+ if (!contentHandler.startArray())
+ return;
+ break;
+ case Yytoken.TYPE_LEFT_BRACE:
+ statusStack.removeFirst();
+ statusStack.addFirst(new Integer(S_IN_PAIR_VALUE));
+ status = S_IN_OBJECT;
+ statusStack.addFirst(new Integer(status));
+ if (!contentHandler.startObject())
+ return;
+ break;
+ default:
+ status = S_IN_ERROR;
+ }
+ break;
+
+ case S_IN_PAIR_VALUE:
+ /*
+ * S_IN_PAIR_VALUE is just a marker to indicate the end of an object entry, it doesn't proccess any token,
+ * therefore delay consuming token until next round.
+ */
+ statusStack.removeFirst();
+ status = peekStatus(statusStack);
+ if (!contentHandler.endObjectEntry())
+ return;
+ break;
+
+ case S_IN_ARRAY:
+ nextToken();
+ switch (token.type)
+ {
+ case Yytoken.TYPE_COMMA:
+ break;
+ case Yytoken.TYPE_VALUE:
+ if (!contentHandler.primitive(token.value))
+ return;
+ break;
+ case Yytoken.TYPE_RIGHT_SQUARE:
+ if (statusStack.size() > 1)
+ {
+ statusStack.removeFirst();
+ status = peekStatus(statusStack);
+ }
+ else
+ {
+ status = S_IN_FINISHED_VALUE;
+ }
+ if (!contentHandler.endArray())
+ return;
+ break;
+ case Yytoken.TYPE_LEFT_BRACE:
+ status = S_IN_OBJECT;
+ statusStack.addFirst(new Integer(status));
+ if (!contentHandler.startObject())
+ return;
+ break;
+ case Yytoken.TYPE_LEFT_SQUARE:
+ status = S_IN_ARRAY;
+ statusStack.addFirst(new Integer(status));
+ if (!contentHandler.startArray())
+ return;
+ break;
+ default:
+ status = S_IN_ERROR;
+ }// inner switch
+ break;
+
+ case S_END:
+ return;
+
+ case S_IN_ERROR:
+ throw new ParseException(getPosition(),
+ ParseException.ERROR_UNEXPECTED_TOKEN, token);
+ }// switch
+ if (status == S_IN_ERROR)
+ {
+ throw new ParseException(getPosition(),
+ ParseException.ERROR_UNEXPECTED_TOKEN, token);
+ }
+ } while (token.type != Yytoken.TYPE_EOF);
+ } catch (IOException ie)
+ {
+ status = S_IN_ERROR;
+ throw ie;
+ } catch (ParseException pe)
+ {
+ status = S_IN_ERROR;
+ throw pe;
+ } catch (RuntimeException re)
+ {
+ status = S_IN_ERROR;
+ throw re;
+ } catch (Error e)
+ {
+ status = S_IN_ERROR;
+ throw e;
+ }
+
+ status = S_IN_ERROR;
+ throw new ParseException(getPosition(),
+ ParseException.ERROR_UNEXPECTED_TOKEN, token);
+ }
+}