| /* |
| * 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.struts2.json; |
| |
| import java.text.CharacterIterator; |
| import java.text.StringCharacterIterator; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * <p> |
| * Deserializes and object from a JSON string |
| * </p> |
| */ |
| public class JSONReader { |
| private static final Object OBJECT_END = new Object(); |
| private static final Object ARRAY_END = new Object(); |
| private static final Object COLON = new Object(); |
| private static final Object COMMA = new Object(); |
| private static Map<Character, Character> escapes = new HashMap<Character, Character>(); |
| |
| static { |
| escapes.put('"', '"'); |
| escapes.put('\\', '\\'); |
| escapes.put('/', '/'); |
| escapes.put('b', '\b'); |
| escapes.put('f', '\f'); |
| escapes.put('n', '\n'); |
| escapes.put('r', '\r'); |
| escapes.put('t', '\t'); |
| } |
| |
| private CharacterIterator it; |
| private char c; |
| private Object token; |
| private StringBuilder buf = new StringBuilder(); |
| |
| protected char next() { |
| this.c = this.it.next(); |
| |
| return this.c; |
| } |
| |
| protected void skipWhiteSpace() { |
| while (Character.isWhitespace(this.c)) { |
| this.next(); |
| } |
| } |
| |
| public Object read(String string) throws JSONException { |
| this.it = new StringCharacterIterator(string); |
| this.c = this.it.first(); |
| |
| return this.read(); |
| } |
| |
| protected Object read() throws JSONException { |
| Object ret; |
| |
| this.skipWhiteSpace(); |
| |
| if (this.c == '"') { |
| this.next(); |
| ret = this.string('"'); |
| } else if (this.c == '\'') { |
| this.next(); |
| ret = this.string('\''); |
| } else if (this.c == '[') { |
| this.next(); |
| ret = this.array(); |
| } else if (this.c == ']') { |
| ret = ARRAY_END; |
| this.next(); |
| } else if (this.c == ',') { |
| ret = COMMA; |
| this.next(); |
| } else if (this.c == '{') { |
| this.next(); |
| ret = this.object(); |
| } else if (this.c == '}') { |
| ret = OBJECT_END; |
| this.next(); |
| } else if (this.c == ':') { |
| ret = COLON; |
| this.next(); |
| } else if ((this.c == 't') && (this.next() == 'r') && (this.next() == 'u') && (this.next() == 'e')) { |
| ret = Boolean.TRUE; |
| this.next(); |
| } else if ((this.c == 'f') && (this.next() == 'a') && (this.next() == 'l') && (this.next() == 's') |
| && (this.next() == 'e')) { |
| ret = Boolean.FALSE; |
| this.next(); |
| } else if ((this.c == 'n') && (this.next() == 'u') && (this.next() == 'l') && (this.next() == 'l')) { |
| ret = null; |
| this.next(); |
| } else if (Character.isDigit(this.c) || (this.c == '-')) { |
| ret = this.number(); |
| } else { |
| throw buildInvalidInputException(); |
| } |
| |
| this.token = ret; |
| |
| return ret; |
| } |
| |
| @SuppressWarnings("unchecked") |
| protected Map object() throws JSONException { |
| Map ret = new HashMap(); |
| Object next = this.read(); |
| if (next != OBJECT_END) { |
| String key = (String) next; |
| while (this.token != OBJECT_END) { |
| this.read(); // should be a colon |
| |
| if (this.token != OBJECT_END) { |
| ret.put(key, this.read()); |
| |
| if (this.read() == COMMA) { |
| Object name = this.read(); |
| |
| if (name instanceof String) { |
| key = (String) name; |
| } else |
| throw buildInvalidInputException(); |
| } |
| } |
| } |
| } |
| |
| return ret; |
| } |
| |
| protected JSONException buildInvalidInputException() { |
| return new JSONException("Input string is not well formed JSON (invalid char " + this.c + ")"); |
| } |
| |
| |
| @SuppressWarnings("unchecked") |
| protected List array() throws JSONException { |
| List ret = new ArrayList(); |
| Object value = this.read(); |
| |
| while (this.token != ARRAY_END) { |
| ret.add(value); |
| |
| Object read = this.read(); |
| if (read == COMMA) { |
| value = this.read(); |
| } else if (read != ARRAY_END) { |
| throw buildInvalidInputException(); |
| } |
| } |
| |
| return ret; |
| } |
| |
| protected Object number() throws JSONException { |
| this.buf.setLength(0); |
| boolean toDouble = false; |
| |
| if (this.c == '-') { |
| this.add(); |
| } |
| |
| this.addDigits(); |
| |
| if (this.c == '.') { |
| toDouble = true; |
| this.add(); |
| this.addDigits(); |
| } |
| |
| if ((this.c == 'e') || (this.c == 'E')) { |
| toDouble = true; |
| this.add(); |
| |
| if ((this.c == '+') || (this.c == '-')) { |
| this.add(); |
| } |
| |
| this.addDigits(); |
| } |
| |
| if (toDouble) { |
| try { |
| return Double.parseDouble(this.buf.toString()); |
| } catch (NumberFormatException e) { |
| throw buildInvalidInputException(); |
| } |
| } else { |
| try { |
| return Long.parseLong(this.buf.toString()); |
| } catch (NumberFormatException e) { |
| throw buildInvalidInputException(); |
| } |
| } |
| } |
| |
| protected Object string(char quote) { |
| this.buf.setLength(0); |
| |
| while ((this.c != quote) && (this.c != CharacterIterator.DONE)) { |
| if (this.c == '\\') { |
| this.next(); |
| |
| if (this.c == 'u') { |
| this.add(this.unicode()); |
| } else { |
| Object value = escapes.get(this.c); |
| |
| if (value != null) { |
| this.add((Character) value); |
| } |
| } |
| } else { |
| this.add(); |
| } |
| } |
| |
| this.next(); |
| |
| return this.buf.toString(); |
| } |
| |
| protected void add(char cc) { |
| this.buf.append(cc); |
| this.next(); |
| } |
| |
| protected void add() { |
| this.add(this.c); |
| } |
| |
| protected void addDigits() { |
| while (Character.isDigit(this.c)) { |
| this.add(); |
| } |
| } |
| |
| protected char unicode() { |
| int value = 0; |
| |
| for (int i = 0; i < 4; ++i) { |
| switch (this.next()) { |
| case '0': |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| case '8': |
| case '9': |
| value = (value << 4) + (this.c - '0'); |
| |
| break; |
| |
| case 'a': |
| case 'b': |
| case 'c': |
| case 'd': |
| case 'e': |
| case 'f': |
| value = (value << 4) + (this.c - 'W'); |
| |
| break; |
| |
| case 'A': |
| case 'B': |
| case 'C': |
| case 'D': |
| case 'E': |
| case 'F': |
| value = (value << 4) + (this.c - '7'); |
| |
| break; |
| } |
| } |
| |
| return (char) value; |
| } |
| } |