package com.venmo.cursor; import android.content.ContentResolver; import android.database.CharArrayBuffer; import android.database.ContentObserver; import android.database.DataSetObserver; import android.net.Uri; import android.os.Bundle; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import static com.venmo.cursor.CursorUtils.nextDocumentHelper; import static com.venmo.cursor.CursorUtils.previousDocumentHelper; /** * A convenience class to turn a {@link List} into a {@link android.database.Cursor}. Useful to * combine one data set which is backed by a {@link List} with another which is a backed by a * {@link * android.database.Cursor} using a {@link com.venmo.cursor.IterableMergeCursor}. */ public class CursorList<E> implements List<E>, IterableCursor<E> { private static final String _ID = "_id"; private static final int _ID_INDEX = 0; private List<E> mList; private int mPosition = 0; /** * Create a {@link CursorList} with an empty-backed {@link List}. The list can, however, be * modified. */ public CursorList() { mList = new ArrayList<E>(); } /** * Create a {@link CursorList} with an empty-backed {@link List}, with an expected size. The * list can, however, be modified. * * @see java.util.ArrayList#ArrayList(int) */ public CursorList(int capacity) { mList = new ArrayList<E>(capacity); } /** * Decorate the {@code list} as both a {@link android.database.Cursor} and also a {@link List} */ public CursorList(List<E> list) { if (list == null) { throw new NullPointerException("List parameter must be non-null"); } mList = list; } /** * Transform the {@link IterableCursor} into a {@link CursorList}. This does not close the * initial cursor. */ public CursorList(IterableCursor<E> cursor) { if (cursor == null) { throw new NullPointerException("Cursor parameter must be non-null"); } if (cursor.isClosed()) { throw new NullPointerException("Cursor parameter must not be closed"); } mList = new ArrayList<E>(cursor.getCount()); cursor.moveToFirst(); for (E e : cursor) { mList.add(e); } } @Override public E peek() { return mList.get(mPosition); } @Override public E nextDocument() { return nextDocumentHelper(this); } @Override public E previousDocument() { return previousDocumentHelper(this); } @Override public int getCount() { return mList.size(); } @Override public int getPosition() { return mPosition; } @Override public boolean move(int offset) { mPosition += offset; return clampPosition(); } @Override public boolean moveToPosition(int position) { mPosition = position; return clampPosition(); } /** * Helper for keeping {@link #mPosition} in bounds. * * @return false if mPosition was modified, true otherwise */ private boolean clampPosition() { if (mPosition < 0) { mPosition = -1; return false; } else if (mPosition > mList.size()) { // TODO should this be >= instead of > mPosition = mList.size(); return false; } return true; } @Override public boolean moveToFirst() { mPosition = 0; return mList.isEmpty(); } @Override public boolean moveToLast() { mPosition = mList.size() - 1; return mList.isEmpty(); } @Override public boolean moveToNext() { return move(1); } @Override public boolean moveToPrevious() { return move(-1); } @Override public boolean isFirst() { return mPosition == 0; } @Override public boolean isLast() { return mPosition == (mList.size() - 1); } @Override public boolean isBeforeFirst() { return mPosition < 0; } @Override public boolean isAfterLast() { return mPosition >= mList.size(); } /** * Unsupported since {@link CursorList}s aren't backed by columns. */ @Override public int getColumnIndex(String columnName) { if (_ID.equals(columnName)) { return _ID_INDEX; } return -1; } /** * Unsupported since {@link CursorList}s aren't backed by columns. */ @Override public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException { int result = getColumnIndex(columnName); if (result == -1) { throw new IllegalArgumentException("CursorList is not backed by columns."); } return result; } /** * Unsupported since {@link CursorList}s aren't backed by columns. */ @Override public String getColumnName(int columnIndex) { if (columnIndex == _ID_INDEX) { return _ID; } return null; } /** * Unsupported since {@link CursorList}s aren't backed by columns. */ @Override public String[] getColumnNames() { return new String[]{_ID}; } /** * Unsupported since {@link CursorList}s aren't backed by columns. */ @Override public int getColumnCount() { return 1; } /** * Unsupported since {@link CursorList}s aren't backed by columns. */ @Override public byte[] getBlob(int columnIndex) { throw new IllegalArgumentException("CursorList is not backed by columns."); } @Override public String getString(int columnIndex) { throw new IllegalArgumentException("CursorList is not backed by columns."); } @Override public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) { throw new IllegalArgumentException("CursorList is not backed by columns."); } @Override public short getShort(int columnIndex) { throw new IllegalArgumentException("CursorList is not backed by columns."); } @Override public int getInt(int columnIndex) { throw new IllegalArgumentException("CursorList is not backed by columns."); } @Override public long getLong(int columnIndex) { if (columnIndex == _ID_INDEX) { return mPosition; } throw new IllegalArgumentException("CursorList is not backed by columns."); } @Override public float getFloat(int columnIndex) { throw new IllegalArgumentException("CursorList is not backed by columns."); } @Override public double getDouble(int columnIndex) { throw new IllegalArgumentException("CursorList is not backed by columns."); } @Override public int getType(int columnIndex) { throw new IllegalArgumentException("CursorList is not backed by columns."); } @Override public boolean isNull(int columnIndex) { if (columnIndex == _ID_INDEX) { return false; } throw new IllegalArgumentException("CursorList is not backed by columns."); } @Deprecated @Override public void deactivate() { // noop } @Deprecated @Override public boolean requery() { // noop return false; } @Override public void close() { mList = null; } @Override public boolean isClosed() { return mList == null; } @Override public void registerContentObserver(ContentObserver observer) { // noop } @Override public void unregisterContentObserver(ContentObserver observer) { // noop } @Override public void registerDataSetObserver(DataSetObserver observer) { // noop } @Override public void unregisterDataSetObserver(DataSetObserver observer) { // noop } @Override public void setNotificationUri(ContentResolver cr, Uri uri) { // noop } @Override public Uri getNotificationUri() { return null; } @Override public boolean getWantsAllOnMoveCalls() { return false; } @Override public Bundle getExtras() { return Bundle.EMPTY; } @Override public Bundle respond(Bundle extras) { return Bundle.EMPTY; } @Override public void add(int location, E object) { mList.add(location, object); } @Override public boolean add(E object) { return mList.add(object); } @Override public boolean addAll(int location, Collection<? extends E> collection) { return mList.addAll(location, collection); } @Override public boolean addAll(Collection<? extends E> collection) { return mList.addAll(collection); } @Override public void clear() { mList.clear(); } @Override public boolean contains(Object object) { return mList.contains(object); } @Override public boolean containsAll(Collection<?> collection) { return mList.containsAll(collection); } @Override public E get(int location) { return mList.get(location); } @Override public int indexOf(Object object) { return mList.indexOf(object); } @Override public boolean isEmpty() { return mList.isEmpty(); } @Override public Iterator<E> iterator() { return mList.iterator(); } @Override public int lastIndexOf(Object object) { return mList.lastIndexOf(object); } @Override public ListIterator<E> listIterator() { return mList.listIterator(); } @Override public ListIterator<E> listIterator(int location) { return mList.listIterator(location); } @Override public E remove(int location) { return mList.remove(location); } @Override public boolean remove(Object object) { return mList.remove(object); } @Override public boolean removeAll(Collection<?> collection) { return mList.removeAll(collection); } @Override public boolean retainAll(Collection<?> collection) { return mList.retainAll(collection); } @Override public E set(int location, E object) { return mList.set(location, object); } @Override public int size() { return mList.size(); } @Override public CursorList<E> subList(int start, int end) { return new CursorList<E>(mList.subList(start, end)); } @Override public Object[] toArray() { return mList.toArray(); } @Override public <T> T[] toArray(T[] array) { return mList.toArray(array); } }