All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.googlecode.fascinator.common.JsonConfigHelper Maven / Gradle / Ivy

There is a newer version: 1.6
Show newest version
/* 
 * The Fascinator - Common Library
 * Copyright (C) 2008-2009 University of Southern Queensland
 * 
 * This program 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 2 of the License, or
 * (at your option) any later version.
 * 
 * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
package com.googlecode.fascinator.common;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.commons.jxpath.AbstractFactory;
import org.apache.commons.jxpath.JXPathContext;
import org.apache.commons.jxpath.Pointer;
import org.apache.commons.lang.text.StrSubstitutor;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.map.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Helper class for working with JSON configuration. Uses the JXPath library to
 * use XPath syntax to access JSON nodes.
 * 
 * @author Oliver Lucido
 */
@SuppressWarnings("unchecked")
public class JsonConfigHelper {

    /** Logging */
    @SuppressWarnings("unused")
    private Logger log = LoggerFactory.getLogger(JsonConfigHelper.class);

    /** JXPath factory for creating JSON nodes */
    private class JsonMapFactory extends AbstractFactory {
        @Override
        public boolean createObject(JXPathContext context, Pointer pointer,
                Object parent, String name, int index) {
            if (parent instanceof Map) {
                ((Map) parent).put(name,
                        new LinkedHashMap());
                return true;
            }
            return false;
        }
    }

    /** JSON root node */
    private Map rootNode;

    /** JXPath context */
    private JXPathContext jxPath;

    /**
     * Creates an empty JSON configuration
     */
    public JsonConfigHelper() {
        rootNode = new LinkedHashMap();
    }

    /**
     * Creates a JSON configuration from a map. This is normally used to create
     * an instance for a subNode returned from one of the get methods.
     * 
     * @param rootNode a JSON structured map
     */
    public JsonConfigHelper(Map rootNode) {
        this.rootNode = rootNode;
    }

    /**
     * Creates a JSON configuration from the specified string
     * 
     * @param jsonContent a JSON content string
     * @throws IOException if there was an error parsing or reading the content
     */
    public JsonConfigHelper(String jsonContent) throws IOException {
        rootNode = new ObjectMapper().readValue(jsonContent, Map.class);
    }

    /**
     * Creates a JSON configuration from the specified file
     * 
     * @param jsonFile a JSON file
     * @throws IOException if there was an error parsing or reading the file
     */
    public JsonConfigHelper(File jsonFile) throws IOException {
        rootNode = new ObjectMapper().readValue(jsonFile, Map.class);
    }

    /**
     * Creates a JSON configuration from the specified input stream
     * 
     * @param jsonIn a JSON stream
     * @throws IOException if there was an error parsing or reading the stream
     */
    public JsonConfigHelper(InputStream jsonIn) throws IOException {
        rootNode = new ObjectMapper().readValue(jsonIn, Map.class);
    }

    /**
     * Creates a JSON configuration from the specified reader
     * 
     * @param jsonReader a reader for a JSON file
     * @throws IOException if there was an error parsing or reading the reader
     */
    public JsonConfigHelper(Reader jsonReader) throws IOException {
        rootNode = new ObjectMapper().readValue(jsonReader, Map.class);
    }

    /**
     * Gets a JXPath context for selecting and creating JSON nodes and values
     * 
     * @return a JXPath context
     */
    private JXPathContext getJXPath() {
        if (jxPath == null) {
            jxPath = JXPathContext.newContext(rootNode);
            jxPath.setFactory(new JsonMapFactory());
            jxPath.setLenient(true);
        }
        return jxPath;
    }

    /**
     * Gets the value of the specified node
     * 
     * @param path XPath to node
     * @return node value or null if not found
     */
    public String get(String path) {
        return get(path, null);
    }

    /**
     * Gets the value of the specified node, with a specified default if the not
     * was not found
     * 
     * @param path XPath to node
     * @param defaultValue value to return if the node was not found
     * @return node value or defaultValue if not found
     */
    public String get(String path, String defaultValue) {
        Object valueNode = null;
        try {
            valueNode = getJXPath().getValue(path);
        } catch (Exception e) {
        }
        String value = valueNode == null ? defaultValue : valueNode.toString();
        return StrSubstitutor.replaceSystemProperties(value);
    }

