Enforce base class for classes annotated with RoomDatabase

Also renamed support db classes to match the framework's naming.

Bug: 32342709
Test: DatabaseProcessorTest
Change-Id: I588d8ddf42b3c91c5b709090659c56ce04a576b8
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/ext/javapoet_ext.kt b/room/compiler/src/main/kotlin/com/android/support/room/ext/javapoet_ext.kt
index 5657867..4968fd1 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/ext/javapoet_ext.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/ext/javapoet_ext.kt
@@ -32,8 +32,8 @@
 fun TypeMirror.typeName() = TypeName.get(this)
 
 object SupportDbTypeNames {
-    val DB: ClassName = ClassName.get("com.android.support.db", "SupportDb")
-    val SQLITE_STMT : ClassName = ClassName.get("com.android.support.db", "SupportSqliteStatement")
+    val DB: ClassName = ClassName.get("com.android.support.db", "SupportSQLiteDatabase")
+    val SQLITE_STMT : ClassName = ClassName.get("com.android.support.db", "SupportSQLiteStatement")
 }
 
 object RoomTypeNames {
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/processor/DatabaseProcessor.kt b/room/compiler/src/main/kotlin/com/android/support/room/processor/DatabaseProcessor.kt
index 320c5d9..9694a24 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/processor/DatabaseProcessor.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/processor/DatabaseProcessor.kt
@@ -16,6 +16,7 @@
 
 package com.android.support.room.processor
 
+import com.android.support.room.ext.RoomTypeNames
 import com.android.support.room.ext.hasAnyOf
 import com.android.support.room.vo.DaoMethod
 import com.android.support.room.vo.Database
@@ -36,13 +37,22 @@
     val entityParser = EntityProcessor(context)
     val daoParser = DaoProcessor(context)
 
+    val baseClassElement : TypeMirror by lazy {
+        context.processingEnv.elementUtils.getTypeElement(
+                RoomTypeNames.ROOM_DB.packageName() + "." + RoomTypeNames.ROOM_DB.simpleName())
+                .asType()
+    }
+
     fun parse(element: TypeElement): Database {
         val dbAnnotation = MoreElements
                 .getAnnotationMirror(element, com.android.support.room.Database::class.java)
                 .orNull()
-
         val entities = processEntities(dbAnnotation, element)
 
+        val extendsRoomDb = context.processingEnv.typeUtils.isAssignable(
+                MoreElements.asType(element).asType(), baseClassElement)
+        context.checker.check(extendsRoomDb, element, ProcessorErrors.DB_MUST_EXTEND_ROOM_DB)
+
         val allMembers = context.processingEnv.elementUtils.getAllMembers(element)
         val daoMethods = allMembers.filter {
             it.hasAnyOf(Modifier.ABSTRACT) && it.kind == ElementKind.METHOD
@@ -53,6 +63,7 @@
             val dao = daoParser.parse(MoreTypes.asTypeElement(executable.returnType))
             DaoMethod(executable, executable.simpleName.toString(), dao)
         }
+
         return Database(element = element,
                 entities = entities,
                 daoMethods = daoMethods)
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/processor/ProcessorErrors.kt b/room/compiler/src/main/kotlin/com/android/support/room/processor/ProcessorErrors.kt
index 5686408..47253bb 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/processor/ProcessorErrors.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/processor/ProcessorErrors.kt
@@ -18,6 +18,7 @@
 
 import com.android.support.room.Insert
 import com.android.support.room.Query
+import com.android.support.room.ext.RoomTypeNames
 import com.android.support.room.vo.Field
 
 object ProcessorErrors {
@@ -74,6 +75,9 @@
     val CANNOT_FIND_ENTITY_FOR_INSERT_PARAMETER = "Cannot find the entity type for the" +
             " insert parameter."
 
+    val DB_MUST_EXTEND_ROOM_DB = "Classes annotated with @Database should extend " +
+            RoomTypeNames.ROOM_DB
+
     private val TOO_MANY_MATCHING_GETTERS = "Ambiguous getter for %s. All of the following " +
             "match: %s. You can @Ignore the ones that you don't want to match."
 
diff --git a/room/compiler/src/test/data/daoWriter/output/WriterDao.java b/room/compiler/src/test/data/daoWriter/output/WriterDao.java
index ee99ec9..e5dc0c9 100644
--- a/room/compiler/src/test/data/daoWriter/output/WriterDao.java
+++ b/room/compiler/src/test/data/daoWriter/output/WriterDao.java
@@ -16,7 +16,7 @@
 
 package foo.bar;
 
-import com.android.support.db.SupportSqliteStatement;
+import com.android.support.db.SupportSQLiteStatement;
 import com.android.support.room.EntityInsertionAdapter;
 import com.android.support.room.RoomDatabase;
 import java.lang.Override;
@@ -39,7 +39,7 @@
             }
 
             @Override
-            public void bind(SupportSqliteStatement stmt, User value) {
+            public void bind(SupportSQLiteStatement stmt, User value) {
                 stmt.bindLong(0, value.uid);
             }
         };
@@ -51,7 +51,7 @@
             }
 
             @Override
-            public void bind(SupportSqliteStatement stmt, User value) {
+            public void bind(SupportSQLiteStatement stmt, User value) {
                 stmt.bindLong(0, value.uid);
             }
         };
@@ -90,4 +90,4 @@
             __db.endTransaction();
         }
     }
