name.martingeisse.common.javascript.analyze.JsonAnalyzer.java Source code

Java tutorial

Introduction

Here is the source code for name.martingeisse.common.javascript.analyze.JsonAnalyzer.java

Source

/**
 * Copyright (c) 2010 Martin Geisse
 *
 * This file is distributed under the terms of the MIT license.
 */

package name.martingeisse.common.javascript.analyze;

import java.io.IOException;
import java.io.Reader;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.json.simple.JSONValue;
import org.json.simple.parser.ParseException;

/**
 * This class provides basic functionality to dissect a JSON
 * value parsed by JSON-Simple.
 */
public final class JsonAnalyzer implements Serializable {

    /**
     * the value
     */
    private final Object value;

    /**
     * the parent
     */
    private final JsonAnalyzer parent;

    /**
     * the contextName
     */
    private final String contextName;

    /**
     * Constructor.
     * @param value the value to analyze
     */
    public JsonAnalyzer(final Object value) {
        this(value, null, null);
    }

    /**
     * Constructor.
     * @param value the value to analyze
     * @param parent the parent value
     * @param contextName the name of this field in the parent value
     */
    private JsonAnalyzer(final Object value, final JsonAnalyzer parent, final String contextName) {
        if ((parent == null) != (contextName == null)) {
            throw new IllegalArgumentException("must either pass both parent and context name or none");
        }
        this.value = value;
        this.parent = parent;
        this.contextName = contextName;
    }

    /**
     * Parses a JSON-encoded string and analyzes the result.
     * @param json the JSON-encoded string
     * @return the analyzer
     */
    public static JsonAnalyzer parse(final String json) {
        try {
            return new JsonAnalyzer(JSONValue.parseWithException(json));
        } catch (ParseException e) {
            throw new JsonAnalysisException("Parse exception in JSON input: " + json);
        }
    }

    /**
     * Parses a JSON-encoded string and analyzes the result.
     * @param jsonReader the reader for the JSON-encoded string
     * @return the analyzer
     */
    public static JsonAnalyzer parse(final Reader jsonReader) {
        try {
            return new JsonAnalyzer(JSONValue.parseWithException(jsonReader));
        } catch (ParseException e) {
            throw new JsonAnalysisException("Parse exception in JSON input");
        } catch (IOException e) {
            throw new RuntimeException("IOException while parsing JSON", e);
        }
    }

    /**
     * Getter method for the value.
     * @return the value
     */
    public Object getValue() {
        return value;
    }

    /**
     * 
     */
    private void buildContextDescription(final StringBuilder builder) {
        if (parent != null) {
            parent.buildContextDescription(builder);
            builder.append('.');
            builder.append(contextName);
        } else {
            builder.append("TOPLEVEL");
        }
    }

    /**
     * Checks if the value is null.
     * @return true if null, false otherwise
     */
    public boolean isNull() {
        return (value == null);
    }

    /**
     * Checks if the value is a list.
     * @return true if list, false otherwise
     */
    public boolean isList() {
        return (value instanceof List<?>);
    }

    /**
     * Checks if the value is a map.
     * @return true if map, false otherwise
     */
    public boolean isMap() {
        return (value instanceof Map<?, ?>);
    }

    /**
     * Returns a new analyzer that contains either this value or, if
     * this value is null, the specified fallback value.
     * @param fallbackValue the fallback value
     * @return the new analyzer
     */
    public JsonAnalyzer fallback(final Object fallbackValue) {
        return new JsonAnalyzer(value == null ? fallbackValue : value, parent, contextName);
    }

    /**
     * Tries to cast the value to {@link Boolean}. Returns the value
     * if the cast succeeds, null otherwise.
     * @return the boolean value or null
     */
    public Boolean tryBoolean() {
        return (value instanceof Boolean) ? (Boolean) value : null;
    }

    /**
     * Expects the value to be of boolean type and returns its value.
     * Throws a {@link JsonAnalysisException} for non-boolean values.
     * @return the boolean value
     */
    public boolean expectBoolean() {
        if (value instanceof Boolean) {
            return (Boolean) value;
        }
        throw expectedException("boolean");
    }

    /**
     * Tries to cast the value to {@link Integer}. Returns the value
     * if the cast succeeds, null otherwise.
     * @return the integer value or null
     */
    public Integer tryInteger() {
        return (value instanceof Number) ? ((Number) value).intValue() : null;
    }

    /**
     * Expects the value to be of integer type and returns its value.
     * Throws a {@link JsonAnalysisException} for non-integer values.
     * @return the integer value
     */
    public int expectInteger() {
        if (value instanceof Number) {
            return ((Number) value).intValue();
        }
        throw expectedException("integer");
    }

