org.apache.wink.json4j.internal.BeanSerializer Maven / Gradle / Ivy
The newest version!
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.wink.json4j.internal;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.wink.json4j.JSONArray;
import org.apache.wink.json4j.JSONArtifact;
import org.apache.wink.json4j.JSONException;
import org.apache.wink.json4j.JSONObject;
/**
* Private class to introspect a JavaBean and convert it to its respective JSON type.
*/
public class BeanSerializer {
/**
* This method inspects a bean and converts it to its corrisponding JSON.
* This function expects non-basic types (no String, Number, etc).
* @param obj The Object to inspect.
* @param includeSuperclass Boolean indicating if superclass properties should be included in the output JSON.
* @return An instance of the JSONArtifact that best represents the data in this JavaBean
* @throws IllegalArgumentException Thrown if input type is a String, Number, Boolean, etc.
* @throws JSONException Thrown if a JSON conversion error occurs.
*/
public static JSONArtifact toJson(Object obj, boolean includeSuperclass) throws IllegalArgumentException, JSONException {
JSONArtifact ja = null;
if (obj != null) {
Class clazz = obj.getClass();
if (String.class == clazz) {
throw new IllegalArgumentException("Class was String type, not a Javabean.");
} else if (Boolean.class == clazz) {
throw new IllegalArgumentException("Class was Boolean type, not a Javabean.");
} else if (Number.class.isAssignableFrom(clazz)) {
throw new IllegalArgumentException("Class was Number type, not a Javabean.");
} else if (JSONObject.class.isAssignableFrom(clazz)) {
ja = (JSONObject)obj;
} else if (JSONArray.class.isAssignableFrom(clazz)) {
ja = (JSONArray)obj;
} else if (Map.class.isAssignableFrom(clazz)) {
ja = new JSONObject((Map)obj);
} else if (Collection.class.isAssignableFrom(clazz)) {
ja = new JSONArray((Collection)obj);
} else if (clazz.isArray()) {
ja = new JSONArray((Object[])obj);
}
else {
//TODO: Bean introspection time.
ja = introspectBean(obj,includeSuperclass, new ArrayList());
}
}
return ja;
}
/**
* Internal method for introspecting a bean and converting it to a JSONAble type.
* @param obj The Object to inspect.
* @param includeSuperclass Boolean indicating if superclass properties should be included in the output JSON.
* @param parsedObjects An array list of objects traversed to try and avoid loops in graphs
* @throws JSONException Thrown if a JSON conversion error occurs.
*/
private static JSONArtifact introspectBean(Object obj, boolean includeSuperclass, ArrayList parsedObjects) throws JSONException {
JSONObject ja = null;
boolean found = false;
for (int i = 0; i < parsedObjects.size(); i++) {
// Check and try to avoid graphs by parsing the same
// object multiple times, which may indicate a cycle.
Object possibleObj = parsedObjects.get(i);
if (possibleObj != null && obj == possibleObj) {
found = true;
break;
}
}
if (!found) {
parsedObjects.add(obj);
ja = new JSONObject();
Class clazz = obj.getClass();
ja.put("_type", "JavaClass");
ja.put("_classname", clazz.getName());
// Fetch all the methods, based on including superclass or not.
Method[] methods = null;
if (includeSuperclass) {
methods = clazz.getMethods();
} else {
methods = clazz.getDeclaredMethods();
}
if (methods != null && methods.length > 0) {
for (int i = 0; i < methods.length; i++) {
Method m = methods[i];
// Include all superclass methods if requested,
// or only those that are part of the actual declaring class.
String mName = m.getName();
Class[] types = m.getParameterTypes();
// Getter, so we can assume this accesses a field.
if (mName.startsWith("get") && mName.length() > 3 && (types == null || types.length == 0)) {
String attr = mName.substring(3, mName.length());
attr = Character.toLowerCase(attr.charAt(0)) + attr.substring(1, attr.length());
try {
Object val = m.invoke(obj);
if (val == null) {
ja.put(attr, (Object)null);
} else {
Class vClazz = val.getClass();
if (String.class == vClazz) {
ja.put(attr, val);
} else if (Boolean.class == vClazz) {
ja.put(attr, val);
} else if (Class.class == vClazz) {
ja.put(attr, ((Class)val).getName());
} else if (Number.class.isAssignableFrom(vClazz)) {
ja.put(attr, val);
} else if (JSONObject.class.isAssignableFrom(vClazz)) {
ja.put(attr, val);
} else if (JSONArray.class.isAssignableFrom(vClazz)) {
ja.put(attr, val);
} else if (Map.class.isAssignableFrom(vClazz)) {
ja.put(attr, new JSONObject((Map)val));
} else if (Collection.class.isAssignableFrom(vClazz)) {
ja.put(attr, new JSONArray((Collection)obj));
} else {
if (val != obj) {
// Try to avoid processing references to itself.
ja.put(attr, introspectBean(val, includeSuperclass, parsedObjects));
}
}
}
} catch (Exception ex) {
ja.put(attr, (Object)null);
}
}
}
}
}
return ja;
}
/**
* Method to try to convert a JSONObject back into its class representation.
* @param jo The JSONObject to try to convert back to a class.
* @throws NullPointerException Thrown if jo is null.
* @throws JSONException Thrown if the JSON cannot be converted to a java class.
* @return An instance of a Java Object that corrisponds to the type in _classname
*/
public static Object fromJson(JSONObject jo) throws NullPointerException, JSONException {
Object obj = null;
if (jo == null) {
throw new NullPointerException("Input JSONObject cannot be null");
} else {
if (jo.get("_classname") != null && "JavaClass".equals(jo.get("_type"))) {
// Okay, we can try to process this back to a class.
try {
String cName = (String)jo.get("_classname");
Class clazz = Class.forName(cName);
if (clazz != null) {
Method[] methods = clazz.getMethods();
obj = clazz.newInstance();
Iterator keys = jo.keys();
if (keys != null) {
while (keys.hasNext()) {
String key = (String)keys.next();
// Ignore our specially named attributes.
if(key != null && !key.equals("_classname") && !key.equals("_type")){
Method m = null;
String setter = "set" + Character.toUpperCase(key.charAt(0)) + key.substring(1, key.length());
Object val = jo.get(key);
if (val != null) {
Class vClazz = val.getClass();
// Try to locate the best matching method.
if (String.class == vClazz) {
// Handle locating a String method.
for (int i = 0; i < methods.length; i++) {
Method tM = methods[i];
if(tM.getName().equals(setter)){
Class[] mParms = tM.getParameterTypes();
if (mParms != null && mParms.length == 1) {
// Possible method, lets check the type.
Class c = mParms[0];
if (c == vClazz) {
// We have a String match, stop here.
m = tM;
break;
}
}
}
}
} else if (Boolean.class == vClazz) {
// Handle locating a boolean method.
for (int i = 0; i < methods.length; i++) {
Method tM = methods[i];
if(tM.getName().equals(setter)){
Class[] mParms = tM.getParameterTypes();
if (mParms != null && mParms.length == 1) {
// Possible method, lets check the type.
Class c = mParms[0];
if (c == vClazz || Boolean.TYPE == c) {
// We have a boolean match, stop here.
m = tM;
break;
}
}
}
}
} else if (Number.class.isAssignableFrom(vClazz)) {
// Handle locating the best-matching number method.
if(Double.class.isAssignableFrom(vClazz)){
for (int i = 0; i < methods.length; i++) {
Method tM = methods[i];
if(tM.getName().equals(setter)){
Class[] mParms = tM.getParameterTypes();
if (mParms != null && mParms.length == 1) {
// Possible method, lets check the type.
Class c = mParms[0];
if (c == Double.class || Double.TYPE == c) {
// We have a double match, stop here.
m = tM;
break;
}
}
}
}
if(m == null){
// Look for a float assigner and if found, set the value to a Float
// type.
for (int i = 0; i < methods.length; i++) {
Method tM = methods[i];
if(tM.getName().equals(setter)){
Class[] mParms = tM.getParameterTypes();
if (mParms != null && mParms.length == 1) {
// Possible method, lets check the type.
Class c = mParms[0];
if (c == Float.class || Float.TYPE == c) {
// We have a Float match, stop here.
m = tM;
val = new Float(((Number)val).floatValue());
break;
}
}
}
}
}
} else if (Float.class.isAssignableFrom(vClazz)) {
for (int i = 0; i < methods.length; i++) {
Method tM = methods[i];
if(tM.getName().equals(setter)){
Class[] mParms = tM.getParameterTypes();
if (mParms != null && mParms.length == 1) {
// Possible method, lets check the type.
Class c = mParms[0];
if (c == Float.class || Float.TYPE == c) {
// We have a Float match, stop here.
m = tM;
break;
}
}
}
}
if(m == null){
// Look for a double assigner and if found, set the value to a Float
// type.
for (int i = 0; i < methods.length; i++) {
Method tM = methods[i];
if(tM.getName().equals(setter)){
Class[] mParms = tM.getParameterTypes();
if (mParms != null && mParms.length == 1) {
// Possible method, lets check the type.
Class c = mParms[0];
if (c == Double.class || Double.TYPE == c) {
// We have a Double match, stop here.
m = tM;
val = new Double(((Number)val).doubleValue());
break;
}
}
}
}
}
} else if (Long.class.isAssignableFrom(vClazz)){
for (int i = 0; i < methods.length; i++) {
Method tM = methods[i];
if(tM.getName().equals(setter)){
Class[] mParms = tM.getParameterTypes();
if (mParms != null && mParms.length == 1) {
// Possible method, lets check the type.
Class c = mParms[0];
if (c == Long.class || Long.TYPE == c) {
// We have a Long match, stop here.
m = tM;
break;
}
}
}
}
if(m == null){
// Look for a integer assigner and if found, set the value to a int
// type.
for (int i = 0; i < methods.length; i++) {
Method tM = methods[i];
if(tM.getName().equals(setter)){
Class[] mParms = tM.getParameterTypes();
if (mParms != null && mParms.length == 1) {
// Possible method, lets check the type.
Class c = mParms[0];
if (c == Integer.class || Integer.TYPE == c) {
// We have an int match, stop here.
m = tM;
val = new Integer(((Number)val).intValue());
break;
}
}
}
}
}
if(m == null){
// Look for a short assigner and if found, set the value to a int
// type.
for (int i = 0; i < methods.length; i++) {
Method tM = methods[i];
if(tM.getName().equals(setter)){
Class[] mParms = tM.getParameterTypes();
if (mParms != null && mParms.length == 1) {
// Possible method, lets check the type.
Class c = mParms[0];
if (c == Short.class || Short.TYPE == c) {
// We have a short match, stop here.
m = tM;
val = new Short(((Number)val).shortValue());
break;
}
}
}
}
}
} else if (Integer.class.isAssignableFrom(vClazz)){
for (int i = 0; i < methods.length; i++) {
Method tM = methods[i];
if(tM.getName().equals(setter)){
Class[] mParms = tM.getParameterTypes();
if (mParms != null && mParms.length == 1) {
// Possible method, lets check the type.
Class c = mParms[0];
if (c == Integer.class || Integer.TYPE == c) {
// We have an int match, stop here.
m = tM;
break;
}
}
}
}
if(m == null){
// Look for a Long assigner and if found, set the value to a int
// type.
for (int i = 0; i < methods.length; i++) {
Method tM = methods[i];
if(tM.getName().equals(setter)){
Class[] mParms = tM.getParameterTypes();
if (mParms != null && mParms.length == 1) {
// Possible method, lets check the type.
Class c = mParms[0];
if (c == Long.class || Long.TYPE == c) {
// We have a long match, stop here.
m = tM;
val = new Long(((Number)val).longValue());
break;
}
}
}
}
}
if(m == null){
// Look for a short assigner and if found, set the value to a int
// type.
for (int i = 0; i < methods.length; i++) {
Method tM = methods[i];
if(tM.getName().equals(setter)){
Class[] mParms = tM.getParameterTypes();
if (mParms != null && mParms.length == 1) {
// Possible method, lets check the type.
Class c = mParms[0];
if (c == Short.class || Short.TYPE == c) {
// We have a short match, stop here.
m = tM;
val = new Short(((Number)val).shortValue());
break;
}
}
}
}
}
} else if (Short.class.isAssignableFrom(vClazz)){
for (int i = 0; i < methods.length; i++) {
Method tM = methods[i];
if(tM.getName().equals(setter)){
Class[] mParms = tM.getParameterTypes();
if (mParms != null && mParms.length == 1) {
// Possible method, lets check the type.
Class c = mParms[0];
if (c == Short.class || Short.TYPE == c) {
// We have a short match, stop here.
m = tM;
break;
}
}
}
}
if(m == null){
// Look for a integer assigner and if found, set the value to a int
// type.
for (int i = 0; i < methods.length; i++) {
Method tM = methods[i];
if(tM.getName().equals(setter)){
Class[] mParms = tM.getParameterTypes();
if (mParms != null && mParms.length == 1) {
// Possible method, lets check the type.
Class c = mParms[0];
if (c == Integer.class || Integer.TYPE == c) {
// We have a int match, stop here.
m = tM;
val = new Integer(((Number)val).intValue());
break;
}
}
}
}
}
if(m == null){
// Look for a Long assigner and if found, set the value to a int
// type.
for (int i = 0; i < methods.length; i++) {
Method tM = methods[i];
if(tM.getName().equals(setter)){
Class[] mParms = tM.getParameterTypes();
if (mParms != null && mParms.length == 1) {
// Possible method, lets check the type.
Class c = mParms[0];
if (c == Long.class || Long.TYPE == c) {
// We have a long match, stop here.
m = tM;
val = new Long(((Number)val).longValue());
break;
}
}
}
}
}
}
} else if (JSONArray.class.isAssignableFrom(vClazz)) {
// Handle determining a collection type to set, which means
// we need to find a Collection setter.
for (int i = 0; i < methods.length; i++) {
Method tM = methods[i];
if(tM.getName().equals(setter)){
Class[] mParms = tM.getParameterTypes();
if (mParms != null && mParms.length == 1) {
// Possible method, lets check the type.
Class c = mParms[0];
if (List.class.isAssignableFrom(c)) {
// We have a Collections match, so we'll use it.
m = tM;
if(c != JSONArray.class){
// Convert it. Whee.
List list = (List)c.newInstance();
JSONArray array = (JSONArray)val;
for (int j = 0; j < array.length(); j++) {
// Convert each type as needed.
Object aVal = array.get(j);
if(aVal != null){
Class aVClazz = aVal.getClass();
if(Number.class.isAssignableFrom(aVClazz) ||
Boolean.class.isAssignableFrom(aVClazz) ||
String.class.isAssignableFrom(aVClazz)) {
list.add(aVal);
} else if (JSONObject.class.isAssignableFrom(aVClazz)) {
list.add(fromJson((JSONObject)aVal));
} else if (JSONObject.class.isAssignableFrom(aVClazz)) {
// Not sure what to do here!
}
} else {
list.add(null);
}
}
val = list;
}
break;
}
}
}
}
} else if (JSONObject.class.isAssignableFrom(vClazz)) {
// Handle determining a map type to set, if there is one.
JSONObject jObj = (JSONObject)val;
Class vC = val.getClass();
if(jObj.get("_classname") != null && "JavaClass".equals(jObj.get("_type"))){
val = fromJson(jObj);
vC = val.getClass();
}
// Handle locating a boolean method.
for (int i = 0; i < methods.length; i++) {
Method tM = methods[i];
if(tM.getName().equals(setter)){
Class[] mParms = tM.getParameterTypes();
if (mParms != null && mParms.length == 1) {
// Possible method, lets check the type.
Class c = mParms[0];
if (c.isAssignableFrom(vC)) {
// We have setter for the conversion.
m = tM;
break;
}
}
}
}
} else {
// Dunno?
throw new JSONException("Unknown type: [" + vClazz.getName() + "]");
}
} else {
try {
m = clazz.getMethod(setter); // FIXME: [rfeng] This doesn't make much sense
} catch (NoSuchMethodException nmex){
// Ignore, no setter.
}
}
if (m != null) {
m.invoke(obj, new Object[] { val });
}
}
}
}
} else {
throw new JSONException("Could not locate class: [" + cName + "]");
}
} catch (Exception ex) {
if (ex instanceof JSONException) {
throw (JSONException)ex;
} else {
JSONException jex = new JSONException("Error in converting JSON to Java Class");
jex.initCause(ex);
throw jex;
}
}
} else {
throw new JSONException("Provided JSONObject does not contain attributes '_classname' or '_type'");
}
}
return obj;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy