package com.quantcomponents.yahoo; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; public abstract class JSON { static abstract class Token { String value; private Token(String value) { this.value = value; } String getValue() { return value; } public String toString() { return getValue(); } } static final class StringToken extends Token { StringToken(String value) { super(value); } } static final Token COLON = new Token(":") {}; static final Token OPEN_BRACKET = new Token("[") {}; static final Token CLOSED_BRACKET = new Token("]") {}; static final Token OPEN_BRACE = new Token("{") {}; static final Token CLOSED_BRACE = new Token("}") {}; static class Lexer { private Token lastToken; private StringBuilder buffer; private Reader input; Lexer(Reader input) { this.input = input; } Token next() throws IOException, JSONException { for (;;) { if (lastToken != null) { return returnAndResetLastToken(); } int c = input.read(); if (c == -1) { if (buffer != null) { return new StringToken(returnAndResetBuffer()); } else { return null; } } char ch = (char) c; switch (ch) { case ' ': case '\t': case '\n': case '\r': case '\f': case ',': if (buffer != null) { return new StringToken(returnAndResetBuffer()); } break; // ignore blanks case ':': return lastTokenOrBuffer(COLON); case '[': return lastTokenOrBuffer(OPEN_BRACKET); case ']': return lastTokenOrBuffer(CLOSED_BRACKET); case '{': return lastTokenOrBuffer(OPEN_BRACE); case '}': return lastTokenOrBuffer(CLOSED_BRACE); case '"':return new StringToken(parseQuote('"', input)); case '\'': return new StringToken(parseQuote('\'', input)); default: if (buffer == null) { buffer = new StringBuilder(); } buffer.append(ch); } } } private static String parseQuote(char quote, Reader is) throws IOException, JSONException { StringBuilder b = new StringBuilder(); for (;;) { int c = is.read(); if (c < 0) { throw new JSONException("Unexpected EOF: expecting closing quote"); } char ch = (char) c; if (ch == quote) { return b.toString(); } else { b.append(ch); } } } private Token lastTokenOrBuffer(Token last) { if (buffer != null) { lastToken = last; return new StringToken(returnAndResetBuffer()); } else { return last; } } private String returnAndResetBuffer() { String value = buffer.toString(); buffer = null; return value; } private Token returnAndResetLastToken() { Token value = lastToken; lastToken = null; return value; } } public static JSON parse(Reader input) throws IOException, JSONException { Lexer lexer = new Lexer(input); Token token = lexer.next(); if (token == OPEN_BRACKET) { return JSONArray.parse(lexer); } else if (token == OPEN_BRACE) { return JSONMap.parse(lexer); } else if (token instanceof StringToken){ return new JSONValue(((StringToken) token).getValue()); } else { throw new JSONException("Unexpected token: '" + token + "' at beginning of JSON"); } } static JSON parse(Lexer lexer) throws IOException, JSONException { return parse(lexer.next(), lexer); } static JSON parse(Token startToken, Lexer lexer) throws IOException, JSONException { for (;;) { if (startToken == OPEN_BRACKET) { return JSONArray.parse(lexer); } else if (startToken == OPEN_BRACE) { return JSONMap.parse(lexer); } else if (startToken instanceof StringToken){ return new JSONValue(((StringToken) startToken).getValue()); } } } public abstract JSON get(Object o); public abstract Object getValue(); static class JSONArray extends JSON { private final List<JSON> array; JSONArray(List<JSON> array) { this.array = new ArrayList<JSON>(array); } public List<JSON> getValue() { return Collections.unmodifiableList(array); } public JSON get(Object o) { Integer index = (Integer) o; return getValue().get(index); } public static JSON parse(Lexer lexer) throws IOException, JSONException { JSONArray json = new JSONArray(new ArrayList<JSON>()); for (;;) { Token token = lexer.next(); if (token instanceof StringToken) { json.array.add(new JSONValue(((StringToken) token).getValue())); } else if (token == CLOSED_BRACKET) { break; } else { json.array.add(JSON.parse(token, lexer)); } } return json; } } static class JSONMap extends JSON { private final Map<String, JSON> map; JSONMap(Map<String, JSON> map) { this.map = new HashMap<String, JSON>(map); } public Map<String, JSON> getValue(){ return Collections.unmodifiableMap(map); } public JSON get(Object o) { String key = (String) o; return getValue().get(key); } public static JSON parse(Lexer lexer) throws IOException, JSONException { JSONMap json = new JSONMap(new HashMap<String, JSON>()); String key = null; JSON value; boolean expectingKey = true; for (;;) { Token token = lexer.next(); if (token instanceof StringToken && expectingKey) { key = token.getValue(); } else if (token == COLON) { expectingKey = false; } else if (token == CLOSED_BRACE) { break; } else { value = JSON.parse(token, lexer); json.map.put(key, value); expectingKey = true; } } return json; } } static class JSONValue extends JSON { private final String value; JSONValue(String value) { this.value = value; } public String getValue(){ return value; } public JSON get(Object o) { return this; } } }