package org.epics.archiverappliance.utils.ui;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import org.apache.log4j.Logger;
import org.epics.archiverappliance.common.TimeUtils;
import org.epics.archiverappliance.config.ArchDBRTypes;
import org.epics.archiverappliance.mgmt.policy.PolicyConfig.SamplingMethod;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
/**
* Generate something that marshalls JSON into a POJO using bean introspection.
* Underlying framework is still json-simple.
* This has a giant switch statement that does things based on types; so please add unit tests as part of adding new fields to objects that use JSONDecoder.
* @author mshankar
*
*/
public class JSONDecoder<T> {
private static Logger logger = Logger.getLogger(JSONDecoder.class.getName());
public static <T> JSONDecoder<T> getDecoder(Class<T> clazz) throws IntrospectionException, NoSuchMethodException {
return new JSONDecoder<T>(clazz);
}
private LinkedList<AttributeDecoder<T>> decoders = new LinkedList<AttributeDecoder<T>>();
private JSONDecoder(Class<T> clazz) throws IntrospectionException, NoSuchMethodException {
BeanInfo info = Introspector.getBeanInfo(clazz);
PropertyDescriptor[] descriptors = info.getPropertyDescriptors();
for(PropertyDescriptor descriptor : descriptors) {
logger.debug("Generating decoder for " + descriptor.getName());
if(descriptor.getPropertyType().equals(String.class)) {
decoders.add(new StringConstructor<T>(descriptor));
} else if(descriptor.getPropertyType().equals(boolean.class)) {
decoders.add(new ValueOfDecoder<T>(descriptor));
} else if(descriptor.getPropertyType().equals(int.class)) {
decoders.add(new ValueOfDecoder<T>(descriptor));
} else if(descriptor.getPropertyType().equals(long.class)) {
decoders.add(new ValueOfDecoder<T>(descriptor));
} else if(descriptor.getPropertyType().equals(float.class)) {
decoders.add(new ValueOfDecoder<T>(descriptor));
} else if(descriptor.getPropertyType().equals(double.class)) {
decoders.add(new ValueOfDecoder<T>(descriptor));
} else if(descriptor.getPropertyType().equals(Long.class)) {
decoders.add(new StringConstructor<T>(descriptor));
} else if(descriptor.getPropertyType().equals(Double.class)) {
decoders.add(new StringConstructor<T>(descriptor));
} else if(descriptor.getPropertyType().equals(Timestamp.class)) {
decoders.add(new ISO8601Decoder<T>(descriptor));
} else if(descriptor.getPropertyType().equals(ArchDBRTypes.class)) {
decoders.add(new EnumDecoder<T>(descriptor, ArchDBRTypes.class));
} else if(descriptor.getPropertyType().equals(SamplingMethod.class)) {
decoders.add(new EnumDecoder<T>(descriptor, SamplingMethod.class));
} else if(descriptor.getPropertyType().equals(String[].class)) {
decoders.add(new ArrayOfStringsDecoder<T>(descriptor));
} else if(descriptor.getPropertyType().equals(HashMap.class)) {
decoders.add(new HashMapDecoder<T>(descriptor));
} else if(descriptor.getName().equals("class")) {
// Skip class...
} else {
throw new IntrospectionException("Do not have JSON decoder for property " + descriptor.getName() + " of type " + descriptor.getPropertyType().getCanonicalName());
}
}
}
public void decode(JSONObject jsonObj, T obj) throws IllegalAccessException, InvocationTargetException, InstantiationException {
for(AttributeDecoder<T> decoder : decoders) {
decoder.decode(jsonObj, obj);
}
}
private static interface AttributeDecoder<T> {
void decode(JSONObject jsonObj, T obj) throws IllegalAccessException, InvocationTargetException, InstantiationException ;
}
private static String WRITE_METHOD_EXPLANATION = ". Our custom JSON decoder requires both get and set methods in standard bean syntax. Use Eclipse to add a 'standard' set method";
private static class StringConstructor<T> implements AttributeDecoder<T> {
private String propertyName;
private Method writeMethod;
private Constructor<?> constructorFromString;
private StringConstructor(PropertyDescriptor descriptor) throws NoSuchMethodException {
propertyName = descriptor.getName();
writeMethod = descriptor.getWriteMethod();
if(writeMethod == null) throw new NoSuchMethodException("No write method for " + propertyName + WRITE_METHOD_EXPLANATION);
constructorFromString = descriptor.getPropertyType().getConstructor(String.class);
}
@Override
public void decode(JSONObject jsonObj, T obj) throws IllegalAccessException, InvocationTargetException, InstantiationException {
if(jsonObj.containsKey(propertyName)) {
String val = (String) jsonObj.get(propertyName);
Object newObj = constructorFromString.newInstance(val);
writeMethod.invoke(obj, newObj);
}
}
}
private static class ArrayOfStringsDecoder<T> implements AttributeDecoder<T> {
private String propertyName;
private Method writeMethod;
private Constructor<?> constructorFromString;
private ArrayOfStringsDecoder(PropertyDescriptor descriptor) throws NoSuchMethodException {
propertyName = descriptor.getName();
writeMethod = descriptor.getWriteMethod();
if(writeMethod == null) throw new NoSuchMethodException("No write method for " + propertyName + WRITE_METHOD_EXPLANATION);
constructorFromString = descriptor.getPropertyType().getComponentType().getConstructor(String.class);
}
@Override
public void decode(JSONObject jsonObj, T obj) throws IllegalAccessException, InvocationTargetException, InstantiationException {
if(jsonObj.containsKey(propertyName)) {
JSONArray vals = (JSONArray) jsonObj.get(propertyName);
LinkedList<String> newvals = new LinkedList<String>();
for(Object val : vals) {
newvals.add((String)constructorFromString.newInstance(val));
}
writeMethod.invoke(obj, new Object[]{newvals.toArray(new String[0])});
}
}
}
private static class HashMapDecoder<T> implements AttributeDecoder<T> {
private String propertyName;
private Method writeMethod;
private HashMapDecoder(PropertyDescriptor descriptor) throws NoSuchMethodException {
propertyName = descriptor.getName();
writeMethod = descriptor.getWriteMethod();
if(writeMethod == null) throw new NoSuchMethodException("No write method for " + propertyName + WRITE_METHOD_EXPLANATION);
}
@SuppressWarnings("unchecked")
@Override
public void decode(JSONObject jsonObj, T obj) throws IllegalAccessException, InvocationTargetException, InstantiationException {
if(jsonObj.containsKey(propertyName)) {
HashMap<String, String> vals = (HashMap<String, String>) jsonObj.get(propertyName);
writeMethod.invoke(obj, new Object[]{vals});
}
}
}
private static class ValueOfDecoder<T> implements AttributeDecoder<T> {
private static Map<Class<?>,Class<?>> primitiveMap = new HashMap<Class<?>,Class<?>>();
static {
primitiveMap.put(boolean.class, Boolean.class);
primitiveMap.put(byte.class, Byte.class);
primitiveMap.put(char.class, Character.class);
primitiveMap.put(short.class, Short.class);
primitiveMap.put(int.class, Integer.class);
primitiveMap.put(long.class, Long.class);
primitiveMap.put(float.class, Float.class);
primitiveMap.put(double.class, Double.class);
}
private String propertyName;
private Method writeMethod;
private Method valueOfMethod;
private ValueOfDecoder(PropertyDescriptor descriptor) throws NoSuchMethodException {
propertyName = descriptor.getName();
writeMethod = descriptor.getWriteMethod();
if(writeMethod == null) throw new NoSuchMethodException("No write method for " + propertyName + WRITE_METHOD_EXPLANATION);
Class<?> wrapperClass = primitiveMap.get(descriptor.getPropertyType());
valueOfMethod = wrapperClass.getMethod("valueOf", String.class);
}
@Override
public void decode(JSONObject jsonObj, T obj) throws IllegalAccessException, InvocationTargetException, InstantiationException {
if(jsonObj.containsKey(propertyName)) {
String val = (String) jsonObj.get(propertyName);
Object newObj = valueOfMethod.invoke(null, val);
writeMethod.invoke(obj, newObj);
}
}
}
private static class ISO8601Decoder<T> implements AttributeDecoder<T> {
private String propertyName;
private Method writeMethod;
private ISO8601Decoder(PropertyDescriptor descriptor) throws NoSuchMethodException {
propertyName = descriptor.getName();
writeMethod = descriptor.getWriteMethod();
}
@Override
public void decode(JSONObject jsonObj, T obj) throws IllegalAccessException, InvocationTargetException, InstantiationException {
if(jsonObj.containsKey(propertyName)) {
String tsstr = (String) jsonObj.get(propertyName);
Timestamp ts = TimeUtils.convertFromISO8601String(tsstr);
writeMethod.invoke(obj, ts);
}
}
}
@SuppressWarnings("rawtypes")
private static class EnumDecoder<T> implements AttributeDecoder<T> {
private String propertyName;
private Method writeMethod;
private Class<? extends Enum> enumClass;
private EnumDecoder(PropertyDescriptor descriptor, Class<? extends Enum> enumClass) throws NoSuchMethodException {
propertyName = descriptor.getName();
writeMethod = descriptor.getWriteMethod();
if(writeMethod == null) throw new NoSuchMethodException("No write method for " + propertyName + WRITE_METHOD_EXPLANATION);
this.enumClass = enumClass;
}
@SuppressWarnings("unchecked")
@Override
public void decode(JSONObject jsonObj, T obj) throws IllegalAccessException, InvocationTargetException, InstantiationException {
if(jsonObj.containsKey(propertyName)) {
String enumvalue = (String) jsonObj.get(propertyName);
Enum enumVal = Enum.valueOf(enumClass, enumvalue);
writeMethod.invoke(obj, enumVal);
}
}
}
}