package org.json.simple.parser; import java.io.IOException; import java.io.Reader; import java.util.*; /** * A managed JSON lexer * with consumer interface similar to {@link javax.xml.stream.XMLStreamReader}. * <p/> * Based on the code of {@link org.json.simple.parser.JSONParser} * * @author karl.wettin@kodapan.se * @since 2009-jul-05 07:08:51 */ public class JSONStreamReader { /** * @param json JSON input reader. This reader will never be closed by this class. */ public JSONStreamReader(Reader json) { lexer.yyreset(json); status = S_INIT; statusStack = new LinkedList(); } 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 Yylex lexer = new Yylex((Reader) null); private int status = S_INIT; private boolean exhausted = false; private int peekStatus(LinkedList statusStack) { if (statusStack.size() == 0) return -1; Integer status = (Integer) statusStack.getFirst(); return status.intValue(); } /** * @return The position of the beginning of the current token. */ public int getPosition() { return lexer.getPosition(); } // todo get rid of this stack, it consume heap without being used! // todo perhaps it can be replaced with a single integer counting up and down? private LinkedList statusStack; private Yytoken token; public Object getObjectValue() { return token == null ? null : token.value; } public String getStringValue() { if (token == null || token.value == null) { return null; } return (String) token.value; } public Double getDoubleValue() { if (token == null || token.value == null) { return null; } return (Double) token.value; } public Number getNumberValue() { if (token == null || token.value == null) { return null; } return (Number) token.value; } public Long getLongValue() { if (token == null || token.value == null) { return null; } return (Long) token.value; } public Boolean getBooleanValue() { if (token == null || token.value == null) { return null; } return (Boolean) token.value; } private boolean nextCalledForTheFirstTime = false; /** * Moves the cursor one step forward. * * @return * @throws IOException * @throws ParseException */ public Event next() throws IOException, ParseException { return dnext(); } /** allows for recursive calls when decorated, eg when using BufferedJSONStreamReader */ private Event dnext() throws IOException, ParseException { if (exhausted) { token = null; // resets value return null; } if (!nextCalledForTheFirstTime) { nextCalledForTheFirstTime = true; return Event.START_DOCUMENT; } token = lexer.yylex(); if (token == null) { exhausted = true; return Event.END_DOCUMENT; } switch (status) { case S_INIT: switch (token.type) { case Yytoken.TYPE_VALUE: status = S_IN_FINISHED_VALUE; statusStack.addFirst(new Integer(status)); return Event.START_ELEMENT_VALUE; case Yytoken.TYPE_LEFT_BRACE: status = S_IN_OBJECT; statusStack.addFirst(new Integer(status)); return Event.START_OBJECT; case Yytoken.TYPE_LEFT_SQUARE: status = S_IN_ARRAY; statusStack.addFirst(new Integer(status)); return Event.START_ARRAY; default: status = S_IN_ERROR; throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); }//inner switch case S_IN_FINISHED_VALUE: if (token.type == Yytoken.TYPE_EOF) { return Event.END_ELEMENT_VALUE; } else { throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); } case S_IN_OBJECT: switch (token.type) { case Yytoken.TYPE_COMMA: return Event.NEXT_VALUE; case Yytoken.TYPE_VALUE: if (token.value instanceof String) { status = S_PASSED_PAIR_KEY; statusStack.addFirst(new Integer(status)); return Event.START_ELEMENT_KEY; } else { status = S_IN_ERROR; throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); } case Yytoken.TYPE_RIGHT_BRACE: if (statusStack.size() > 1) { statusStack.removeFirst(); status = peekStatus(statusStack); } else { status = S_IN_FINISHED_VALUE; } return Event.END_OBJECT; default: status = S_IN_ERROR; throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); }//inner switch case S_PASSED_PAIR_KEY: switch (token.type) { case Yytoken.TYPE_COLON: return dnext(); // nobody needs to see these. case Yytoken.TYPE_VALUE: statusStack.removeFirst(); status = peekStatus(statusStack); return Event.START_ELEMENT_VALUE; case Yytoken.TYPE_LEFT_SQUARE: statusStack.removeFirst(); status = S_IN_ARRAY; statusStack.addFirst(new Integer(status)); return Event.START_ARRAY; case Yytoken.TYPE_LEFT_BRACE: statusStack.removeFirst(); status = S_IN_OBJECT; statusStack.addFirst(new Integer(status)); return Event.START_OBJECT; default: status = S_IN_ERROR; throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); } case S_IN_ARRAY: switch (token.type) { case Yytoken.TYPE_COMMA: return Event.NEXT_VALUE; case Yytoken.TYPE_VALUE: return Event.START_ELEMENT_VALUE; case Yytoken.TYPE_RIGHT_SQUARE: if (statusStack.size() > 1) { statusStack.removeFirst(); status = peekStatus(statusStack); } else { status = S_IN_FINISHED_VALUE; } return Event.END_ARRAY; case Yytoken.TYPE_LEFT_BRACE: status = S_IN_OBJECT; statusStack.addFirst(new Integer(status)); return Event.START_OBJECT; case Yytoken.TYPE_LEFT_SQUARE: status = S_IN_ARRAY; statusStack.addFirst(new Integer(status)); return Event.START_ARRAY; default: status = S_IN_ERROR; throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); }//inner switch case S_IN_ERROR: throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); }//switch throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); } /** * An instance of this class is the return value of {@link org.json.simple.parser.JSONStreamReader#next()}. * * <p/> * * @author karl.wettin@kodapan.se * @since 2009-jul-05 14:01:27 */ public static final class Event { private final String name; private final Integer value; public static final Event START_DOCUMENT = new Event("START_DOCUMENT", 0); public static final Event END_DOCUMENT = new Event("END_DOCUMENT", 1); public static final Event START_OBJECT = new Event("START_OBJECT", 2); public static final Event END_OBJECT = new Event("END_OBJECT", 3); public static final Event START_ELEMENT_KEY = new Event("START_ELEMENT_KEY", 4); public static final Event START_ELEMENT_VALUE = new Event("START_ELEMENT_VALUE", 5); public static final Event END_ELEMENT_VALUE = new Event("END_ELEMENT_VALUE", 6); public static final Event START_ARRAY = new Event("START_ARRAY", 7); public static final Event END_ARRAY = new Event("END_ARRAY", 8); public static final Event NEXT_VALUE = new Event("NEXT_VALUE", 9); // public static final Event KEY_VALUE_SEPARATOR = new Event("KEY_VALUE_SEPARATOR", 10); private static Map eventsByName = new HashMap(); private static Map eventsByValue = new HashMap(); private static void add(Event event) { eventsByName.put(event.name, event); eventsByValue.put(event.value, event); } static { add(START_DOCUMENT); add(END_DOCUMENT); add(START_OBJECT); add(END_OBJECT); add(START_ELEMENT_KEY); add(START_ELEMENT_VALUE); add(END_ELEMENT_VALUE); add(START_ARRAY); add(END_ARRAY); add(NEXT_VALUE); // add(KEY_VALUE_SEPARATOR); } public static Event valueOf(int value) { return (Event) eventsByValue.get(value); } public static Event valueOf(String name) { return (Event) eventsByName.get(name); } private Event(String name, Integer value) { this.name = name; this.value = value; } public String name() { return name; } public Integer value() { return value; } public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Event event = (Event) o; return name.equals(event.name) && value.equals(event.value); } public int hashCode() { int result = name.hashCode(); result = 31 * result + value.hashCode(); return result; } public String toString() { return name; } } public Yytoken getToken() { return token; } }