net.hasor.utils.json.JSONPojoConvertor Maven / Gradle / Ivy
The newest version!
// ========================================================================
// Copyright (c) 2009-2009 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
package net.hasor.utils.json;
import net.hasor.utils.json.JSON.Output;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.logging.Logger;
/**
* Converts POJOs to JSON and vice versa.
* The key difference:
* - returns the actual object from Convertor.fromJSON (JSONObjectConverter returns a Map)
* - the getters/setters are resolved at initialization (JSONObjectConverter resolves it at runtime)
* - correctly sets the number fields
*
*/
public class JSONPojoConvertor implements JSON.Convertor {
protected final static Logger logger = Logger.getLogger(JSONPojoConvertor.class.getName());
public static final Object[] GETTER_ARG = new Object[] {}, NULL_ARG = new Object[] { null };
private static final Map, NumberType> __numberTypes = new HashMap, NumberType>();
public static NumberType getNumberType(Class> clazz) {
return __numberTypes.get(clazz);
}
protected boolean _fromJSON;
protected Class> _pojoClass;
protected Map _getters = new HashMap();
protected Map _setters = new HashMap();
protected Set _excluded;
/**
* @param pojoClass The class to convert
*/
public JSONPojoConvertor(Class> pojoClass) {
this(pojoClass, (Set) null, true);
}
/**
* @param pojoClass The class to convert
* @param excluded The fields to exclude
*/
public JSONPojoConvertor(Class> pojoClass, String[] excluded) {
this(pojoClass, new HashSet(Arrays.asList(excluded)), true);
}
/**
* @param pojoClass The class to convert
* @param excluded The fields to exclude
*/
public JSONPojoConvertor(Class> pojoClass, Set excluded) {
this(pojoClass, excluded, true);
}
/**
* @param pojoClass The class to convert
* @param excluded The fields to exclude
* @param fromJSON If true, add a class field to the JSON
*/
public JSONPojoConvertor(Class> pojoClass, Set excluded, boolean fromJSON) {
_pojoClass = pojoClass;
_excluded = excluded;
_fromJSON = fromJSON;
init();
}
/**
* @param pojoClass The class to convert
* @param fromJSON If true, add a class field to the JSON
*/
public JSONPojoConvertor(Class> pojoClass, boolean fromJSON) {
this(pojoClass, (Set) null, fromJSON);
}
/* ------------------------------------------------------------ */
protected void init() {
Method[] methods = _pojoClass.getMethods();
for (int i = 0; i < methods.length; i++) {
Method m = methods[i];
if (!Modifier.isStatic(m.getModifiers()) && m.getDeclaringClass() != Object.class) {
String name = m.getName();
switch (m.getParameterTypes().length) {
case 0:
if (m.getReturnType() != null) {
if (name.startsWith("is") && name.length() > 2)
name = name.substring(2, 3).toLowerCase() + name.substring(3);
else if (name.startsWith("get") && name.length() > 3)
name = name.substring(3, 4).toLowerCase() + name.substring(4);
else
break;
if (includeField(name, m))
addGetter(name, m);
}
break;
case 1:
if (name.startsWith("set") && name.length() > 3) {
name = name.substring(3, 4).toLowerCase() + name.substring(4);
if (includeField(name, m))
addSetter(name, m);
}
break;
}
}
}
}
/* ------------------------------------------------------------ */
protected void addGetter(String name, Method method) {
_getters.put(name, method);
}
/* ------------------------------------------------------------ */
protected void addSetter(String name, Method method) {
_setters.put(name, new Setter(name, method));
}
/* ------------------------------------------------------------ */
protected Setter getSetter(String name) {
return _setters.get(name);
}
/* ------------------------------------------------------------ */
protected boolean includeField(String name, Method m) {
return _excluded == null || !_excluded.contains(name);
}
/* ------------------------------------------------------------ */
protected int getExcludedCount() {
return _excluded == null ? 0 : _excluded.size();
}
/* ------------------------------------------------------------ */
public Object fromJSON(Map object) {
Object obj = null;
try {
obj = _pojoClass.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
setProps(obj, object);
return obj;
}
/* ------------------------------------------------------------ */
public int setProps(Object obj, Map, ?> props) {
int count = 0;
for (Iterator> iterator = props.entrySet().iterator(); iterator.hasNext(); ) {
Map.Entry, ?> entry = (Map.Entry, ?>) iterator.next();
Setter setter = getSetter((String) entry.getKey());
if (setter != null) {
try {
setter.invoke(obj, entry.getValue());
count++;
} catch (Exception e) {
logger.warning(_pojoClass.getName() + "#" + setter.getPropertyName() + " not set from " + (entry.getValue().getClass().getName()) + "=" + entry.getValue().toString());
log(e);
}
}
}
return count;
}
/* ------------------------------------------------------------ */
public void toJSON(Object obj, Output out) {
if (_fromJSON)
out.addClass(_pojoClass);
for (Map.Entry entry : _getters.entrySet()) {
try {
out.add(entry.getKey(), entry.getValue().invoke(obj, GETTER_ARG));
} catch (Exception e) {
logger.warning(_pojoClass.getName() + " property '" + entry.getKey() + "' excluded. (errors)");
log(e);
}
}
}
/* ------------------------------------------------------------ */
protected void log(Throwable t) {
logger.fine(t.getMessage());
}
/* ------------------------------------------------------------ */
public static class Setter {
protected String _propertyName;
protected Method _setter;
protected NumberType _numberType;
protected Class> _type;
protected Class> _componentType;
public Setter(String propertyName, Method method) {
_propertyName = propertyName;
_setter = method;
_type = method.getParameterTypes()[0];
_numberType = __numberTypes.get(_type);
if (_numberType == null && _type.isArray()) {
_componentType = _type.getComponentType();
_numberType = __numberTypes.get(_componentType);
}
}
public String getPropertyName() {
return _propertyName;
}
public Method getMethod() {
return _setter;
}
public NumberType getNumberType() {
return _numberType;
}
public Class> getType() {
return _type;
}
public Class> getComponentType() {
return _componentType;
}
public boolean isPropertyNumber() {
return _numberType != null;
}
public void invoke(Object obj, Object value) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
if (value == null)
_setter.invoke(obj, NULL_ARG);
else
invokeObject(obj, value);
}
protected void invokeObject(Object obj, Object value) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
if (_type.isEnum()) {
if (value instanceof Enum)
_setter.invoke(obj, new Object[] { value });
else
_setter.invoke(obj, new Object[] { Enum.valueOf((Class extends Enum>) _type, value.toString()) });
} else if (_numberType != null && value instanceof Number) {
_setter.invoke(obj, new Object[] { _numberType.getActualValue((Number) value) });
} else if (Character.TYPE.equals(_type) || Character.class.equals(_type)) {
_setter.invoke(obj, new Object[] { String.valueOf(value).charAt(0) });
} else if (_componentType != null && value.getClass().isArray()) {
if (_numberType == null) {
int len = Array.getLength(value);
Object array = Array.newInstance(_componentType, len);
try {
System.arraycopy(value, 0, array, 0, len);
} catch (Exception e) {
// unusual array with multiple types
logger.fine(e.getMessage());
_setter.invoke(obj, new Object[] { value });
return;
}
_setter.invoke(obj, new Object[] { array });
} else {
Object[] old = (Object[]) value;
Object array = Array.newInstance(_componentType, old.length);
try {
for (int i = 0; i < old.length; i++)
Array.set(array, i, _numberType.getActualValue((Number) old[i]));
} catch (Exception e) {
// unusual array with multiple types
logger.fine(e.getMessage());
_setter.invoke(obj, new Object[] { value });
return;
}
_setter.invoke(obj, new Object[] { array });
}
} else
_setter.invoke(obj, new Object[] { value });
}
}
public interface NumberType {
public Object getActualValue(Number number);
}
public static final NumberType SHORT = new NumberType() {
public Object getActualValue(Number number) {
return new Short(number.shortValue());
}
};
public static final NumberType INTEGER = new NumberType() {
public Object getActualValue(Number number) {
return new Integer(number.intValue());
}
};
public static final NumberType FLOAT = new NumberType() {
public Object getActualValue(Number number) {
return new Float(number.floatValue());
}
};
public static final NumberType LONG = new NumberType() {
public Object getActualValue(Number number) {
return number instanceof Long ? number : new Long(number.longValue());
}
};
public static final NumberType DOUBLE = new NumberType() {
public Object getActualValue(Number number) {
return number instanceof Double ? number : new Double(number.doubleValue());
}
};
static {
__numberTypes.put(Short.class, SHORT);
__numberTypes.put(Short.TYPE, SHORT);
__numberTypes.put(Integer.class, INTEGER);
__numberTypes.put(Integer.TYPE, INTEGER);
__numberTypes.put(Long.class, LONG);
__numberTypes.put(Long.TYPE, LONG);
__numberTypes.put(Float.class, FLOAT);
__numberTypes.put(Float.TYPE, FLOAT);
__numberTypes.put(Double.class, DOUBLE);
__numberTypes.put(Double.TYPE, DOUBLE);
}
}