/* * Copyright 2009 Red Hat, Inc. * * Red Hat 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.jboss.netty.handler.codec.bayeux; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; /** * Parse JSON string to Java Object following mappings below: * * JSON => Java: object => Map array => Object[] string => String number => * long, or double null => null boolean => boolean * * And convert from Java Object to JSON types following this: * * Java => JSON: Map => object, List => array, Array => array, Integer, Long, * Float, Double => number, int, long, float, double => number, null => null, * boolean, Boolean=> boolean String => string * * @author daijun */ public class JSONParser { private JSONString json;// Reprents the JSON string, which is been parsing /** * Validate and parse a string to a JSON object. If succesful it will * returns a Map or Array object, otherwise, it returns null. In below, * there are data types mappings JSON to Java. * * JSON => Java: object => Map array => Object[] string => String number => * long, or double null => null boolean => boolean * * @param s * @return * @throws java.lang.IllegalStateException */ public Object parse(String s) throws IllegalStateException { if (s == null || s.trim().length() == 0) { return null; } json = new JSONString(s); while (json.hasNext()) { switch (json.next()) { case '/': if (json.hasNext() && (json.next() == '/' || json.current() == '*')) { skipComment(); } else { throwIllegalJSONStatementException(); } skipWhitespace(); break; case '{': return parseToObject(); case '[': return parseToArray(); default: throwIllegalJSONStatementException(); break; } } return null; } /** * * @param obj * @return * @throws Exception */ static public String toJSON(Object obj) { StringBuilder sb = new StringBuilder(); if (obj == null) { sb.append("null"); } else if (obj instanceof Map) { sb.append("{"); Map<String, Object> map = (Map<String, Object>) obj; for (Entry<String, Object> entry : map.entrySet()) { sb.append(quote(entry.getKey())).append(":").append( toJSON(entry.getValue())).append(","); } if (!map.isEmpty()) { sb.deleteCharAt(sb.length() - 1); } sb.append("}"); } else if (obj instanceof Boolean) { Boolean bool = (Boolean) obj; if (bool) { sb.append("true"); } else { sb.append("false"); } } else if (obj instanceof Long) { sb.append((Long) obj); } else if (obj instanceof Integer) { sb.append((Integer) obj); } else if (obj instanceof Float) { sb.append((Float) obj); } else if (obj instanceof Double) { sb.append((Double) obj); } else if (obj instanceof String) { sb.append(quote((String) obj)); } else if (obj instanceof List) { sb.append("["); for (Object o : (List) obj) { sb.append(toJSON(o)).append(","); } if (((List) obj).size() > 0) { sb.deleteCharAt(sb.length() - 1); } sb.append("]"); } else if (obj instanceof Object[]) { sb.append("["); for (Object o : (Object[]) obj) { sb.append(toJSON(o)).append(","); } if (((Object[]) obj).length > 0) { sb.deleteCharAt(sb.length() - 1); } sb.append("]"); } else if (obj instanceof BayeuxMessage) { sb.append(((BayeuxMessage) obj).toJSON()); } return sb.toString(); } static private String quote(String s) { return "\"" + s.replace("\"", "\\\"") + "\""; } /** * Throw illegal JSON statement exception during parsing * * When detects an unknown(or unvalid) char, it throws a * IllegalStateException with a message that where and what the char is. */ private void throwIllegalJSONStatementException() { throw new IllegalStateException("Unknown char '" + json.current() + "' at position " + json.getIndex() + " : " + json.getStr()); } /** * Handle JSON comment * * Skip the comments in the head of a JSON string. It supports two types * comments, \"//coments\" and \"/*coments*\/\" */ private void skipComment() { if (json.current() == '/') { while (json.hasNext()) { if (json.next() == '\n') { break; } } } else if (json.current() == '*') { while (json.hasNext()) { if (json.next() == '*' && json.next() == '/') { break; } } } } /** * Skip white spaces inside the JSON string */ private void skipWhitespace() { while (json.hasNext()) { if (json.next() != ' ') { json.back(); break; } } } /** * Skip white spaces and determine whether the first non-white character is * expected * * @param c * @return */ private boolean skipWhitespace(char c) { skipWhitespace(); return (json.hasNext() && json.next() == c); } /** * Parse string to a object * * Current character is \"{\", it means next serveral characters imply a * JSON object. According to mappings before, them should be parsed to a * Java object. * * @return */ private Map<String, Object> parseToObject() { skipWhitespace(); Map<String, Object> map = new HashMap<String, Object>(); while (json.hasNext()) { switch (json.next()) { case '}': return map; case '"': String key = parseToString(); if (skipWhitespace(':')) { Object value = parseValue(); map.put(key, value); } else { throwIllegalJSONStatementException(); } break; case ',': skipWhitespace(); break; default: throwIllegalJSONStatementException(); } } return map; } /** * Parse string to a array * * Current character is \"[\", it means next serveral characters imply a * JSON array. According to mappings before, them should be parsed to a Java * array. * * @return */ private Object[] parseToArray() { skipWhitespace(); ArrayList<Object> list = new ArrayList<Object>(); while (json.hasNext()) { switch (json.next()) { case ']': return list.toArray(); case ',': skipWhitespace(); break; default: json.back(); list.add(parseValue()); break; } } return null; } /** * Parse a JSON value * * Any element of JSON array, or a property value of a JSON object, it maybe * a JSON value. So according to mappings before, them should be parsed to a * Java array. * * @return */ private Object parseValue() { skipWhitespace(); while (json.hasNext()) { switch (json.next()) { case '"': return parseToString(); case '{': return parseToObject(); case '[': return parseToArray(); case 't': case 'T': json.next(); json.next(); json.next(); return true; case 'f': case 'F': json.next(); json.next(); json.next(); json.next(); return false; case 'n': case 'N': json.next(); json.next(); json.next(); return null; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return parseToNumber(); default: throwIllegalJSONStatementException(); } } return null; } /** * Parse string to a string * * Current character is \"\\"\", it means next serveral characters imply a * JSON string. According to mappings before, them should be parsed to a * Java string. * * @return */ private String parseToString() { skipWhitespace(); StringBuilder sb = new StringBuilder(); while (json.hasNext()) { switch (json.next()) { case '"': return sb.toString(); default: sb.append(json.current()); break; } } return sb.toString(); } /** * Parse string to a number * * In JSON value, if it starts with demical digits, it means next serveral * characters may reprent a number. According to mappings before, them * should be parsed to a Java long or double. * * @return */ private Object parseToNumber() { StringBuilder sb = new StringBuilder(); sb.append(json.current()); boolean isLong = true; while (json.hasNext()) { switch (json.next()) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': sb.append(json.current()); break; case '.': case 'e': case 'E': sb.append(json.current()); isLong = false; break; default: json.back(); if (isLong) { return Long.parseLong(sb.toString()); } else { return Double.parseDouble(sb.toString()); } } } return null; } /** * A wrapper class of JSON string. It supports some simple and usefull * methods, which are used in JSONParser class. */ class JSONString { private String str; private int index; public JSONString(String str) { this.str = str; this.index = -1; } /** * Get current index of JSON string * * @return */ public int getIndex() { return index; } /** * Get current character of JSON string * * @return */ public char current() { return index == -1 ? next() : str.charAt(index); } /** * Determine whether JSON string has next character or not * * @return */ public boolean hasNext() { return (index + 1) < str.length(); } /** * Get a character following the current one * * @return */ public char next() { return str.charAt(++index); } /** * Get the rest of JSON string from current character * * @return */ public String sub() { return str.substring(index); } /** * Move the index back by one */ private void back() { index--; } public String getStr() { return str; } public void setStr(String str) { this.str = str; } } }