-}
\ No newline at end of file
+}
diff --git a/room/compiler/src/test/kotlin/com/android/support/room/processor/DatabaseProcessorTest.kt b/room/compiler/src/test/kotlin/com/android/support/room/processor/DatabaseProcessorTest.kt
index 3fa8d27..b772007 100644
--- a/room/compiler/src/test/kotlin/com/android/support/room/processor/DatabaseProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/com/android/support/room/processor/DatabaseProcessorTest.kt
@@ -37,6 +37,7 @@
         const val DATABASE_PREFIX = """
             package foo.bar;
             import com.android.support.room.*;
+            import com.android.support.db.SupportSQLiteDatabase;
             """
         val USER: JavaFileObject = JavaFileObjects.forSourceString("foo.bar.User",
                 """
@@ -84,8 +85,11 @@
     fun simple() {
         singleDb("""
             @Database(entities = {User.class})
-            public interface MyDb {
-                UserDao userDao();
+            public abstract class MyDb extends RoomDatabase {
+                public MyDb(SupportSQLiteDatabase supportDb) {
+                    super(supportDb);
+                }
+                abstract UserDao userDao();
             }
             """, USER, USER_DAO) { db, invocation ->
             assertThat(db.daoMethods.size, `is`(1))
@@ -97,7 +101,10 @@
     fun multiple() {
         singleDb("""
             @Database(entities = {User.class, Book.class})
-            public abstract class MyDb {
+            public abstract class MyDb extends RoomDatabase {
+                public MyDb(SupportSQLiteDatabase supportDb) {
+                    super(supportDb);
+                }
                 abstract UserDao userDao();
                 abstract BookDao bookDao();
             }
@@ -110,6 +117,16 @@
         }.compilesWithoutError()
     }
 
+    @Test
+    fun detectMissingBaseClass() {
+        singleDb("""
+            @Database(entities = {User.class, Book.class})
+            public abstract class MyDb {
+            }
+            """, USER, BOOK) { db, invocation ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.DB_MUST_EXTEND_ROOM_DB)
+    }
+
     fun singleDb(input: String, vararg otherFiles: JavaFileObject,
                  handler: (Database, TestInvocation) -> Unit): CompileTester {
         return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
diff --git a/room/db/src/main/java/com/android/support/db/SupportDb.java b/room/db/src/main/java/com/android/support/db/SupportSQLiteDatabase.java
similarity index 94%
rename from room/db/src/main/java/com/android/support/db/SupportDb.java
rename to room/db/src/main/java/com/android/support/db/SupportSQLiteDatabase.java
index e1bf01d..f9d9c1f 100644
--- a/room/db/src/main/java/com/android/support/db/SupportDb.java
+++ b/room/db/src/main/java/com/android/support/db/SupportSQLiteDatabase.java
@@ -23,8 +23,8 @@
  * sql versions.
  */
 @SuppressWarnings("unused")
-public interface SupportDb {
-    // TODO override all and rename to SupportSqliteDatabase
+public interface SupportSQLiteDatabase {
+    // TODO override all methods
     /**
      * Runs the query with the given arguments and returns the result Cursor.
      *
@@ -42,7 +42,7 @@
      *
      * @return Compiled statement.
      */
-    SupportSqliteStatement compileStatement(String sql);
+    SupportSQLiteStatement compileStatement(String sql);
 
     /**
      * Begins a transaction in EXCLUSIVE mode.
diff --git a/room/db/src/main/java/com/android/support/db/SupportSqliteProgram.java b/room/db/src/main/java/com/android/support/db/SupportSQLiteProgram.java
similarity index 98%
rename from room/db/src/main/java/com/android/support/db/SupportSqliteProgram.java
rename to room/db/src/main/java/com/android/support/db/SupportSQLiteProgram.java
index d042551..c23568c 100644
--- a/room/db/src/main/java/com/android/support/db/SupportSqliteProgram.java
+++ b/room/db/src/main/java/com/android/support/db/SupportSQLiteProgram.java
@@ -21,7 +21,7 @@
  */
 
 @SuppressWarnings("unused")
-public interface SupportSqliteProgram {
+public interface SupportSQLiteProgram {
     /**
      * Bind a NULL value to this statement. The value remains bound until
      * {@link #clearBindings} is called.
diff --git a/room/db/src/main/java/com/android/support/db/SupportSqliteStatement.java b/room/db/src/main/java/com/android/support/db/SupportSQLiteStatement.java
similarity index 97%
rename from room/db/src/main/java/com/android/support/db/SupportSqliteStatement.java
rename to room/db/src/main/java/com/android/support/db/SupportSQLiteStatement.java
index ba40227..e3ebc5e 100644
--- a/room/db/src/main/java/com/android/support/db/SupportSqliteStatement.java
+++ b/room/db/src/main/java/com/android/support/db/SupportSQLiteStatement.java
@@ -22,7 +22,7 @@
  * An interface to map the behavior of {@link android.database.sqlite.SQLiteStatement}.
  */
 @SuppressWarnings("unused")
-public interface SupportSqliteStatement extends SupportSqliteProgram {
+public interface SupportSQLiteStatement extends SupportSQLiteProgram {
     /**
      * Execute this SQL statement, if it is not a SELECT / INSERT / DELETE / UPDATE, for example
      * CREATE / DROP table, view, trigger, index etc.
diff --git a/room/runtime/src/main/java/com/android/support/room/EntityInsertionAdapter.java b/room/runtime/src/main/java/com/android/support/room/EntityInsertionAdapter.java
index 66e0686..8882b72 100644
--- a/room/runtime/src/main/java/com/android/support/room/EntityInsertionAdapter.java
+++ b/room/runtime/src/main/java/com/android/support/room/EntityInsertionAdapter.java
@@ -16,8 +16,8 @@
 
 package com.android.support.room;
 
-import com.android.support.db.SupportDb;
-import com.android.support.db.SupportSqliteStatement;
+import com.android.support.db.SupportSQLiteDatabase;
+import com.android.support.db.SupportSQLiteStatement;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -35,15 +35,15 @@
 @SuppressWarnings({"WeakerAccess", "unused"})
 public abstract class EntityInsertionAdapter<T> {
     private final AtomicBoolean mStmtLock = new AtomicBoolean(false);
-    private final SupportDb mDatabase;
-    private volatile SupportSqliteStatement mStmt;
+    private final SupportSQLiteDatabase mDatabase;
+    private volatile SupportSQLiteStatement mStmt;
 
     /**
      * Creates an InsertionAdapter that can insert the entity type T into the given database.
      *
      * @param database The database to insert into.
      */
-    public EntityInsertionAdapter(SupportDb database) {
+    public EntityInsertionAdapter(SupportSQLiteDatabase database) {
         mDatabase = database;
     }
 
@@ -61,15 +61,15 @@
      *                  createInsertQuery.
      * @param entity    The entity of type T.
      */
-    protected abstract void bind(SupportSqliteStatement statement, T entity);
+    protected abstract void bind(SupportSQLiteStatement statement, T entity);
 
-    private SupportSqliteStatement createNewStatement() {
+    private SupportSQLiteStatement createNewStatement() {
         String query = createInsertQuery();
         return mDatabase.compileStatement(query);
     }
 
-    private SupportSqliteStatement getStmt(boolean canUseCached) {
-        final SupportSqliteStatement stmt;
+    private SupportSQLiteStatement getStmt(boolean canUseCached) {
+        final SupportSQLiteStatement stmt;
         if (canUseCached) {
             if (mStmt == null) {
                 mStmt = createNewStatement();
@@ -90,7 +90,7 @@
     public final void insert(T entity) {
         boolean useCached = !mStmtLock.getAndSet(true);
         try {
-            final SupportSqliteStatement stmt = getStmt(useCached);
+            final SupportSQLiteStatement stmt = getStmt(useCached);
             bind(stmt, entity);
             stmt.executeInsert();
         } finally {
@@ -108,7 +108,7 @@
     public final void insert(T[] entities) {
         boolean useCached = !mStmtLock.getAndSet(true);
         try {
-            final SupportSqliteStatement stmt = getStmt(useCached);
+            final SupportSQLiteStatement stmt = getStmt(useCached);
             for (T entity : entities) {
                 bind(stmt, entity);
             }
@@ -127,7 +127,7 @@
     public final void insert(List<T> entities) {
         boolean useCached = !mStmtLock.getAndSet(true);
         try {
-            final SupportSqliteStatement stmt = getStmt(useCached);
+            final SupportSQLiteStatement stmt = getStmt(useCached);
             for (T entity : entities) {
                 bind(stmt, entity);
             }
@@ -147,7 +147,7 @@
     public final long insertAndReturnId(T entity) {
         boolean useCached = !mStmtLock.getAndSet(true);
         try {
-            final SupportSqliteStatement stmt = getStmt(useCached);
+            final SupportSQLiteStatement stmt = getStmt(useCached);
             bind(stmt, entity);
             return stmt.executeInsert();
         } finally {
@@ -167,7 +167,7 @@
         boolean useCached = !mStmtLock.getAndSet(true);
         try {
             final long[] result = new long[entities.size()];
-            final SupportSqliteStatement stmt = getStmt(useCached);
+            final SupportSQLiteStatement stmt = getStmt(useCached);
             int index = 0;
             for (T entity : entities) {
                 bind(stmt, entity);
@@ -192,7 +192,7 @@
         boolean useCached = !mStmtLock.getAndSet(true);
         try {
             final long[] result = new long[entities.length];
-            final SupportSqliteStatement stmt = getStmt(useCached);
+            final SupportSQLiteStatement stmt = getStmt(useCached);
             int index = 0;
             for (T entity : entities) {
                 bind(stmt, entity);
@@ -217,7 +217,7 @@
         boolean useCached = !mStmtLock.getAndSet(true);
         try {
             final List<Long> result = new ArrayList<>(entities.length);
-            final SupportSqliteStatement stmt = getStmt(useCached);
+            final SupportSQLiteStatement stmt = getStmt(useCached);
             int index = 0;
             for (T entity : entities) {
                 bind(stmt, entity);
@@ -242,7 +242,7 @@
         boolean useCached = !mStmtLock.getAndSet(true);
         try {
             final List<Long> result = new ArrayList<>(entities.size());
-            final SupportSqliteStatement stmt = getStmt(useCached);
+            final SupportSQLiteStatement stmt = getStmt(useCached);
             int index = 0;
             for (T entity : entities) {
                 bind(stmt, entity);
diff --git a/room/runtime/src/main/java/com/android/support/room/RoomDatabase.java b/room/runtime/src/main/java/com/android/support/room/RoomDatabase.java
index 3d43bc5..e7e9a25 100644
--- a/room/runtime/src/main/java/com/android/support/room/RoomDatabase.java
+++ b/room/runtime/src/main/java/com/android/support/room/RoomDatabase.java
@@ -18,23 +18,18 @@
 
 import android.database.Cursor;
 
-import com.android.support.db.SupportDb;
-import com.android.support.db.SupportSqliteStatement;
-
-import java.util.HashMap;
-import java.util.Map;
+import com.android.support.db.SupportSQLiteDatabase;
+import com.android.support.db.SupportSQLiteStatement;
 
 /**
  * Base class for all Room databases.
  */
 @SuppressWarnings("unused")
-public abstract class RoomDatabase implements SupportDb {
-    private ThreadLocal<Map<String, SupportSqliteStatement>> mStatementCache;
-    private final SupportDb mDb;
+public abstract class RoomDatabase implements SupportSQLiteDatabase {
+    private final SupportSQLiteDatabase mDb;
 
-    public RoomDatabase(SupportDb supportDb) {
+    public RoomDatabase(SupportSQLiteDatabase supportDb) {
         mDb = supportDb;
-        mStatementCache = new ThreadLocal<>();
     }
 
     @Override
@@ -43,17 +38,22 @@
     }
 
     @Override
-    public SupportSqliteStatement compileStatement(String sql) {
-        Map<String, SupportSqliteStatement> cache = mStatementCache.get();
-        if (cache == null) {
-            cache = new HashMap<>();
-            mStatementCache.set(cache);
-        }
-        SupportSqliteStatement cached = cache.get(sql);
-        if (cached == null) {
-            cached = mDb.compileStatement(sql);
-            cache.put(sql, cached);
-        }
-        return cached;
+    public SupportSQLiteStatement compileStatement(String sql) {
+        return mDb.compileStatement(sql);
+    }
+
+    @Override
+    public void beginTransaction() {
+        mDb.beginTransaction();
+    }
+
+    @Override
+    public void endTransaction() {
+        mDb.endTransaction();
+    }
+
+    @Override
+    public void setTransactionSuccessful() {
+        mDb.setTransactionSuccessful();
     }
 }