/* * 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.chemistry.opencmis.commons.impl.json.parser; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.util.ArrayDeque; import java.util.Deque; import java.util.List; import java.util.Map; import org.apache.chemistry.opencmis.commons.impl.JSONConstraints; import org.apache.chemistry.opencmis.commons.impl.json.JSONArray; import org.apache.chemistry.opencmis.commons.impl.json.JSONObject; /** * Parser for JSON text. Please note that JSONParser is NOT thread-safe. * * (Taken from JSON.simple <http://code.google.com/p/json-simple/> and * modified for OpenCMIS.) * * @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 ArrayDeque<Integer> handlerStatusStack; private Yylex lexer = new Yylex((Reader) null); private Yytoken token = null; private int status = S_INIT; private int peekStatus(Deque<Integer> statusStack) { if (statusStack.isEmpty()) { return -1; } return statusStack.getFirst(); } /** * 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 */ 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 JSONParseException { return parse(s, (ContainerFactory) null); } public Object parse(String s, ContainerFactory containerFactory) throws JSONParseException { StringReader in = new StringReader(s); try { return parse(in, containerFactory); } catch (IOException ie) { /* * Actually it will never happen. */ throw new JSONParseException(-1, JSONParseException.ERROR_UNEXPECTED_EXCEPTION, ie); } } public Object parse(Reader in) throws IOException, JSONParseException { return parse(in, (ContainerFactory) null); } /** * Parse JSON text into java object from the input source. * * @param in * @param containerFactory * - Use this factory to create your own JSON object and JSON * array containers. * @return Instance of the following: JSONObject, JSONArray, * java.lang.String, java.lang.Number, java.lang.Boolean, null */ @SuppressWarnings("unchecked") public Object parse(Reader in, ContainerFactory containerFactory) throws IOException, JSONParseException { reset(in); Deque<Integer> statusStack = new ArrayDeque<Integer>(); Deque<Object> valueStack = new ArrayDeque<Object>(); try { do { nextToken(); switch (status) { case S_INIT: switch (token.type) { case Yytoken.TYPE_VALUE: status = S_IN_FINISHED_VALUE; statusStack.addFirst(Integer.valueOf(status)); valueStack.addFirst(token.value); break; case Yytoken.TYPE_LEFT_BRACE: status = S_IN_OBJECT; statusStack.addFirst(Integer.valueOf(status)); valueStack.addFirst(createObjectContainer(containerFactory)); break; case Yytoken.TYPE_LEFT_SQUARE: status = S_IN_ARRAY; statusStack.addFirst(Integer.valueOf(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 JSONParseException(getPosition(), JSONParseException.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(Integer.valueOf(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<String, Object> parent = (Map<String, Object>) valueStack.getFirst(); if (parent.size() + 1 > JSONConstraints.MAX_OBJECT_SIZE) { throw new JSONParseException(JSONParseException.ERROR_JSON_TOO_BIG); } parent.put(key, token.value); status = peekStatus(statusStack); break; case Yytoken.TYPE_LEFT_SQUARE: statusStack.removeFirst(); key = (String) valueStack.removeFirst(); parent = (Map<String, Object>) valueStack.getFirst(); if (parent.size() + 1 > JSONConstraints.MAX_OBJECT_SIZE) { throw new JSONParseException(JSONParseException.ERROR_JSON_TOO_BIG); } if (valueStack.size() + 1 > JSONConstraints.MAX_DEPTH) { throw new JSONParseException(JSONParseException.ERROR_JSON_TOO_BIG); } List<Object> newArray = createArrayContainer(containerFactory); parent.put(key, newArray); status = S_IN_ARRAY; statusStack.addFirst(Integer.valueOf(status)); valueStack.addFirst(newArray); break; case Yytoken.TYPE_LEFT_BRACE: statusStack.removeFirst(); key = (String) valueStack.removeFirst(); parent = (Map<String, Object>) valueStack.getFirst(); if (parent.size() + 1 > JSONConstraints.MAX_OBJECT_SIZE) { throw new JSONParseException(JSONParseException.ERROR_JSON_TOO_BIG); } if (valueStack.size() + 1 > JSONConstraints.MAX_DEPTH) { throw new JSONParseException(JSONParseException.ERROR_JSON_TOO_BIG); } Map<String, Object> newObject = createObjectContainer(containerFactory); parent.put(key, newObject); status = S_IN_OBJECT; statusStack.addFirst(Integer.valueOf(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<Object> val = (List<Object>) valueStack.getFirst(); if (val.size() + 1 > JSONConstraints.MAX_ARRAY_SIZE) { throw new JSONParseException(JSONParseException.ERROR_JSON_TOO_BIG); } 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<Object>) valueStack.getFirst(); if (val.size() + 1 > JSONConstraints.MAX_ARRAY_SIZE) { throw new JSONParseException(JSONParseException.ERROR_JSON_TOO_BIG); } if (valueStack.size() + 1 > JSONConstraints.MAX_DEPTH) { throw new JSONParseException(JSONParseException.ERROR_JSON_TOO_BIG); } Map<String, Object> newObject = createObjectContainer(containerFactory); val.add(newObject); status = S_IN_OBJECT; statusStack.addFirst(Integer.valueOf(status)); valueStack.addFirst(newObject); break; case Yytoken.TYPE_LEFT_SQUARE: val = (List<Object>) valueStack.getFirst(); if (val.size() + 1 > JSONConstraints.MAX_ARRAY_SIZE) { throw new JSONParseException(JSONParseException.ERROR_JSON_TOO_BIG); } if (valueStack.size() + 1 > JSONConstraints.MAX_DEPTH) { throw new JSONParseException(JSONParseException.ERROR_JSON_TOO_BIG); } List<Object> newArray = createArrayContainer(containerFactory); val.add(newArray); status = S_IN_ARRAY; statusStack.addFirst(Integer.valueOf(status)); valueStack.addFirst(newArray); break; default: status = S_IN_ERROR; }// inner switch break; case S_IN_ERROR: throw new JSONParseException(getPosition(), JSONParseException.ERROR_UNEXPECTED_TOKEN, token); }// switch if (status == S_IN_ERROR) { throw new JSONParseException(getPosition(), JSONParseException.ERROR_UNEXPECTED_TOKEN, token); } } while (token.type != Yytoken.TYPE_EOF); } catch (IOException ie) { throw ie; } throw new JSONParseException(getPosition(), JSONParseException.ERROR_UNEXPECTED_TOKEN, token); } private void nextToken() throws JSONParseException, IOException { token = lexer.yylex(); if (token == null) { token = new Yytoken(Yytoken.TYPE_EOF, null); } } private Map<String, Object> createObjectContainer(ContainerFactory containerFactory) { if (containerFactory == null) { return new JSONObject(); } Map<String, Object> m = containerFactory.createObjectContainer(); if (m == null) { return new JSONObject(); } return m; } private List<Object> createArrayContainer(ContainerFactory containerFactory) { if (containerFactory == null) { return new JSONArray(); } List<Object> l = containerFactory.creatArrayContainer(); if (l == null) { return new JSONArray(); } return l; } public void parse(String s, ContentHandler contentHandler) throws JSONParseException { parse(s, contentHandler, false); } public void parse(String s, ContentHandler contentHandler, boolean isResume) throws JSONParseException { StringReader in = new StringReader(s); try { parse(in, contentHandler, isResume); } catch (IOException ie) { /* * Actually it will never happen. */ throw new JSONParseException(-1, JSONParseException.ERROR_UNEXPECTED_EXCEPTION, ie); } } public void parse(Reader in, ContentHandler contentHandler) throws IOException, JSONParseException { 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 JSONParseException */ public void parse(Reader in, ContentHandler contentHandler, boolean isResume) throws IOException, JSONParseException { if (!isResume) { reset(in); handlerStatusStack = new ArrayDeque<Integer>(); } else { if (handlerStatusStack == null) { isResume = false; reset(in); handlerStatusStack = new ArrayDeque<Integer>(); } } ArrayDeque<Integer> 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(Integer.valueOf(status)); if (!contentHandler.primitive(token.value)) { return; } break; case Yytoken.TYPE_LEFT_BRACE: status = S_IN_OBJECT; statusStack.addFirst(Integer.valueOf(status)); if (!contentHandler.startObject()) { return; } break; case Yytoken.TYPE_LEFT_SQUARE: status = S_IN_ARRAY; statusStack.addFirst(Integer.valueOf(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 JSONParseException(getPosition(), JSONParseException.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(Integer.valueOf(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(Integer.valueOf(S_IN_PAIR_VALUE)); status = S_IN_ARRAY; statusStack.addFirst(Integer.valueOf(status)); if (!contentHandler.startArray()) { return; } break; case Yytoken.TYPE_LEFT_BRACE: statusStack.removeFirst(); statusStack.addFirst(Integer.valueOf(S_IN_PAIR_VALUE)); status = S_IN_OBJECT; statusStack.addFirst(Integer.valueOf(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(Integer.valueOf(status)); if (!contentHandler.startObject()) { return; } break; case Yytoken.TYPE_LEFT_SQUARE: status = S_IN_ARRAY; statusStack.addFirst(Integer.valueOf(status)); if (!contentHandler.startArray()) { return; } break; default: status = S_IN_ERROR; }// inner switch break; case S_END: return; case S_IN_ERROR: throw new JSONParseException(getPosition(), JSONParseException.ERROR_UNEXPECTED_TOKEN, token); }// switch if (status == S_IN_ERROR) { throw new JSONParseException(getPosition(), JSONParseException.ERROR_UNEXPECTED_TOKEN, token); } } while (token.type != Yytoken.TYPE_EOF); } catch (IOException ie) { status = S_IN_ERROR; throw ie; } catch (JSONParseException pe) { status = S_IN_ERROR; throw pe; } catch (RuntimeException re) { status = S_IN_ERROR; throw re; } status = S_IN_ERROR; throw new JSONParseException(getPosition(), JSONParseException.ERROR_UNEXPECTED_TOKEN, token); } }