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
+}