package de.westnordost.streetcomplete.data; import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; import java.util.ArrayList; import java.util.Collection; import java.util.List; import de.westnordost.osmapi.map.data.BoundingBox; public abstract class AQuestDao<T extends Quest> { private static final String TAG = "QuestDao"; private final SQLiteOpenHelper dbHelper; public AQuestDao(SQLiteOpenHelper dbHelper) { this.dbHelper = dbHelper; } public T get(long id) { SQLiteDatabase db = dbHelper.getReadableDatabase(); Cursor cursor = db.query(getMergedViewName(), null, getIdColumnName() + " = " + id, null, null, null, null, "1"); try { if(!cursor.moveToFirst()) return null; return createObjectFrom(cursor); } finally { cursor.close(); } } public List<T> getAll(BoundingBox bbox, QuestStatus status) { WhereSelectionBuilder qb = new WhereSelectionBuilder(); addBBox(bbox, qb); addQuestStatus(status, qb); return getAllThings(getMergedViewName(), null, qb, new CreateFromCursor<T>() { @Override public T create(Cursor cursor) { return createObjectFrom(cursor); } }); } public int getCount(BoundingBox bbox, QuestStatus status) { SQLiteDatabase db = dbHelper.getReadableDatabase(); WhereSelectionBuilder qb = new WhereSelectionBuilder(); addBBox(bbox, qb); addQuestStatus(status, qb); Cursor cursor = db.query(getMergedViewName(), new String[]{"COUNT(*)"}, qb.getWhere(), qb.getArgs(), null, null, null, null); try { cursor.moveToFirst(); return cursor.getInt(0); } finally { cursor.close(); } } protected final void addBBox(BoundingBox bbox, WhereSelectionBuilder builder) { if(bbox != null) { builder.appendAnd("(" + getLatitudeColumnName() + " BETWEEN ? AND ?)", String.valueOf(bbox.getMinLatitude()), String.valueOf(bbox.getMaxLatitude())); builder.appendAnd("(" + getLongitudeColumnName() + " BETWEEN ? AND ?)", String.valueOf(bbox.getMinLongitude()), String.valueOf(bbox.getMaxLongitude())); } } protected final void addQuestStatus(QuestStatus status, WhereSelectionBuilder builder) { if(status != null) { builder.appendAnd(getQuestStatusColumnName() + " = ?", status.name()); } } protected final <E> List<E> getAllThings(String tablename, String[] cols, WhereSelectionBuilder query, CreateFromCursor<E> creator) { SQLiteDatabase db = dbHelper.getReadableDatabase(); Cursor cursor = db.query(tablename, cols, query.getWhere(), query.getArgs(), null, null, null, null); List<E> result = new ArrayList<>(); List<Long> invalidIds = new ArrayList<>(); try { if(cursor.moveToFirst()) { while(!cursor.isAfterLast()) { try { result.add(creator.create(cursor)); } catch (Exception e) { Log.e(TAG,"Getting quest from db caused an exception",e); int idCol = cursor.getColumnIndex(getIdColumnName()); invalidIds.add(cursor.getLong(idCol)); } cursor.moveToNext(); } } } finally { cursor.close(); } if(!invalidIds.isEmpty()) { Log.i(TAG, "The previously encountered corrupt quests are now removed from database"); deleteAll(invalidIds); } return result; } protected interface CreateFromCursor<E> { E create(Cursor cursor); } public void update(T quest) { SQLiteDatabase db = dbHelper.getWritableDatabase(); int rows = db.update(getTableName(), createNonFinalContentValuesFrom(quest), getIdColumnName() + " = " + quest.getId(), null); if(rows == 0) { throw new NullPointerException(quest.getClass().getSimpleName() + " with the id " + quest.getId() + " does not exist."); } } public boolean delete(long id) { SQLiteDatabase db = dbHelper.getWritableDatabase(); return db.delete(getTableName(), getIdColumnName() + " = " + id, null) == 1; } public int deleteAll(Collection<Long> ids) { SQLiteDatabase db = dbHelper.getWritableDatabase(); StringBuilder idsString = new StringBuilder(); boolean first = true; for (Long id : ids) { if(first) first = false; else idsString.append(","); idsString.append(id); } return db.delete(getTableName(), getIdColumnName() + " IN (" + idsString.toString() + ")", null); } public int addAll(Collection<T> quests) { return insertAll(quests, false); } public int replaceAll(Collection<T> quests) { return insertAll(quests, true); } private int insertAll(Collection<T> quests, boolean replace) { SQLiteDatabase db = dbHelper.getWritableDatabase(); int addRows = 0; db.beginTransaction(); for(T quest : quests) { long rowId = executeInsert(quest, replace); boolean alreadyExists = rowId == -1; if(!alreadyExists) { quest.setId(rowId); addRows++; } } db.setTransactionSuccessful(); db.endTransaction(); return addRows; } /** Add given quest to DB and sets the quest's id after inserting it * @return true if successfully inserted, false if quest already exists in DB (= not inserted) */ public boolean add(T quest) { return insert(quest, false); } /** Add given quest to DB and sets the quest's id after inserting it. If the quest already * exists, replaces it with the given one. */ public boolean replace(T quest) { return insert(quest, true); } private boolean insert(T quest, boolean replace) { SQLiteDatabase db = dbHelper.getWritableDatabase(); db.beginTransaction(); long rowId = executeInsert(quest, replace); boolean alreadyExists = rowId == -1; db.setTransactionSuccessful(); db.endTransaction(); if(!alreadyExists) { quest.setId(rowId); } return !alreadyExists; } private ContentValues createContentValuesFrom(T object) { ContentValues result = createFinalContentValuesFrom(object); result.putAll(createNonFinalContentValuesFrom(object)); return result; } protected abstract String getTableName(); protected abstract String getMergedViewName(); protected abstract String getIdColumnName(); protected abstract String getQuestStatusColumnName(); protected abstract String getLatitudeColumnName(); protected abstract String getLongitudeColumnName(); protected abstract long executeInsert(T object, boolean replace); protected abstract ContentValues createNonFinalContentValuesFrom(T object); protected abstract ContentValues createFinalContentValuesFrom(T object); protected abstract T createObjectFrom(Cursor cursor); }