    /**
     * Returns null if the value is null. Otherwise turns the value
     * into a string using {@link Object#toString()}, then parses
     * it as an integer. Throws a {@link JsonAnalysisException}
     * if parsing fails.
     * @return the parsed integer or null
     */
    public Integer toIntegerOrNull() {
        if (value == null) {
            return null;
        } else {
            try {
                return new Integer(value.toString());
            } catch (final NumberFormatException e) {
                throw expectedException("integer");
            }
        }
    }

    /**
     * Turns the value into a string using {@link Object#toString()},
     * then parses it as an integer. Throws a {@link JsonAnalysisException}
     * if parsing fails.
     * @return the parsed integer
     */
    public int toInt() {
        try {
            return Integer.parseInt(value.toString());
        } catch (final Exception e) {
            throw expectedException("integer");
        }
    }

    /**
     * Tries to cast the value to {@link Long}. Returns the value
     * if the cast succeeds, null otherwise.
     * @return the long value or null
     */
    public Long tryLong() {
        return (value instanceof Number) ? ((Number) value).longValue() : null;
    }

    /**
     * Expects the value to be of long type and returns its value.
     * Throws a {@link JsonAnalysisException} for non-long values.
     * @return the long value
     */
    public long expectLong() {
        if (value instanceof Number) {
            return ((Number) value).longValue();
        }
        throw expectedException("long");
    }

    /**
     * Returns null if the value is null. Otherwise turns the value
     * into a string using {@link Object#toString()}, then parses
     * it as an long. Throws a {@link JsonAnalysisException}
     * if parsing fails.
     * @return the parsed long or null
     */
    public Long toLongOrNull() {
        if (value == null) {
            return null;
        } else {
            try {
                return new Long(value.toString());
            } catch (final NumberFormatException e) {
                throw expectedException("long");
            }
        }
    }

    /**
     * Turns the value into a string using {@link Object#toString()},
     * then parses it as an long. Throws a {@link JsonAnalysisException}
     * if parsing fails.
     * @return the parsed long
     */
    public long toLong() {
        try {
            return Long.parseLong(value.toString());
        } catch (final Exception e) {
            throw expectedException("long");
        }
    }

    /**
     * Tries to cast the value to {@link Double}. Returns the value
     * if the cast succeeds, null otherwise.
     * @return the double value or null
     */
    public Double tryDouble() {
        return (value instanceof Number) ? ((Number) value).doubleValue() : null;
    }

    /**
     * Expects the value to be of double type and returns its value.
     * Throws a {@link JsonAnalysisException} for non-double values.
     * @return the double value
     */
    public double expectDouble() {
        if (value instanceof Number) {
            return ((Number) value).doubleValue();
        }
        throw expectedException("double");
    }

    /**
     * Returns null if the value is null. Otherwise turns the value
     * into a string using {@link Object#toString()}, then parses
     * it as an double. Throws a {@link JsonAnalysisException}
     * if parsing fails.
     * @return the parsed double or null
     */
    public Double toDoubleOrNull() {
        if (value == null) {
            return null;
        } else {
            try {
                return new Double(value.toString());
            } catch (final NumberFormatException e) {
                throw expectedException("double");
            }
        }
    }

    /**
     * Turns the value into a string using {@link Object#toString()},
     * then parses it as an double. Throws a {@link JsonAnalysisException}
     * if parsing fails.
     * @return the parsed double
     */
    public double toDouble() {
        try {
            return Double.parseDouble(value.toString());
        } catch (final Exception e) {
            throw expectedException("double");
        }
    }

    /**
     * Tries to cast the value to {@link String}. Returns the value
     * if the cast succeeds, null otherwise.
     * @return the string value or null
     */
    public String tryString() {
        return (value instanceof String) ? (String) value : null;
    }

    /**
     * Expects the value to be of string type and returns its value.
     * Throws a {@link JsonAnalysisException} for non-string values.
     * @return the string value
     */
    public String expectString() {
        if (value instanceof String) {
            return (String) value;
        }
        throw expectedException("string");
    }

    /**
     * Returns null if the value is null. Otherwise turns the value
     * into a string using {@link Object#toString()}.
     * @return the result of {@link Object#toString()} or null
     */
    public String toStringOrNull() {
        return (value == null ? null : value.toString());
    }

    /**
     * Throws a {@link JsonAnalysisException} if the value is null.
     * Otherwise turns the value into a string using {@link Object#toString()}.
     * @return the result of {@link Object#toString()}
     */
    public String toStringNotNull() {
        if (value == null) {
            throw exception("null not allowed here");
        } else {
            return value.toString();
        }
    }

    /**
     * Tries to cast the value to {@link List}. Returns the value
     * if the cast succeeds, null otherwise.
     * @return the list value or null
     */
    @SuppressWarnings("unchecked")
    public List<Object> tryList() {
        return (value instanceof List) ? (List<Object>) value : null;
    }

