package com.gettingmobile.google.reader.db; import android.database.Cursor; import android.database.CursorIndexOutOfBoundsException; import android.util.Log; import com.gettingmobile.google.reader.Item; import java.util.ArrayList; import java.util.List; final class StandardItemCursor extends AbsEntityCursor<Item> implements ItemCursor { private static final String LOG_TAG = "goodnews.ItemCursor"; private final ArrayList<Entry> entries; private int pos = -1; private final int feedTitleCol; public StandardItemCursor(boolean group, Cursor indexCursor, Cursor cursor) { super(new ItemCursorAdapter(), cursor); try { if (indexCursor.getCount() != cursor.getCount()) { Log.w(LOG_TAG, "Detected differing cursor counts: indexCursor=" + indexCursor.getCount() + "; cursor=" + cursor.getCount()); } feedTitleCol = cursor.getColumnIndex("feedTitle"); /* * process index */ final int groupIdCol = 0; final int entityIdCol = 1; final int joinCountCol = 2; final int readStateCol = 3; entries = new ArrayList<Entry>(indexCursor.getCount()); if (indexCursor.moveToFirst()) { int cursorPos = 0; long lastGroupId = 0; do { /* * insert group header if this position starts a new group */ if (group) { final long groupId = indexCursor.getLong(groupIdCol); if (lastGroupId != groupId) { // a new group starts entries.add(new GroupHeader(groupId, cursorPos)); lastGroupId = groupId; } } /* * add item entry */ final long entityId = indexCursor.getLong(entityIdCol); int joinCount = indexCursor.getInt(joinCountCol); if (joinCount < 1) { joinCount = 1; } entries.add(new Entity(entityId, cursorPos, joinCount, indexCursor.getInt(readStateCol) == 0)); cursorPos+= joinCount; } while (indexCursor.moveToNext()); } } finally { indexCursor.close(); } } @Override public List<? extends IndexEntry> getIndex() { return entries; } @Override public long getEntityId() { checkPosition(); return entries.get(pos).id; } @Override public Item getEntity() { checkPosition(); try { final Entity entry = (Entity) entries.get(pos); cursor.moveToPosition(entry.startPos); final Item item = adapter.readEntity(cursor); for (int i = 1; i < entry.count; ++i) { cursor.moveToNext(); adapter.readEntityJoin(item, cursor); } return item; } catch (ClassCastException ex) { throw new IllegalStateException("The entry at cursor position " + pos + " is a header item"); } } @Override public boolean isGroupHeader() { checkPosition(); return entries.get(pos).isGroupHeader; } @Override public String getGroupTitle() { checkPosition(); try { final GroupHeader gh = (GroupHeader) entries.get(pos); cursor.moveToPosition(gh.startPos); return cursor.getString(feedTitleCol); } catch (ClassCastException ex) { throw new IllegalStateException("The entry at cursor position " + pos + " is no group header"); } } /* * position query */ public int getCount() { return entries.size(); } public int getPosition() { return pos; } public boolean isBeforeFirst() { return pos < 0; } public boolean isAfterLast() { return pos >= entries.size(); } public boolean isFirst() { return pos == 0; } public boolean isLast() { return pos == entries.size() - 1; } /* * moving */ public boolean moveToFirst() { return moveToPosition(0); } public boolean moveToLast() { return moveToPosition(getCount() - 1); } public boolean moveToNext() { return moveToPosition(pos + 1); } public boolean moveToPrevious() { return moveToPosition(pos - 1); } public boolean move(int offset) { return moveToPosition(pos + offset); } public boolean moveToPosition(int position) { // Make sure position isn't past the end of the cursor final int count = getCount(); if (position >= count) { pos = count; return false; } // Make sure position isn't before the beginning of the cursor if (position < 0) { pos = -1; return false; } // Check for no-op moves, and skip the rest of the work for them if (position == pos) { return true; } pos = position; return true; } /* * helpers */ protected void checkPosition() { if (-1 == pos || getCount() == pos) { throw new CursorIndexOutOfBoundsException(pos, getCount()); } } /* * inner classes */ private static abstract class Entry implements IndexEntry { public final long id; public final int startPos; public final boolean isGroupHeader; public final boolean isUnread; protected Entry(long id, int startPos, boolean isGroupHeader, boolean isUnread) { this.id = id; this.startPos = startPos; this.isGroupHeader = isGroupHeader; this.isUnread = isUnread; } @Override public long getId() { return id; } @Override public boolean isGroupHeader() { return isGroupHeader; } @Override public boolean isUnread() { return isUnread; } } private static class GroupHeader extends Entry { public GroupHeader(long id, int startPos) { super(-1 * id, startPos, true, false); } } private static class Entity extends Entry { public final int count; public Entity(long id, int startPos, int count, boolean isUnread) { super(id, startPos, false, isUnread); this.count = count; } } }