package org.openrosa.client.jr.core.services.storage; import java.util.NoSuchElementException; import java.util.Vector; import org.openrosa.client.jr.core.util.InvalidIndexException; import org.openrosa.client.jr.core.util.externalizable.Externalizable; /** * A wrapper implementation of IStorageUtility that lets you serialize an object with a serialization * scheme other than the default scheme provided by the object's readExternal/writeExternal methods. * * For example, FormInstance contains lots of redundant information about the structure of the instance * which doesn't change among saved instances. The extra space used for this redundant info can seriously * limit the number of saved forms we can store on a device. We can use this utility to serialize * FormInstances in a different way that excludes this redundant info (meaning we have to take the more * complicated step of restoring it from elsewhere during deserialization), with the benefit of much * smaller record sizes. * * The alternate scheme is provided via a wrapper object, which accepts the base object and whose * readExternal/writeExternal methods implement the new scheme. * * All methods pass through to an underlying StorageUtility; you may get warnings about type mismatches * * @author Drew Roos * */ public class WrappingStorageUtility implements IStorageUtilityIndexed { IStorageUtility storage; /* underlying StorageUtility */ SerializationWrapper wrapper; /* wrapper that defines the alternate serialization scheme; the wrapper is set once for * the life of the StorageUtility and is re-used all read and write calls */ /** * Defines an alternate serialization scheme. The alternate scheme is implemented in this class's * readExternal and writeExternal methods. * * (kind of like ExternalizableWrapper -- but not quite a drop-in replacement) */ public interface SerializationWrapper extends Externalizable { /** * set the underlying object (to be followed by a call to writeExternal) * @param e */ void setData (Externalizable e); /** * retrieve the underlying object (to be followed by a call to readExternal) * @return */ Externalizable getData (); /** * return type of underlying object * @return */ Class baseType (); } /** * Create a new wrapping StorageUtility * @param name unique name for underlying StorageUtility * @param wrapper serialization wrapper * @param storageFactory factory to create underlying StorageUtility */ public WrappingStorageUtility (String name, SerializationWrapper wrapper, IStorageFactory storageFactory) { this.storage = storageFactory.newStorage(name, wrapper.getClass()); this.wrapper = wrapper; } public Externalizable read(int id) { return ((SerializationWrapper)storage.read(id)).getData(); } public void write(final Persistable p) throws StorageFullException { synchronized(wrapper) { wrapper.setData(p); if(wrapper instanceof IMetaData) { storage.write(new FauxIndexedPersistable(p, wrapper, (IMetaData)wrapper)); } else { storage.write(new FauxIndexedPersistable(p, wrapper)); } } } public int add(Externalizable e) throws StorageFullException { synchronized(wrapper) { wrapper.setData(e); return storage.add(wrapper); } } public void update(int id, Externalizable e) throws StorageFullException { synchronized(wrapper) { wrapper.setData(e); storage.update(id, wrapper); } } public IStorageIterator iterate() { final IStorageIterator baseIterator = storage.iterate(); return new IStorageIterator () { public boolean hasMore() { return baseIterator.hasMore(); } public int nextID() { return baseIterator.nextID(); } public Externalizable nextRecord() { return ((SerializationWrapper)baseIterator.nextRecord()).getData(); } public int numRecords() { return baseIterator.numRecords(); } }; } /* pass-through methods */ public byte[] readBytes(int id) { return storage.readBytes(id); } public void remove(int id) { storage.remove(id); } public void remove(Persistable p) { storage.remove(p); } public void removeAll() { storage.removeAll(); } public Vector<Integer> removeAll(EntityFilter ef) { return storage.removeAll(ef); } public boolean exists(int id) { return storage.exists(id); } public boolean isEmpty() { return storage.isEmpty(); } public int getNumRecords() { return storage.getNumRecords(); } public int getRecordSize(int id) { return storage.getRecordSize(id); } public int getTotalSize() { return storage.getTotalSize(); } public void close() { storage.close(); } public void destroy() { storage.destroy(); } public void repack() { storage.repack(); } public void repair() { storage.repair(); } public Object getAccessLock() { return storage.getAccessLock(); } public Vector getIDsForValue(String fieldName, Object value) { return indexedStorage().getIDsForValue(fieldName, value); } public Externalizable getRecordForValue(String fieldName, Object value) throws NoSuchElementException, InvalidIndexException { return ((SerializationWrapper)indexedStorage().getRecordForValue(fieldName, value)).getData(); } private IStorageUtilityIndexed indexedStorage() { if(!(storage instanceof IStorageUtilityIndexed)) { throw new RuntimeException("WrappingStorageUtility's factory is not of an indexed type, but had indexed operations requested. Please implement storage " + storage.getClass().getName() + " as indexed storage"); } return (IStorageUtilityIndexed)storage; } }