Custom column name and entity name

Provides ability to set custom column names for fields and
custom table names for entities.

Bug: 32342709
Test: FieldProcessorTest#columnName,emptyColumnName
Test: EntitiyProcessorTest#customName,emptyCustomName
Change-Id: I68916d8d8276a1ebadf5df244002d675b9815d8d
diff --git a/room/common/src/main/java/com/android/support/room/ColumnName.java b/room/common/src/main/java/com/android/support/room/ColumnName.java
new file mode 100644
index 0000000..841ee65
--- /dev/null
+++ b/room/common/src/main/java/com/android/support/room/ColumnName.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.support.room;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.SOURCE)
+public @interface ColumnName {
+    /**
+     * Name of the column in the database. Defaults to the field name if not set.
+     *
+     * @return Name of the column in the database.
+     */
+    String value();
+}
diff --git a/room/common/src/main/java/com/android/support/room/Entity.java b/room/common/src/main/java/com/android/support/room/Entity.java
index e494eb5..44ec3d5 100644
--- a/room/common/src/main/java/com/android/support/room/Entity.java
+++ b/room/common/src/main/java/com/android/support/room/Entity.java
@@ -24,4 +24,5 @@
 @Target(ElementType.TYPE)
 @Retention(RetentionPolicy.SOURCE)
 public @interface Entity {
+    String tableName() default "";
 }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/preconditions/Checks.kt b/room/compiler/src/main/kotlin/com/android/support/room/preconditions/Checks.kt
index 64619f4..039f744 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/preconditions/Checks.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/preconditions/Checks.kt
@@ -41,12 +41,16 @@
         }
     }
 
-    fun assertNotUnbound(typeName: TypeName, element: Element, errorMsg : String,
-                         vararg args : Any) {
+    fun notUnbound(typeName: TypeName, element: Element, errorMsg : String,
+                   vararg args : Any) {
         // TODO support bounds cases like <T extends Foo> T bar()
         Checks.check(typeName !is TypeVariableName, element, errorMsg, args)
         if (typeName is ParameterizedTypeName) {
-            typeName.typeArguments.forEach { assertNotUnbound(it, element, errorMsg, args) }
+            typeName.typeArguments.forEach { notUnbound(it, element, errorMsg, args) }
         }
     }
+
+    fun notBlank(value: String?, element: Element, msg: String, vararg args : Any) {
+        Checks.check(value != null && value.isNotBlank(), element, msg, args)
+    }
 }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/processor/DaoProcessor.kt b/room/compiler/src/main/kotlin/com/android/support/room/processor/DaoProcessor.kt
index ea328f5..6b59458 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/processor/DaoProcessor.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/processor/DaoProcessor.kt
@@ -45,8 +45,8 @@
             queryParser.parse(declaredType, MoreElements.asExecutable(it))
         }
         val type = TypeName.get(declaredType)
-        Checks.assertNotUnbound(type, element,
+        Checks.notUnbound(type, element,
                 ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_DAO_CLASSES)
         return Dao(element = element, type = type, queryMethods = methods)
     }
-}
\ No newline at end of file
+}
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/processor/EntityProcessor.kt b/room/compiler/src/main/kotlin/com/android/support/room/processor/EntityProcessor.kt
index 83cd1d7..aaba338 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/processor/EntityProcessor.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/processor/EntityProcessor.kt
@@ -22,6 +22,7 @@
 import com.android.support.room.ext.hasAnyOf
 import com.android.support.room.preconditions.Checks
 import com.android.support.room.vo.*
+import com.google.auto.common.AnnotationMirrors
 import com.google.auto.common.MoreElements
 import com.google.auto.common.MoreTypes
 import com.squareup.javapoet.TypeName
@@ -69,7 +70,22 @@
 
         assignGetters(fields, getterCandidates)
         assignSetters(fields, setterCandidates)
