# HG changeset patch # User Sascha L. Teichmann # Date 1334769010 0 # Node ID bed51de8ae589144595c6012ac5396dd35e4b4a4 # Parent 9798e4d83681621b2e6951d93d2e6d86d0466462 Added JSON parser from Artefact Server NG to bridge from JSON to XML speaking services. artifacts/trunk@4270 c6561f87-3c4e-4783-a992-168aeb5c3f6f diff -r 9798e4d83681 -r bed51de8ae58 ChangeLog --- a/ChangeLog Tue Apr 03 11:15:10 2012 +0000 +++ b/ChangeLog Wed Apr 18 17:10:10 2012 +0000 @@ -1,3 +1,13 @@ +2012-04-18 Sascha L. Teichmann + + * artifacts-common/src/main/java/de/intevation/artifacts/common/utils/JSON.java: + New. JSON parser from Artefact Server NG (which relies on JSON more heavily). + + * artifacts-common/src/main/java/de/intevation/artifacts/common/utils/XMLUtils.java: + Added method to convert JSON to XML. This is needed by the client which + internally uses JSON to talk to the server which services only understands + XML atm. + 2012-04-04 Sascha L. Teichmann Modified services so that they are now able to return more than just diff -r 9798e4d83681 -r bed51de8ae58 artifacts-common/src/main/java/de/intevation/artifacts/common/utils/JSON.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/artifacts-common/src/main/java/de/intevation/artifacts/common/utils/JSON.java Wed Apr 18 17:10:10 2012 +0000 @@ -0,0 +1,436 @@ +package de.intevation.artifacts.common.utils; + +import java.util.Map; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.ArrayList; +import java.util.Iterator; + +import java.io.IOException; +import java.io.PushbackInputStream; +import java.io.InputStream; +import java.io.PrintWriter; +import java.io.ByteArrayInputStream; + +import java.nio.charset.Charset; +import java.nio.charset.UnsupportedCharsetException; + +public final class JSON +{ + private JSON() { + } + + private static final boolean isDigit(int c) { + return c >= '0' && c <= '9'; + } + + public static final boolean isWhitespace(int c) { + return c == ' ' || c == '\n' || c == '\r' + || c == '\t' || c == '\f'; + } + + private static final void match(int c, int x) throws IOException { + if (c != x) { + throw new IOException( + "Expecting '" + (char)c + "' found '" + (char)x + "'"); + } + } + + private static final int eof(InputStream in) + throws IOException + { + int c = in.read(); + if (c == -1) { + throw new IOException("EOF unexpected."); + } + return c; + } + + private static final int whitespace(InputStream in) + throws IOException + { + int c; + while (isWhitespace(c = eof(in))); + return c; + } + + private static final int parseHex(String hex) throws IOException { + try { + return Integer.parseInt(hex, 16); + } + catch (NumberFormatException nfe) { + throw new IOException("'" + hex + "' is not a hex string."); + } + } + + public static final String jsonString(String string) { + StringBuilder sb = new StringBuilder(string.length()+2); + + sb.append('"'); + + for (int i = 0, N = string.length(); i < N; ++i) { + char c = string.charAt(i); + switch (c) { + case '"': sb.append("\\\""); break; + case '\t': sb.append("\\t"); break; + case '\r': sb.append("\\r"); break; + case '\n': sb.append("\\n"); break; + case '\b': sb.append("\\b"); break; + case '\f': sb.append("\\f"); break; + default: + if (c >= 128) { + sb.append("\\u"); + String hex = Integer.toHexString((int)c); + for (int j = 4-hex.length(); j > 0; --j) { + sb.append('0'); + } + sb.append(hex); + } + else { + sb.append(c); + } + } + } + + sb.append('"'); + + return sb.toString(); + } + + + public static void write(PrintWriter out, Map map) { + writeObject(out, map); + } + + private static void writeValue(PrintWriter out, Object value) { + if (value instanceof Map) { + writeObject(out, (Map)value); + } + else if (value instanceof List) { + writeList(out, (List)value); + } + else if (value instanceof Number) { + out.print(value); + } + else if (value instanceof Boolean) { + out.print(((Boolean)value) ? "true" : "false"); + } + else if (value == null) { + out.print("null"); + } + else { + out.print(jsonString(value.toString())); + } + } + + private static void writeObject(PrintWriter out, Map map) { + + out.print('{'); + Iterator iter = map.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = (Map.Entry)iter.next(); + out.print(jsonString(entry.getKey().toString())); + out.print(':'); + writeValue(out, entry.getValue()); + if (iter.hasNext()) { + out.print(','); + } + } + out.print('}'); + } + + private static void writeList(PrintWriter out, List list) { + out.print('['); + Iterator iter = list.iterator(); + while (iter.hasNext()) { + writeValue(out, iter.next()); + if (iter.hasNext()) { + out.print(','); + } + } + out.print(']'); + } + + public static Map parse(String in) + throws IOException + { + return parse(asInputStream(in)); + } + + private static InputStream asInputStream(String in) { + byte [] bytes; + try { + bytes = in.getBytes(Charset.forName("US-ASCII")); + } + catch (UnsupportedCharsetException uce) { + // Should not happen. + bytes = in.getBytes(); + } + return new ByteArrayInputStream(bytes); + } + + public static Map parse(InputStream in) + throws IOException + { + return parseObject(new PushbackInputStream(in, 1)); + } + + public static Map parse(PushbackInputStream in) + throws IOException + { + return parseObject(in); + } + + private static final String parseString( + PushbackInputStream in + ) + throws IOException + { + StringBuilder sb = new StringBuilder(); + + int mode = 0; + + char [] hex = new char[4]; + + match('"', eof(in)); + + OUT: for (int c = eof(in);; c = eof(in)) { + + switch (mode) { + case 0: + if (c == '"') { + break OUT; + } + if (c == '\\') { + mode = 1; + } + else { + sb.append((char)c); + } + break; + case 1: + switch (c) { + case 'u': + mode = 2; + continue; + case 'b': + sb.append('\b'); + break; + case 'f': + sb.append('\f'); + break; + case 'n': + sb.append('\n'); + break; + case 'r': + sb.append('\r'); + break; + case 't': + sb.append('\t'); + break; + default: + sb.append((char)c); + } + mode = 0; + break; + case 2: + hex[0] = (char)c; + mode = 3; + break; + case 3: + hex[1] = (char)c; + mode = 4; + break; + case 4: + hex[2] = (char)c; + mode = 5; + break; + case 5: + hex[3] = (char)c; + sb.append((char)parseHex(new String(hex))); + mode = 0; + break; + } + } + return sb.toString(); + } + + private static final Boolean parseTrue(InputStream in) + throws IOException + { + match('t', eof(in)); + match('r', eof(in)); + match('u', eof(in)); + match('e', eof(in)); + return Boolean.TRUE; + } + + private static final Boolean parseFalse(InputStream in) + throws IOException + { + match('f', eof(in)); + match('a', eof(in)); + match('l', eof(in)); + match('s', eof(in)); + match('e', eof(in)); + return Boolean.FALSE; + } + + private static final Object parseNull(InputStream in) + throws IOException + { + match('n', eof(in)); + match('u', eof(in)); + match('l', eof(in)); + match('l', eof(in)); + return null; + } + + private static final Number parseNumber(PushbackInputStream in) + throws IOException + { + StringBuilder sb = new StringBuilder(); + + boolean isInteger = true; + + int c; + OUT: for (;;) { + switch (c = eof(in)) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case '-': case '+': + sb.append((char)c); + break; + case '.': case 'e': case 'E': + isInteger = false; + sb.append((char)c); + break; + default: + in.unread(c); + break OUT; + } + } + + try { + if (isInteger) { + return sb.length() > 9 + ? (Number)Long .valueOf(sb.toString()) + : (Number)Integer.valueOf(sb.toString()); + } + return (Number)Double.valueOf(sb.toString()); + } + catch (NumberFormatException nfe) { + throw new IOException("Not a number '" + sb + "'"); + } + } + + private static List parseList(PushbackInputStream in) + throws IOException + { + List list = new ArrayList(); + match('[', whitespace(in)); + int c = whitespace(in); + if (c == ']') { + return list; + } + + for (;; c = whitespace(in)) { + Object value; + in.unread(c); + switch (c) { + case '{': + value = parseObject(in); + break; + case '[': + value = parseList(in); + break; + case '"': + value = parseString(in); + break; + case 't': + value = parseTrue(in); + break; + case 'f': + value = parseFalse(in); + break; + case 'n': + value = parseNull(in); + break; + default: + value = parseNumber(in); + } + list.add(value); + + if ((c = whitespace(in)) == ']') break; + match(',', c); + } + return list; + } + + private static void parsePair( + PushbackInputStream in, + Map pairs + ) + throws IOException + { + in.unread(whitespace(in)); + String string = parseString(in); + match(':', whitespace(in)); + + Object value; + + int c = whitespace(in); + in.unread(c); + switch (c) { + case '{': + value = parseObject(in); + break; + case '[': + value = parseList(in); + break; + case '"': + value = parseString(in); + break; + case 't': + value = parseTrue(in); + break; + case 'f': + value = parseFalse(in); + break; + case 'n': + value = parseNull(in); + break; + default: + value = parseNumber(in); + } + pairs.put(string, value); + } + + private static Map parseObject(PushbackInputStream in) + throws IOException + { + Map pairs = new LinkedHashMap(); + + int c = whitespace(in); + match('{', c); + + if ((c = whitespace(in)) == '}') { + return pairs; + } + + in.unread(c); + + for (;;) { + parsePair(in, pairs); + + if ((c = whitespace(in)) == '}') { + break; + } + + if (c == '}') break; + match(',', c); + } + + return pairs; + } +} diff -r 9798e4d83681 -r bed51de8ae58 artifacts-common/src/main/java/de/intevation/artifacts/common/utils/XMLUtils.java --- a/artifacts-common/src/main/java/de/intevation/artifacts/common/utils/XMLUtils.java Tue Apr 03 11:15:10 2012 +0000 +++ b/artifacts-common/src/main/java/de/intevation/artifacts/common/utils/XMLUtils.java Wed Apr 18 17:10:10 2012 +0000 @@ -8,7 +8,10 @@ package de.intevation.artifacts.common.utils; +import java.util.List; +import java.util.ArrayList; import java.util.Map; +import java.util.LinkedHashMap; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; @@ -464,5 +467,119 @@ } return null; } + + private static class BuildResult { + List children; + Map attributes; + BuildResult() { + children = new ArrayList(); + attributes = new LinkedHashMap(); + } + + void setAttributes(Element element) { + for (Map.Entry entry: attributes.entrySet()) { + element.setAttribute(entry.getKey(), entry.getValue()); + } + } + void add(Node node) { + children.add(node); + } + + void add(String key, Object value) { + attributes.put(key, value != null ? value.toString() : "null"); + } + } // class BuildResult + + private static BuildResult recursiveBuild( + List list, + Document document + ) { + BuildResult result = new BuildResult(); + for (Object entry: list) { + if (entry instanceof Map) { + Element element = document.createElement("map"); + BuildResult subResult = recursiveBuild( + (Map)entry, document); + subResult.setAttributes(element); + result.add(element); + } + else if (entry instanceof List) { + Element element = document.createElement("list"); + BuildResult subResult = recursiveBuild((List)entry, document); + subResult.setAttributes(element); + result.add(element); + } + else { + Element element = document.createElement("entry"); + element.setAttribute( + "value", + entry != null ? entry.toString() : "null"); + } + } + return result; + } + + private static BuildResult recursiveBuild( + Map map, + Document document + ) { + BuildResult result = new BuildResult(); + + List nodes = new ArrayList(); + for (Map.Entry entry: map.entrySet()) { + Object value = entry.getValue(); + if (value instanceof Map) { + Element element = document.createElement(entry.getKey()); + BuildResult subResult = recursiveBuild( + (Map)value, document); + subResult.setAttributes(element); + result.add(element); + } + else if (value instanceof List) { + Element element = document.createElement(entry.getKey()); + BuildResult subResult = recursiveBuild((List)value, document); + subResult.setAttributes(element); + result.add(element); + } + else { + result.add(entry.getKey(), value); + } + } + return result; + } + + public static Document jsonToXML(String input) { + Document document = newDocument(); + + if (document == null) { + return null; + } + + Map map; + try { + map = JSON.parse(input); + } + catch (IOException ioe) { + logger.error(ioe); + return null; + } + + BuildResult roots = recursiveBuild(map, document); + + int N = roots.children.size(); + + if (N == 1) { + document.appendChild(roots.children.get(0)); + } + else if (N > 1) { + Node root = document.createElement("root"); + for (int i = 0; i < N; ++i) { + root.appendChild(roots.children.get(i)); + } + document.appendChild(root); + } + + return document; + } } // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :