org.quantumbadger.redreader.jsonwrap.JsonValue.java Source code

Java tutorial

Introduction

Here is the source code for org.quantumbadger.redreader.jsonwrap.JsonValue.java

Source

/*******************************************************************************
 * This file is part of RedReader.
 *
 * RedReader is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * RedReader is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with RedReader.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/

package org.quantumbadger.redreader.jsonwrap;

import android.support.annotation.IntDef;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;

/**
 * Contains a literal, object, or array value, and is responsible for parsing
 * the incoming JSON stream.
 *
 * <p>
 * <b>To parse a JSON stream, call the JsonValue constructor with a JsonParser
 * as the argument, then call the "build" method (with the same JsonParser as an
 * argument) in another thread.</b>
 * </p>
 */
public final class JsonValue {

    public static final int TYPE_OBJECT = 0;
    public static final int TYPE_ARRAY = 1;
    public static final int TYPE_NULL = 2;
    public static final int TYPE_BOOLEAN = 3;
    public static final int TYPE_STRING = 4;
    public static final int TYPE_FLOAT = 5;
    public static final int TYPE_INTEGER = 6;

    @IntDef({ TYPE_OBJECT, TYPE_ARRAY, TYPE_NULL, TYPE_BOOLEAN, TYPE_STRING, TYPE_FLOAT, TYPE_INTEGER })
    @Retention(RetentionPolicy.SOURCE)
    public @interface Type {
    }

    private final @Type int type;
    private final Object value;

    private JsonParser jp = null;

    /**
     * Begins parsing a JSON stream into a tree structure. The JsonValue object
     * created contains the value at the root of the tree.
     *
     * This constructor will block until the first JSON token is received. To
     * continue building the tree, the "build" method (inherited from
     * JsonBuffered) must be called in another thread.
     *
     * @param jp
     *         The incoming JSON stream
     * @throws java.io.IOException
     */
    public JsonValue(final JsonParser jp) throws IOException {
        this(jp, jp.nextToken());
    }

    /**
     * Begins parsing a JSON stream into a tree structure. The JsonValue object
     * created contains the value at the root of the tree.
     *
     * This constructor will block until the first JSON token is received. To
     * continue building the tree, the "build" method (inherited from
     * JsonBuffered) must be called in another thread.
     *
     * @param source
     *         The source of incoming JSON data.
     * @throws java.io.IOException
     */
    public JsonValue(final InputStream source) throws IOException {
        this(new JsonFactory().createParser(source));
    }

    /**
     * Begins parsing a JSON stream into a tree structure. The JsonValue object
     * created contains the value at the root of the tree.
     *
     * This constructor will block until the first JSON token is received. To
     * continue building the tree, the "build" method (inherited from
     * JsonBuffered) must be called in another thread.
     *
     * @param source
     *         The source of incoming JSON data.
     * @throws java.io.IOException
     */
    public JsonValue(final URL source) throws IOException {
        this(new JsonFactory().createParser(source));
    }

    /**
     * Begins parsing a JSON stream into a tree structure. The JsonValue object
     * created contains the value at the root of the tree.
     *
     * This constructor will block until the first JSON token is received. To
     * continue building the tree, the "build" method (inherited from
     * JsonBuffered) must be called in another thread.
     *
     * @param source
     *         The source of incoming JSON data.
     * @throws java.io.IOException
     */
    public JsonValue(final byte[] source) throws IOException {
        this(new JsonFactory().createParser(source));
    }

    /**
     * Begins parsing a JSON stream into a tree structure. The JsonValue object
     * created contains the value at the root of the tree.
     *
     * This constructor will block until the first JSON token is received. To
     * continue building the tree, the "build" method (inherited from
     * JsonBuffered) must be called in another thread.
     *
     * @param source
     *         The source of incoming JSON data.
     * @throws java.io.IOException
     */
    public JsonValue(final String source) throws IOException {
        this(new JsonFactory().createParser(source));
    }

    /**
     * Begins parsing a JSON stream into a tree structure. The JsonValue object
     * created contains the value at the root of the tree.
     *
     * This constructor will block until the first JSON token is received. To
     * continue building the tree, the "build" method (inherited from
     * JsonBuffered) must be called in another thread.
     *
     * @param source
     *         The source of incoming JSON data.
     * @throws java.io.IOException
     */
    public JsonValue(final File source) throws IOException {
        this(new JsonFactory().createParser(source));
    }

    /**
     * Begins parsing a JSON stream into a tree structure. The JsonValue object
     * created contains the value at the root of the tree.
     *
     * This constructor will block until the first JSON token is received. To
     * continue building the tree, the "build" method (inherited from
     * JsonBuffered) must be called in another thread.
     *
     * @param source
     *         The source of incoming JSON data.
     * @throws java.io.IOException
     */
    public JsonValue(final Reader source) throws IOException {
        this(new JsonFactory().createParser(source));
    }

    // The main constructor
    protected JsonValue(final JsonParser jp, final JsonToken firstToken) throws IOException {

        switch (firstToken) {

        case START_OBJECT:
            type = TYPE_OBJECT;
            value = new JsonBufferedObject();
            this.jp = jp;
            break;

        case START_ARRAY:
            type = TYPE_ARRAY;
            value = new JsonBufferedArray();
            this.jp = jp;
            break;

        case VALUE_FALSE:
            type = TYPE_BOOLEAN;
            value = false;
            break;

        case VALUE_TRUE:
            type = TYPE_BOOLEAN;
            value = true;
            break;

        case VALUE_NULL:
            type = TYPE_NULL;
            value = null;
            break;

        case VALUE_STRING:
            type = TYPE_STRING;
            value = jp.getValueAsString();
            break;

        case VALUE_NUMBER_FLOAT:

            //noinspection FloatingPointEquality,UnnecessaryExplicitNumericCast
            if (jp.getValueAsDouble() == (double) jp.getValueAsLong()) {
                type = TYPE_INTEGER;
                value = jp.getValueAsLong();
            } else {
                type = TYPE_FLOAT;
                value = jp.getValueAsDouble();
            }

            break;

        case VALUE_NUMBER_INT:
            type = TYPE_INTEGER;
            value = jp.getValueAsLong();
            break;

        default:
            throw new JsonParseException("Expecting an object, literal, or array", jp.getCurrentLocation());
        }
    }

    /**
     * Continues the process of parsing the specified JSON stream.
     *
     * This method will block until the stream is fully parsed, but it is
     * possible to make use of this value and its descendants in other threads
     * while this is occurring.
     *
     * @throws java.io.IOException
     */
    public void buildInThisThread() throws IOException {

        if (type == TYPE_OBJECT || type == TYPE_ARRAY) {
            ((JsonBuffered) value).build(jp);
        }

        this.jp = null;
    }

    /**
     * @return The type of value this JsonValue contains.
     */
    public @Type int getType() {
        return type;
    }

    /**
     * @return True if the type of this value is NULL.
     */
    public boolean isNull() {
        return type == TYPE_NULL;
    }

    /**
     * @return If this JsonValue contains a JSON object, then this method
     *       returns that object. The type of value this JsonValue contains
     *       can be checked with the getType() method.
     */
    public JsonBufferedObject asObject() {

        switch (type) {
        case TYPE_NULL:
            return null;
        default:
            return (JsonBufferedObject) value;
        }
    }

    public <E> E asObject(final Class<E> clazz) throws InstantiationException, IllegalAccessException,
            InterruptedException, IOException, NoSuchMethodException, InvocationTargetException {

        switch (type) {
        case TYPE_NULL:
            return null;
        default:
            return asObject().asObject(clazz);
        }
    }

    /**
     * @return If this JsonValue contains a JSON array, then this method returns
     *       that array. The type of value this JsonValue contains can be
     *       checked with the getType() method.
     */
    public JsonBufferedArray asArray() {

        switch (type) {
        case TYPE_NULL:
            return null;
        default:
            return (JsonBufferedArray) value;
        }
    }

    /**
     * @return If this JsonValue contains a boolean, then this method returns
     *       that boolean. The type of value this JsonValue contains can be
     *       checked with the getType() method.
     */
    public Boolean asBoolean() {

        switch (type) {
        case TYPE_NULL:
            return null;
        default:
            return (Boolean) value;
        }
    }

    /**
     * @return If this JsonValue contains a string, then this method returns
     *       that string. The type of value this JsonValue contains can be
     *       checked with the getType() method.
     */
    public String asString() {

        switch (type) {
        case TYPE_FLOAT:
            return String.valueOf(asDouble());
        case TYPE_INTEGER:
            return String.valueOf(asLong());
        case TYPE_BOOLEAN:
            return String.valueOf(asBoolean());
        case TYPE_NULL:
            return null;
        default:
            return (String) value;
        }
    }

    /**
     * @return If this JsonValue contains a double, then this method returns
     *       that double. The type of value this JsonValue contains can be
     *       checked with the getType() method.
     */
    public Double asDouble() {
        switch (type) {
        case TYPE_NULL:
            return null;
        case TYPE_INTEGER:
            return ((Long) value).doubleValue();
        case TYPE_STRING:
            return Double.parseDouble(asString());
        default:
            return (Double) value;
        }
    }

    /**
     * @return If this JsonValue contains an integer, then this method returns
     *       that integer. The type of value this JsonValue contains can be
     *       checked with the getType() method.
     */
    public Long asLong() {
        switch (type) {
        case TYPE_NULL:
            return null;
        case TYPE_FLOAT:
            return ((Double) value).longValue();
        case TYPE_STRING:
            return Long.parseLong(asString());
        default:
            return (Long) value;
        }
    }

    @Override
    public String toString() {

        final StringBuilder sb = new StringBuilder();

        try {
            prettyPrint(0, sb);

        } catch (InterruptedException e) {
            e.printStackTrace();

        } catch (IOException e) {
            e.printStackTrace();
        }

        return sb.toString();
    }

    protected void prettyPrint(final int indent, final StringBuilder sb) throws InterruptedException, IOException {

        switch (type) {
        case TYPE_BOOLEAN:
            sb.append(asBoolean());
            break;
        case TYPE_FLOAT:
            sb.append(asDouble());
            break;
        case TYPE_INTEGER:
            sb.append(asLong());
            break;
        case TYPE_NULL:
            sb.append("null");
            break;
        case TYPE_STRING:
            sb.append("\"").append(asString().replace("\\", "\\\\").replace("\"", "\\\"")).append("\"");
            break;
        case TYPE_ARRAY:
        case TYPE_OBJECT:
            ((JsonBuffered) value).prettyPrint(indent, sb);
            break;
        }
    }

    public void join() throws InterruptedException {
        if (getType() == TYPE_ARRAY || getType() == TYPE_OBJECT) {
            ((JsonBuffered) value).join();
        }
    }
}