org.glassfish.admingui.common.util.JSONUtil Maven / Gradle / Ivy
Show all versions of payara-micro Show documentation
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2010-2013 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.glassfish.admingui.common.util;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.text.StringCharacterIterator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class provides basic JSON encoding / decoding. It has 2 primary
* methods that are of interest. The first allows you to encode a Java
* Object into JSON. The other allows you to create a Java data
* structure from a JSON String. See:
*
* - {@link #jsonToJava(String json)}
* - {@link #javaToJSON(Object obj, int depth)}
*/
public class JSONUtil {
private static final String ABORT_PROCESSING = "____EnD___";
private static final String COLON = "____CoLoN___";
private static final String COMMA = "____CoMmA___";
private static final String NULL = "____NuLl___";
/**
* This method returns a Java representation of the given JSON
* String. The Java data structure created will be created using
* Map's, String's, Long's, Float's, Boolean's, and List's as
* specified by the JSON String.
*/
public static Object jsonToJava(String json) {
return replaceSpecial(jsonToJava(new JsonChars(json)));
}
/**
* This method attempts to convert the given Object into a JSON String
* to given depth. If -1 (or lower) is supplied for depth, it will
* walk upto a default depth of 10 levels of the given Object. If 0
* is supplied, it will simply return "". 1 will encode the current
* Object, but no children. 2 will encode the given Object and its
* direct children (if any), and so on.
*
* Strings, Longs, Float, and primitives are considered to not have
* child Objects. Objects which have a public no-argument getXYZ()
* method are considered to be child Objects. Maps and Collections
* will be walked.
*/
public static String javaToJSON(Object obj, int depth) {
if (depth == 0) {
// Make sure we do nothing if told to do nothing...
return "";
} else if (depth == -1) {
// To prevent recursion...
depth = 10;
}
String value = "";
if (obj == null) {
value = "null";
} else if (obj instanceof String) {
String chStr;
int len;
StringCharacterIterator it =
new StringCharacterIterator((String) obj);
char ch = it.first();
StringBuilder builder =
new StringBuilder(((String) obj).length() << 2);
builder.append("\"");
while (ch != StringCharacterIterator.DONE) {
switch (ch) {
case '\t':
builder.append("\\t");
break;
case '\n':
builder.append("\\n");
break;
case '\r':
builder.append("\\r");
break;
case '\b':
builder.append("\\b");
break;
case '\f':
builder.append("\\f");
break;
case '&':
case '<':
case '>':
case '(':
case ')':
case '{':
case '}':
case ':':
case '/':
case '\\':
case '\'':
case '"':
builder.append("\\");
builder.append(ch);
break;
default:
// Check if we should unicode escape this...
if ((ch > 0x7e) || (ch < 0x20)) {
builder.append("\\u");
chStr = Integer.toHexString(ch);
len = chStr.length();
for (int idx=4; idx > len; idx--) {
// Add leading 0's
builder.append('0');
}
builder.append(chStr);
} else {
builder.append(ch);
}
break;
}
ch = it.next();
}
builder.append("\"");
value = builder.toString();
} else if ((obj instanceof Boolean) || (obj instanceof Number)) {
value = obj.toString();
} else if (obj instanceof Object[]) {
StringBuilder builder = new StringBuilder("[");
boolean first = true;
for (Object element : ((Object []) obj)) {
if (first) {
first = false;
} else {
builder.append(',');
}
if (depth == 1) {
// Treat as String, but don't try to go deeper...
builder.append(javaToJSON(element.toString(), 1));
} else {
// Recurse...
builder.append(javaToJSON(element, depth-1));
}
}
builder.append("]");
value = builder.toString();
} else if (obj instanceof Map) {
StringBuilder builder = new StringBuilder("{");
String key;
boolean first = true;
Map map = ((Map) obj);
Iterator it = map.keySet().iterator();
while (it.hasNext()) {
if (first) {
first = false;
} else {
builder.append(',');
}
key = it.next().toString();
builder.append(javaToJSON(key, 1) + ":");
if (depth == 1) {
// Treat as String, but don't try to go deeper...
builder.append(javaToJSON(map.get(key).toString(), 1));
} else {
// Recurse...
builder.append(javaToJSON(map.get(key), depth-1));
}
}
builder.append("}");
value = builder.toString();
} else if (obj instanceof Collection) {
StringBuilder builder = new StringBuilder("[");
boolean first = true;
Iterator it = ((Collection) obj).iterator();
while (it.hasNext()) {
if (first) {
first = false;
} else {
builder.append(',');
}
if (depth == 1) {
// Treat as String, but don't try to go deeper...
builder.append(javaToJSON(it.next().toString(), 1));
} else {
// Recurse...
builder.append(javaToJSON(it.next(), depth-1));
}
}
builder.append("]");
value = builder.toString();
} else {
// Object
StringBuilder builder = new StringBuilder("{");
String methodName;
Object result;
boolean first = true;
Iterator it = getGetters(obj).iterator();
while (it.hasNext()) {
if (first) {
first = false;
} else {
builder.append(',');
}
methodName = it.next();
// Drop "get"...
builder.append(javaToJSON(methodName.substring(3), 1) + ":");
result = invokeGetter(obj, methodName);
if ((result != null) && (depth == 1)) {
// Treat as String, but don't try to go deeper...
builder.append(javaToJSON(result.toString(), 1));
} else {
// Recurse...
builder.append(javaToJSON(result, depth-1));
}
}
builder.append("}");
value = builder.toString();
}
return value;
}
/**
* This method invokes a getter on the given object.
*
* NOTE: I found a VERY similar method defined in IntegrationPoint...
* at least I'm consistent. ;) These should probably be combined.
*/
private static Object invokeGetter(Object obj, String methodName) {
try {
return obj.getClass().getMethod(methodName).invoke(obj);
} catch (Exception ex) {
// Unable to execute it, return null...
return null;
}
}
/**
* This method returns the names of the public no-arg getters on the
* given Object.
*/
private static List getGetters(Object obj) {
List result = new ArrayList();
for (Method method : obj.getClass().getMethods()) {
if (method.getName().startsWith("get")
&& ((method.getModifiers() & Modifier.PUBLIC) != 0)
&& (method.getParameterTypes().length == 0)
&& (!method.getName().equals("getClass"))
&& (!method.getReturnType().getName().equals("void"))) {
result.add(method.getName());
}
}
return result;
}
/**
* This is the primary switching method which determines the context
* in which the processing should occur.
*/
private static Object jsonToJava(JsonChars json) {
Object value = null;
while (json.hasNext() && (value == null)) {
char ch = json.next();
switch (ch) {
case '{' :
value = readObject(json);
break;
case '[' :
value = readArray(json);
break;
case '}' :
case ']' :
if (json.isAtContextEnd()) {
// Stop processing
value = ABORT_PROCESSING;
} else {
throw new IllegalArgumentException("Expected '"
+ json.peekContextEnd() + "' but found '"
+ json.current() + "' instead!");
}
break;
case '-' :
case '0' :
case '1' :
case '2' :
case '3' :
case '4' :
case '5' :
case '6' :
case '7' :
case '8' :
case '9' :
value = readNumber(json);
break;
case '\'' :
case '"' :
value = readString(json);
break;
case 'T' :
case 't' :
value = readConstant(json, "true");
break;
case 'F' :
case 'f' :
value = readConstant(json, "false");
break;
case 'N' :
case 'n' :
value = readConstant(json, "null");
break;
case ' ' :
case '\t' :
case '\r' :
case '\n' :
case '\b' :
case '\f' :
// Ignore whitespace
break;
case ':' :
value = COLON;
break;
case ',' :
value = COMMA;
break;
default:
throw new IllegalArgumentException(
"Unexpected char '" + json.current() + "' near: " + json.getContext(30) + "!");
}
}
return value;
}
/**
* This method creates a HashMap to represent the JSON Object.
*/
private static Map readObject(JsonChars json) {
// Save the ending char...
json.pushContextEnd('}');
// Create the Map
Map map = new HashMap(10);
Object tmp = null;
Object key = null;
Object value = null;
while (!json.isAtContextEnd()) {
// Get the key
key = replaceSpecial(jsonToJava(json));
if (json.isAtContextEnd()) {
// Abort...
break;
}
if (!(key instanceof String)) {
throw new IllegalArgumentException(
"Object keys must be a String!");
}
// Get the Colon...
if (!(jsonToJava(json).equals(COLON))) {
throw new IllegalArgumentException(
"Object keys must be followed by a colon (:)!");
}
// Get the value
value = replaceSpecial(jsonToJava(json));
// Get the comma between properties (may also be context end)
tmp = jsonToJava(json);
if ( (!(tmp.equals(COMMA))) && !json.isAtContextEnd()) {
throw new IllegalArgumentException(
"Expected comma (,) or end curly brace (}), but found ("
+ tmp + ") instead! Near: (" + json.getContext(30) + ")");
}
// Add the value to the Map...
map.put((String) key, value);
}
// Remove the context end and return
json.popContextEnd();
return map;
}
/**
* This function will process a JSON string and convert it into
* an array.
*/
private static List