net.metanotion.json.JsonWriter.java Source code

Java tutorial

Introduction

Here is the source code for net.metanotion.json.JsonWriter.java

Source

/***************************************************************************
   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();
    }
}