-        val entity = Entity(TypeName.get(declaredType), fields)
+        val annotation = MoreElements.getAnnotationMirror(element,
+                com.android.support.room.Entity::class.java)
+        val tableName : String
+        if (annotation.isPresent) {
+            val annotationValue = AnnotationMirrors
+                    .getAnnotationValue(annotation.get(), "tableName").value.toString()
+            if (annotationValue == "") {
+                tableName = element.simpleName.toString()
+            } else {
+                tableName = annotationValue
+            }
+        } else {
+            tableName = element.simpleName.toString()
+        }
+        Checks.notBlank(tableName, element, ProcessorErrors.ENTITY_TABLE_NAME_CANNOT_BE_EMPTY)
+        val entity = Entity(tableName, TypeName.get(declaredType), fields)
         Checks.check(entity.primaryKeys.isNotEmpty(), element, ProcessorErrors.MISSING_PRIMARY_KEY)
         return entity
     }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/processor/FieldProcessor.kt b/room/compiler/src/main/kotlin/com/android/support/room/processor/FieldProcessor.kt
index f774427..153cd11 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/processor/FieldProcessor.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/processor/FieldProcessor.kt
@@ -16,9 +16,11 @@
 
 package com.android.support.room.processor
 
+import com.android.support.room.ColumnName
 import com.android.support.room.PrimaryKey
 import com.android.support.room.preconditions.Checks
 import com.android.support.room.vo.Field
+import com.google.auto.common.AnnotationMirrors
 import com.google.auto.common.MoreElements
 import com.squareup.javapoet.TypeName
 import javax.annotation.processing.ProcessingEnvironment
@@ -31,11 +33,23 @@
     fun parse(containing : DeclaredType, element : Element) : Field {
         val member = processingEnvironment.typeUtils.asMemberOf(containing, element)
         val type = TypeName.get(member)
-        Checks.assertNotUnbound(type, element,
+        val columnNameAnnotation = MoreElements.getAnnotationMirror(element,
+                ColumnName::class.java)
+        val name = element.simpleName.toString()
+        val columnName : String
+        if (columnNameAnnotation.isPresent) {
+            columnName = AnnotationMirrors
+                    .getAnnotationValue(columnNameAnnotation.get(), "value").value.toString()
+        } else {
+            columnName = name
+        }
+        Checks.notBlank(columnName, element, ProcessorErrors.COLUMN_NAME_CANNOT_BE_EMPTY)
+        Checks.notUnbound(type, element,
                 ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_ENTITY_FIELDS)
-        return Field(name = element.simpleName.toString(),
+        return Field(name = name,
                 type = type,
                 primaryKey = MoreElements.isAnnotationPresent(element, PrimaryKey::class.java),
-                element = element)
+                element = element,
+                columnName = columnName)
     }
 }
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 627088a..3f1c511 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
@@ -42,6 +42,11 @@
     val ENTITY_MUST_BE_ANNOTATED_WITH_ENTITY = "Entity class must be annotated with @Entity"
     val DATABASE_ANNOTATION_MUST_HAVE_LIST_OF_ENTITIES = "@Database annotation must specify list" +
             " of entities"
