blob: 0c0ff49286fe3c765deb771b108a8327d00cd4cc [file] [log] [blame]
/*
* 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 android.arch.persistence.room.processor
import android.arch.persistence.room.Delete
import android.arch.persistence.room.Insert
import android.arch.persistence.room.Query
import android.arch.persistence.room.SkipQueryVerification
import android.arch.persistence.room.Update
import android.arch.persistence.room.ext.hasAnnotation
import android.arch.persistence.room.ext.hasAnyOf
import android.arch.persistence.room.ext.typeName
import android.arch.persistence.room.verifier.DatabaseVerifier
import android.arch.persistence.room.vo.Dao
import com.google.auto.common.MoreElements
import com.google.auto.common.MoreTypes
import com.squareup.javapoet.TypeName
import javax.lang.model.element.ElementKind
import javax.lang.model.element.ExecutableElement
import javax.lang.model.element.Modifier.ABSTRACT
import javax.lang.model.element.TypeElement
import javax.lang.model.type.DeclaredType
class DaoProcessor(baseContext : Context, val element: TypeElement, val dbType: DeclaredType,
val dbVerifier : DatabaseVerifier?) {
val context = baseContext.fork(element)
companion object {
val PROCESSED_ANNOTATIONS = listOf(Insert::class, Delete::class, Query::class,
Update::class)
}
fun process() : Dao {
context.checker.hasAnnotation(element, android.arch.persistence.room.Dao::class,
ProcessorErrors.DAO_MUST_BE_ANNOTATED_WITH_DAO)
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())
val allMembers = context.processingEnv.elementUtils.getAllMembers(element)
val methods = allMembers
.filter {
it.hasAnyOf(ABSTRACT) && it.kind == ElementKind.METHOD
}.map {
MoreElements.asExecutable(it)
}.groupBy { method ->
context.checker.check(
PROCESSED_ANNOTATIONS.count { method.hasAnnotation(it) } == 1, method,
ProcessorErrors.CANNOT_USE_MORE_THAN_ONE_DAO_METHOD_ANNOTATION
)
if (method.hasAnnotation(Query::class)) {
Query::class
} else if (method.hasAnnotation(Insert::class)) {
Insert::class
} else if (method.hasAnnotation(Delete::class)) {
Delete::class
} else if (method.hasAnnotation(Update::class)) {
Update::class
} else {
Any::class
}
}
val processorVerifier = if (element.hasAnnotation(SkipQueryVerification::class)) {
null
} else {
dbVerifier
}
val queryMethods = methods[Query::class]?.map {
QueryMethodProcessor(
baseContext = context,
containing = declaredType,
executableElement = it,
dbVerifier = processorVerifier).process()
} ?: emptyList()
val insertionMethods = methods[Insert::class]?.map {
InsertionMethodProcessor(
baseContext = context,
containing = declaredType,
executableElement = it).process()
} ?: emptyList()
val deletionMethods = methods[Delete::class]?.map {
DeletionMethodProcessor(
baseContext = context,
containing = declaredType,
executableElement = it).process()
} ?: emptyList()
val updateMethods = methods[Update::class]?.map {
UpdateMethodProcessor(
baseContext = context,
containing = declaredType,
executableElement = it).process()
} ?: emptyList()
val constructors = allMembers
.filter { it.kind == ElementKind.CONSTRUCTOR }
.map { MoreElements.asExecutable(it) }
val typeUtils = context.processingEnv.typeUtils
val goodConstructor = constructors
.filter {
it.parameters.size == 1
&& typeUtils.isAssignable(dbType, it.parameters[0].asType())
}
.firstOrNull()
val constructorParamType = if (goodConstructor != null) {
goodConstructor.parameters[0].asType().typeName()
} else {
validateEmptyConstructor(constructors)
null
}
context.checker.check(methods[Any::class] == null, element,
ProcessorErrors.ABSTRACT_METHOD_IN_DAO_MISSING_ANY_ANNOTATION)
val type = TypeName.get(declaredType)
context.checker.notUnbound(type, element,
ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_DAO_CLASSES)
return Dao(element = element,
type = declaredType,
queryMethods = queryMethods,
insertionMethods = insertionMethods,
deletionMethods = deletionMethods,
updateMethods = updateMethods,
constructorParamType = constructorParamType)
}
private fun validateEmptyConstructor(constructors: List<ExecutableElement>) {
if (constructors.isNotEmpty() && constructors.all { it.parameters.isNotEmpty() }) {
context.logger.e(element, ProcessorErrors.daoMustHaveMatchingConstructor(
element.toString(), dbType.toString()))
}
}
}