/**
*
*/
package org.commcare.android.storage.framework;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Hashtable;
import org.javarosa.core.services.storage.IMetaData;
import org.javarosa.core.services.storage.Persistable;
import org.javarosa.core.util.externalizable.DeserializationException;
import org.javarosa.core.util.externalizable.ExtUtil;
import org.javarosa.core.util.externalizable.PrototypeFactory;
/**
* @author ctsims
*
*/
public class Persisted implements Persistable, IMetaData {
private static Hashtable<Class, ArrayList<Field>> fieldOrderings = new Hashtable<Class, ArrayList<Field>>();
protected int recordId = -1;
/* (non-Javadoc)
* @see org.javarosa.core.util.externalizable.Externalizable#readExternal(java.io.DataInputStream, org.javarosa.core.util.externalizable.PrototypeFactory)
*/
@Override
public void readExternal(DataInputStream in, PrototypeFactory pf) throws IOException, DeserializationException {
recordId = ExtUtil.readInt(in);
String currentField = null;
try {
for(Field f : getPersistedFieldsInOrder()) {
currentField = f.getName();
readVal(f, this, in, pf);
}
} catch(IllegalAccessException iae) {
throw new DeserializationException(iae.getMessage()+ (currentField == null ? "" : (" for field" + currentField)));
}
}
private ArrayList<Field> getPersistedFieldsInOrder() {
ArrayList<Field> orderings;
synchronized(fieldOrderings) {
orderings = fieldOrderings.get(this.getClass());
if(orderings == null) {
orderings = new ArrayList<Field>();
fieldOrderings.put(this.getClass(), orderings);
}
}
synchronized(orderings) {
if(orderings.size() == 0) {
for(Field f : this.getClass().getDeclaredFields()) {
if(f.isAnnotationPresent(Persisting.class)) {
orderings.add(f);
}
}
Collections.sort(orderings, orderedComparator);
}
return orderings;
}
}
public static final Comparator<Field> orderedComparator = new Comparator<Field>() {
@Override
public int compare(Field f1, Field f2) {
int i1 = f1.getAnnotation(Persisting.class).value();
int i2 = f2.getAnnotation(Persisting.class).value();
return (i1<i2 ? -1 : (i1==i2 ? 0 : 1));
}
};
/* (non-Javadoc)
* @see org.javarosa.core.util.externalizable.Externalizable#writeExternal(java.io.DataOutputStream)
*/
@Override
public void writeExternal(DataOutputStream out) throws IOException {
ExtUtil.writeNumeric(out, recordId);
try {
for(Field f: getPersistedFieldsInOrder()) {
writeVal(f, this, out);
}
} catch(IllegalAccessException iae) {
throw new RuntimeException(iae);
}
}
/* (non-Javadoc)
* @see org.javarosa.core.services.storage.Persistable#setID(int)
*/
@Override
public void setID(int ID) {
recordId = ID;
}
/* (non-Javadoc)
* @see org.javarosa.core.services.storage.Persistable#getID()
*/
@Override
public int getID() {
return recordId;
}
private void readVal(Field f, Object o, DataInputStream in, PrototypeFactory pf) throws DeserializationException, IOException, IllegalAccessException {
Persisting p = f.getAnnotation(Persisting.class);
Class type = f.getType();
try {
f.setAccessible(true);
if(type.equals(String.class)) {
String read = ExtUtil.readString(in);
f.set(o, p.nullable() ? ExtUtil.nullIfEmpty(read) : read);
return;
} else if(type.equals(Integer.TYPE)) {
//Primitive Integers
f.setInt(o, ExtUtil.readInt(in));
return;
} else if(type.equals(Date.class)) {
f.set(o, ExtUtil.readDate(in));
return;
} else if(type.isArray()){
//We only support byte arrays for now
if(type.getComponentType().equals(Byte.TYPE)) {
f.set(o, ExtUtil.readBytes(in));
return;
}
}
} finally {
f.setAccessible(false);
}
//By Default
throw new DeserializationException("Couldn't read persisted type " + f.getType().toString());
}
private void writeVal(Field f, Object o, DataOutputStream out) throws IOException, IllegalAccessException {
try {
Persisting p = f.getAnnotation(Persisting.class);
Class type = f.getType();
f.setAccessible(true);
if(type.equals(String.class)) {
String s = (String)f.get(o);
ExtUtil.writeString(out, p.nullable() ? ExtUtil.emptyIfNull(s) : s);
return;
} else if(type.equals(Integer.TYPE)) {
ExtUtil.writeNumeric(out,f.getInt(o));
return;
} else if(type.equals(Date.class)) {
ExtUtil.writeDate(out, (Date)f.get(o));
return;
} else if(type.isArray()){
//We only support byte arrays for now
if(type.getComponentType().equals(Byte.TYPE)) {
ExtUtil.writeBytes(out,(byte[])f.get(o));
return;
}
}
} finally {
f.setAccessible(false);
}
//By Default
throw new RuntimeException("Couldn't write persisted type " + f.getType().toString());
}
@Override
public String[] getMetaDataFields() {
ArrayList<String> fields = new ArrayList<String>();
for(Field f : this.getClass().getDeclaredFields()) {
try {
f.setAccessible(true);
if(f.isAnnotationPresent(MetaField.class)) {
MetaField mf = f.getAnnotation(MetaField.class);
fields.add(mf.value());
}
} finally {
f.setAccessible(false);
}
}
for(Method m : this.getClass().getDeclaredMethods()) {
try {
m.setAccessible(true);
if(m.isAnnotationPresent(MetaField.class)) {
MetaField mf = m.getAnnotation(MetaField.class);
fields.add(mf.value());
}
} finally {
m.setAccessible(false);
}
}
return fields.toArray(new String[0]);
}
//TODO: This looks like it's gonna be sllllooowwwww
@Override
public Object getMetaData(String fieldName) {
try {
for(Field f : this.getClass().getDeclaredFields()) {
try {
f.setAccessible(true);
if(f.isAnnotationPresent(MetaField.class)) {
MetaField mf = f.getAnnotation(MetaField.class);
if(mf.value().equals(fieldName)) {
return f.get(this);
}
}}
finally {
f.setAccessible(false);
}
}
for(Method m : this.getClass().getDeclaredMethods()) {
try {
m.setAccessible(true);
if(m.isAnnotationPresent(MetaField.class)) {
MetaField mf = m.getAnnotation(MetaField.class);
if(mf.value().equals(fieldName)) {
return m.invoke(this, (Object[])null);
}
}
}finally {
m.setAccessible(false);
}
}
} catch(IllegalAccessException iae) {
throw new RuntimeException(iae.getMessage());
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
throw new RuntimeException(e.getMessage());
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getMessage());
}
//If we didn't find the field
throw new IllegalArgumentException("No metadata field " + fieldName + " in the case storage system");
}
}