    /**
     * Expects the value to be of list type and returns its value.
     * Throws a {@link JsonAnalysisException} for non-list values.
     * @return the list value
     */
    @SuppressWarnings("unchecked")
    public List<Object> expectList() {
        if (value instanceof List) {
            return (List<Object>) value;
        }
        throw expectedException("list");
    }

    /**
     * Expects the value to be of list type, wraps each element in
     * a {@link JsonAnalyzer}, and returns the analyzers in a list.
     * @return the list of analyzers
     */
    public List<JsonAnalyzer> analyzeList() {
        if (value instanceof List) {
            final List<?> list = (List<?>) value;
            final List<JsonAnalyzer> result = new ArrayList<JsonAnalyzer>();
            int i = 0;
            for (final Object element : list) {
                result.add(new JsonAnalyzer(element, this, Integer.toString(i)));
                i++;
            }
            return result;
        }
        throw expectedException("list");
    }

    /**
     * Expects the value to be either of list type, or to represent a
     * single-element list; wraps each element in a {@link JsonAnalyzer},
     * and returns the analyzers in a list.
     * 
     * @return the list of analyzers
     */
    public List<JsonAnalyzer> analyzeListOrSingle() {
        if (value instanceof List) {
            return analyzeList();
        } else {
            List<JsonAnalyzer> list = new ArrayList<JsonAnalyzer>();
            list.add(this);
            return list;
        }
    }

    /**
     * Expects the value to be of list type, obtains the specified
     * element, and wraps it in a new instance of this class. Unlike
     * {@link #analyzeMapElement(String)}, this method will throw an
     * exception if the index is out of range.
     * 
     * @param index the index to read from
     * @return the analyzer for the list element
     * @throws IndexOutOfBoundsException if the index is out of range
     */
    public JsonAnalyzer analyzeListElement(final int index) throws IndexOutOfBoundsException {
        if (value instanceof List) {
            final List<?> list = (List<?>) value;
            return new JsonAnalyzer(list.get(index), this, Integer.toString(index));
        }
        throw expectedException("list");
    }

    /**
     * Tries to cast the value to {@link Map}. Returns the value
     * if the cast succeeds, null otherwise.
     * @return the map value or null
     */
    @SuppressWarnings("unchecked")
    public Map<String, Object> tryMap() {
        return (value instanceof Map) ? (Map<String, Object>) value : null;
    }

    /**
     * Expects the value to be of map type and returns its value.
     * Throws a {@link JsonAnalysisException} for non-map values.
     * @return the map value
     */
    @SuppressWarnings("unchecked")
    public Map<String, Object> expectMap() {
        if (value instanceof Map) {
            return (Map<String, Object>) value;
        }
        throw expectedException("map");
    }

    /**
     * Expects the value to be of map type, wraps each element in
     * a {@link JsonAnalyzer}, and returns the analyzers in a map
     * using the keys from the original map, turned to strings using
     * {@link Object#toString()}.
     * 
     * @return the map of analyzers
     */
    public Map<String, JsonAnalyzer> analyzeMap() {
        if (value instanceof Map) {
            final Map<?, ?> map = (Map<?, ?>) value;
            final Map<String, JsonAnalyzer> result = new HashMap<String, JsonAnalyzer>();
            for (final Map.Entry<?, ?> entry : map.entrySet()) {
                String key = entry.getKey().toString();
                result.put(key, new JsonAnalyzer(entry.getValue(), this, key));
            }
            return result;
        }
        throw expectedException("map");
    }

    /**
     * Expects the value to be of map type, obtains the specified
     * element, and wraps it in a new instance of this class.
     * @param key the key to read from
     * @return the analyzer for the map element
     */
    public JsonAnalyzer analyzeMapElement(final String key) {
        if (value instanceof Map) {
            final Map<?, ?> map = (Map<?, ?>) value;
            return new JsonAnalyzer(map.get(key), this, key);
        }
        throw expectedException("map");
    }

    /**
     * Helper method to create {@link JsonAnalysisException}s for
     * unexpected values.
     * @param what a description of what was expected
     * @return the exception
     */
    public JsonAnalysisException expectedException(String what) {
        return exception("expected " + what + ", found " + value
                + (value == null ? "(NULL)" : " (" + value.getClass().getSimpleName() + ")"));
    }

    /**
     * Helper method to create {@link JsonAnalysisException}s.
     * @param message a description of the problem
     * @return the exception
     */
    public JsonAnalysisException exception(final String message) {
        final StringBuilder builder = new StringBuilder();
        buildContextDescription(builder);
        if (parent != null) {
            builder.append(": ");
        }
        builder.append(message);
        return new JsonAnalysisException(builder.toString());
    }

}