/* * Copyright (c) 2008-2010 by Bjoern Kolbeck, Jan Stender, * Zuse Institute Berlin * * Licensed under the BSD License, see LICENSE file for details. * */ package de.mxro.thrd.xstreemfs.foundation.json; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; /** * JSON Parser routines. This parser accepts any value as top level element, not * just an object or array. * * @author bjko */ public class JSONParser { /** * Creates a new instance of JSONParser */ public JSONParser() { } private static String parseString(JSONInput input) throws JSONException { boolean nonEscaped = true; StringBuilder str = new StringBuilder(); while (input.hasMore()) { char ch = input.read(); if (nonEscaped) { if (ch == '\\') { nonEscaped = false; continue; } else if (ch == '"') { return str.toString(); } else { str.append(ch); } } else { if (ch == 'n') { str.append('\n'); } else if (ch == 'r') { str.append('\r'); } else if (ch == 't') { str.append('\t'); } else { str.append(ch); } } nonEscaped = true; } throw new JSONException("[ E | JSONParser ] Unexpected end while parsing string"); } private static Object parseNumber(JSONInput input) throws JSONException { StringBuilder str = new StringBuilder(); input.skip(-1); boolean isFP = false; while (input.hasMore()) { char ch = input.read(); if ((ch == '-') || (ch >= '0') && (ch <= '9')) { str.append(ch); } else if ((ch == '.') || (ch == 'E') || (ch == 'e')) { str.append(ch); isFP = true; } else { input.skip(-1); if (isFP) return new BigDecimal(str.toString()); else return Long.valueOf(str.toString()); } } if (isFP) return new BigDecimal(str.toString()); else return Long.valueOf(str.toString()); } private static Object parseArray(JSONInput input) throws JSONException { LinkedList<Object> arr = new LinkedList<Object>(); while (input.hasMore()) { char ch = input.read(); if (ch == ']') { return arr; } else if (ch == ',') { arr.add(parseJSON(input)); } else if ((ch == ' ') || (ch == '\t')) { continue; } else { input.skip(-1); arr.add(parseJSON(input)); } } throw new JSONException("[ E | JSONParser ] Unexpected end while parsing array"); } private static Object parseObject(JSONInput input) throws JSONException { HashMap<String, Object> map = new HashMap<String, Object>(); while (input.hasMore()) { char ch = input.read(); if (ch == '}') { return map; } // skip all ws if ((ch == ' ') || (ch == '\t')) { continue; } String name = parseString(input); ch = input.read(); while ((ch == ' ') || (ch == '\t')) { ch = input.read(); } if (ch != ':') { throw new JSONException("[ E | JSONParser ] Unexpected token '" + ((char) ch) + "' or EOF. Expected : in Object."); } while ((ch == ' ') || (ch == '\t')) { ch = input.read(); } Object value = parseJSON(input); map.put(name, value); ch = input.read(); while ((ch == ' ') || (ch == '\t')) { ch = input.read(); } if (ch == '}') { return map; } if (ch != ',') { throw new JSONException("[ E | JSONParser ] Unexpected token '" + ((char) ch) + "' or EOF. Expected , or } in Object."); } } throw new JSONException("[ E | JSONParser ] Unexpected end while parsing object"); } /** * Parses a JSON message. * * @return the objects encoded in input. * @attention This routine may cause a StackOverflow exception when parsing * incorrect, very deep or maliciously malformed JSON messages. * @param input * the JSON string * @throws org.xtreemos.wp34.mrc.utils.JSONException * if input is not valid JSON */ public static Object parseJSON(JSONInput input) throws JSONException { while (input.hasMore()) { char ch = input.read(); if (ch == '[') { return parseArray(input); } else if (ch == '{') { return parseObject(input); } else if (ch == '"') { return parseString(input); } else if ((ch == '-') || ((ch >= '0') && (ch <= '9'))) { return parseNumber(input); } else if (ch == 't') { input.skip(3); return Boolean.valueOf(true); } else if (ch == 'f') { input.skip(4); return Boolean.valueOf(false); } else if (ch == 'n') { input.skip(3); return null; } else if ((ch == ' ') || (ch == '\t')) { continue; } else { throw new JSONException("[ E | JSONParser ] Unexpected token '" + ((char) ch) + "' expected Object, Array or Value."); } } throw new JSONException("[ E | JSONParser ] Unexpected end while parsing root element"); } /** * Creates a JSON encoded message from an object. Can handle Boolean, * Integer, Long, BigDecimal, List and Map. * * @param input * object to encode, objects can be nested. * @return a JSON encoded message * @throws org.xtreemos.wp34.mrc.utils.JSONException * if there are one or more objects it cannot encode */ public static String writeJSON(Object input) throws JSONException { return writeJSON(input,new StringBuilder()).toString(); } /** * Creates a JSON encoded message from an object. Can handle Boolean, * Integer, Long, BigDecimal, List and Map. * * @param input * object to encode, objects can be nested. * @return a JSON encoded message * @throws org.xtreemos.wp34.mrc.utils.JSONException * if there are one or more objects it cannot encode */ @SuppressWarnings("unchecked") public static StringBuilder writeJSON(Object input, StringBuilder result) throws JSONException { if (input == null) { return result.append("null"); } else if (input instanceof Boolean) { return result.append(((Boolean) input).booleanValue() ? "true" : "false"); } else if (input instanceof BigDecimal) { return result.append(((BigDecimal) input).toString()); } else if (input instanceof Integer) { return result.append((Integer) input); } else if (input instanceof Long) { return result.append((Long) input); } else if (input instanceof String) { return writeJSONString(input,result); } else if (input instanceof Map) { return writeJSONObject(input,result); } else if (input instanceof Collection) { return writeJSONArray(input,result); } else { throw new JSONException( "[ E | JSONParser ] Unexpected Object type: " + input.getClass().getName()); } } @SuppressWarnings("unchecked") private static StringBuilder writeJSONObject(Object input, StringBuilder result) throws JSONException { Map<Object, Object> map = (Map) input; result.append("{"); int i = 1; for (Object key : map.keySet()) { writeJSONString(key.toString(),result); result.append(":"); writeJSON(map.get(key),result); if (i < map.size()) result.append(","); i++; } return result.append("}"); } @SuppressWarnings("unchecked") private static StringBuilder writeJSONArray(Object input, StringBuilder result) throws JSONException { Collection<Object> arr = (Collection<Object>) input; result.append("["); int i = 1; for (Object obj : arr) { writeJSON(obj,result); if (i < arr.size()) result.append(","); i++; } return result.append("]"); } private static StringBuilder writeJSONString(Object input, StringBuilder result) { /* * This is 10 times faster than using str.replace */ final String str = input.toString(); result.append("\""); for (int i = 0; i < str.length(); i++) { char ch = str.charAt(i); switch (ch) { case '\n' : result.append("\\n"); break; case '\r' : result.append("\\r"); break; case '\t' : result.append("\\t"); break; case '"' : result.append("\\\""); break; case '\\' : result.append("\\\\"); break; case '/' : result.append("\\/"); break; default: result.append(ch); } } result.append("\""); return result; } public static String toJSON(Object... args) throws JSONException { List<Object> argList = new ArrayList<Object>(args.length); for (Object arg : args) argList.add(arg); return writeJSON(argList); } }