Entity Parser
This is the initial implementation for the Entity parser.
It is very basic, only understands the fields and their matching getters
and setter.
There is no primary key, field type, class type etc validation.
Bug: 32342709
Test: EntityParserTest.kt, EntityNameMatchingVariationsTest.kt
Change-Id: I9a2cbf6a5fa763bde1774ece3feb504b1b67de6a
diff --git a/room/common/src/main/java/com/android/support/room/Ignore.java b/room/common/src/main/java/com/android/support/room/Ignore.java
new file mode 100644
index 0000000..5453c99
--- /dev/null
+++ b/room/common/src/main/java/com/android/support/room/Ignore.java
@@ -0,0 +1,27 @@
+/*
+ * 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.METHOD, ElementType.FIELD})
+@Retention(RetentionPolicy.SOURCE)
+public @interface Ignore {
+}
diff --git a/room/compiler/build.gradle b/room/compiler/build.gradle
index 19efcd4..5b536cd 100644
--- a/room/compiler/build.gradle
+++ b/room/compiler/build.gradle
@@ -37,10 +37,12 @@
compile "com.google.auto:auto-common:$auto_common_version"
compile "com.squareup:javapoet:$javapoet_version"
compile 'org.antlr:antlr4:4.5.3'
+
testCompile "com.google.testing.compile:compile-testing:$compile_testing_version"
testCompile files(org.gradle.internal.jvm.Jvm.current().getToolsJar())
testCompile "junit:junit:$junit_version"
testCompile "com.intellij:annotations:$intellij_annotation"
+ testCompile "org.mockito:mockito-core:$mockito_version"
}
uploadArchives {
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/ext/element_ext.kt b/room/compiler/src/main/kotlin/com/android/support/room/ext/element_ext.kt
new file mode 100644
index 0000000..45237ee
--- /dev/null
+++ b/room/compiler/src/main/kotlin/com/android/support/room/ext/element_ext.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.ext
+
+import com.google.auto.common.MoreElements
+import javax.lang.model.element.Element
+import javax.lang.model.element.Modifier
+import kotlin.reflect.KClass
+
+fun Element.hasAnyOf(vararg modifiers: Modifier) : Boolean {
+ return this.modifiers.any { modifiers.contains(it) }
+}
+
+fun Element.hasAnnotation(klass : KClass<out Annotation>) : Boolean {
+ return MoreElements.isAnnotationPresent(this, klass.java)
+}
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/processor/EntityParser.kt b/room/compiler/src/main/kotlin/com/android/support/room/processor/EntityParser.kt
new file mode 100644
index 0000000..c2b1a0b
--- /dev/null
+++ b/room/compiler/src/main/kotlin/com/android/support/room/processor/EntityParser.kt
@@ -0,0 +1,129 @@
+/*
+ * 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.processor
+
+import com.android.support.room.Ignore
+import com.android.support.room.errors.ElementBoundException
+import com.android.support.room.ext.hasAnnotation
+import com.android.support.room.ext.hasAnyOf
+import com.android.support.room.vo.*
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import com.squareup.javapoet.TypeName
+import javax.annotation.processing.ProcessingEnvironment
+import javax.annotation.processing.RoundEnvironment
+import javax.lang.model.element.ElementKind
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.Modifier
+import javax.lang.model.element.Modifier.*
+import javax.lang.model.element.TypeElement
+import javax.lang.model.type.TypeKind
+
+class EntityParser(val roundEnv: RoundEnvironment,
+ val processingEnvironment: ProcessingEnvironment) {
+ val fieldParser = FieldParser(roundEnv, processingEnvironment)
+
+ fun parse(element: TypeElement): Entity {
+ val declaredType = MoreTypes.asDeclared(element.asType())
+ val allMembers = processingEnvironment.elementUtils.getAllMembers(element)
+ val fields = allMembers
+ .filter {
+ it.kind == ElementKind.FIELD
+ && !it.hasAnnotation(Ignore::class)
+ && !it.hasAnyOf(Modifier.STATIC)
+ }
+ .map { fieldParser.parse(declaredType, it) }
+
+ val methods = allMembers
+ .filter {
+ it.kind == ElementKind.METHOD
+ && !it.hasAnyOf(PRIVATE, ABSTRACT, STATIC)
+ && !it.hasAnnotation(Ignore::class)
+ }
+ .map { MoreElements.asExecutable(it) }
+
+ val getterCandidates = methods.filter {
+ it.parameters.size == 0 && it.returnType.kind != TypeKind.VOID
+ }
+
+ val setterCandidates = methods.filter {
+ it.parameters.size == 1 && it.returnType.kind == TypeKind.VOID
+ }
+
+ assignGetters(fields, getterCandidates)
+ assignSetters(fields, setterCandidates)
+ return Entity(TypeName.get(declaredType), fields)
+ }
+
+ private fun assignGetters(fields: List<Field>, getterCandidates: List<ExecutableElement>) {
+ val types = processingEnvironment.typeUtils
+
+ fields.forEach { field ->
+ if (!field.element.hasAnyOf(PRIVATE)) {
+ field.getter = FieldGetter(field.name, CallType.FIELD)
+ } else {
+ val matching = getterCandidates
+ .filter {
+ types.isSameType(field.element.asType(), it.returnType)
+ && field.nameWithVariations.contains(it.simpleName.toString())
+ || field.getterNameWithVariations
+ .contains(it.simpleName.toString())
+ }
+ if (matching.isEmpty()) {
+ throw ElementBoundException(field.element,
+ ProcessorErrors.CANNOT_FIND_GETTER_FOR_FIELD)
+ }
+ if (matching.size > 1) {
+ throw ElementBoundException(field.element,
+ ProcessorErrors.tooManyMatchingGetters(field,
+ matching.map { it.simpleName.toString() }))
+ }
+ val match = matching.first()
+ field.getter = FieldGetter(match.simpleName.toString(), CallType.METHOD)
+ }
+ }
+ }
+
+ private fun assignSetters(fields: List<Field>, setterCandidates: List<ExecutableElement>) {
+ val types = processingEnvironment.typeUtils
+
+ fields.forEach { field ->
+ if (!field.element.hasAnyOf(PRIVATE)) {
+ field.setter = FieldSetter(field.name, CallType.FIELD)
+ } else {
+ val matching = setterCandidates
+ .filter {
+ types.isSameType(field.element.asType(), it.parameters.first().asType())
+ && field.nameWithVariations.contains(it.simpleName.toString())
+ || field.setterNameWithVariations
+ .contains(it.simpleName.toString())
+ }
+ if (matching.isEmpty()) {
+ throw ElementBoundException(field.element,
+ ProcessorErrors.CANNOT_FIND_SETTER_FOR_FIELD)
+ }
+ if (matching.size > 1) {
+ throw ElementBoundException(field.element,
+ ProcessorErrors.tooManyMatchingSetter(field,
+ matching.map { it.simpleName.toString() }))
+ }
+ val match = matching.first()
+ field.setter = FieldSetter(match.simpleName.toString(), CallType.METHOD)
+ }
+ }
+ }
+}
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/processor/FieldParser.kt b/room/compiler/src/main/kotlin/com/android/support/room/processor/FieldParser.kt
index d02f25a..c22f8af 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/processor/FieldParser.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/processor/FieldParser.kt
@@ -35,6 +35,7 @@
ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_ENTITY_FIELDS)
return Field(name = element.simpleName.toString(),
type = type,
- primaryKey = MoreElements.isAnnotationPresent(element, PrimaryKey::class.java))
+ primaryKey = MoreElements.isAnnotationPresent(element, PrimaryKey::class.java),
+ element = element)
}
-}
\ No newline at end of file
+}
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 c2fe0e4..f3ceb53 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
@@ -17,6 +17,7 @@
package com.android.support.room.processor
import com.android.support.room.Query
+import com.android.support.room.vo.Field
object ProcessorErrors {
val MISSING_QUERY_ANNOTATION = "Query methods must be annotated with ${Query::class.java}"
@@ -24,4 +25,18 @@
val CANNOT_USE_UNBOUND_GENERICS_IN_QUERY_METHODS = "Cannot use unbound generics in query " +
"methods. It must be bound to a type through base Dao class."
val CANNOT_USE_UNBOUND_GENERICS_IN_ENTITY_FIELDS = "Cannot use unbound fields in entities."
+ val CANNOT_FIND_GETTER_FOR_FIELD = "Cannot find getter for field."
+ val CANNOT_FIND_SETTER_FOR_FIELD = "Cannot find setter for field."
+ 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."
+ private val TOO_MANY_MATCHING_SETTERS = "Ambiguous setter for %s. All of the following " +
+ "match: %s. You can @Ignore the ones that you don't want to match."
+
+ fun tooManyMatchingGetters(field : Field, methodNames : List<String>) : String {
+ return TOO_MANY_MATCHING_GETTERS.format(field, methodNames.joinToString(", "))
+ }
+
+ fun tooManyMatchingSetter(field: Field, methodNames: List<String>) : String {
+ return TOO_MANY_MATCHING_SETTERS.format(field, methodNames.joinToString(", "))
+ }
}
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/processor/QueryParser.kt b/room/compiler/src/main/kotlin/com/android/support/room/processor/QueryParser.kt
index 838fa87..0dcf3cf 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/processor/QueryParser.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/processor/QueryParser.kt
@@ -22,12 +22,9 @@
import com.android.support.room.vo.QueryMethod
import com.google.auto.common.MoreElements
import com.google.auto.common.MoreTypes
-import com.squareup.javapoet.ParameterizedTypeName
import com.squareup.javapoet.TypeName
-import com.squareup.javapoet.TypeVariableName
import javax.annotation.processing.ProcessingEnvironment
import javax.annotation.processing.RoundEnvironment
-import javax.lang.model.element.Element
import javax.lang.model.element.ExecutableElement
import javax.lang.model.type.DeclaredType
import javax.lang.model.type.TypeKind
@@ -51,9 +48,11 @@
Checks.assertNotUnbound(returnTypeName, executableElement,
ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_QUERY_METHODS)
return QueryMethod(
- query,
- executableElement.simpleName.toString(),
- returnTypeName,
- executableElement.parameters.map { parameterParser.parse(containing, it) })
+ element = executableElement,
+ query = query,
+ name = executableElement.simpleName.toString(),
+ returnType = returnTypeName,
+ parameters = executableElement.parameters
+ .map { parameterParser.parse(containing, it) })
}
}
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/vo/CallType.kt b/room/compiler/src/main/kotlin/com/android/support/room/vo/CallType.kt
new file mode 100644
index 0000000..94b09a9
--- /dev/null
+++ b/room/compiler/src/main/kotlin/com/android/support/room/vo/CallType.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.vo
+
+enum class CallType {
+ FIELD,
+ METHOD
+}
\ No newline at end of file
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 100d26b..07edcf1 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,4 +18,4 @@
import com.squareup.javapoet.TypeName
-data class Entity(val type: TypeName)
\ No newline at end of file
+data class Entity(val type: TypeName, val fields : List<Field>)
\ 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 17488ca..841dbcd 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
@@ -17,5 +17,44 @@
package com.android.support.room.vo
import com.squareup.javapoet.TypeName
+import javax.lang.model.element.Element
-data class Field(val name : String, val type: TypeName, val primaryKey : Boolean)
\ No newline at end of file
+data class Field(val element : Element, val name : String, val type: TypeName,
+ val primaryKey : Boolean) {
+ lateinit var getter : FieldGetter
+ lateinit var setter : FieldSetter
+ /**
+ * List of names that include variations.
+ * e.g. if it is mUser, user is added to the list
+ * or if it is isAdmin, admin is added to the list
+ */
+ val nameWithVariations by lazy {
+ val result = arrayListOf(name)
+ if (name.length > 1) {
+ if (name.startsWith('_')) {
+ result.add(name.substring(1))
+ }
+ if (name.startsWith("m") && name[1].isUpperCase()) {
+ result.add(name.substring(1).decapitalize())
+ }
+
+ if (type == TypeName.BOOLEAN || type == TypeName.BOOLEAN.box()) {
+ if (name.length > 2 && name.startsWith("is") && name[2].isUpperCase()) {
+ result.add(name.substring(2).decapitalize())
+ }
+ if (name.length > 3 && name.startsWith("has") && name[3].isUpperCase()) {
+ result.add(name.substring(3).decapitalize())
+ }
+ }
+ }
+ result
+ }
+
+ val getterNameWithVariations by lazy {
+ nameWithVariations.map { "get${it.capitalize()}" }
+ }
+
+ val setterNameWithVariations by lazy {
+ nameWithVariations.map { "set${it.capitalize()}" }
+ }
+}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/vo/FieldGetter.kt b/room/compiler/src/main/kotlin/com/android/support/room/vo/FieldGetter.kt
new file mode 100644
index 0000000..bc8c912
--- /dev/null
+++ b/room/compiler/src/main/kotlin/com/android/support/room/vo/FieldGetter.kt
@@ -0,0 +1,19 @@
+/*
+ * 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.vo
+
+data class FieldGetter(val name : String, val callType: CallType)
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/vo/FieldSetter.kt b/room/compiler/src/main/kotlin/com/android/support/room/vo/FieldSetter.kt
new file mode 100644
index 0000000..60720d0
--- /dev/null
+++ b/room/compiler/src/main/kotlin/com/android/support/room/vo/FieldSetter.kt
@@ -0,0 +1,19 @@
+/*
+ * 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.vo
+
+data class FieldSetter(val name : String, val callType: CallType)
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/vo/QueryMethod.kt b/room/compiler/src/main/kotlin/com/android/support/room/vo/QueryMethod.kt
index 952f026..9ecaabe 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/vo/QueryMethod.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/vo/QueryMethod.kt
@@ -18,10 +18,11 @@
import com.android.support.room.parser.ParsedQuery
import com.squareup.javapoet.TypeName
+import javax.lang.model.element.Element
/**
* A class that holds information about a QueryMethod.
* It is self sufficient and must have all generics etc resolved once created.
*/
-data class QueryMethod(val query: ParsedQuery, val name: String, val returnType: TypeName,
- val parameters: List<Parameter>)
+data class QueryMethod(val element : Element, val query: ParsedQuery, val name: String,
+ val returnType: TypeName, val parameters: List<Parameter>)
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
new file mode 100644
index 0000000..011fc10
--- /dev/null
+++ b/room/compiler/src/test/kotlin/com/android/support/room/processor/BaseEntityParserTest.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.processor
+
+import com.android.support.room.testing.TestInvocation
+import com.android.support.room.testing.TestProcessor
+import com.android.support.room.vo.Entity
+import com.google.auto.common.MoreElements
+import com.google.common.truth.Truth
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourceSubjectFactory
+
+abstract class BaseEntityParserTest {
+ companion object {
+ const val ENTITY_PREFIX = """
+ package foo.bar;
+ import com.android.support.room.*;
+ @Entity
+ abstract class MyEntity {
+ """
+ const val ENTITY_SUFFIX = "}"
+ }
+
+ fun singleEntity(vararg input: String, handler: (Entity, TestInvocation) -> Unit):
+ CompileTester {
+ return Truth.assertAbout(JavaSourceSubjectFactory.javaSource())
+ .that(JavaFileObjects.forSourceString("foo.bar.MyClass",
+ ENTITY_PREFIX + input.joinToString("\n") + ENTITY_SUFFIX
+ ))
+ .processedWith(TestProcessor.builder()
+ .forAnnotations(com.android.support.room.Entity::class)
+ .nextRunHandler { invocation ->
+ val entity = invocation.roundEnv
+ .getElementsAnnotatedWith(
+ com.android.support.room.Entity::class.java)
+ .first()
+ val parser = EntityParser(invocation.roundEnv, invocation.processingEnv)
+ val parsedQuery = parser.parse(MoreElements.asType(entity))
+ handler(parsedQuery, invocation)
+ true
+ }
+ .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
new file mode 100644
index 0000000..fa1da2c
--- /dev/null
+++ b/room/compiler/src/test/kotlin/com/android/support/room/processor/EntityNameMatchingVariationsTest.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.processor
+
+import com.android.support.room.vo.CallType
+import com.android.support.room.vo.Field
+import com.android.support.room.vo.FieldGetter
+import com.android.support.room.vo.FieldSetter
+import com.squareup.javapoet.TypeName
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class EntityNameMatchingVariationsTest(triple: Triple<String, String, String>) :
+ BaseEntityParserTest() {
+ val fieldName = triple.first
+ val getterName = triple.second
+ val setterName = triple.third
+
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun params() : List<Triple<String, String, String>> {
+ val result = arrayListOf<Triple<String, String, String>>()
+ arrayListOf("x", "_x", "mX").forEach { field ->
+ arrayListOf("getX", "x").forEach { getter ->
+ arrayListOf("setX", "x").forEach { setter ->
+ result.add(Triple(field, getter, setter))
+ }
+ }
+ }
+ return result
+ }
+ }
+
+ @Test
+ fun testSuccessfulParamToMethodMatching() {
+ singleEntity("""
+ @PrimaryKey
+ private int $fieldName;
+ public int $getterName() { return $fieldName; }
+ public void $setterName(int id) { this.$fieldName = id; }
+ """) { entity, invocation ->
+ assertThat(entity.type.toString(), `is`("foo.bar.MyEntity"))
+ assertThat(entity.fields.size, `is`(1))
+ val field = entity.fields.first()
+ assertThat(field, `is`(Field(
+ element = field.element,
+ name = fieldName,
+ type = TypeName.INT,
+ primaryKey = true)))
+ 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/EntityParserTest.kt b/room/compiler/src/test/kotlin/com/android/support/room/processor/EntityParserTest.kt
new file mode 100644
index 0000000..9c8c6f9
--- /dev/null
+++ b/room/compiler/src/test/kotlin/com/android/support/room/processor/EntityParserTest.kt
@@ -0,0 +1,126 @@
+/*
+ * 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.processor
+
+import com.android.support.room.vo.CallType
+import com.android.support.room.vo.Field
+import com.android.support.room.vo.FieldGetter
+import com.android.support.room.vo.FieldSetter
+import com.squareup.javapoet.TypeName
+import org.hamcrest.CoreMatchers.`is`
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class EntityParserTest : BaseEntityParserTest() {
+ @Test
+ fun simple() {
+ singleEntity("""
+ @PrimaryKey
+ private int id;
+ public int getId() { return id; }
+ public void setId(int id) { this.id = id; }
+ """) { entity, invocation ->
+ assertThat(entity.type.toString(), `is`("foo.bar.MyEntity"))
+ assertThat(entity.fields.size, `is`(1))
+ val field = entity.fields.first()
+ assertThat(field, `is`(Field(
+ element = field.element,
+ name = "id",
+ type = TypeName.INT,
+ primaryKey = true)))
+ assertThat(field.setter, `is`(FieldSetter("setId", CallType.METHOD)))
+ assertThat(field.getter, `is`(FieldGetter("getId", CallType.METHOD)))
+ }.compilesWithoutError()
+ }
+
+ @Test
+ fun noGetter() {
+ singleEntity("""
+ @PrimaryKey
+ private int id;
+ public void setId(int id) {this.id = id;}
+ """) { entity, invocation -> }
+ .failsToCompile()
+ .withErrorContaining(ProcessorErrors.CANNOT_FIND_GETTER_FOR_FIELD)
+ }
+
+ @Test
+ fun noSetter() {
+ singleEntity("""
+ @PrimaryKey
+ private int id;
+ public int getId(){ return id; }
+ """) { entity, invocation -> }
+ .failsToCompile()
+ .withErrorContaining(ProcessorErrors.CANNOT_FIND_SETTER_FOR_FIELD)
+ }
+
+ @Test
+ fun tooManyGetters() {
+ singleEntity("""
+ @PrimaryKey
+ private int id;
+ public void setId(int id) {}
+ public int getId(){ return id; }
+ public int id(){ return id; }
+ """) { entity, invocation -> }
+ .failsToCompile()
+ .withErrorContaining("getId, id")
+ }
+
+ @Test
+ fun tooManyGettersWithIgnore() {
+ singleEntity("""
+ @PrimaryKey
+ private int id;
+ public void setId(int id) {}
+ public int getId(){ return id; }
+ @Ignore public int id(){ return id; }
+ """) { entity, invocation ->
+ assertThat(entity.fields.first().getter.name, `is`("getId"))
+ }.compilesWithoutError()
+ }
+
+ @Test
+ fun tooManySetters() {
+ singleEntity("""
+ @PrimaryKey
+ private int id;
+ public void setId(int id) {}
+ public void id(int id) {}
+ public int getId(){ return id; }
+ """) { entity, invocation -> }
+ .failsToCompile()
+ .withErrorContaining("setId, id")
+ }
+
+ @Test
+ fun tooManySettersWithIgnore() {
+ singleEntity("""
+ @PrimaryKey
+ private int id;
+ public void setId(int id) {}
+ @Ignore public void id(int id) {}
+ public int getId(){ return id; }
+ """) { entity, invocation ->
+ assertThat(entity.fields.first().setter.name, `is`("setId"))
+ }.compilesWithoutError()
+ }
+}
diff --git a/room/compiler/src/test/kotlin/com/android/support/room/processor/FieldParserTest.kt b/room/compiler/src/test/kotlin/com/android/support/room/processor/FieldParserTest.kt
index 23a4cb9..a54a38a 100644
--- a/room/compiler/src/test/kotlin/com/android/support/room/processor/FieldParserTest.kt
+++ b/room/compiler/src/test/kotlin/com/android/support/room/processor/FieldParserTest.kt
@@ -17,6 +17,7 @@
package com.android.support.room.processor
import com.android.support.room.Entity
+import com.android.support.room.testing.TestInvocation
import com.android.support.room.testing.TestProcessor
import com.android.support.room.vo.Field
import com.google.auto.common.MoreElements
@@ -32,6 +33,8 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import org.mockito.Mockito.mock
+import javax.lang.model.element.Element
import javax.lang.model.element.ElementKind
@RunWith(JUnit4::class)
@@ -58,9 +61,11 @@
@Test
fun primitives() {
ALL_PRIMITIVES.forEach { primitive ->
- singleEntity("$primitive x;") { field ->
+ singleEntity("$primitive x;") { field, invocation ->
assertThat(field, `is`(
- Field(name = "x", type = primitive, primaryKey = false)))
+ Field(name = "x", type = primitive, primaryKey = false,
+ element = field.element
+ )))
}.compilesWithoutError()
}
}
@@ -68,9 +73,10 @@
@Test
fun boxed() {
ALL_PRIMITIVES.forEach { primitive ->
- singleEntity("${primitive.box()} y;") { field ->
+ singleEntity("${primitive.box()} y;") { field, invocation ->
assertThat(field, `is`(
- Field(name = "y", type = primitive.box(), primaryKey = false)))
+ Field(name = "y", type = primitive.box(), primaryKey = false,
+ element = field.element)))
}.compilesWithoutError()
}
}
@@ -80,20 +86,20 @@
singleEntity("""
@PrimaryKey
int x;
- """) { field ->
+ """) { field, invocation ->
assertThat(field, `is`(
- Field(name = "x", type = TypeName.INT, primaryKey = true)
- ))
+ Field(name = "x", type = TypeName.INT, primaryKey = true,
+ element = field.element)))
}.compilesWithoutError()
}
@Test
fun primitiveArray() {
ALL_PRIMITIVES.forEach { primitive ->
- singleEntity("$primitive[] arr;") { field ->
+ singleEntity("$primitive[] arr;") { field, invocation ->
assertThat(field, `is`(
- Field(name = "arr", type = ArrayTypeName.of(primitive), primaryKey = false)
- ))
+ Field(name = "arr", type = ArrayTypeName.of(primitive), primaryKey = false,
+ element = field.element)))
}.compilesWithoutError()
}
}
@@ -101,11 +107,11 @@
@Test
fun boxedArray() {
ALL_PRIMITIVES.forEach { primitive ->
- singleEntity("${primitive.box()}[] arr;") { field ->
+ singleEntity("${primitive.box()}[] arr;") { field, invocation ->
assertThat(field, `is`(
Field(name = "arr", type = ArrayTypeName.of(primitive.box()),
- primaryKey = false)
- ))
+ primaryKey = false,
+ element = field.element)))
}.compilesWithoutError()
}
}
@@ -119,10 +125,11 @@
@Entity
static class Extending extends BaseClass<java.lang.Integer> {
}
- """) { field ->
+ """) { field, invocation ->
assertThat(field, `is`(Field(name = "item",
type = TypeName.INT.box(),
- primaryKey = false)))
+ primaryKey = false,
+ element = field.element)))
}.compilesWithoutError()
}
@@ -133,11 +140,72 @@
static class BaseClass<T> {
T item;
}
- """) {}.failsToCompile()
+ """) {field, invocation -> }.failsToCompile()
.withErrorContaining(ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_ENTITY_FIELDS)
}
- fun singleEntity(vararg input: String, handler: (Field) -> Unit): CompileTester {
+ @Test
+ fun nameVariations() {
+ assertThat(Field(mock(Element::class.java), "x", TypeName.INT, false)
+ .nameWithVariations, `is`(arrayListOf("x")))
+ assertThat(Field(mock(Element::class.java), "x", TypeName.BOOLEAN, false)
+ .nameWithVariations, `is`(arrayListOf("x")))
+ assertThat(Field(mock(Element::class.java), "xAll", TypeName.BOOLEAN, false)
+ .nameWithVariations, `is`(arrayListOf("xAll")))
+ }
+
+ @Test
+ fun nameVariations_is() {
+ assertThat(Field(mock(Element::class.java), "isX", TypeName.BOOLEAN, false)
+ .nameWithVariations, `is`(arrayListOf("isX", "x")))
+ assertThat(Field(mock(Element::class.java), "isX", TypeName.INT, false)
+ .nameWithVariations, `is`(arrayListOf("isX")))
+ assertThat(Field(mock(Element::class.java), "is", TypeName.BOOLEAN, false)
+ .nameWithVariations, `is`(arrayListOf("is")))
+ assertThat(Field(mock(Element::class.java), "isAllItems", TypeName.BOOLEAN, false)
+ .nameWithVariations, `is`(arrayListOf("isAllItems", "allItems")))
+ }
+
+ @Test
+ fun nameVariations_has() {
+ assertThat(Field(mock(Element::class.java), "hasX", TypeName.BOOLEAN, false)
+ .nameWithVariations, `is`(arrayListOf("hasX", "x")))
+ assertThat(Field(mock(Element::class.java), "hasX", TypeName.INT, false)
+ .nameWithVariations, `is`(arrayListOf("hasX")))
+ assertThat(Field(mock(Element::class.java), "has", TypeName.BOOLEAN, false)
+ .nameWithVariations, `is`(arrayListOf("has")))
+ assertThat(Field(mock(Element::class.java), "hasAllItems", TypeName.BOOLEAN, false)
+ .nameWithVariations, `is`(arrayListOf("hasAllItems", "allItems")))
+ }
+
+ @Test
+ fun nameVariations_m() {
+ assertThat(Field(mock(Element::class.java), "mall", TypeName.BOOLEAN, false)
+ .nameWithVariations, `is`(arrayListOf("mall")))
+ assertThat(Field(mock(Element::class.java), "mallVars", TypeName.BOOLEAN, false)
+ .nameWithVariations, `is`(arrayListOf("mallVars")))
+ assertThat(Field(mock(Element::class.java), "mAll", TypeName.BOOLEAN, false)
+ .nameWithVariations, `is`(arrayListOf("mAll", "all")))
+ assertThat(Field(mock(Element::class.java), "m", TypeName.INT, false)
+ .nameWithVariations, `is`(arrayListOf("m")))
+ assertThat(Field(mock(Element::class.java), "mallItems", TypeName.BOOLEAN, false)
+ .nameWithVariations, `is`(arrayListOf("mallItems")))
+ assertThat(Field(mock(Element::class.java), "mAllItems", TypeName.BOOLEAN, false)
+ .nameWithVariations, `is`(arrayListOf("mAllItems", "allItems")))
+ }
+
+ @Test
+ fun nameVariations_underscore() {
+ assertThat(Field(mock(Element::class.java), "_all", TypeName.BOOLEAN, false)
+ .nameWithVariations, `is`(arrayListOf("_all", "all")))
+ assertThat(Field(mock(Element::class.java), "_", TypeName.INT, false)
+ .nameWithVariations, `is`(arrayListOf("_")))
+ assertThat(Field(mock(Element::class.java), "_allItems", TypeName.BOOLEAN, false)
+ .nameWithVariations, `is`(arrayListOf("_allItems", "allItems")))
+ }
+
+ fun singleEntity(vararg input: String, handler: (Field, invocation : TestInvocation) -> Unit):
+ CompileTester {
return Truth.assertAbout(JavaSourceSubjectFactory.javaSource())
.that(JavaFileObjects.forSourceString("foo.bar.MyEntity",
ENTITY_PREFIX + input.joinToString("\n") + ENTITY_SUFFIX
@@ -155,7 +223,7 @@
.first { it.second != null }
val parser = FieldParser(invocation.roundEnv, invocation.processingEnv)
handler(parser.parse(
- MoreTypes.asDeclared(owner.asType()), field!!))
+ MoreTypes.asDeclared(owner.asType()), field!!), invocation)
true
}
.build())