+    val COLUMN_NAME_CANNOT_BE_EMPTY = "Column name cannot be blank. If you don't want to set it" +
+            ", just remove the @ColumnName annotation."
+
+    val ENTITY_TABLE_NAME_CANNOT_BE_EMPTY = "Entity table name cannot be blank. If you don't want" +
+            " to set it, just remove the tableName property."
 
     fun tooManyMatchingGetters(field : Field, methodNames : List<String>) : String {
         return TOO_MANY_MATCHING_GETTERS.format(field, methodNames.joinToString(", "))
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/processor/QueryMethodProcessor.kt b/room/compiler/src/main/kotlin/com/android/support/room/processor/QueryMethodProcessor.kt
index a70547a..340154d 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/processor/QueryMethodProcessor.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/processor/QueryMethodProcessor.kt
@@ -45,7 +45,7 @@
 
         val returnTypeName = TypeName.get(executableType.returnType)
 
-        Checks.assertNotUnbound(returnTypeName, executableElement,
+        Checks.notUnbound(returnTypeName, executableElement,
                 ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_QUERY_METHODS)
         return QueryMethod(
                 element = executableElement,
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/vo/Entity.kt b/room/compiler/src/main/kotlin/com/android/support/room/vo/Entity.kt
index 84013a7..7a233ba 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/vo/Entity.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/vo/Entity.kt
@@ -18,8 +18,8 @@
 
 import com.squareup.javapoet.TypeName
 
-data class Entity(val type: TypeName, val fields : List<Field>) {
+data class Entity(val tableName : String, val type: TypeName, val fields : List<Field>) {
     val primaryKeys by lazy {
         fields.filter { it.primaryKey }
     }
-}
\ No newline at end of file
+}
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/vo/Field.kt b/room/compiler/src/main/kotlin/com/android/support/room/vo/Field.kt
index 841dbcd..d5a401b 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/vo/Field.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/vo/Field.kt
@@ -19,8 +19,9 @@
 import com.squareup.javapoet.TypeName
 import javax.lang.model.element.Element
 
-data class Field(val element : Element, val name : String, val type: TypeName,
-                 val primaryKey : Boolean) {
+data class Field(val element : Element, val name : String,
+                 val type: TypeName,
+                 val primaryKey : Boolean, val columnName : String = name) {
     lateinit var getter : FieldGetter
     lateinit var setter : FieldSetter
     /**
@@ -57,4 +58,4 @@
     val setterNameWithVariations by lazy {
         nameWithVariations.map { "set${it.capitalize()}" }
     }
-}
\ No newline at end of file
+}
diff --git a/room/compiler/src/test/kotlin/com/android/support/room/processor/BaseEntityParserTest.kt b/room/compiler/src/test/kotlin/com/android/support/room/processor/BaseEntityParserTest.kt
index 1fc4ac3..fd1a5e1 100644
--- a/room/compiler/src/test/kotlin/com/android/support/room/processor/BaseEntityParserTest.kt
+++ b/room/compiler/src/test/kotlin/com/android/support/room/processor/BaseEntityParserTest.kt
@@ -30,17 +30,26 @@
         const val ENTITY_PREFIX = """
             package foo.bar;
             import com.android.support.room.*;
-            @Entity
+            @Entity%s
             abstract class MyEntity {
             """
         const val ENTITY_SUFFIX = "}"
     }
 
-    fun singleEntity(vararg input: String, handler: (Entity, TestInvocation) -> Unit):
+    fun singleEntity(input: String, attributes: Map<String, String> = mapOf(),
+                     handler: (Entity, TestInvocation) -> Unit):
             CompileTester {
+        val attributesReplacement : String
+        if (attributes.isEmpty()) {
+            attributesReplacement = ""
+        } else {
+            attributesReplacement = "(" +
+                    attributes.entries.map { "${it.key} = ${it.value}" }.joinToString(",") +
+                    ")".trimIndent()
+        }
         return Truth.assertAbout(JavaSourceSubjectFactory.javaSource())
                 .that(JavaFileObjects.forSourceString("foo.bar.MyClass",
-                        ENTITY_PREFIX + input.joinToString("\n") + ENTITY_SUFFIX
+                        ENTITY_PREFIX.format(attributesReplacement) + input + ENTITY_SUFFIX
                 ))
                 .processedWith(TestProcessor.builder()
                         .forAnnotations(com.android.support.room.Entity::class)
@@ -57,4 +66,4 @@
                         }
                         .build())
     }
-}
\ No newline at end of file
+}
diff --git a/room/compiler/src/test/kotlin/com/android/support/room/processor/EntityNameMatchingVariationsTest.kt b/room/compiler/src/test/kotlin/com/android/support/room/processor/EntityNameMatchingVariationsTest.kt
index fa1da2c..c9de1ea 100644
--- a/room/compiler/src/test/kotlin/com/android/support/room/processor/EntityNameMatchingVariationsTest.kt
+++ b/room/compiler/src/test/kotlin/com/android/support/room/processor/EntityNameMatchingVariationsTest.kt
@@ -65,9 +65,10 @@
                     element = field.element,
                     name = fieldName,
                     type = TypeName.INT,
-                    primaryKey = true)))
+                    primaryKey = true,
+                    columnName = fieldName)))
             assertThat(field.setter, `is`(FieldSetter(setterName, CallType.METHOD)))
             assertThat(field.getter, `is`(FieldGetter(getterName, CallType.METHOD)))
         }.compilesWithoutError()
     }
-}
\ No newline at end of file
+}
diff --git a/room/compiler/src/test/kotlin/com/android/support/room/processor/EntityProcessorTest.kt b/room/compiler/src/test/kotlin/com/android/support/room/processor/EntityProcessorTest.kt
index f1add6b..1b8e444 100644
--- a/room/compiler/src/test/kotlin/com/android/support/room/processor/EntityProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/com/android/support/room/processor/EntityProcessorTest.kt
@@ -44,7 +44,8 @@
                     element = field.element,
                     name = "id",
                     type = TypeName.INT,
-                    primaryKey = true)))
+                    primaryKey = true,
+                    columnName = "id")))
             assertThat(field.setter, `is`(FieldSetter("setId", CallType.METHOD)))
             assertThat(field.getter, `is`(FieldGetter("getId", CallType.METHOD)))
             assertThat(entity.primaryKeys, `is`(listOf(field)))
@@ -138,6 +139,25 @@
     }
 
     @Test
+    fun customName() {
+        singleEntity("""
+                @PrimaryKey
+                int x;
+                """, hashMapOf(Pair("tableName", "\"foo_table\""))) { entity , invocation ->
+            assertThat(entity.tableName, `is`("foo_table"))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun emptyCustomName() {
+        singleEntity("""
+                @PrimaryKey
+                int x;
+                """, hashMapOf(Pair("tableName", "\" \""))) { entity , invocation ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.ENTITY_TABLE_NAME_CANNOT_BE_EMPTY)
+    }
+
+    @Test
     fun missingPrimaryKey() {
         singleEntity("""
                 """) { entity, invocation ->
diff --git a/room/compiler/src/test/kotlin/com/android/support/room/processor/FieldProcessorTest.kt b/room/compiler/src/test/kotlin/com/android/support/room/processor/FieldProcessorTest.kt
index c9c1b34..4b6a566 100644
--- a/room/compiler/src/test/kotlin/com/android/support/room/processor/FieldProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/com/android/support/room/processor/FieldProcessorTest.kt
@@ -94,6 +94,28 @@
     }
 
     @Test
+    fun columnName() {
+        singleEntity("""
+            @ColumnName("foo")
+            @PrimaryKey
+            int x;
+            """) { field, invocation ->
+            assertThat(field, `is`(
+                    Field(name = "x", type = TypeName.INT, primaryKey = true,
+                            element = field.element, columnName = "foo")))
+        }.compilesWithoutError()
+    }
+
+    @Test
+    fun emptyColumnName() {
+        singleEntity("""
+            @ColumnName("")
+            int x;
+            """) { field, invocation ->
+        }.failsToCompile().withErrorContaining(ProcessorErrors.COLUMN_NAME_CANNOT_BE_EMPTY)
+    }
+
+    @Test
     fun primitiveArray() {
         ALL_PRIMITIVES.forEach { primitive ->
             singleEntity("$primitive[] arr;") { field, invocation ->
@@ -229,4 +251,4 @@
                         }
                         .build())
     }
-}
\ No newline at end of file
+}