    /**
     * Get the value of specified node, with a specified default if it's not
     * found
     * 
     * @param path
     * @param defaultValue
     * @return node value or default Value if not found WITHOUT string
     *         substitution
     */
    public String getPlainText(String path, String defaultValue) {
        Object valueNode = null;
        try {
            valueNode = getJXPath().getValue(path);
        } catch (Exception e) {
        }
        String value = valueNode == null ? defaultValue : valueNode.toString();
        return value;
    }

    /**
     * Gets values of the specified node as a list. Use this method for JSON
     * arrays.
     * 
     * @param path XPath to node
     * @return value list, possibly empty
     */
    public List getList(String path) {
        List valueList = new ArrayList();
        Iterator valueIterator = getJXPath().iterate(path);
        while (valueIterator.hasNext()) {
            Object value = valueIterator.next();
            valueList.add(value instanceof String ? StrSubstitutor
                    .replaceSystemProperties(value) : value);
        }
        return valueList;
    }

    /**
     * Gets a map of the child nodes of the specified node
     * 
     * @param path XPath to node
     * @return node map, possibly empty
     */
    public Map getMap(String path) {
        Map valueMap = new LinkedHashMap();
        Object valueNode = getJXPath().getValue(path);
        if (valueNode instanceof Map) {
            Map map = (Map) valueNode;
            for (String key : map.keySet()) {
                Object value = map.get(key);
                if (value instanceof String) {
                    valueMap.put(key,
                            StrSubstitutor.replaceSystemProperties(value));
                } else {
                    valueMap.put(key, value);
                }
            }
        }
        return valueMap;
    }

    /**
     * Get list of JsonConfigHelper of the specified node
     * 
     * @param path XPath to node
     * @return node list, possibly empty
     */
    public List getJsonList(String path) {
        List list = getList(path);
        List newList = new ArrayList();
        for (Object obj : list) {
            if (obj instanceof Map) {
                newList.add(new JsonConfigHelper(((Map) obj)));
            }
        }
        return newList;
    }

    /**
     * Get the JSON Map of the specified node
     * 
     * @param path XPath to node
     * @return node map, possibly empty
     */
    public Map getJsonMap(String path) {
        Map jsonMap = new LinkedHashMap();

        Object valueNode;
        try {
            valueNode = getJXPath().getValue(path);
        } catch (Exception e) {
            return null;
        }
        if (valueNode instanceof Map) {
            Map map = (Map) valueNode;
            for (String key : map.keySet()) {
                jsonMap.put(
                        key,
                        new JsonConfigHelper((Map) map.get(key)));
            }
        }
        return jsonMap;
    }

    /**
     * Set map with its child
     * 
     * @param path XPath to node
     * @param map node Map
     */
    public void setMap(String path, Map map) {
        try {
            getJXPath().setValue(path, map);
        } catch (Exception e) {
            getJXPath().createPathAndSetValue(path, map);
        }
    }

    /**
     * Set Multiple nested map on the specified path
     * 
     * @param path XPath to node
     * @param json node Map
     */
    public void setMultiMap(String path, Map json) {
        for (String key : json.keySet()) {
            if (json.get(key) instanceof Map) {
                setMultiMap(path + "/" + key,
                        (Map) json.get(key));
            } else {
                getJXPath().createPathAndSetValue(path + "/" + key,
                        json.get(key));
            }
        }
    }

    /**
     * Set Map on the specified path
     * 
     * @param path XPath to node
     * @param map node Map
     */
    public void setJsonMap(String path, Map map) {
        for (String key : map.keySet()) {
            JsonConfigHelper json = map.get(key);
            try {
                getJXPath().setValue(path + "/" + key, json.getMap("/"));
            } catch (Exception e) {
                getJXPath().createPathAndSetValue(path + "/" + key,
                        json.getMap("/"));
            }
        }
    }

    /**
     * Set Map on the specified path
     * 
     * @param path XPath to node
     * @param map node Map
     */
    public void setJsonList(String path, List jsonList) {
        Object valueNode = getJXPath().getValue(path);
        List valueList = new ArrayList();
        if (valueNode == null) {
            getJXPath().createPathAndSetValue(path, valueList);
        }
        for (JsonConfigHelper json : jsonList) {
            valueList.add(json.getMap("/"));
        }
    }

    /**
     * Gets a map of the child (and the 2nd level children) nodes of the
     * specified node
     * 
     * @param path XPath to node
     * @return node map, possibly empty
     */
    public Map getMapWithChild(String path) {
        Map valueMap = new LinkedHashMap();
        Object valueNode = getJXPath().getValue(path);
        if (valueNode instanceof Map) {
            Map map = (Map) valueNode;
            for (String key : map.keySet()) {
                valueMap.put(key, map.get(key));
            }
        }
        return valueMap;
    }

