| /******************************************************************************* |
| * Copyright (c) 2009 IBM Corporation and others All rights reserved. This |
| * program and the accompanying materials are made available under the terms of |
| * the Eclipse Public License v1.0 which accompanies this distribution, and is |
| * available at http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.e4.internal.javascript; |
| |
| import java.math.BigDecimal; |
| import java.text.CharacterIterator; |
| import java.text.StringCharacterIterator; |
| import java.util.*; |
| |
| // JSON <--> Java |
| //------------------ |
| // string <--> java.lang.String |
| // number <--> java.math.Number (BigDecimal) |
| // object <--> java.util.Map (HashMap) |
| // array <--> java.util.Collection (ArrayList) |
| // true <--> java.lang.Boolean.TRUE |
| // false <--> java.lang.Boolean.FALSE |
| // null <--> null |
| |
| public class JSONUtil { |
| private static final String EMPTY_STRING = ""; //$NON-NLS-1$ |
| private static final String NULL = "null"; //$NON-NLS-1$ |
| |
| public static Object read(String jsonString) { |
| return parse(new StringCharacterIterator(jsonString)); |
| } |
| |
| public static String write(Object jsonObject) { |
| StringBuffer buffer = new StringBuffer(); |
| writeValue(jsonObject, buffer); |
| return buffer.toString(); |
| } |
| |
| private static RuntimeException error(String message, CharacterIterator it) { |
| return new IllegalStateException("[" + it.getIndex() + "] " + message); //$NON-NLS-1$//$NON-NLS-2$ |
| } |
| |
| private static RuntimeException error(String message) { |
| return new IllegalStateException(message); |
| } |
| |
| private static Object parse(CharacterIterator it) { |
| parseWhitespace(it); |
| Object result = parseValue(it); |
| parseWhitespace(it); |
| |
| if (it.current() != CharacterIterator.DONE) |
| throw error("should be done", it); //$NON-NLS-1$ |
| return result; |
| } |
| |
| private static void parseWhitespace(CharacterIterator it) { |
| char c = it.current(); |
| while (Character.isWhitespace(c)) |
| c = it.next(); |
| } |
| |
| private static Object parseValue(CharacterIterator it) { |
| switch (it.current()) { |
| case '{' : |
| return parseObject(it); |
| case '[' : |
| return parseArray(it); |
| case '"' : |
| return parseString(it); |
| case '-' : |
| case '0' : |
| case '1' : |
| case '2' : |
| case '3' : |
| case '4' : |
| case '5' : |
| case '6' : |
| case '7' : |
| case '8' : |
| case '9' : |
| return parseNumber(it); |
| case 't' : |
| parseText(Boolean.TRUE.toString(), it); |
| return Boolean.TRUE; |
| case 'f' : |
| parseText(Boolean.FALSE.toString(), it); |
| return Boolean.FALSE; |
| case 'n' : |
| parseText(NULL, it); |
| return null; |
| } |
| throw error("Bad JSON starting character '" + it.current() + "'", it); //$NON-NLS-1$ //$NON-NLS-2$; |
| } |
| |
| private static Map parseObject(CharacterIterator it) { |
| it.next(); |
| parseWhitespace(it); |
| if (it.current() == '}') { |
| it.next(); |
| return Collections.EMPTY_MAP; |
| } |
| |
| Map map = new HashMap(); |
| while (true) { |
| if (it.current() != '"') |
| throw error("expected a string start '\"' but was '" + it.current() + "'", it); //$NON-NLS-1$ //$NON-NLS-2$ |
| String key = parseString(it); |
| if (map.containsKey(key)) |
| throw error("' already defined" + "key '" + key, it); //$NON-NLS-1$ //$NON-NLS-2$ |
| parseWhitespace(it); |
| if (it.current() != ':') |
| throw error("expected a pair separator ':' but was '" + it.current() + "'", it); //$NON-NLS-1$ //$NON-NLS-2$ |
| it.next(); |
| parseWhitespace(it); |
| Object value = parseValue(it); |
| map.put(key, value); |
| parseWhitespace(it); |
| if (it.current() == ',') { |
| it.next(); |
| parseWhitespace(it); |
| continue; |
| } |
| |
| if (it.current() != '}') |
| throw error("expected an object close '}' but was '" + it.current() + "'", it); //$NON-NLS-1$ //$NON-NLS-2$ |
| break; |
| } |
| it.next(); |
| return map; |
| } |
| |
| private static List parseArray(CharacterIterator it) { |
| it.next(); |
| parseWhitespace(it); |
| if (it.current() == ']') { |
| it.next(); |
| return Collections.EMPTY_LIST; |
| } |
| |
| List list = new ArrayList(); |
| while (true) { |
| Object value = parseValue(it); |
| list.add(value); |
| parseWhitespace(it); |
| if (it.current() == ',') { |
| it.next(); |
| parseWhitespace(it); |
| continue; |
| } |
| |
| if (it.current() != ']') |
| throw error("expected an array close ']' but was '" + it.current() + "'", it); //$NON-NLS-1$ //$NON-NLS-2$ |
| break; |
| } |
| it.next(); |
| return list; |
| } |
| |
| private static void parseText(String string, CharacterIterator it) { |
| int length = string.length(); |
| char c = it.current(); |
| for (int i = 0; i < length; i++) { |
| if (c != string.charAt(i)) |
| throw error("expected to parse '" + string + "' but character " + (i + 1) + " was '" + c + "'", it); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$; |
| c = it.next(); |
| } |
| } |
| |
| private static Object parseNumber(CharacterIterator it) { |
| StringBuffer buffer = new StringBuffer(); |
| char c = it.current(); |
| while (Character.isDigit(c) || c == '-' || c == '+' || c == '.' || c == 'e' || c == 'E') { |
| buffer.append(c); |
| c = it.next(); |
| } |
| try { |
| return new BigDecimal(buffer.toString()); |
| } catch (NumberFormatException e) { |
| throw error("expected a number but was '" + buffer.toString() + "'", it); //$NON-NLS-1$ //$NON-NLS-2$; |
| } |
| } |
| |
| private static String parseString(CharacterIterator it) { |
| char c = it.next(); |
| if (c == '"') { |
| it.next(); |
| return EMPTY_STRING; |
| } |
| StringBuffer buffer = new StringBuffer(); |
| while (c != '"') { |
| if (Character.isISOControl(c)) |
| throw error("illegal iso control character: '" + Integer.toHexString(c) + "'", it); //$NON-NLS-1$ //$NON-NLS-2$); |
| |
| if (c == '\\') { |
| c = it.next(); |
| switch (c) { |
| case '"' : |
| case '\\' : |
| case '/' : |
| buffer.append(c); |
| break; |
| case 'b' : |
| buffer.append('\b'); |
| break; |
| case 'f' : |
| buffer.append('\f'); |
| break; |
| case 'n' : |
| buffer.append('\n'); |
| break; |
| case 'r' : |
| buffer.append('\r'); |
| break; |
| case 't' : |
| buffer.append('\t'); |
| break; |
| case 'u' : |
| StringBuffer unicode = new StringBuffer(4); |
| for (int i = 0; i < 4; i++) { |
| unicode.append(it.next()); |
| } |
| try { |
| buffer.append((char) Integer.parseInt(unicode.toString(), 16)); |
| } catch (NumberFormatException e) { |
| throw error("expected a unicode hex number but was '" + unicode.toString() + "'", it); //$NON-NLS-1$ //$NON-NLS-2$);); |
| } |
| break; |
| default : |
| throw error("illegal escape character '" + c + "'", it); //$NON-NLS-1$ //$NON-NLS-2$);); |
| } |
| } else |
| buffer.append(c); |
| |
| c = it.next(); |
| } |
| c = it.next(); |
| return buffer.toString(); |
| } |
| |
| private static void writeValue(Object value, StringBuffer buffer) { |
| if (value == null) |
| buffer.append(NULL); |
| else if (value instanceof Boolean || value instanceof Number) |
| buffer.append(value.toString()); |
| else if (value instanceof String) |
| writeString((String) value, buffer); |
| else if (value instanceof Collection) |
| writeArray((Collection) value, buffer); |
| else if (value instanceof Map) |
| writeObject((Map) value, buffer); |
| else |
| throw error("Unexpected object instance type was '" + value.getClass().getName() + "'"); //$NON-NLS-1$ //$NON-NLS-2$);); |
| } |
| |
| private static void writeObject(Map map, StringBuffer buffer) { |
| buffer.append('{'); |
| for (Iterator iterator = map.keySet().iterator(); iterator.hasNext();) { |
| Object key = iterator.next(); |
| if (!(key instanceof String)) |
| throw error("Map keys must be an instance of String but was '" + key.getClass().getName() + "'"); //$NON-NLS-1$ //$NON-NLS-2$);); |
| writeString((String) key, buffer); |
| buffer.append(':'); |
| writeValue(map.get(key), buffer); |
| buffer.append(','); |
| } |
| if (buffer.charAt(buffer.length() - 1) == ',') |
| buffer.setCharAt(buffer.length() - 1, '}'); |
| else |
| buffer.append('}'); |
| } |
| |
| private static void writeArray(Collection collection, StringBuffer buffer) { |
| buffer.append('['); |
| for (Iterator iterator = collection.iterator(); iterator.hasNext();) { |
| writeValue(iterator.next(), buffer); |
| buffer.append(','); |
| } |
| if (buffer.charAt(buffer.length() - 1) == ',') |
| buffer.setCharAt(buffer.length() - 1, ']'); |
| else |
| buffer.append(']'); |
| } |
| |
| private static void writeString(String string, StringBuffer buffer) { |
| buffer.append('"'); |
| int length = string.length(); |
| for (int i = 0; i < length; i++) { |
| char c = string.charAt(i); |
| switch (c) { |
| case '"' : |
| case '\\' : |
| case '/' : |
| buffer.append('\\'); |
| buffer.append(c); |
| break; |
| case '\b' : |
| buffer.append("\\b"); //$NON-NLS-1$ |
| break; |
| case '\f' : |
| buffer.append("\\f"); //$NON-NLS-1$ |
| break; |
| case '\n' : |
| buffer.append("\\n"); //$NON-NLS-1$ |
| break; |
| case '\r' : |
| buffer.append("\\r"); //$NON-NLS-1$ |
| break; |
| case '\t' : |
| buffer.append("\\t"); //$NON-NLS-1$ |
| break; |
| default : |
| if (Character.isISOControl(c)) { |
| buffer.append("\\u"); //$NON-NLS-1$ |
| String hexString = Integer.toHexString(c); |
| for (int j = hexString.length(); j < 4; j++) |
| buffer.append('0'); |
| buffer.append(hexString); |
| } else |
| buffer.append(c); |
| } |
| } |
| buffer.append('"'); |
| } |
| } |