package com.googlecode.jsonplugin;
import com.googlecode.jsonplugin.annotations.JSON;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.math.BigDecimal;
import java.math.BigInteger;
/**
* Isolate the process of populating JSON objects from the Interceptor class itself.
*/
public class JSONPopulator {
private static final Log log = LogFactory.getLog(JSONPopulator.class);
private String dateFormat = JSONUtil.RFC3339_FORMAT;
public JSONPopulator() {
}
public JSONPopulator(String dateFormat) {
this.dateFormat = dateFormat;
}
public String getDateFormat() {
return dateFormat;
}
public void setDateFormat(String dateFormat) {
this.dateFormat = dateFormat;
}
@SuppressWarnings("unchecked")
public void populateObject(Object object, final Map elements)
throws IllegalAccessException, InvocationTargetException, NoSuchMethodException,
IntrospectionException, IllegalArgumentException, JSONException,
InstantiationException {
Class clazz = object.getClass();
BeanInfo info = Introspector.getBeanInfo(clazz);
PropertyDescriptor[] props = info.getPropertyDescriptors();
//iterate over class fields
for (int i = 0; i < props.length; ++i) {
PropertyDescriptor prop = props[i];
String name = prop.getName();
if (elements.containsKey(name)) {
Object value = elements.get(name);
Method method = prop.getWriteMethod();
if (method != null) {
JSON json = method.getAnnotation(JSON.class);
if ((json != null) && !json.deserialize()) {
continue;
}
//use only public setters
if (Modifier.isPublic(method.getModifiers())) {
Class[] paramTypes = method.getParameterTypes();
Type[] genericTypes = method.getGenericParameterTypes();
if (paramTypes.length == 1) {
Object convertedValue = this.convert(paramTypes[0], genericTypes[0], value, method);
method.invoke(object, new Object[]{convertedValue});
}
}
}
}
}
}
@SuppressWarnings("unchecked")
public Object convert(Class clazz, Type type, Object value, Method method)
throws IllegalArgumentException, JSONException, IllegalAccessException,
InvocationTargetException, InstantiationException, NoSuchMethodException,
IntrospectionException {
if (value == null) {
//if it is a java primitive then get a default value, otherwise leave it as null
return clazz.isPrimitive() ? convertPrimitive(clazz, value, method) : null;
}
else if (isJSONPrimitive(clazz))
return convertPrimitive(clazz, value, method);
else if (Collection.class.isAssignableFrom(clazz))
return convertToCollection(clazz, type, value, method);
else if (Map.class.isAssignableFrom(clazz))
return convertToMap(clazz, type, value, method);
else if (clazz.isArray())
return convertToArray(clazz, type, value, method);
else if (value instanceof Map) {
// nested bean
Object convertedValue = clazz.newInstance();
this.populateObject(convertedValue, (Map) value);
return convertedValue;
} else if (BigDecimal.class.equals(clazz)) {
return new BigDecimal(value != null ? value.toString() : "0");
} else if (BigInteger.class.equals(clazz)) {
return new BigInteger(value != null ? value.toString() : "0");
}
else
throw new JSONException("Incompatible types for property " + method.getName());
}
private static boolean isJSONPrimitive(Class clazz) {
return clazz.isPrimitive() || clazz.equals(String.class) ||
clazz.equals(Date.class) || clazz.equals(Boolean.class) ||
clazz.equals(Byte.class) || clazz.equals(Character.class) ||
clazz.equals(Double.class) || clazz.equals(Float.class) ||
clazz.equals(Integer.class) || clazz.equals(Long.class) ||
clazz.equals(Short.class) || clazz.equals(Locale.class) ||
clazz.isEnum();
}
@SuppressWarnings("unchecked")
private Object convertToArray(Class clazz, Type type, Object value, Method accessor)
throws JSONException, IllegalArgumentException, IllegalAccessException,
InvocationTargetException, InstantiationException, NoSuchMethodException,
IntrospectionException {
if (value == null)
return null;
else if (value instanceof List) {
Class arrayType = clazz.getComponentType();
List values = (List) value;
Object newArray = Array.newInstance(arrayType, values.size());
//create an object for each element
for (int j = 0; j < values.size(); j++) {
Object listValue = values.get(j);
if (arrayType.equals(Object.class)) {
//Object[]
Array.set(newArray, j, listValue);
} else if (isJSONPrimitive(arrayType)) {
//primitive array
Array.set(newArray, j, this.convertPrimitive(arrayType, listValue,
accessor));
} else if (listValue instanceof Map) {
//array of other class
Object newObject = null;
if (Map.class.isAssignableFrom(arrayType)) {
newObject = convertToMap(arrayType, type, listValue, accessor);
} else if (List.class.isAssignableFrom(arrayType)) {
newObject = convertToCollection(arrayType, type, listValue, accessor);
} else {
newObject = arrayType.newInstance();
this.populateObject(newObject, (Map) listValue);
}
Array.set(newArray, j, newObject);
} else
throw new JSONException("Incompatible types for property " +
accessor.getName());
}
return newArray;
} else
throw new JSONException("Incompatible types for property " +
accessor.getName());
}
@SuppressWarnings("unchecked")
private Object convertToCollection(Class clazz, Type type, Object value, Method accessor)
throws JSONException, IllegalArgumentException, IllegalAccessException,
InvocationTargetException, InstantiationException, NoSuchMethodException,
IntrospectionException {
if (value == null)
return null;