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())