/* * $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.util.LinkedList; import java.util.List; import java.util.Map; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import rejava.io.Reader; import rejava.io.StringReader; /** * Parser for JSON text. Please note that JSONParser is NOT thread-safe. * * @author FangYidong */ 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 final Yylex lexer = new Yylex((Reader) null); private Yytoken token = null; private int status = JSONParser.S_INIT; private int peekStatus(final LinkedList statusStack) { if (statusStack.size() == 0) { return -1; } final Integer status = (Integer) statusStack.getFirst(); return status.intValue(); } /** * Reset the parser to the initial state without resetting the underlying * reader. * */ public void reset() { this.token = null; this.status = JSONParser.S_INIT; this.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(final Reader in) { this.lexer.yyreset(in); this.reset(); } /** * @return The position of the beginning of the current token. */ public int getPosition() { return this.lexer.getPosition(); } public Object parse(final String s) throws ParseException { return this.parse(s, (ContainerFactory) null); } public Object parse(final String s, final ContainerFactory containerFactory) throws ParseException { final StringReader in = new StringReader(s); try { return this.parse(in, containerFactory); } catch (final IOException ie) { /* * Actually it will never happen. */ throw new ParseException(-1, ParseException.ERROR_UNEXPECTED_EXCEPTION, ie); } } public Object parse(final Reader in) throws IOException, ParseException { return this.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 */ @SuppressWarnings({ "rawtypes", "unchecked" }) public Object parse(final Reader in, final ContainerFactory containerFactory) throws IOException, ParseException { this.reset(in); final LinkedList statusStack = new LinkedList(); final LinkedList valueStack = new LinkedList(); try { do { this.nextToken(); switch (this.status) { case S_INIT: switch (this.token.type) { case Yytoken.TYPE_VALUE: this.status = JSONParser.S_IN_FINISHED_VALUE; statusStack.addFirst(new Integer(this.status)); valueStack.addFirst(this.token.value); break; case Yytoken.TYPE_LEFT_BRACE: this.status = JSONParser.S_IN_OBJECT; statusStack.addFirst(new Integer(this.status)); valueStack.addFirst(this.createObjectContainer(containerFactory)); break; case Yytoken.TYPE_LEFT_SQUARE: this.status = JSONParser.S_IN_ARRAY; statusStack.addFirst(new Integer(this.status)); valueStack.addFirst(this.createArrayContainer(containerFactory)); break; default: this.status = JSONParser.S_IN_ERROR; }// inner switch break; case S_IN_FINISHED_VALUE: if (this.token.type == Yytoken.TYPE_EOF) { return valueStack.removeFirst(); } else { throw new ParseException(this.getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, this.token); } case S_IN_OBJECT: switch (this.token.type) { case Yytoken.TYPE_COMMA: break; case Yytoken.TYPE_VALUE: if (this.token.value instanceof String) { final String key = (String) this.token.value; valueStack.addFirst(key); this.status = JSONParser.S_PASSED_PAIR_KEY; statusStack.addFirst(new Integer(this.status)); } else { this.status = JSONParser.S_IN_ERROR; } break; case Yytoken.TYPE_RIGHT_BRACE: if (valueStack.size() > 1) { statusStack.removeFirst(); valueStack.removeFirst(); this.status = this.peekStatus(statusStack); } else { this.status = JSONParser.S_IN_FINISHED_VALUE; } break; default: this.status = JSONParser.S_IN_ERROR; break; }// inner switch break; case S_PASSED_PAIR_KEY: switch (this.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, this.token.value); this.status = this.peekStatus(statusStack); break; case Yytoken.TYPE_LEFT_SQUARE: statusStack.removeFirst(); key = (String) valueStack.removeFirst(); parent = (Map) valueStack.getFirst(); final List newArray = this.createArrayContainer(containerFactory); parent.put(key, newArray); this.status = JSONParser.S_IN_ARRAY; statusStack.addFirst(new Integer(this.status)); valueStack.addFirst(newArray); break; case Yytoken.TYPE_LEFT_BRACE: statusStack.removeFirst(); key = (String) valueStack.removeFirst(); parent = (Map) valueStack.getFirst(); final Map newObject = this.createObjectContainer(containerFactory); parent.put(key, newObject); this.status = JSONParser.S_IN_OBJECT; statusStack.addFirst(new Integer(this.status)); valueStack.addFirst(newObject); break; default: this.status = JSONParser.S_IN_ERROR; } break; case S_IN_ARRAY: switch (this.token.type) { case Yytoken.TYPE_COMMA: break; case Yytoken.TYPE_VALUE: List val = (List) valueStack.getFirst(); val.add(this.token.value); break; case Yytoken.TYPE_RIGHT_SQUARE: if (valueStack.size() > 1) { statusStack.removeFirst(); valueStack.removeFirst(); this.status = this.peekStatus(statusStack); } else { this.status = JSONParser.S_IN_FINISHED_VALUE; } break; case Yytoken.TYPE_LEFT_BRACE: val = (List) valueStack.getFirst(); final Map newObject = this.createObjectContainer(containerFactory); val.add(newObject); this.status = JSONParser.S_IN_OBJECT; statusStack.addFirst(new Integer(this.status)); valueStack.addFirst(newObject); break; case Yytoken.TYPE_LEFT_SQUARE: val = (List) valueStack.getFirst(); final List newArray = this.createArrayContainer(containerFactory); val.add(newArray); this.status = JSONParser.S_IN_ARRAY; statusStack.addFirst(new Integer(this.status)); valueStack.addFirst(newArray); break; default: this.status = JSONParser.S_IN_ERROR; }// inner switch break; case S_IN_ERROR: throw new ParseException(this.getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, this.token); }// switch if (this.status == JSONParser.S_IN_ERROR) { throw new ParseException(this.getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, this.token); } } while (this.token.type != Yytoken.TYPE_EOF); } catch (final IOException ie) { throw ie; } throw new ParseException(this.getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, this.token); } private void nextToken() throws ParseException, IOException { this.token = this.lexer.yylex(); if (this.token == null) { this.token = new Yytoken(Yytoken.TYPE_EOF, null); } } private Map createObjectContainer(final ContainerFactory containerFactory) { if (containerFactory == null) { return new JSONObject(); } final Map m = containerFactory.createObjectContainer(); if (m == null) { return new JSONObject(); } return m; } private List createArrayContainer(final ContainerFactory containerFactory) { if (containerFactory == null) { return new JSONArray(); } final List l = containerFactory.creatArrayContainer(); if (l == null) { return new JSONArray(); } return l; } public void parse(final String s, final ContentHandler contentHandler) throws ParseException { this.parse(s, contentHandler, false); } public void parse(final String s, final ContentHandler contentHandler, final boolean isResume) throws ParseException { final StringReader in = new StringReader(s); try { this.parse(in, contentHandler, isResume); } catch (final IOException ie) { /* * Actually it will never happen. */ throw new ParseException(-1, ParseException.ERROR_UNEXPECTED_EXCEPTION, ie); } } public void parse(final Reader in, final ContentHandler contentHandler) throws IOException, ParseException { this.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 */ @SuppressWarnings({ "unchecked", "rawtypes" }) public void parse(final Reader in, final ContentHandler contentHandler, boolean isResume) throws IOException, ParseException { if (!isResume) { this.reset(in); this.handlerStatusStack = new LinkedList<>(); } else { if (this.handlerStatusStack == null) { isResume = false; this.reset(in); this.handlerStatusStack = new LinkedList<>(); } } final LinkedList statusStack = this.handlerStatusStack; try { do { switch (this.status) { case S_INIT: contentHandler.startJSON(); this.nextToken(); switch (this.token.type) { case Yytoken.TYPE_VALUE: this.status = JSONParser.S_IN_FINISHED_VALUE; statusStack.addFirst(new Integer(this.status)); if (!contentHandler.primitive(this.token.value)) { return; } break; case Yytoken.TYPE_LEFT_BRACE: this.status = JSONParser.S_IN_OBJECT; statusStack.addFirst(new Integer(this.status)); if (!contentHandler.startObject()) { return; } break; case Yytoken.TYPE_LEFT_SQUARE: this.status = JSONParser.S_IN_ARRAY; statusStack.addFirst(new Integer(this.status)); if (!contentHandler.startArray()) { return; } break; default: this.status = JSONParser.S_IN_ERROR; }// inner switch break; case S_IN_FINISHED_VALUE: this.nextToken(); if (this.token.type == Yytoken.TYPE_EOF) { contentHandler.endJSON(); this.status = JSONParser.S_END; return; } else { this.status = JSONParser.S_IN_ERROR; throw new ParseException(this.getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, this.token); } case S_IN_OBJECT: this.nextToken(); switch (this.token.type) { case Yytoken.TYPE_COMMA: break; case Yytoken.TYPE_VALUE: if (this.token.value instanceof String) { final String key = (String) this.token.value; this.status = JSONParser.S_PASSED_PAIR_KEY; statusStack.addFirst(new Integer(this.status)); if (!contentHandler.startObjectEntry(key)) { return; } } else { this.status = JSONParser.S_IN_ERROR; } break; case Yytoken.TYPE_RIGHT_BRACE: if (statusStack.size() > 1) { statusStack.removeFirst(); this.status = this.peekStatus(statusStack); } else { this.status = JSONParser.S_IN_FINISHED_VALUE; } if (!contentHandler.endObject()) { return; } break; default: this.status = JSONParser.S_IN_ERROR; break; }// inner switch break; case S_PASSED_PAIR_KEY: this.nextToken(); switch (this.token.type) { case Yytoken.TYPE_COLON: break; case Yytoken.TYPE_VALUE: statusStack.removeFirst(); this.status = this.peekStatus(statusStack); if (!contentHandler.primitive(this.token.value)) { return; } if (!contentHandler.endObjectEntry()) { return; } break; case Yytoken.TYPE_LEFT_SQUARE: statusStack.removeFirst(); statusStack.addFirst(new Integer(JSONParser.S_IN_PAIR_VALUE)); this.status = JSONParser.S_IN_ARRAY; statusStack.addFirst(new Integer(this.status)); if (!contentHandler.startArray()) { return; } break; case Yytoken.TYPE_LEFT_BRACE: statusStack.removeFirst(); statusStack.addFirst(new Integer(JSONParser.S_IN_PAIR_VALUE)); this.status = JSONParser.S_IN_OBJECT; statusStack.addFirst(new Integer(this.status)); if (!contentHandler.startObject()) { return; } break; default: this.status = JSONParser.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(); this.status = this.peekStatus(statusStack); if (!contentHandler.endObjectEntry()) { return; } break; case S_IN_ARRAY: this.nextToken(); switch (this.token.type) { case Yytoken.TYPE_COMMA: break; case Yytoken.TYPE_VALUE: if (!contentHandler.primitive(this.token.value)) { return; } break; case Yytoken.TYPE_RIGHT_SQUARE: if (statusStack.size() > 1) { statusStack.removeFirst(); this.status = this.peekStatus(statusStack); } else { this.status = JSONParser.S_IN_FINISHED_VALUE; } if (!contentHandler.endArray()) { return; } break; case Yytoken.TYPE_LEFT_BRACE: this.status = JSONParser.S_IN_OBJECT; statusStack.addFirst(new Integer(this.status)); if (!contentHandler.startObject()) { return; } break; case Yytoken.TYPE_LEFT_SQUARE: this.status = JSONParser.S_IN_ARRAY; statusStack.addFirst(new Integer(this.status)); if (!contentHandler.startArray()) { return; } break; default: this.status = JSONParser.S_IN_ERROR; }// inner switch break; case S_END: return; case S_IN_ERROR: throw new ParseException(this.getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, this.token); }// switch if (this.status == JSONParser.S_IN_ERROR) { throw new ParseException(this.getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, this.token); } } while (this.token.type != Yytoken.TYPE_EOF); } catch (final IOException ie) { this.status = JSONParser.S_IN_ERROR; throw ie; } catch (final ParseException pe) { this.status = JSONParser.S_IN_ERROR; throw pe; } catch (final RuntimeException re) { this.status = JSONParser.S_IN_ERROR; throw re; } catch (final Error e) { this.status = JSONParser.S_IN_ERROR; throw e; } this.status = JSONParser.S_IN_ERROR; throw new ParseException(this.getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, this.token); } }