Java tutorial
/*************************************************************************** Copyright 2014 Emily Estes Licensed 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 net.metanotion.json; import java.io.Closeable; import java.io.IOException; import java.io.Writer; import java.math.BigDecimal; import org.apache.commons.text.StringEscapeUtils; /** This class implements a handler that writes JSON encoded values into a stream. @param <T> The type parameter for the handler. This class always returns null from finish(). */ public final class JsonWriter<T> implements Handler<T>, Closeable { private static final String COMMA = ","; private static final String LBRACE = "{"; private static final String RBRACE = "}"; private static final String LARRAY = "["; private static final String RARRAY = "]"; private static final String QUOTE = "\""; private static final String COLON = ":"; private final Writer out; private static final class ListWriter<T> implements Handler<T> { private final Writer out; private final Handler<T> parent; private final JsonWriter writer; private String sep = ""; public ListWriter(final Writer out, final Handler<T> parent, final JsonWriter<T> writer) { this.out = out; this.parent = parent; this.writer = writer; } @Override public Handler<T> start() { return this; } @Override public T finish() { return null; } @Override public Handler<T> key(final String key) { throw new RuntimeException("Expecting a value or end of list, instead found key."); } @Override public Handler<T> endObject() { throw new RuntimeException("Expecting a value or end of list, instead found }."); } @Override public Handler<T> startObject() { try { out.append(sep); sep = COMMA; out.append(LBRACE); return new ObjectWriter<T>(out, this, writer); } catch (final IOException ioe) { throw new RuntimeException(ioe); } } @Override public Handler<T> startList() { try { out.append(sep); sep = COMMA; out.append(LARRAY); return new ListWriter<T>(out, this, writer); } catch (final IOException ioe) { throw new RuntimeException(ioe); } } @Override public Handler<T> endList() { try { out.append(RARRAY); return parent; } catch (final IOException ioe) { throw new RuntimeException(ioe); } } @Override public Handler<T> integer(final Long val) { try { out.append(sep); sep = COMMA; writer.integer(val); return this; } catch (final IOException ioe) { throw new RuntimeException(ioe); } } @Override public Handler<T> decimal(final Double val) { try { out.append(sep); sep = COMMA; writer.decimal(val); return this; } catch (final IOException ioe) { throw new RuntimeException(ioe); } } @Override public Handler<T> string(final String val) { try { out.append(sep); sep = COMMA; writer.string(val); return this; } catch (final IOException ioe) { throw new RuntimeException(ioe); } } @Override public Handler<T> bool(final boolean val) { try { out.append(sep); sep = COMMA; writer.bool(val); return this; } catch (final IOException ioe) { throw new RuntimeException(ioe); } } @Override public Handler<T> jsonNull() { try { out.append(sep); sep = COMMA; writer.jsonNull(); return this; } catch (final IOException ioe) { throw new RuntimeException(ioe); } } } private static final class ObjectWriter<T> implements Handler<T> { private final Writer out; private final Handler<T> parent; private final JsonWriter<T> writer; private String sep = ""; private boolean keyNext = true; public ObjectWriter(final Writer out, final Handler<T> parent, final JsonWriter<T> writer) { this.out = out; this.parent = parent; this.writer = writer; } @Override public Handler<T> start() { return this; } @Override public T finish() { return null; } @Override public Handler<T> key(final String key) { try { if (!keyNext) { throw new RuntimeException("Expecting a value next, instead found key."); } out.append(sep); sep = COMMA; keyNext = false; out.append(QUOTE + StringEscapeUtils.escapeJson(key) + QUOTE); out.append(COLON); return this; } catch (final IOException ioe) { throw new RuntimeException(ioe); } } @Override public Handler<T> endObject() { try { out.append(RBRACE); return parent; } catch (final IOException ioe) { throw new RuntimeException(ioe); } } @Override public Handler<T> startObject() { if (keyNext) { throw new RuntimeException("Expecting a key next, instead found object."); } keyNext = true; try { out.append(LBRACE); return new ObjectWriter<T>(out, this, writer); } catch (final IOException ioe) { throw new RuntimeException(ioe); } } @Override public Handler<T> startList() { if (keyNext) { throw new RuntimeException("Expecting a key next, instead found list."); } keyNext = true; try { out.append(LARRAY); return new ListWriter<T>(out, this, writer); } catch (final IOException ioe) { throw new RuntimeException(ioe); } } @Override public Handler<T> endList() { throw new RuntimeException("Expecting a value or a key, instead found end of a list."); } @Override public Handler<T> integer(final Long val) { if (keyNext) { throw new RuntimeException("Expecting a key next, instead found integer."); } writer.integer(val); keyNext = true; return this; } @Override public Handler<T> decimal(final Double val) { if (keyNext) { throw new RuntimeException("Expecting a key next, instead found decimal."); } writer.decimal(val); keyNext = true; return this; } @Override public Handler<T> string(final String val) { if (keyNext) { throw new RuntimeException("Expecting a key next, instead found string."); } writer.string(val); keyNext = true; return this; } @Override public Handler<T> bool(final boolean val) { if (keyNext) { throw new RuntimeException("Expecting a key next, instead found boolean."); } writer.bool(val); keyNext = true; return this; } @Override public Handler<T> jsonNull() { if (keyNext) { throw new RuntimeException("Expecting a key next, instead found null."); } writer.jsonNull(); keyNext = true; return this; } } /** Produce a JsonWriter to emit Json encoded values into a stream. @param out A stream to emit Json encoded values into. */ public JsonWriter(final Writer out) { this.out = out; } @Override public void close() throws IOException { out.close(); } @Override public Handler<T> start() { return this; } @Override public T finish() { return null; } @Override public Handler<T> startObject() { try { out.append(LBRACE); return new ObjectWriter<T>(out, this, this); } catch (final IOException ioe) { throw new RuntimeException(ioe); } } @Override public Handler<T> key(final String key) { try { out.append(QUOTE + StringEscapeUtils.escapeJson(key) + QUOTE); out.append(COLON); return this; } catch (final IOException ioe) { throw new RuntimeException(ioe); } } @Override public Handler<T> endObject() { try { out.append(RBRACE); return this; } catch (final IOException ioe) { throw new RuntimeException(ioe); } } @Override public Handler<T> startList() { try { out.append(LARRAY); return new ListWriter<T>(out, this, this); } catch (final IOException ioe) { throw new RuntimeException(ioe); } } @Override public Handler<T> endList() { try { out.append(RARRAY); return this; } catch (final IOException ioe) { throw new RuntimeException(ioe); } } @Override public Handler<T> integer(final Long val) { try { out.append(val.toString()); return this; } catch (final IOException ioe) { throw new RuntimeException(ioe); } } @Override public Handler<T> decimal(final Double val) { try { out.append(val.toString()); return this; } catch (final IOException ioe) { throw new RuntimeException(ioe); } } @Override public Handler<T> string(final String val) { try { out.append(QUOTE + StringEscapeUtils.escapeJson(val) + QUOTE); return this; } catch (final IOException ioe) { throw new RuntimeException(ioe); } } @Override public Handler<T> bool(final boolean val) { try { out.append(val ? "true" : "false"); return this; } catch (final IOException ioe) { throw new RuntimeException(ioe); } } @Override public Handler<T> jsonNull() { try { out.append("null"); return this; } catch (final IOException ioe) { throw new RuntimeException(ioe); } } private static <T> Handler<T> write(final Object v, final Handler<T> h) { if (v == null) { return h.jsonNull(); } else if (v instanceof Boolean) { return h.bool(((Boolean) v).booleanValue()); } else if (v instanceof Long) { return h.integer((Long) v); } else if (v instanceof Integer) { return h.integer(((Integer) v).longValue()); } else if (v instanceof Double) { return h.decimal((Double) v); } else if (v instanceof Float) { return h.decimal(((Float) v).doubleValue()); } else if (v instanceof BigDecimal) { try { return h.integer(((BigDecimal) v).longValueExact()); } catch (final ArithmeticException ae) { return h.decimal(((BigDecimal) v).doubleValue()); } } else if (v instanceof JsonObject) { return write((JsonObject) v, h); } else if (v instanceof JsonArray) { return write((JsonArray) v, h); } else { return h.string(v.toString()); } } /** Process a JsonObject instance into a JSON encoded value into the stream backing this writer. @param obj The JsonObject to emit. */ public void write(final JsonObject obj) { JsonWriter.write(obj, this); } /** Process a JsonObject instance into a JSON encoded value into the stream backing this writer. @param <T> The type parameter for the handler. @param obj The JsonObject to emit. @param h The current handler to use. @return The next handler value. */ public static <T> Handler<T> write(final JsonObject obj, Handler<T> h) { h = h.startObject(); for (final String key : obj.keySet()) { h = h.key(key); h = write(obj.get(key), h); } return h.endObject(); } /** Process a JsonArray instance into a JSON encoded value into the stream backing this writer. @param arr The JsonArray to emit. */ public void write(final JsonArray arr) { JsonWriter.write(arr, this); } /** Process a JsonArray instance into a JSON encoded value into the stream backing this writer. @param <T> The type parameter for the handler. @param arr The JsonArray to emit. @param h The current handler to use. @return The next handler value. */ public static <T> Handler<T> write(final JsonArray arr, Handler<T> h) { h = h.startList(); for (final Object v : arr) { h = write(v, h); } return h.endList(); } }