package com.applang.provider; import com.applang.provider.NotePad.NoteColumns; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.UriMatcher; import android.database.Cursor; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; import android.util.Log; import java.util.HashMap; import java.util.Map; import java.util.Set; import static com.applang.Util.*; import static com.applang.Util1.*; /** * Provides access to a database of notes. Each note has a title, the note * itself, a creation and a modification date. */ public class NotePadProvider extends ContentProvider { private static final String TAG = "NotePadProvider"; public static final String DATABASE_NAME = "note_pad.db"; public static final int DATABASE_VERSION = 4; public static final String[] DATABASE_TABLES = {"notes", "bausteine", "words"}; public static String tableName(int index) { if (index > -1 && index < DATABASE_TABLES.length) return DATABASE_TABLES[index]; else return ""; } public static int tableIndex(String name) { return asList(DATABASE_TABLES).indexOf(name); } public static Uri contentUri(int index) { return contentUri(tableName(index)); } public static Uri contentUri(String name) { return com.applang.Util1.contentUri(NotePad.AUTHORITY, name); } public static int tableIndex(int defaultValue, Uri uri) { if (uri == null) return defaultValue; int segments = uri.getPathSegments().size(); if (segments < 1) return defaultValue; else { int index = tableIndex(uri.getPathSegments().get(0)); return index < 0 ? defaultValue : index; } } public static ContentValues contentValues(int tableIndex, Object... args) { ContentValues values = new ContentValues(); if (args.length > 0) values.put(NoteColumns._ID, (Long)args[0]); if (args.length > 1) values.put(NoteColumns.TITLE, (String)args[1]); if (args.length > 2) values.put(NoteColumns.NOTE, (String)args[2]); if (args.length > 3) values.put(NoteColumns.CREATED_DATE, (Long)args[3]); if (args.length > 4) values.put(NoteColumns.MODIFIED_DATE, (Long)args[4]); return values; } private static HashMap<String, String> sNotesProjectionMap, sNotesProjectionMap2, sNotesProjectionMap3; public static Map<String, String> projectionMap(int type) { switch (type) { case NOTES_BAUSTEINE: return sNotesProjectionMap3; case NOTES_WORDS: return sNotesProjectionMap2; default: return sNotesProjectionMap; } }; public static final int NOTES = 0; public static final int NOTES_BAUSTEINE = 1; public static final int NOTES_WORDS = 2; public static final int RAW = 3; public static final int NOTE_ID = 4; public static final int NOTES_CREATE = 5; public static final int NOTES_DROP = 6; public static final int NOTES_MEMORY = 7; private static final UriMatcher sUriMatcher; public static String[] FULL_PROJECTION = strings( NoteColumns._ID, // 0 NoteColumns.TITLE, // 1 NoteColumns.NOTE, // 2 NoteColumns.CREATED_DATE, // 3 NoteColumns.MODIFIED_DATE // 4 ); public static class DatabaseHelper extends SQLiteOpenHelper { public DatabaseHelper(Context context, String dbName) { super(context, dbName, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { turnForeignKeys(db, true); for (int i = 0; i < DATABASE_TABLES.length; i++) { createTable(i, db); } } @Override public void onUpgrade(final SQLiteDatabase db, int oldVersion, int newVersion) { Log.w(TAG, "Upgrading database from version " + oldVersion + " to " + newVersion + ", which will try to loose as few as possible of the old data"); turnForeignKeys(db, false); for (int i = 0; i < DATABASE_TABLES.length; i++) table_upgrade(db, DATABASE_TABLES[i], new Job<Void>() { public void perform(Void t, Object[] parms) throws Exception { createTable(param_Integer(null, 0, parms), db); } }, i); turnForeignKeys(db, true); } } private static void dropTable(int index, SQLiteDatabase db) { db.execSQL("DROP TABLE IF EXISTS " + DATABASE_TABLES[index]); } public static void createTable(int index, SQLiteDatabase db) { String sql = "CREATE TABLE IF NOT EXISTS " + DATABASE_TABLES[index] + " (" + NoteColumns._ID + " INTEGER PRIMARY KEY," + NoteColumns.TITLE + " TEXT," + NoteColumns.NOTE + " TEXT," + NoteColumns.CREATED_DATE + " INTEGER," + NoteColumns.MODIFIED_DATE + " INTEGER"; switch (index) { default: sql += ", UNIQUE (" + NoteColumns.CREATED_DATE + ", " + NoteColumns.TITLE + ")"; break; } switch (index) { case 2: sql += ", foreign key(" + NoteColumns.REF_ID + ") references " + DATABASE_TABLES[0] + "(" + NoteColumns._ID + ")"; break; } sql += ");"; db.execSQL(sql); switch (index) { case 2: sql = "CREATE TRIGGER on_delete_note" + " BEFORE DELETE ON " + DATABASE_TABLES[0] + " FOR EACH ROW BEGIN" + " DELETE FROM " + DATABASE_TABLES[2] + " WHERE " + DATABASE_TABLES[2] + "." + NoteColumns.REF_ID + "=" + "old." + NoteColumns._ID + ";" + " END;"; db.execSQL(sql); break; } } private DatabaseHelper mOpenHelper = null; public SQLiteOpenHelper openHelper() { return mOpenHelper; } @Override public boolean onCreate() { mOpenHelper = new DatabaseHelper(getContext(), DATABASE_NAME); return true; } @Override public String getType(Uri uri) { switch (sUriMatcher.match(uri)) { case RAW: case NOTES_CREATE: case NOTES_DROP: case NOTES_MEMORY: case NOTES_WORDS: case NOTES_BAUSTEINE: case NOTES: return NoteColumns.CONTENT_TYPE; case NOTE_ID: return NoteColumns.CONTENT_ITEM_TYPE; default: throw new IllegalArgumentException("Unknown URI " + uri); } } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); String tableName = dbTableName(uri); int type = sUriMatcher.match(uri); qb.setProjectionMap(projectionMap(type)); switch (type) { case RAW: return mOpenHelper.getReadableDatabase().rawQuery(selection, selectionArgs); case NOTE_ID: qb.appendWhere(NoteColumns._ID + "=" + uri.getPathSegments().get(1)); case NOTES: qb.setTables(tableName); break; case NOTES_WORDS: qb.setTables(DATABASE_TABLES[0] .concat(" JOIN ").concat(DATABASE_TABLES[2]) .concat(" ON (").concat(projectionMap(type).get(NoteColumns._ID)) .concat(" = ").concat(projectionMap(type).get(NoteColumns.REF_ID)).concat(")")); break; case NOTES_BAUSTEINE: qb.setTables(DATABASE_TABLES[1] .concat(" JOIN ").concat(DATABASE_TABLES[2]) .concat(" ON (").concat(projectionMap(type).get(NoteColumns._ID)) .concat(" = ").concat(projectionMap(type).get(NoteColumns.REF_ID2)).concat(")")); break; case NOTES_MEMORY: if (mOpenHelper != null) mOpenHelper.close(); mOpenHelper = new DatabaseHelper(getContext(), uri.getPathSegments().get(1).equals("on") ? null : DATABASE_NAME); return null; case NOTES_DROP: dropTable(tableIndex(tableName), mOpenHelper.getWritableDatabase()); return null; case NOTES_CREATE: createTable(tableIndex(tableName), mOpenHelper.getWritableDatabase()); return null; default: throw new IllegalArgumentException("Unknown URI " + uri); } SQLiteDatabase db = mOpenHelper.getReadableDatabase(); String orderBy; if (notNullOrEmpty(sortOrder)) { orderBy = sortOrder; } else { orderBy = NoteColumns.DEFAULT_SORT_ORDER; } String groupBy = null, having = null; int index = notNullOrEmpty(selection) ? selection.indexOf("group by") : -1; if (index > -1) { groupBy = selection.substring(index + 8).trim(); selection = selection.substring(0, index).trim(); having = ""; index = groupBy.indexOf("having"); if (index > -1) { having = groupBy.substring(index + 6).trim(); groupBy = groupBy.substring(0, index).trim(); } } Cursor c = qb.query(db, projection, selection, selectionArgs, groupBy, having, orderBy); // Tell the cursor what uri to watch, so it knows when its source data changes c.setNotificationUri(getContext().getContentResolver(), uri); return c; } private void notifyChange(Uri uri) { getContext().getContentResolver().notifyChange(uri, null); } @Override public Uri insert(Uri uri, ContentValues initialValues) { if (sUriMatcher.match(uri) != NOTES) { throw new IllegalArgumentException("Unknown URI " + uri); } ContentValues values; if (initialValues != null) { values = new ContentValues(initialValues); } else { values = new ContentValues(); } Long now = Long.valueOf(System.currentTimeMillis()); // Make sure that the fields are all set if (values.containsKey(NoteColumns.CREATED_DATE) == false) { values.put(NoteColumns.CREATED_DATE, now); } if (values.containsKey(NoteColumns.MODIFIED_DATE) == false) { values.put(NoteColumns.MODIFIED_DATE, now); } if (values.containsKey(NoteColumns.TITLE) == false) { values.put(NoteColumns.TITLE, ""); } if (values.containsKey(NoteColumns.NOTE) == false) { values.put(NoteColumns.NOTE, ""); } SQLiteDatabase db = mOpenHelper.getWritableDatabase(); String tableName = dbTableName(uri); long rowId = db.insert(tableName, NoteColumns.NOTE, values); if (rowId > 0) { Uri noteUri = ContentUris.withAppendedId(contentUri(tableName), rowId); notifyChange(noteUri); return noteUri; } throw new SQLException(String.format("Failed to insert row %s into %s", values, uri)); } @Override public int delete(Uri uri, String where, String[] whereArgs) { SQLiteDatabase db = mOpenHelper.getWritableDatabase(); int count; String tableName = dbTableName(uri); switch (sUriMatcher.match(uri)) { case NOTES: count = db.delete(tableName, where, whereArgs); break; case NOTE_ID: String noteId = uri.getPathSegments().get(1); count = db.delete(tableName, NoteColumns._ID + "=" + noteId + (notNullOrEmpty(where) ? " AND (" + where + ')' : ""), whereArgs); break; default: throw new IllegalArgumentException("Unknown URI " + uri); } notifyChange(uri); return count; } @Override public int update(Uri uri, ContentValues values, String where, String[] whereArgs) { SQLiteDatabase db = mOpenHelper.getWritableDatabase(); String tableName = dbTableName(uri); int count; switch (sUriMatcher.match(uri)) { case NOTES: count = db.update(tableName, values, where, whereArgs); break; case NOTE_ID: String noteId = uri.getPathSegments().get(1); count = db.update(tableName, values, NoteColumns._ID + "=" + noteId + (notNullOrEmpty(where) ? " AND (" + where + ')' : ""), whereArgs); break; default: throw new IllegalArgumentException("Unknown URI " + uri); } notifyChange(uri); return count; } static { sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); sUriMatcher.addURI(NotePad.AUTHORITY, null, RAW); sUriMatcher.addURI(NotePad.AUTHORITY, DATABASE_TABLES[0], NOTES); sUriMatcher.addURI(NotePad.AUTHORITY, DATABASE_TABLES[0] + "/#", NOTE_ID); sUriMatcher.addURI(NotePad.AUTHORITY, DATABASE_TABLES[1], NOTES); sUriMatcher.addURI(NotePad.AUTHORITY, DATABASE_TABLES[1] + "/#", NOTE_ID); sUriMatcher.addURI(NotePad.AUTHORITY, DATABASE_TABLES[1] + "/" + NoteColumns.TITLE + "/*", NOTE_ID); sUriMatcher.addURI(NotePad.AUTHORITY, DATABASE_TABLES[2], NOTES); sUriMatcher.addURI(NotePad.AUTHORITY, DATABASE_TABLES[2] + "/#", NOTE_ID); sUriMatcher.addURI(NotePad.AUTHORITY, DATABASE_TABLES[0] + "/" + DATABASE_TABLES[2], NOTES_WORDS); sUriMatcher.addURI(NotePad.AUTHORITY, DATABASE_TABLES[0] + "/" + DATABASE_TABLES[1], NOTES_BAUSTEINE); sUriMatcher.addURI(NotePad.AUTHORITY, "memory/on", NOTES_MEMORY); sUriMatcher.addURI(NotePad.AUTHORITY, "memory/off", NOTES_MEMORY); for (int j = 0; j < DATABASE_TABLES.length; j++) { sUriMatcher.addURI(NotePad.AUTHORITY, DATABASE_TABLES[j] + "/create", NOTES_CREATE); sUriMatcher.addURI(NotePad.AUTHORITY, DATABASE_TABLES[j] + "/drop", NOTES_DROP); } sNotesProjectionMap = new HashMap<String, String>(); sNotesProjectionMap.put(NoteColumns._ID, NoteColumns._ID); sNotesProjectionMap.put(NoteColumns.TITLE, NoteColumns.TITLE); sNotesProjectionMap.put(NoteColumns.NOTE, NoteColumns.NOTE); sNotesProjectionMap.put(NoteColumns.CREATED_DATE, NoteColumns.CREATED_DATE); sNotesProjectionMap.put(NoteColumns.MODIFIED_DATE, NoteColumns.MODIFIED_DATE); sNotesProjectionMap.put("count", "count(*)"); sNotesProjectionMap2 = new HashMap<String, String>(); sNotesProjectionMap2.put(NoteColumns._ID, DATABASE_TABLES[0].concat(".").concat(NoteColumns._ID)); sNotesProjectionMap2.put(NoteColumns.TITLE, DATABASE_TABLES[2].concat(".").concat(NoteColumns.TITLE)); sNotesProjectionMap2.put(NoteColumns.NOTE, DATABASE_TABLES[0].concat(".").concat(NoteColumns.NOTE)); sNotesProjectionMap2.put(NoteColumns.REF_ID, DATABASE_TABLES[2].concat(".").concat(NoteColumns.CREATED_DATE)); sNotesProjectionMap2.put(NoteColumns.REF_ID2, DATABASE_TABLES[2].concat(".").concat(NoteColumns.MODIFIED_DATE)); sNotesProjectionMap2.put("date", DATABASE_TABLES[0].concat(".").concat(NoteColumns.CREATED_DATE)); sNotesProjectionMap3 = new HashMap<String, String>(); sNotesProjectionMap3.put(NoteColumns._ID, DATABASE_TABLES[1].concat(".").concat(NoteColumns._ID)); sNotesProjectionMap3.put(NoteColumns.NOTE, DATABASE_TABLES[1].concat(".").concat(NoteColumns.TITLE)); sNotesProjectionMap3.put(NoteColumns.REF_ID, DATABASE_TABLES[2].concat(".").concat(NoteColumns.CREATED_DATE)); sNotesProjectionMap3.put(NoteColumns.REF_ID2, DATABASE_TABLES[2].concat(".").concat(NoteColumns.MODIFIED_DATE)); } public static Integer[] countNotes(ContentResolver contentResolver, int tableIndex, String selection, String[] selectionArgs) { ValList counts = vlist(); Cursor cursor = contentResolver.query(contentUri(tableIndex), strings("count"), selection, selectionArgs, NoteColumns.DEFAULT_SORT_ORDER); traverse(cursor, new Job<Cursor>() { public void perform(Cursor cursor, Object[] parms) throws Exception { ValList counts = (ValList) parms[0]; counts.add(cursor.getInt(0)); } }, counts); return counts.toArray(new Integer[0]); } public static String[] getTitles(ContentResolver contentResolver, int tableIndex, String selection, String[] selectionArgs) { ValList titles = vlist(); Cursor cursor = contentResolver.query(contentUri(tableIndex), strings(NoteColumns.TITLE), selection, selectionArgs, NoteColumns.DEFAULT_SORT_ORDER); traverse(cursor, new Job<Cursor>() { public void perform(Cursor cursor, Object[] parms) throws Exception { ValList titles = (ValList) parms[0]; titles.add(cursor.getString(0)); } }, titles); return toStrings(titles); } public static long getIdOfNote(ContentResolver contentResolver, int tableIndex, String selection, String[] selectionArgs) { Cursor cursor = contentResolver.query(contentUri(tableIndex), strings(NoteColumns._ID), selection, selectionArgs, null); Object[] parms = {-1L}; traverse(cursor, new Job<Cursor>() { public void perform(Cursor cursor, Object[] parms) throws Exception { parms[0] = cursor.getLong(0); } }, parms); return (Long) parms[0]; } public static boolean fetchNoteById(long id, ContentResolver contentResolver, int tableIndex, Job<Cursor> job, Object... params) { return traverse( contentResolver.query( ContentUris.withAppendedId(contentUri(tableIndex), id), NotePadProvider.FULL_PROJECTION, "", null, null), job, params); } public static Set<String> wordSet(ContentResolver contentResolver, int tableIndex, String selection, String... selectionArgs) { Cursor cursor = contentResolver.query( contentUri(tableIndex), strings(NoteColumns.TITLE), selection, selectionArgs, null); ValMap map = getResults(cursor, new Function<String>() { public String apply(Object... params) { Cursor cursor = param(null, 0, params); return cursor.getString(0); } }, new Function<Object>() { public Object apply(Object... params) { return null; } } ); return sortedSet(map.keySet()); } public static ValMap bausteinMap(ContentResolver contentResolver, String selection, String... selectionArgs) { Cursor cursor = contentResolver.query( contentUri(1), strings(NoteColumns.TITLE, NoteColumns.NOTE), selection, selectionArgs, null); ValMap map = getResults(cursor, new Function<String>() { public String apply(Object... params) { Cursor cursor = param(null, 0, params); return cursor.getString(0); } }, new Function<Object>() { public Object apply(Object... params) { Cursor cursor = param(null, 0, params); return cursor.getString(1); } } ); return map; } }