    /**
     * Remove specified path from json
     * 
     * @param path to node
     */
    public void removePath(String path) {
        if (get(path) != null) {
            getJXPath().removePath(path);
        }
    }

    /**
     * Sets the value of the specified node. If the node doesn't exist it is
     * created.
     * 
     * @param path XPath to node
     * @param value value to set
     */
    public void set(String path, String value) {
        try {
            getJXPath().setValue(path, value);
        } catch (Exception e) {
            getJXPath().createPathAndSetValue(path, value);
        }
    }

    /**
     * Move node from one path to another path in the JSON
     * 
     * @param source XPath to node
     * @param dest XPath to node
     */
    public void move(String source, String dest) {
        Object copyValue = getJXPath().getValue(source);
        getJXPath().removePath(source);
        try {
            getJXPath().setValue(dest, copyValue);
        } catch (Exception e) {
            getJXPath().createPathAndSetValue(dest, copyValue);
        }
    }

    /**
     * Move node to a node before the specified node
     * 
     * @param path XPath to node
     * @param refPath XPath to node
     */
    public void moveBefore(String path, String refPath) {
        Map newMap = new LinkedHashMap();
        Object node = getJXPath().getValue(path);
        Object refNode = getJXPath().getValue(refPath);
        Map refParent = getMap(refPath + "/..");
        for (String key : refParent.keySet()) {
            // find the reference node
            Object value = refParent.get(key);
            if (value.equals(refNode)) {
                // and insert the node to be moved before it
                Map parent = getMap(path + "/..");
                for (String nodeKey : parent.keySet()) {
                    if (parent.get(nodeKey).equals(node)) {
                        newMap.put(nodeKey, node);
                        getJXPath().removePath(path);
                        break;
                    }
                }
            }
            if (!value.equals(node)) {
                // insert existing nodes
                newMap.put(key, value);
            }
        }
        setMap(refPath.substring(0, refPath.lastIndexOf('/')), newMap);
    }

    /**
     * Move node to a node after the specified node
     * 
     * @param path XPath to node
     * @param refPath XPath to node
     */
    public void moveAfter(String path, String refPath) {
        Map newMap = new LinkedHashMap();
        Object node = getJXPath().getValue(path);
        Object refNode = getJXPath().getValue(refPath);
        Map refParent = getMap(refPath + "/..");
        for (String key : refParent.keySet()) {
            // find the reference node
            Object value = refParent.get(key);
            if (!value.equals(node)) {
                // insert existing nodes
                newMap.put(key, value);
            }
            if (value.equals(refNode)) {
                // and insert the node to be moved after it
                Map parent = getMap(path + "/..");
                for (String nodeKey : parent.keySet()) {
                    if (parent.get(nodeKey).equals(node)) {
                        newMap.put(nodeKey, node);
                        getJXPath().removePath(path);
                        break;
                    }
                }
            }
        }
        setMap(refPath.substring(0, refPath.lastIndexOf('/')), newMap);
    }

    /**
     * Serialises the current state of the JSON configuration to the specified
     * writer. By default this doesn't use a pretty printer.
     * 
     * @param writer a writer
     * @throws IOException if there was an error writing the configuration
     */
    public void store(Writer writer) throws IOException {
        store(writer, false);
    }

    /**
     * Serialises the current state of the JSON configuration to the specified
     * writer. The output can be set to be pretty printed if required.
     * 
     * @param writer a writer
     * @param pretty use pretty printer
     * @throws IOException if there was an error writing the configuration
     */
    public void store(Writer writer, boolean pretty) throws IOException {
        JsonGenerator generator = new JsonFactory().createJsonGenerator(writer);
        if (pretty) {
            generator.useDefaultPrettyPrinter();
        }
        new ObjectMapper().writeValue(generator, rootNode);
    }

    /**
     * Convert Json to String
     * 
     * @return Json configuration in String
     */
    @Override
    public String toString() {
        return toString(true);
    }

    /**
     * Convert Json to String
     * 
     * @param pretty state to format the layout of the Json configuration file
     * @return Json configuration in String
     */
    public String toString(boolean pretty) {
        String json = "{}";
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try {
            store(new OutputStreamWriter(out, "UTF-8"), pretty);
            json = out.toString("UTF-8");
        } catch (IOException e) {
        }
        return json;
    }
}