/**
*
*/
package smartkv.client.tables;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Map.Entry;
import org.slf4j.Logger;
import smartkv.client.util.Serializer;
import smartkv.client.util.UnsafeJavaSerializer;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
/**
* @author fabiim
*
*/
public class AnnotatedColumnObject<T> implements ColumnObject<T>{
//TODO: set loggers up and running
//private static final Logger log = Logger.getLoger("com.bonafide");
public static interface Constructor<T>{
T newInstance();
}
public static Map<Class<?>, AnnotatedColumnObject<?>> instances = Maps.newHashMap();
//FIXME
@SuppressWarnings("unchecked")
public synchronized static <T> AnnotatedColumnObject<T> newAnnotatedColumnObject(Class<T> type){
if (!instances.containsKey(type)){
instances.put(type, new AnnotatedColumnObject<T>(type));
}
return (AnnotatedColumnObject<T>) instances.get(type);
}
private final Map<String,Method> getters;
private final Map<String,Method> setters;
private final Class<T> clazz;
private final Serializer<Object> serializer = UnsafeJavaSerializer.getInstance();
private AnnotatedColumnObject.Constructor<T> constructor;
/**
* FIXME: documentation.
* @throws IllegalArgumentException if type does not implements an appropriate constructor.
*/
private AnnotatedColumnObject(Class<T> type){
clazz = type;
try {
type.newInstance();
} catch (Exception e){
//XXX be nicer, get info from exception e maybe...
throw new IllegalArgumentException("Class : " + type.getName() + " does not implements a valid constructor. Reason: " + e.getMessage());
}
///XXX could i have used final in paremeter?
this.constructor =new Constructor<T> (){
@Override
public T newInstance() {
//TODO :cleanup
try {
return clazz.newInstance();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
};
Map<String,Method> setters = Maps.newHashMap();
Map<String,Method> getters = Maps.newHashMap();
_construct(type, getters, setters);
this.setters = ImmutableMap.copyOf(setters);
this.getters =ImmutableMap.copyOf(getters);
}
private AnnotatedColumnObject(Class<T> type, Constructor<T> constructor){
this.clazz = type;
this.constructor = constructor;
Map<String,Method> setters = Maps.newHashMap();
Map<String,Method> getters = Maps.newHashMap();
_construct(type, getters, setters);
this.setters = ImmutableMap.copyOf(setters);
this.getters =ImmutableMap.copyOf(getters);
}
//XXX is this ok (method shared by constructors) ?
private final void _construct(Class<T> type, Map<String,Method> getters, Map<String,Method> setters){
for (Method m : type.getDeclaredMethods()) {
if (m.isAnnotationPresent(Column.class)) {
String getter = m.getAnnotation(Column.class).getter();
//FIXME
if (getter.equals("DEFAULT")){
getter = m.getName();
}
getters.put(getter, m);
String setter = m.getAnnotation(Column.class).setter();
if (setter.equals("DEFAULT")){
setter = "set" + m.getName().substring(3);
}
Method setterMethod = null;;
try {
setterMethod = type.getMethod( setter, m.getReturnType());
if (setterMethod == null){
//FIXME
throw new IllegalArgumentException("Method: " + m.getName() + " is qualified by Column with default setter, but there is no method: " + setter );
}
} catch (Exception e ) {
// TODO Auto-generated catch block
e.printStackTrace();
throw new IllegalArgumentException("Method: " + m.getName() + " is qualified by Column with default setter, but there is no method: " + setter );
}
setters.put( getter, setterMethod);
}
}
}
//TODO: T implements serializable?
/*
* @see bonafide.datastore.tables.ColumnObject#toColumns(java.lang.Object)
*/
@Override
public Map<String, byte[]> toColumns(T obj){
Map<String, byte[]> values= Maps.newHashMap();
for (Entry<String, Method> field : getters.entrySet()){
try {
values.put(field.getKey(), serializer.serialize(field.getValue().invoke(obj)));
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return values;
}
//FIXME: try running methods once at constructor... ignore exceptions later...
/* (non-Javadoc)
* @see bonafide.datastore.tables.ColumnObject#fromColumns(java.util.Map)
*/
@Override
public T fromColumns(Map<String, byte[]> fields) {
T object = constructor.newInstance();
for (Entry<String, byte[]> en: fields.entrySet()){
Method m = setters.get(en.getKey());
if (m!= null){
try {
m.invoke(object, serializer.deserialize(en.getValue()));
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}}
else{
System.out.println("Data store object has unknown columns: " + en.getKey());
//log.warn("Data store object has unknown columns: ");
}
}
return object;
}
/* (non-Javadoc)
* @see bonafide.datastore.tables.ColumnObject#serializeColumn(java.lang.String, java.lang.Object)
*/
@Override
public byte[] serializeColumn(String columnName, Object val) {
return serializer.serialize(val);
}
/* (non-Javadoc)
* @see bonafide.datastore.tables.ColumnObject#deserializeColumn(java.lang.String, byte[])
*/
@Override
public Object deserializeColumn(String columnName, byte[] val) {
return val != null ? serializer.deserialize(val): null;
}
}