| package android.content; |
| |
| import android.database.Cursor; |
| import android.database.sqlite.SQLiteDatabase; |
| import android.os.RemoteException; |
| |
| /** |
| * An abstract class that makes it easy to implement an EntityIterator over a cursor. |
| * The user must implement {@link #newEntityFromCursorLocked}, which runs inside of a |
| * database transaction. |
| * @hide |
| */ |
| public abstract class AbstractCursorEntityIterator implements EntityIterator { |
| private final Cursor mEntityCursor; |
| private final SQLiteDatabase mDb; |
| private volatile Entity mNextEntity; |
| private volatile boolean mIsClosed; |
| |
| public AbstractCursorEntityIterator(SQLiteDatabase db, Cursor entityCursor) { |
| mEntityCursor = entityCursor; |
| mDb = db; |
| mNextEntity = null; |
| mIsClosed = false; |
| } |
| |
| /** |
| * If there are entries left in the cursor then advance the cursor and use the new row to |
| * populate mNextEntity. If the cursor is at the end or if advancing it causes the cursor |
| * to become at the end then set mEntityCursor to null. If newEntityFromCursor returns null |
| * then continue advancing until it either returns a non-null Entity or the cursor reaches |
| * the end. |
| */ |
| private void fillEntityIfAvailable() { |
| while (mNextEntity == null) { |
| if (!mEntityCursor.moveToNext()) { |
| // the cursor is at then end, bail out |
| return; |
| } |
| // This may return null if newEntityFromCursor is not able to create an entity |
| // from the current cursor position. In that case this method will loop and try |
| // the next cursor position |
| mNextEntity = newEntityFromCursorLocked(mEntityCursor); |
| } |
| mDb.beginTransaction(); |
| try { |
| int position = mEntityCursor.getPosition(); |
| mNextEntity = newEntityFromCursorLocked(mEntityCursor); |
| int newPosition = mEntityCursor.getPosition(); |
| if (newPosition != position) { |
| throw new IllegalStateException("the cursor position changed during the call to" |
| + "newEntityFromCursorLocked, from " + position + " to " + newPosition); |
| } |
| } finally { |
| mDb.endTransaction(); |
| } |
| } |
| |
| /** |
| * Checks if there are more Entities accessible via this iterator. This may not be called |
| * if the iterator is already closed. |
| * @return true if the call to next() will return an Entity. |
| */ |
| public boolean hasNext() { |
| if (mIsClosed) { |
| throw new IllegalStateException("calling hasNext() when the iterator is closed"); |
| } |
| fillEntityIfAvailable(); |
| return mNextEntity != null; |
| } |
| |
| /** |
| * Returns the next Entity that is accessible via this iterator. This may not be called |
| * if the iterator is already closed. |
| * @return the next Entity that is accessible via this iterator |
| */ |
| public Entity next() { |
| if (mIsClosed) { |
| throw new IllegalStateException("calling next() when the iterator is closed"); |
| } |
| if (!hasNext()) { |
| throw new IllegalStateException("you may only call next() if hasNext() is true"); |
| } |
| |
| try { |
| return mNextEntity; |
| } finally { |
| mNextEntity = null; |
| } |
| } |
| |
| public void reset() throws RemoteException { |
| if (mIsClosed) { |
| throw new IllegalStateException("calling reset() when the iterator is closed"); |
| } |
| mEntityCursor.moveToPosition(-1); |
| mNextEntity = null; |
| } |
| |
| /** |
| * Closes this iterator making it invalid. If is invalid for the user to call any public |
| * method on the iterator once it has been closed. |
| */ |
| public void close() { |
| if (mIsClosed) { |
| throw new IllegalStateException("closing when already closed"); |
| } |
| mIsClosed = true; |
| mEntityCursor.close(); |
| } |
| |
| /** |
| * Returns a new Entity from the current cursor position. This is called from within a |
| * database transaction. If a new entity cannot be created from this cursor position (e.g. |
| * if the row that is referred to no longer exists) then this may return null. The cursor |
| * is guaranteed to be pointing to a valid row when this call is made. The implementation |
| * of newEntityFromCursorLocked is not allowed to change the position of the cursor. |
| * @param cursor from where to read the data for the Entity |
| * @return an Entity that corresponds to the current cursor position or null |
| */ |
| public abstract Entity newEntityFromCursorLocked(Cursor cursor); |
| } |