/** * */ 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"); } }