
com.googlecode.fascinator.common.JsonSimple Maven / Gradle / Ivy
/*
* The Fascinator - JSON Simple
* Copyright (C) 2011 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.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.text.StrSubstitutor;
import org.json.simple.JSONArray;
import org.json.simple.parser.ContainerFactory;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Introduction
*
* This class wraps objects and methods of the
* JSON.simple library.
*
*
* Purpose
* It provides the following functionality:
*
* - Basic retrieval methods for complicated paths / data structures.
* - Typed retrieval for String, Integer and Boolean data types.
* - Object retrieval from the JSON.simple API (JsonObject and JSONArray).
* - Static utility methods for common operations manipulating data structures.
* - The ability to use place holder string for runtime substitution with system properties.
*
*
* @author Greg Pendlebury
*/
public class JsonSimple {
/** Logging */
private static Logger log = LoggerFactory.getLogger(JsonSimple.class);
/** Holds this object's JSON */
private JsonObject jsonObject;
/** Hold the unmodified json object as parse(String) essentially throws away the JSONArray */
private JSONArray jsonArray;
/** Flag for system property substitution */
private boolean substitueProperties;
/**
* Creates an empty JSON object
*
* @throws IOException if there was an error during creation
*/
public JsonSimple() {
substitueProperties = true;
jsonObject = new JsonObject();
}
/**
* Creates a JSON object from the specified file
*
* @param jsonFile a JSON file
* @throws IOException if there was an error parsing or reading the file
*/
public JsonSimple(File jsonFile) throws IOException {
substitueProperties = true;
if (jsonFile == null) {
jsonObject = new JsonObject();
} else {
InputStream is = new FileInputStream(jsonFile);
String json = IOUtils.toString(is, "UTF-8");
is.close();
parse(json);
}
}
/**
* Creates a JSON object from the specified input stream
*
* @param jsonIn a JSON stream
* @throws IOException if there was an error parsing or reading the stream
*/
public JsonSimple(InputStream jsonIn) throws IOException {
substitueProperties = true;
if (jsonIn == null) {
jsonObject = new JsonObject();
} else {
// Stream the data into a string
parse(IOUtils.toString(jsonIn, "UTF-8"));
jsonIn.close();
}
}
/**
* Creates a JSON object from the specified string
*
* @param jsonIn a JSON string
* @throws IOException if there was an error parsing the string
*/
public JsonSimple(String jsonString) throws IOException {
substitueProperties = true;
if (jsonString == null) {
jsonObject = new JsonObject();
} else {
parse(jsonString);
}
}
/**
* Wrap a JsonObject in this class
*
* @param newJsonObject : The JsonObject to wrap
*/
public JsonSimple(JsonObject newJsonObject) {
substitueProperties = true;
if (newJsonObject == null) {
newJsonObject = new JsonObject();
}
jsonObject = newJsonObject;
}
/**
* Parse the provided JSON
*
* @param jsonString a JSON string
* @throws IOException if there was an error parsing the string
*/
private void parse(String jsonString) throws IOException {
JSONParser parser = new JSONParser();
ContainerFactory containerFactory = new ContainerFactory() {
@Override
public List> creatArrayContainer() {
return new JSONArray();
}
@Override
public Map, ?> createObjectContainer() {
return new JsonObject();
}
};
// Parse the String
Object object;
try {
object = parser.parse(jsonString, containerFactory);
} catch(ParseException pe) {
log.error("JSON Parse Error: ", pe);
throw new IOException(pe);
}
// Take a look at what we have now
if (object instanceof JsonObject) {
jsonObject = (JsonObject) object;
} else {
if (object instanceof JSONArray) {
// @TODO: the reasons as to why the original JSONArray is discarded has been lost in time - will need to determine this is by design
jsonArray = (JSONArray) object;
jsonObject = getFromArray((JSONArray) object);
} else {
log.error("Expected JsonObject or at least JSONArray, but" +
" found neither. Please check JSON syntax: '{}'",
jsonString);
jsonObject = null;
}
}
}
/**
*
* Set method for behaviour flag on substituting system properties during
* string retrieval.
*
*
*
* If set to true (default) strings of the form "{$system.property}"
* will be substituted for matching system properties during retrieval.
*
*
* @param newFlag : The new flag value to set for this behaviour
*/
public void setPropertySubstitution(boolean newFlag) {
substitueProperties = newFlag;
}
/**
* Find the first valid JsonObject in a JSONArray.
*
* @param array : The array to search
* @return JsonObject : A JSON object
* @throws IOException if there was an error
*/
private JsonObject getFromArray(JSONArray array) {
if (array.isEmpty()) {
log.warn("Found only empty array, starting new object");
return new JsonObject();
}
// Grab the first element
Object object = array.get(0);
if (object == null) {
log.warn("Null entry, starting new object");
return new JsonObject();
}
// Nested array, go deeper
if (object instanceof JSONArray) {
return getFromArray((JSONArray) object);
}
return (JsonObject) object;
}
/**
* Retrieve the given node from the provided object.
*
* @param path : An array of indeterminate length to use as the path
* @return JsonObject : The JSON representation
*/
private Object getNode(Object object, Object path) {
if (isArray(object)) {
try {
return ((JSONArray) object).get((Integer) path);
} catch(ArrayIndexOutOfBoundsException ex) {
return null;
}
}
if (isObject(object)) {
return ((JsonObject) object).get(path);
}
return null;
}
/**
* Return the JsonObject holding this object's JSON representation
*
* @return JsonObject : The JSON representation
*/
public JsonObject getJsonObject() {
return jsonObject;
}
/**
* Walk down the JSON nodes specified by the path and retrieve the target
* JSONArray.
*
* @param path : Variable length array of path segments
* @return JSONArray : The target node, or NULL if path invalid or not an
* array
*/
public JSONArray getArray(Object... path) {
Object object = getPath(path);
if (object instanceof JSONArray) {
return (JSONArray) object;
}
return null;
}
/**
* Walk down the JSON nodes specified by the path and retrieve the target
* JsonObject.
*
* @param path : Variable length array of path segments
* @return JsonObject : The target node, or NULL if path invalid or not an
* object
*/
public JsonObject getObject(Object... path) {
Object object = getPath(path);
if (object instanceof JsonObject) {
return (JsonObject) object;
}
return null;
}
/**
* Walk down the JSON nodes specified by the path and retrieve the target.
*
* @param path : Variable length array of path segments
* @return Object : The target node, or NULL if invalid
*/
public Object getPath(Object... path) {
Object object = jsonObject;
boolean valid = true;
for (Object node : path) {
if (isValidPath(object, node)) {
object = getNode(object, node);
} else {
valid = false;
break;
}
}
if (valid) {
return object;
}
return null;
}
/**
* Retrieve the Boolean value on the given path.
*
* IMPORTANT: The default value only applies if the path is
* not found. If a string on the path is found it will be considered
* false unless the value is 'true' (ignoring case). This is the
* default behaviour of the Boolean.parseBoolean() method.
*
* @param defaultValue : The fallback value to use if the path is
* invalid or not found
* @param path : An array of indeterminate length to use as the path
* @return Boolean : The Boolean value found on the given path, or null if
* no default provided
*/
public Boolean getBoolean(Boolean defaultValue, Object... path) {
Object object = getPath(path);
if (object == null) {
return defaultValue;
}
if (isNumber(object)) {
log.warn("getBoolean() : Integer value targeted. Expected Boolean");
return defaultValue;
}
if (object instanceof String) {
return Boolean.parseBoolean((String) object);
}
if (object instanceof Boolean) {
return (Boolean) object;
}
return null;
}
/**
* Retrieve the Integer value on the given path.
*
* @param defaultValue : The fallback value to use if the path is
* invalid or not found
* @param path : An array of indeterminate length to use as the path
* @return Integer : The Integer value found on the given path, or null if
* no default provided
*/
public Integer getInteger(Integer defaultValue, Object... path) {
Object object = getPath(path);
if (object == null) {
return defaultValue;
}
if (isNumber(object)) {
return makeNumber(object);
}
if (object instanceof String) {
try {
return Integer.parseInt((String) object);
} catch (NumberFormatException ex) {
log.warn("getInteger() : String is not a parsable Integer '{}'",
(String) object);
return defaultValue;
}
}
if (object instanceof Boolean) {
log.warn("getInteger() : Boolean value targeted. Expected Integer");
return defaultValue;
}
return null;
}
/**
* Retrieve the String value on the given path.
*
* @param defaultValue : The fallback value to use if the path is
* invalid or not found
* @param path : An array of indeterminate length to use as the path
* @return String : The String value found on the given path, or null if
* no default provided
*/
public String getString(String defaultValue, Object... path) {
String response = null;
Object object = getPath(path);
if (object == null) {
response = defaultValue;
} else {
}
if (isNumber(object)) {
response = Integer.toString(makeNumber(object));
}
if (object instanceof String) {
response = (String) object;
}
if (object instanceof Boolean) {
response = Boolean.toString((Boolean) object);
}
if (object instanceof JSONArray) {
jsonArray = (JSONArray)object;
if (jsonArray.size() == 1) {
Object value = jsonArray.get(0);
if (value instanceof String) {
response = (String)value;
}
}
if (! (response instanceof String)) {
log.warn("Unable to convert JSONArray: " + object + " to string.");
}
}
// Are we substituting system properites?
if (substitueProperties) {
response = StrSubstitutor.replaceSystemProperties(response);
}
return response;
}
/**
*
* Retrieve a list of Strings found on the given path. Note that this is a
* utility function, and not designed for data traversal. It will
* only retrieve Strings found on the provided node, and the node must be
* a JSONArray.
*
*
* @param path : An array of indeterminate length to use as the path
* @return List : A list of Strings, null if the node is not found
*/
public List getStringList(Object... path) {
Object target = getPath(path);
List response = new LinkedList();
if (isArray(target)) {
if (substitueProperties) {
List temp = JsonSimple.getStringList((JSONArray) target);
for (String string : temp) {
response.add(StrSubstitutor.replaceSystemProperties(string));
}
return response;
} else {
return JsonSimple.getStringList((JSONArray) target);
}
}
if (isString(target)) {
// Are we substituting system properites?
if (substitueProperties) {
response.add(StrSubstitutor.replaceSystemProperties((String) target));
} else {
response.add((String) target);
}
return response;
}
return null;
}
/**
*
* Retrieve a list of JsonSimple objects found on the given path. Note that
* this is a utility function, and not designed for data traversal. It
* will only retrieve valid JsonObjects found on the provided node,
* and wrap them in JsonSimple objects.
*
*
*
* Other objects found on that path will be ignored, and if the path itself
* is not a JSONArray or not found, the function will return NULL.
*
*
* @param path : An array of indeterminate length to use as the path
* @return List : A list of JSONSimple objects, or null
*/
public List getJsonSimpleList(Object... path) {
JSONArray array = getArray(path);
if (isArray(array)) {
return JsonSimple.toJavaList(array);
}
return null;
}
/**
*
* Retrieve a map of JsonSimple objects found on the given path. Note that
* this is a utility function, and not designed for data traversal. It
* will only retrieve valid JsonObjects found on the provided node,
* and wrap them in JsonSimple objects.
*
*
*
* Other objects found on that path will be ignored, and if the path itself
* is not a JsonObject or not found, the function will return NULL.
*
*
* @param path : An array of indeterminate length to use as the path
* @return Map : A map of JSONSimple objects, or null
*/
public Map getJsonSimpleMap(Object... path) {
JsonObject object = getObject(path);
if (isObject(object)) {
return JsonSimple.toJavaMap(object);
}
return null;
}
/**
*
* Search through the JSON for any nodes (at any depth) matching the
* requested name and return them. The returned List will be of type
* Object and require type interrogation for detailed use, but will be
* implemented as a LinkedList to preserve order.
*
*
* @param node : The node name we are looking for
* @return List