Make processors exception free

If a processor throws an exception, it halts all of the processing,
resulting in a lot of errors in javac output.
This CL changes room's processor behavior to just print errors and
continue as much as it can. This will eventually allow Room to
procude as much code as it can to avoid false positive erros in
javac output.

Bug: 32342709
Test: clean check passes w/o any changes
Change-Id: I54c9ad0cd64d88f8994d514479c9177fd6e598bb
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/errors/ElementBoundException.kt b/room/compiler/src/main/kotlin/com/android/support/room/errors/ElementBoundException.kt
deleted file mode 100644
index 72e7d2b..0000000
--- a/room/compiler/src/main/kotlin/com/android/support/room/errors/ElementBoundException.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * 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.errors
-
-import javax.lang.model.element.Element
-
-class ElementBoundException(val element: Element, val msg: String)
-    : RuntimeException("[$element] $msg")
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/log/RLog.kt b/room/compiler/src/main/kotlin/com/android/support/room/log/RLog.kt
new file mode 100644
index 0000000..0161378
--- /dev/null
+++ b/room/compiler/src/main/kotlin/com/android/support/room/log/RLog.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.log
+
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.element.Element
+import javax.tools.Diagnostic.Kind.ERROR
+import javax.tools.Diagnostic.Kind.NOTE
+
+class RLog(val processingEnv : ProcessingEnvironment) {
+    fun d(element : Element, msg : String, vararg args : Any) {
+        processingEnv.messager.printMessage(NOTE, msg.format(args), element)
+    }
+
+    fun d(msg : String, vararg args : Any) {
+        processingEnv.messager.printMessage(NOTE, msg.format(args))
+    }
+
+    fun e(element : Element, msg : String, vararg args : Any) {
+        processingEnv.messager.printMessage(ERROR, msg.format(args), element)
+    }
+}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/parser/ParsedQuery.kt b/room/compiler/src/main/kotlin/com/android/support/room/parser/ParsedQuery.kt
index 4b66361..c0fcd70 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/parser/ParsedQuery.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/parser/ParsedQuery.kt
@@ -36,6 +36,7 @@
                        val syntaxErrors: List<String>) {
     companion object {
         val STARTS_WITH_NUMBER = "^\\?[0-9]".toRegex()
+        val MISSING = ParsedQuery("missing query", emptyList(), emptyList())
     }
 
     val sections by lazy {
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 039f744..39faa9c 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
@@ -16,8 +16,8 @@
 
 package com.android.support.room.preconditions
 
-import com.android.support.room.errors.ElementBoundException
 import com.android.support.room.ext.hasAnnotation
+import com.android.support.room.log.RLog
 import com.squareup.javapoet.ParameterizedTypeName
 import com.squareup.javapoet.TypeName
 import com.squareup.javapoet.TypeVariableName
@@ -25,32 +25,44 @@
 import kotlin.reflect.KClass
 
 /**
- * Similar to preconditions but element bound.
+ * Similar to preconditions but element bound and just logs the error instead of throwing an
+ * exception.
+ * <p>
+ * It is important for processing to continue when some errors happen so that we can generate as
+ * much code as possible, leaving only the errors in javac output.
  */
-object Checks {
-    fun check(predicate: Boolean, element: Element, errorMsg: String, vararg args: Any) {
+class Checks(private val logger: RLog) {
+    fun check(predicate: Boolean, element: Element, errorMsg: String, vararg args: Any): Boolean {
         if (!predicate) {
-            throw ElementBoundException(element, errorMsg.format(args))
+            logger.e(element, errorMsg, args)
+        }
+        return predicate
+    }
+
+    fun hasAnnotation(element: Element, annotation: KClass<out Annotation>, errorMsg: String,
+                      vararg args: Any): Boolean {
+        return if (!element.hasAnnotation(annotation)) {
+            logger.e(element, errorMsg, args)
+            false
+        } else {
+            true
         }
     }
 
-    fun hasAnnotation(element : Element, annotation: KClass<out Annotation>, errorMsg: String,
-                      vararg args: Any) {
-        if (!element.hasAnnotation(annotation)) {
-            throw ElementBoundException(element, errorMsg.format(args))
-        }
-    }
-
-    fun notUnbound(typeName: TypeName, element: Element, errorMsg : String,
-                   vararg args : Any) {
+    fun notUnbound(typeName: TypeName, element: Element, errorMsg: String,
+                   vararg args: Any): Boolean {
         // TODO support bounds cases like <T extends Foo> T bar()
-        Checks.check(typeName !is TypeVariableName, element, errorMsg, args)
+        val failed = check(typeName !is TypeVariableName, element, errorMsg, args)
         if (typeName is ParameterizedTypeName) {
-            typeName.typeArguments.forEach { notUnbound(it, element, errorMsg, args) }
+            val nestedFailure = typeName.typeArguments
+                    .map { notUnbound(it, element, errorMsg, args) }
+                    .any { it == true }
+            return !(failed || nestedFailure)
         }
+        return !failed
     }
 
-    fun notBlank(value: String?, element: Element, msg: String, vararg args : Any) {
-        Checks.check(value != null && value.isNotBlank(), element, msg, args)
+    fun notBlank(value: String?, element: Element, msg: String, vararg args: Any) : Boolean {
+        return check(value != null && value.isNotBlank(), element, msg, args)
     }
 }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/processor/Context.kt b/room/compiler/src/main/kotlin/com/android/support/room/processor/Context.kt
index 89ec984..6757309 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/processor/Context.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/processor/Context.kt
@@ -16,8 +16,13 @@
 
 package com.android.support.room.processor
 
+import com.android.support.room.log.RLog
+import com.android.support.room.preconditions.Checks
 import javax.annotation.processing.ProcessingEnvironment
 import javax.annotation.processing.RoundEnvironment
 
 data class Context(val roundEnv: RoundEnvironment,
-                   val processingEnv: ProcessingEnvironment)
+                   val processingEnv: ProcessingEnvironment) {
+    val logger = RLog(processingEnv)
+    val checker = Checks(logger)
+}
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 35ee5f2..213db73 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
@@ -29,9 +29,9 @@
 class DaoProcessor(val context : Context) {
     val queryParser = QueryMethodProcessor(context)
     fun parse(element: TypeElement) : Dao {
-        Checks.hasAnnotation(element, com.android.support.room.Dao::class,
+        context.checker.hasAnnotation(element, com.android.support.room.Dao::class,
                 ProcessorErrors.DAO_MUST_BE_ANNOTATED_WITH_DAO)
-        Checks.check(element.hasAnyOf(ABSTRACT) || element.kind == ElementKind.INTERFACE,
+        context.checker.check(element.hasAnyOf(ABSTRACT) || element.kind == ElementKind.INTERFACE,
                 element, ProcessorErrors.DAO_MUST_BE_AN_ABSTRACT_CLASS_OR_AN_INTERFACE)
 
         val declaredType = MoreTypes.asDeclared(element.asType())
@@ -42,7 +42,7 @@
             queryParser.parse(declaredType, MoreElements.asExecutable(it))
         }
         val type = TypeName.get(declaredType)
-        Checks.notUnbound(type, element,
+        context.checker.notUnbound(type, element,
                 ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_DAO_CLASSES)
         return Dao(element = element, type = type, queryMethods = methods)
     }
diff --git a/room/compiler/src/main/kotlin/com/android/support/room/processor/DatabaseProcessor.kt b/room/compiler/src/main/kotlin/com/android/support/room/processor/DatabaseProcessor.kt
index d797d21..320c5d9 100644
--- a/room/compiler/src/main/kotlin/com/android/support/room/processor/DatabaseProcessor.kt
+++ b/room/compiler/src/main/kotlin/com/android/support/room/processor/DatabaseProcessor.kt
@@ -17,12 +17,13 @@
 package com.android.support.room.processor
 
 import com.android.support.room.ext.hasAnyOf
-import com.android.support.room.preconditions.Checks
 import com.android.support.room.vo.DaoMethod
 import com.android.support.room.vo.Database
+import com.android.support.room.vo.Entity
 import com.google.auto.common.AnnotationMirrors
 import com.google.auto.common.MoreElements
 import com.google.auto.common.MoreTypes
+import javax.lang.model.element.AnnotationMirror
 import javax.lang.model.element.AnnotationValue
 import javax.lang.model.element.ElementKind
 import javax.lang.model.element.Modifier
@@ -36,19 +37,11 @@
     val daoParser = DaoProcessor(context)
 
     fun parse(element: TypeElement): Database {
-        Checks.hasAnnotation(element, com.android.support.room.Database::class,
-                ProcessorErrors.DATABASE_MUST_BE_ANNOTATED_WITH_DATABASE)
         val dbAnnotation = MoreElements
                 .getAnnotationMirror(element, com.android.support.room.Database::class.java)
-                .get()
-        val entityList = AnnotationMirrors.getAnnotationValue(dbAnnotation, "entities")
-        val listOfTypes = TO_LIST_OF_TYPES.visit(entityList, "entities")
-        Checks.check(listOfTypes.isNotEmpty(), element,
-                ProcessorErrors.DATABASE_ANNOTATION_MUST_HAVE_LIST_OF_ENTITIES)
+                .orNull()
 
-        val entities = listOfTypes.map {
-            entityParser.parse(MoreTypes.asTypeElement(it))
-        }
+        val entities = processEntities(dbAnnotation, element)
 
         val allMembers = context.processingEnv.elementUtils.getAllMembers(element)
         val daoMethods = allMembers.filter {
@@ -65,6 +58,22 @@
                 daoMethods = daoMethods)
     }
 
+    private fun processEntities(dbAnnotation: AnnotationMirror?, element: TypeElement) :
+            List<Entity> {
+        if (!context.checker.check(dbAnnotation != null, element,
+                ProcessorErrors.DATABASE_MUST_BE_ANNOTATED_WITH_DATABASE)) {
+            return listOf()
+        }
+
+        val entityList = AnnotationMirrors.getAnnotationValue(dbAnnotation, "entities")
+        val listOfTypes = TO_LIST_OF_TYPES.visit(entityList, "entities")
+        context.checker.check(listOfTypes.isNotEmpty(), element,
+                ProcessorErrors.DATABASE_ANNOTATION_MUST_HAVE_LIST_OF_ENTITIES)
+        return listOfTypes.map {
+            entityParser.parse(MoreTypes.asTypeElement(it))
+        }
+    }
+
     // code below taken from dagger2
     // compiler/src/main/java/dagger/internal/codegen/ConfigurationAnnotations.java
     private val TO_LIST_OF_TYPES = object
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 b5fe562..03cc86d 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
@@ -17,11 +17,13 @@
 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.preconditions.Checks
-import com.android.support.room.vo.*
+import com.android.support.room.vo.CallType
+import com.android.support.room.vo.Entity
+import com.android.support.room.vo.Field
+import com.android.support.room.vo.FieldGetter
+import com.android.support.room.vo.FieldSetter
 import com.google.auto.common.AnnotationMirrors
 import com.google.auto.common.MoreElements
 import com.google.auto.common.MoreTypes
@@ -29,7 +31,9 @@
 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.Modifier.ABSTRACT
+import javax.lang.model.element.Modifier.PRIVATE
+import javax.lang.model.element.Modifier.STATIC
 import javax.lang.model.element.TypeElement
 import javax.lang.model.type.TypeKind
 
@@ -37,7 +41,7 @@
     val fieldParser = FieldProcessor(context)
 
     fun parse(element: TypeElement): Entity {
-        Checks.hasAnnotation(element, com.android.support.room.Entity::class,
+        context.checker.hasAnnotation(element, com.android.support.room.Entity::class,
                 ProcessorErrors.ENTITY_MUST_BE_ANNOTATED_WITH_ENTITY)
         val declaredType = MoreTypes.asDeclared(element.asType())
         val allMembers = context.processingEnv.elementUtils.getAllMembers(element)
@@ -68,11 +72,11 @@
         assignGetters(fields, getterCandidates)
         assignSetters(fields, setterCandidates)
         val annotation = MoreElements.getAnnotationMirror(element,
-                com.android.support.room.Entity::class.java)
+                com.android.support.room.Entity::class.java).orNull()
         val tableName : String
-        if (annotation.isPresent) {
+        if (annotation != null) {
             val annotationValue = AnnotationMirrors
-                    .getAnnotationValue(annotation.get(), "tableName").value.toString()
+                    .getAnnotationValue(annotation, "tableName").value.toString()
             if (annotationValue == "") {
                 tableName = element.simpleName.toString()
             } else {
@@ -81,9 +85,11 @@
         } else {
             tableName = element.simpleName.toString()
         }
-        Checks.notBlank(tableName, element, ProcessorErrors.ENTITY_TABLE_NAME_CANNOT_BE_EMPTY)
+        context.checker.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)
+        context.checker.check(entity.primaryKeys.isNotEmpty(), element,
+                ProcessorErrors.MISSING_PRIMARY_KEY)
         return entity
     }
 
@@ -101,17 +107,19 @@
                                     || field.getterNameWithVariations
                                     .contains(it.simpleName.toString())
                         }
-                if (matching.isEmpty()) {
-                    throw ElementBoundException(field.element,
-                            ProcessorErrors.CANNOT_FIND_GETTER_FOR_FIELD)
+                context.checker.check(matching.isNotEmpty(), field.element,
+                        ProcessorErrors.CANNOT_FIND_GETTER_FOR_FIELD)
+                context.checker.check(matching.size < 2, field.element,
+                        ProcessorErrors.tooManyMatchingGetters(field,
+                                matching.map { it.simpleName.toString() }))
+                val match = matching.firstOrNull()
+                if (match == null) {
+                    // just assume we can set it. the error will block javac anyways.
+                    field.getter = FieldGetter(field.name, CallType.FIELD)
+                } else {
+                    field.getter = FieldGetter(match.simpleName.toString(), CallType.METHOD)
                 }
-                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)
+
             }
         }
     }
@@ -130,17 +138,18 @@
                                     || field.setterNameWithVariations
                                     .contains(it.simpleName.toString())
                         }
-                if (matching.isEmpty()) {
-                    throw ElementBoundException(field.element,
-                            ProcessorErrors.CANNOT_FIND_SETTER_FOR_FIELD)
+                context.checker.check(matching.isNotEmpty(), field.element,
+                        ProcessorErrors.CANNOT_FIND_SETTER_FOR_FIELD)
+                context.checker.check(matching.size < 2, field.element,
+                        ProcessorErrors.tooManyMatchingSetter(field,
+                                matching.map { it.simpleName.toString() }))
+                val match = matching.firstOrNull()
+                if (match == null) {
+                    // default to field setter
+                    field.setter = FieldSetter(field.name, CallType.FIELD)
+                } else {
+                    field.setter = FieldSetter(match.simpleName.toString(), CallType.METHOD)
                 }
-                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/FieldProcessor.kt b/room/compiler/src/main/kotlin/com/android/support/room/processor/FieldProcessor.kt
index 05fea9c..87ef1ee 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
@@ -40,8 +40,9 @@
         } else {
             columnName = name
         }
-        Checks.notBlank(columnName, element, ProcessorErrors.COLUMN_NAME_CANNOT_BE_EMPTY)
-        Checks.notUnbound(type, element,
+        context.checker.notBlank(columnName, element,
+                ProcessorErrors.COLUMN_NAME_CANNOT_BE_EMPTY)
+        context.checker.notUnbound(type, element,
                 ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_ENTITY_FIELDS)
         return Field(name = name,
                 type = type,
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 a6ddd33..7a72319 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
@@ -17,9 +17,10 @@
 package com.android.support.room.processor
 
 import com.android.support.room.Query
+import com.android.support.room.parser.ParsedQuery
 import com.android.support.room.parser.SqlParser
-import com.android.support.room.preconditions.Checks
 import com.android.support.room.vo.QueryMethod
+import com.google.auto.common.AnnotationMirrors
 import com.google.auto.common.MoreElements
 import com.google.auto.common.MoreTypes
 import com.squareup.javapoet.TypeName
@@ -32,17 +33,28 @@
     fun parse(containing: DeclaredType, executableElement: ExecutableElement): QueryMethod {
         val asMember = context.processingEnv.typeUtils.asMemberOf(containing, executableElement)
         val executableType = MoreTypes.asExecutable(asMember)
-        Checks.check(MoreElements.isAnnotationPresent(executableElement, Query::class.java),
-                executableElement, ProcessorErrors.MISSING_QUERY_ANNOTATION)
-        val annotation = executableElement.getAnnotation(Query::class.java)
-        val query = SqlParser.parse(annotation.value)
-        Checks.check(query.errors.isEmpty(), executableElement, query.errors.joinToString("\n"))
-        Checks.check(executableType.returnType.kind != TypeKind.ERROR, executableElement,
-                ProcessorErrors.CANNOT_RESOLVE_RETURN_TYPE, executableElement)
+
+        val annotation = MoreElements.getAnnotationMirror(executableElement,
+                Query::class.java).orNull()
+        context.checker.check(annotation != null, executableElement,
+                ProcessorErrors.MISSING_QUERY_ANNOTATION)
+
+        val query = if (annotation != null) {
+            val query = SqlParser.parse(
+                    AnnotationMirrors.getAnnotationValue(annotation, "value").value.toString())
+            context.checker.check(query.errors.isEmpty(), executableElement,
+                    query.errors.joinToString("\n"))
+            context.checker.check(executableType.returnType.kind != TypeKind.ERROR,
+                    executableElement, ProcessorErrors.CANNOT_RESOLVE_RETURN_TYPE,
+                    executableElement)
+            query
+        } else {
+            ParsedQuery.MISSING
+        }
 
         val returnTypeName = TypeName.get(executableType.returnType)
 
-        Checks.notUnbound(returnTypeName, executableElement,
+        context.checker.notUnbound(returnTypeName, executableElement,
                 ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_QUERY_METHODS)
         return QueryMethod(
                 element = executableElement,
diff --git a/room/compiler/src/test/kotlin/com/android/support/room/testing/TestProcessor.kt b/room/compiler/src/test/kotlin/com/android/support/room/testing/TestProcessor.kt
index 7e04f96..43bd915 100644
--- a/room/compiler/src/test/kotlin/com/android/support/room/testing/TestProcessor.kt
+++ b/room/compiler/src/test/kotlin/com/android/support/room/testing/TestProcessor.kt
@@ -16,13 +16,11 @@
 
 package com.android.support.room.testing
 
-import com.android.support.room.errors.ElementBoundException
 import javax.annotation.processing.AbstractProcessor
 import javax.annotation.processing.RoundEnvironment
 import javax.annotation.processing.SupportedSourceVersion
 import javax.lang.model.SourceVersion
 import javax.lang.model.element.TypeElement
-import javax.tools.Diagnostic.Kind.ERROR
 import kotlin.reflect.KClass
 
 @SupportedSourceVersion(SourceVersion.RELEASE_7)
@@ -31,13 +29,8 @@
     var count = 0
     override fun process(annotations: MutableSet<out TypeElement>, roundEnv: RoundEnvironment)
             : Boolean {
-        try {
-            return handlers.getOrNull(count++)?.invoke(
+        return handlers.getOrNull(count++)?.invoke(
                     TestInvocation(processingEnv, annotations, roundEnv)) ?: true
-        } catch (elmBound: ElementBoundException) {
-            processingEnv.messager.printMessage(ERROR, elmBound.msg, elmBound.element)
-        }
-        return true
     }
 
     override fun getSupportedAnnotationTypes(): MutableSet<String> {