package gueei.binding.collections;
import gueei.binding.CollectionChangedEventArg;
import gueei.binding.CollectionChangedEventArg.Action;
import gueei.binding.cursor.CursorField;
import gueei.binding.cursor.ICursorRowModel;
import gueei.binding.cursor.IRowModelFactory;
import gueei.binding.cursor.RowModelFactory;
import gueei.binding.utility.CacheHashMap;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.WeakHashMap;
import android.database.Cursor;
import android.database.DataSetObserver;
/**
* User: =ra=
* Date: 08.10.11
* Time: 11:58
*/
public class CursorCollection<T extends ICursorRowModel> extends ObservableCollection<T>{
//
public static interface ICursorCacheManager<ElType> {
public void clear(); // remove all items from cache
public void put(int key, ElType value);
public ElType get(int key);
public int getSize();
/**
* Hint the Cache Manager the desire cache size needed,
* Size is not compulsory and implementations can choose it's own strategy to use this
* Most probably this size is the total visible item on screen
* @param originator
* @param total
*/
public void hintCacheSize(Object originator, int total);
}
public CursorCollection(Class<T> rowModelType, ICursorCacheManager<T> cacheManager) {
this(rowModelType, null, cacheManager, null);
}
public CursorCollection(Class<T> rowModelType) {
this(rowModelType, null, null, null);
}
public CursorCollection(Class<T> rowModelType, Cursor cursor) {
this(rowModelType, null, null, cursor);
}
public CursorCollection(Class<T> rowModelType, IRowModelFactory<T> factory) {
this(rowModelType, factory, null, null);
}
public CursorCollection(Class<T> rowModelType, IRowModelFactory<T> factory,
ICursorCacheManager<T> cacheManager) {
this(rowModelType, factory, cacheManager, null);
}
public CursorCollection(Class<T> rowModelType, IRowModelFactory<T> factory,
ICursorCacheManager<T> cacheManager, Cursor cursor) {
mRowModelType = rowModelType;
mFactory = factory == null ?
new RowModelFactory<T>(rowModelType) : factory;
mCursor = cursor;
mCacheManager = cacheManager == null ?
new DefaultCursorCacheManager<T>() : cacheManager;
initFieldDataFromModel();
if (null != cursor) {
cursor.registerDataSetObserver(mCursorDataSetObserver);
mCursorDataSetObserver.onChanged();
}
}
public void setCursor(Cursor cursor) {
if (mCursor == cursor) {
// cursor is the same, nothing to do
return;
}
if (null != mCursor) {
// unregister previous cursor listener
mCursor.unregisterDataSetObserver(mCursorDataSetObserver);
}
mCursor = cursor;
if (null != mCursor) {
// register listener to new cursor
mCursor.registerDataSetObserver(mCursorDataSetObserver);
}
mCursorDataSetObserver.onChanged(); // imitate changes
}
public Cursor getCursor() {
return mCursor;
}
public T getItem(int position) {
if (mCursor==null) return null;
// Check the cache first
T row = mCacheManager.get(position);
if (null == row) { // no such position row cached
mCursor.moveToPosition(position);
row = createRowModel();
mCacheManager.put(position, row);
}
return row;
}
public Class<T> getComponentType() {
return mRowModelType;
}
public int size() {
return mCursorRowsCount;
}
@Override
public long getItemId(int position) {
if (0 < mCursorRowsCount) {
return getItem(position).getId(position);
}
return position;
}
public void onLoad(int position) {
}
public void requery() {
// to be sure data is correct
if (null != mCursor) {
mCursor.requery(); // fires mCursorDataSetObserver.onChanged()
}
else {
mCursorDataSetObserver.onChanged();// fire manually
}
}
protected void reInitCacheCursorRowCount() {
mCacheManager.clear();
mCursorRowsCount = (null == mCursor) ? 0 : mCursor.getCount();
}
protected void initFieldDataFromModel() {
for (Field f : mRowModelType.getFields()) {
if (!CursorField.class.isAssignableFrom(f.getType())) {
continue;
}
mCursorFields.add(f);
}
}
protected T createRowModel() {
T rowModel = mFactory.createInstance();
for (Field f : mCursorFields) {
try {
((CursorField<?>) f.get(rowModel)).fillValue(mCursor);
} catch (Exception ignored) {
}
}
rowModel.onInitialize();
return rowModel;
}
protected void finalize() throws Throwable {
try {
mCursorRowsCount = 0;
if (null != mCursor) {
mCursor.unregisterDataSetObserver(mCursorDataSetObserver);
if (!mCursor.isClosed()) {
mCursor.close();
}
mCursor = null;
}
} catch (Exception ignored) {
} finally {
super.finalize();
}
}
protected final Class<T> mRowModelType;
protected final IRowModelFactory<T> mFactory;
protected final ArrayList<Field> mCursorFields = new ArrayList<Field>();
protected int mCursorRowsCount;
protected Cursor mCursor;
// Hold the cached row models
protected ICursorCacheManager<T> mCacheManager;
protected final DataSetObserver mCursorDataSetObserver = new DataSetObserver() {
@Override
public void onChanged() {
reInitCacheCursorRowCount();
CollectionChangedEventArg e = new CollectionChangedEventArg(Action.Reset, (List<?>)null);
notifyCollectionChanged(e);
}
};
/**
* Default Implementation of CursorCacheManager.
* This will grow according to the number of widget accessing it, where
* size = (Sum of Visible Item on Each widget) * Extra Percentage
* and size is always larger than 10
* @author andy
*
* @param <T>
*/
public static class DefaultCursorCacheManager<T extends ICursorRowModel> implements ICursorCacheManager<T> {
private CacheHashMap<Integer, T> mCache;
private WeakHashMap<Object, Integer> cachingOriginators =
new WeakHashMap<Object, Integer>();
private int mMinSize = 30;
private float mExtra = 2.0f;
public DefaultCursorCacheManager() {
mCache = new CacheHashMap<Integer, T>(mMinSize);
}
public DefaultCursorCacheManager(int minSize, float extra) {
mCache = new CacheHashMap<Integer, T>((int)(minSize * extra));
mMinSize = minSize > 0 ? minSize : mMinSize;
mExtra = extra > 1.0 ? extra: mExtra;
}
@Override
public void clear() {
mCache.clear();
}
@Override
public void put(int key, T value) {
mCache.put((Integer) key, value);
}
@Override
public T get(int key) {
return mCache.get((Integer) key);
}
@Override
public int getSize() {
return mCache.size();
}
@Override
public void hintCacheSize(Object originator, int total) {
cachingOriginators.put(originator, total);
int size = 0;
for(Integer t: cachingOriginators.values()){
size += t;
}
if (size<mMinSize){
size = mMinSize;
}
mCache.reSize((int)(size * mExtra));
}
}
@Override
public void setVisibleChildrenCount(Object setter, int total) {
mCacheManager.hintCacheSize(setter, total);
}
@Override
public boolean isNull() {
return false;
}
}