sascha@381: package de.intevation.artifacts.common.utils; sascha@381: sascha@381: import java.util.Map; sascha@381: import java.util.LinkedHashMap; sascha@381: import java.util.List; sascha@381: import java.util.ArrayList; sascha@381: import java.util.Iterator; sascha@381: sascha@381: import java.io.IOException; sascha@381: import java.io.PushbackInputStream; sascha@381: import java.io.InputStream; sascha@381: import java.io.PrintWriter; sascha@381: import java.io.ByteArrayInputStream; sascha@384: import java.io.StringWriter; sascha@381: sascha@381: import java.nio.charset.Charset; sascha@381: import java.nio.charset.UnsupportedCharsetException; sascha@381: sascha@381: public final class JSON sascha@381: { sascha@381: private JSON() { sascha@381: } sascha@381: sascha@381: private static final boolean isDigit(int c) { sascha@381: return c >= '0' && c <= '9'; sascha@381: } sascha@381: sascha@381: public static final boolean isWhitespace(int c) { sascha@394: return c == ' ' || c == '\n' || c == '\r' sascha@381: || c == '\t' || c == '\f'; sascha@381: } sascha@381: sascha@381: private static final void match(int c, int x) throws IOException { sascha@381: if (c != x) { sascha@381: throw new IOException( sascha@381: "Expecting '" + (char)c + "' found '" + (char)x + "'"); sascha@381: } sascha@381: } sascha@381: sascha@381: private static final int eof(InputStream in) sascha@381: throws IOException sascha@381: { sascha@381: int c = in.read(); sascha@381: if (c == -1) { sascha@381: throw new IOException("EOF unexpected."); sascha@381: } sascha@381: return c; sascha@381: } sascha@381: sascha@381: private static final int whitespace(InputStream in) sascha@381: throws IOException sascha@381: { sascha@381: int c; sascha@381: while (isWhitespace(c = eof(in))); sascha@381: return c; sascha@381: } sascha@381: sascha@381: private static final int parseHex(String hex) throws IOException { sascha@381: try { sascha@381: return Integer.parseInt(hex, 16); sascha@381: } sascha@381: catch (NumberFormatException nfe) { sascha@381: throw new IOException("'" + hex + "' is not a hex string."); sascha@381: } sascha@381: } sascha@381: sascha@381: public static final String jsonString(String string) { sascha@381: StringBuilder sb = new StringBuilder(string.length()+2); sascha@381: sascha@381: sb.append('"'); sascha@381: sascha@381: for (int i = 0, N = string.length(); i < N; ++i) { sascha@381: char c = string.charAt(i); sascha@381: switch (c) { sascha@381: case '"': sb.append("\\\""); break; sascha@381: case '\t': sb.append("\\t"); break; sascha@381: case '\r': sb.append("\\r"); break; sascha@381: case '\n': sb.append("\\n"); break; sascha@381: case '\b': sb.append("\\b"); break; sascha@381: case '\f': sb.append("\\f"); break; sascha@381: default: sascha@381: if (c >= 128) { sascha@381: sb.append("\\u"); sascha@381: String hex = Integer.toHexString((int)c); sascha@381: for (int j = 4-hex.length(); j > 0; --j) { sascha@381: sb.append('0'); sascha@381: } sascha@381: sb.append(hex); sascha@381: } sascha@381: else { sascha@381: sb.append(c); sascha@381: } sascha@381: } sascha@381: } sascha@381: sascha@381: sb.append('"'); sascha@381: sascha@381: return sb.toString(); sascha@381: } sascha@381: sascha@384: public static String toJSONString(Map map) { sascha@384: StringWriter sw = new StringWriter(); sascha@384: PrintWriter pw = new PrintWriter(sw); sascha@384: write(pw, map); sascha@384: pw.flush(); sascha@384: return sw.toString(); sascha@384: } sascha@384: sascha@381: sascha@381: public static void write(PrintWriter out, Map map) { sascha@381: writeObject(out, map); sascha@381: } sascha@381: sascha@381: private static void writeValue(PrintWriter out, Object value) { sascha@381: if (value instanceof Map) { sascha@381: writeObject(out, (Map)value); sascha@381: } sascha@381: else if (value instanceof List) { sascha@381: writeList(out, (List)value); sascha@381: } sascha@381: else if (value instanceof Number) { sascha@381: out.print(value); sascha@381: } sascha@381: else if (value instanceof Boolean) { sascha@381: out.print(((Boolean)value) ? "true" : "false"); sascha@381: } sascha@381: else if (value == null) { sascha@381: out.print("null"); sascha@381: } sascha@381: else { sascha@381: out.print(jsonString(value.toString())); sascha@381: } sascha@381: } sascha@381: sascha@381: private static void writeObject(PrintWriter out, Map map) { sascha@381: sascha@381: out.print('{'); sascha@381: Iterator iter = map.entrySet().iterator(); sascha@381: while (iter.hasNext()) { sascha@381: Map.Entry entry = (Map.Entry)iter.next(); sascha@381: out.print(jsonString(entry.getKey().toString())); sascha@381: out.print(':'); sascha@381: writeValue(out, entry.getValue()); sascha@381: if (iter.hasNext()) { sascha@381: out.print(','); sascha@381: } sascha@381: } sascha@381: out.print('}'); sascha@381: } sascha@381: sascha@381: private static void writeList(PrintWriter out, List list) { sascha@381: out.print('['); sascha@381: Iterator iter = list.iterator(); sascha@381: while (iter.hasNext()) { sascha@381: writeValue(out, iter.next()); sascha@381: if (iter.hasNext()) { sascha@381: out.print(','); sascha@381: } sascha@381: } sascha@381: out.print(']'); sascha@381: } sascha@381: sascha@394: public static Map parse(String in) sascha@394: throws IOException sascha@381: { sascha@381: return parse(asInputStream(in)); sascha@381: } sascha@381: sascha@381: private static InputStream asInputStream(String in) { sascha@381: byte [] bytes; sascha@381: try { sascha@381: bytes = in.getBytes(Charset.forName("US-ASCII")); sascha@381: } sascha@381: catch (UnsupportedCharsetException uce) { sascha@381: // Should not happen. sascha@381: bytes = in.getBytes(); sascha@381: } sascha@381: return new ByteArrayInputStream(bytes); sascha@381: } sascha@381: sascha@394: public static Map parse(InputStream in) sascha@394: throws IOException sascha@381: { sascha@381: return parseObject(new PushbackInputStream(in, 1)); sascha@381: } sascha@381: sascha@394: public static Map parse(PushbackInputStream in) sascha@394: throws IOException sascha@381: { sascha@381: return parseObject(in); sascha@381: } sascha@381: sascha@381: private static final String parseString( sascha@381: PushbackInputStream in sascha@381: ) sascha@381: throws IOException sascha@381: { sascha@381: StringBuilder sb = new StringBuilder(); sascha@381: sascha@381: int mode = 0; sascha@381: sascha@381: char [] hex = new char[4]; sascha@381: sascha@381: match('"', eof(in)); sascha@381: sascha@381: OUT: for (int c = eof(in);; c = eof(in)) { sascha@381: sascha@381: switch (mode) { sascha@381: case 0: sascha@381: if (c == '"') { sascha@381: break OUT; sascha@381: } sascha@381: if (c == '\\') { sascha@381: mode = 1; sascha@381: } sascha@381: else { sascha@381: sb.append((char)c); sascha@381: } sascha@381: break; sascha@381: case 1: sascha@381: switch (c) { sascha@381: case 'u': sascha@381: mode = 2; sascha@381: continue; sascha@381: case 'b': sascha@381: sb.append('\b'); sascha@381: break; sascha@381: case 'f': sascha@381: sb.append('\f'); sascha@381: break; sascha@381: case 'n': sascha@381: sb.append('\n'); sascha@381: break; sascha@381: case 'r': sascha@381: sb.append('\r'); sascha@381: break; sascha@381: case 't': sascha@381: sb.append('\t'); sascha@381: break; sascha@381: default: sascha@381: sb.append((char)c); sascha@381: } sascha@381: mode = 0; sascha@381: break; sascha@381: case 2: sascha@381: hex[0] = (char)c; sascha@381: mode = 3; sascha@381: break; sascha@381: case 3: sascha@381: hex[1] = (char)c; sascha@381: mode = 4; sascha@381: break; sascha@381: case 4: sascha@381: hex[2] = (char)c; sascha@381: mode = 5; sascha@381: break; sascha@381: case 5: sascha@381: hex[3] = (char)c; sascha@381: sb.append((char)parseHex(new String(hex))); sascha@381: mode = 0; sascha@381: break; sascha@381: } sascha@381: } sascha@381: return sb.toString(); sascha@381: } sascha@381: sascha@381: private static final Boolean parseTrue(InputStream in) sascha@381: throws IOException sascha@394: { sascha@381: match('t', eof(in)); sascha@381: match('r', eof(in)); sascha@381: match('u', eof(in)); sascha@381: match('e', eof(in)); sascha@381: return Boolean.TRUE; sascha@381: } sascha@381: sascha@381: private static final Boolean parseFalse(InputStream in) sascha@381: throws IOException sascha@394: { sascha@381: match('f', eof(in)); sascha@381: match('a', eof(in)); sascha@381: match('l', eof(in)); sascha@381: match('s', eof(in)); sascha@381: match('e', eof(in)); sascha@381: return Boolean.FALSE; sascha@381: } sascha@381: sascha@381: private static final Object parseNull(InputStream in) sascha@381: throws IOException sascha@394: { sascha@381: match('n', eof(in)); sascha@381: match('u', eof(in)); sascha@381: match('l', eof(in)); sascha@381: match('l', eof(in)); sascha@381: return null; sascha@381: } sascha@381: sascha@381: private static final Number parseNumber(PushbackInputStream in) sascha@381: throws IOException sascha@381: { sascha@381: StringBuilder sb = new StringBuilder(); sascha@381: sascha@381: boolean isInteger = true; sascha@381: sascha@381: int c; sascha@381: OUT: for (;;) { sascha@381: switch (c = eof(in)) { sascha@381: case '0': case '1': case '2': case '3': case '4': sascha@381: case '5': case '6': case '7': case '8': case '9': sascha@381: case '-': case '+': sascha@381: sb.append((char)c); sascha@381: break; sascha@381: case '.': case 'e': case 'E': sascha@381: isInteger = false; sascha@381: sb.append((char)c); sascha@381: break; sascha@381: default: sascha@381: in.unread(c); sascha@381: break OUT; sascha@381: } sascha@381: } sascha@381: sascha@381: try { sascha@381: if (isInteger) { sascha@381: return sb.length() > 9 sascha@381: ? (Number)Long .valueOf(sb.toString()) sascha@381: : (Number)Integer.valueOf(sb.toString()); sascha@381: } sascha@381: return (Number)Double.valueOf(sb.toString()); sascha@381: } sascha@381: catch (NumberFormatException nfe) { sascha@381: throw new IOException("Not a number '" + sb + "'"); sascha@381: } sascha@381: } sascha@381: sascha@381: private static List parseList(PushbackInputStream in) sascha@381: throws IOException sascha@381: { sascha@381: List list = new ArrayList(); sascha@381: match('[', whitespace(in)); sascha@381: int c = whitespace(in); sascha@381: if (c == ']') { sascha@381: return list; sascha@381: } sascha@381: sascha@381: for (;; c = whitespace(in)) { sascha@381: Object value; sascha@381: in.unread(c); sascha@381: switch (c) { sascha@381: case '{': sascha@381: value = parseObject(in); sascha@381: break; sascha@381: case '[': sascha@381: value = parseList(in); sascha@381: break; sascha@381: case '"': sascha@381: value = parseString(in); sascha@381: break; sascha@381: case 't': sascha@381: value = parseTrue(in); sascha@381: break; sascha@381: case 'f': sascha@381: value = parseFalse(in); sascha@381: break; sascha@381: case 'n': sascha@381: value = parseNull(in); sascha@381: break; sascha@381: default: sascha@381: value = parseNumber(in); sascha@381: } sascha@381: list.add(value); sascha@381: sascha@381: if ((c = whitespace(in)) == ']') break; sascha@381: match(',', c); sascha@381: } sascha@381: return list; sascha@381: } sascha@381: sascha@381: private static void parsePair( sascha@381: PushbackInputStream in, sascha@381: Map pairs sascha@381: ) sascha@381: throws IOException sascha@381: { sascha@381: in.unread(whitespace(in)); sascha@381: String string = parseString(in); sascha@381: match(':', whitespace(in)); sascha@381: sascha@381: Object value; sascha@381: sascha@381: int c = whitespace(in); sascha@381: in.unread(c); sascha@381: switch (c) { sascha@381: case '{': sascha@381: value = parseObject(in); sascha@381: break; sascha@381: case '[': sascha@381: value = parseList(in); sascha@381: break; sascha@381: case '"': sascha@381: value = parseString(in); sascha@381: break; sascha@381: case 't': sascha@381: value = parseTrue(in); sascha@381: break; sascha@381: case 'f': sascha@381: value = parseFalse(in); sascha@381: break; sascha@381: case 'n': sascha@381: value = parseNull(in); sascha@381: break; sascha@381: default: sascha@381: value = parseNumber(in); sascha@381: } sascha@381: pairs.put(string, value); sascha@381: } sascha@381: sascha@394: private static Map parseObject(PushbackInputStream in) sascha@381: throws IOException sascha@381: { sascha@381: Map pairs = new LinkedHashMap(); sascha@381: sascha@381: int c = whitespace(in); sascha@381: match('{', c); sascha@381: sascha@381: if ((c = whitespace(in)) == '}') { sascha@381: return pairs; sascha@381: } sascha@381: sascha@381: in.unread(c); sascha@381: sascha@381: for (;;) { sascha@381: parsePair(in, pairs); sascha@381: sascha@381: if ((c = whitespace(in)) == '}') { sascha@381: break; sascha@381: } sascha@381: sascha@381: if (c == '}') break; sascha@381: match(',', c); sascha@381: } sascha@381: sascha@381: return pairs; sascha@381: } sascha@381: }