blob: e83d1efe9d9f1d6bc640c90645f79aadcf5f24e5 [file] [log] [blame]
/*
* Copyright (C) 2017 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.Relation
import android.arch.persistence.room.ColumnInfo
import android.arch.persistence.room.Embedded
import android.arch.persistence.room.Ignore
import android.arch.persistence.room.ext.getAllFieldsIncludingPrivateSupers
import android.arch.persistence.room.ext.getAnnotationValue
import android.arch.persistence.room.ext.getAsString
import android.arch.persistence.room.ext.getAsStringList
import android.arch.persistence.room.ext.hasAnnotation
import android.arch.persistence.room.ext.hasAnyOf
import android.arch.persistence.room.ext.isCollection
import android.arch.persistence.room.ext.toClassType
import android.arch.persistence.room.processor.ProcessorErrors.CANNOT_FIND_GETTER_FOR_FIELD
import android.arch.persistence.room.processor.ProcessorErrors.CANNOT_FIND_SETTER_FOR_FIELD
import android.arch.persistence.room.processor.ProcessorErrors.CANNOT_FIND_TYPE
import android.arch.persistence.room.processor.ProcessorErrors.POJO_FIELD_HAS_DUPLICATE_COLUMN_NAME
import android.arch.persistence.room.processor.cache.Cache
import android.arch.persistence.room.vo.CallType
import android.arch.persistence.room.vo.Constructor
import android.arch.persistence.room.vo.Field
import android.arch.persistence.room.vo.FieldGetter
import android.arch.persistence.room.vo.EmbeddedField
import android.arch.persistence.room.vo.Entity
import android.arch.persistence.room.vo.FieldSetter
import android.arch.persistence.room.vo.Pojo
import com.google.auto.common.AnnotationMirrors
import com.google.auto.common.MoreElements
import com.google.auto.common.MoreTypes
import javax.lang.model.element.Element
import javax.lang.model.element.ExecutableElement
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.PROTECTED
import javax.lang.model.element.Modifier.PUBLIC
import javax.lang.model.element.Modifier.STATIC
import javax.lang.model.element.Modifier.TRANSIENT
import javax.lang.model.element.TypeElement
import javax.lang.model.element.VariableElement
import javax.lang.model.type.DeclaredType
import javax.lang.model.type.TypeKind
import javax.lang.model.type.TypeMirror
import javax.lang.model.util.ElementFilter
/**
* Processes any class as if it is a Pojo.
*/
class PojoProcessor(baseContext: Context, val element: TypeElement,
val bindingScope: FieldProcessor.BindingScope,
val parent: EmbeddedField?) {
val context = baseContext.fork(element)
companion object {
val PROCESSED_ANNOTATIONS = listOf(ColumnInfo::class, Embedded::class,
Relation::class)
}
fun process() : Pojo {
return context.cache.pojos.get(Cache.PojoKey(element, bindingScope, parent), {
doProcess()
})
}
private fun doProcess(): Pojo {
// TODO handle recursion: b/35980205
val declaredType = MoreTypes.asDeclared(element.asType())
// TODO handle conflicts with super: b/35568142
val allFields = element.getAllFieldsIncludingPrivateSupers(context.processingEnv)
.filter {
!it.hasAnnotation(Ignore::class)
&& !it.hasAnyOf(STATIC)
&& (!it.hasAnyOf(TRANSIENT)
|| it.hasAnnotation(ColumnInfo::class)
|| it.hasAnnotation(Embedded::class)
|| it.hasAnnotation(Relation::class))
}
.groupBy { field ->
context.checker.check(
PROCESSED_ANNOTATIONS.count { field.hasAnnotation(it) } < 2, field,
ProcessorErrors.CANNOT_USE_MORE_THAN_ONE_POJO_FIELD_ANNOTATION
)
if (field.hasAnnotation(Embedded::class)) {
Embedded::class
} else if (field.hasAnnotation(Relation::class)) {
Relation::class
} else {
null
}
}
val myFields = allFields[null]
?.map {
FieldProcessor(
baseContext = context,
containing = declaredType,
element = it,
bindingScope = bindingScope,
fieldParent = parent).process()
} ?: emptyList()
val embeddedFields = allFields[Embedded::class]
?.map {
processEmbeddedField(declaredType, it)
} ?: emptyList()
val subFields = embeddedFields.flatMap { it.pojo.fields }
val fields = myFields + subFields
val myRelationsList = allFields[Relation::class]
?.map {
processRelationField(fields, declaredType, it)
}
?.filterNotNull() ?: emptyList()
val subRelations = embeddedFields.flatMap { it.pojo.relations }
val relations = myRelationsList + subRelations
fields.groupBy { it.columnName }
.filter { it.value.size > 1 }
.forEach {
context.logger.e(element, ProcessorErrors.pojoDuplicateFieldNames(
it.key, it.value.map(Field::getPath)
))
it.value.forEach {
context.logger.e(it.element, POJO_FIELD_HAS_DUPLICATE_COLUMN_NAME)
}
}
val methods = MoreElements.getLocalAndInheritedMethods(element,
context.processingEnv.elementUtils)
.filter {
!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
}
// don't try to find a constructor for binding to statement.
val constructor = if (bindingScope == FieldProcessor.BindingScope.BIND_TO_STMT) {
// we don't need to construct this POJO.
null
} else {
chooseConstructor(myFields, embeddedFields)
}
assignGetters(myFields, getterCandidates)
assignSetters(myFields, setterCandidates, constructor)
embeddedFields.forEach {
assignGetter(it.field, getterCandidates)
assignSetter(it.field, setterCandidates, constructor)
}
myRelationsList.forEach {
assignGetter(it.field, getterCandidates)
assignSetter(it.field, setterCandidates, constructor)
}
val pojo = Pojo(element = element,
type = declaredType,
fields = fields,
embeddedFields = embeddedFields,
relations = relations,
constructor = constructor)
return pojo
}
private fun chooseConstructor(myFields: List<Field>, embedded: List<EmbeddedField>)
: Constructor? {
val constructors = ElementFilter.constructorsIn(element.enclosedElements)
.filterNot { it.hasAnnotation(Ignore::class) || it.hasAnyOf(PRIVATE) }
val fieldMap = myFields.associateBy { it.name }
val embeddedMap = embedded.associateBy { it.field.name }
val typeUtils = context.processingEnv.typeUtils
val failedConstructors = mutableMapOf<ExecutableElement, List<Constructor.Param?>>()
val goodConstructors = constructors.map { constructor ->
val params = constructor.parameters.map param@ { param ->
val paramName = param.simpleName.toString()
val paramType = param.asType()
val matches = fun(field: Field?): Boolean {
return if (field == null) {
false
} else if (!field.nameWithVariations.contains(paramName)) {
false
} else {
typeUtils.isAssignable(paramType, field.type)
}
}
val exactFieldMatch = fieldMap[paramName]
if (matches(exactFieldMatch)) {
return@param Constructor.FieldParam(exactFieldMatch!!)
}
val exactEmbeddedMatch = embeddedMap[paramName]
if (matches(exactEmbeddedMatch?.field)) {
return@param Constructor.EmbeddedParam(exactEmbeddedMatch!!)
}
val matchingFields = myFields.filter {
matches(it)
}
val embeddedMatches = embedded.filter {
matches(it.field)
}
if (matchingFields.isEmpty() && embeddedMatches.isEmpty()) {
null
} else if (matchingFields.size + embeddedMatches.size == 1) {
if (matchingFields.isNotEmpty()) {
Constructor.FieldParam(matchingFields.first())
} else {
Constructor.EmbeddedParam(embeddedMatches.first())
}
} else {
context.logger.e(param, ProcessorErrors.ambigiousConstructor(
pojo = element.qualifiedName.toString(),
paramName = param.simpleName.toString(),
matchingFields = matchingFields.map { it.getPath() }
+ embedded.map { it.field.getPath() }
))
null
}
}
if (params.any { it == null }) {
failedConstructors.put(constructor, params)
null
} else {
@Suppress("UNCHECKED_CAST")
Constructor(constructor, params as List<Constructor.Param>)
}
}.filterNotNull()
if (goodConstructors.isEmpty()) {
if (failedConstructors.isNotEmpty()) {
val failureMsg = failedConstructors.entries.joinToString("\n") { entry ->
val paramsMatching = entry.key.parameters.withIndex().joinToString(", ") {
"${it.value.simpleName} : ${entry.value[it.index]?.log()}"
}
"${entry.key} : [$paramsMatching]"
}
context.logger.e(element, ProcessorErrors.MISSING_POJO_CONSTRUCTOR +
"\nTried the following constructors but they failed to match:\n$failureMsg")
}
context.logger.e(element, ProcessorErrors.MISSING_POJO_CONSTRUCTOR)
return null
}
if (goodConstructors.size > 1) {
goodConstructors.forEach {
context.logger.e(it.element, ProcessorErrors.TOO_MANY_POJO_CONSTRUCTORS)
}
return null
}
return goodConstructors.first()
}
private fun processEmbeddedField(declaredType: DeclaredType?, it: Element): EmbeddedField {
val fieldPrefix = it.getAnnotationValue(Embedded::class.java, "prefix")
?.toString() ?: ""
val inheritedPrefix = parent?.prefix ?: ""
val embeddedField = Field(
it,
it.simpleName.toString(),
type = context.processingEnv.typeUtils.asMemberOf(declaredType, it),
affinity = null,
parent = parent)
val subParent = EmbeddedField(
field = embeddedField,
prefix = inheritedPrefix + fieldPrefix,
parent = parent)
val asVariable = MoreElements.asVariable(it)
subParent.pojo = PojoProcessor(baseContext = context.fork(it),
element = MoreTypes.asTypeElement(asVariable.asType()),
bindingScope = bindingScope,
parent = subParent).process()
return subParent
}
private fun processRelationField(myFields : List<Field>, container: DeclaredType?,
relationElement: VariableElement)
: android.arch.persistence.room.vo.Relation? {
val annotation = MoreElements.getAnnotationMirror(relationElement, Relation::class.java)
.orNull()!!
val parentColumnInput = AnnotationMirrors.getAnnotationValue(annotation, "parentColumn")
.getAsString("") ?: ""
val parentField = myFields.firstOrNull {
it.columnName == parentColumnInput
}
if (parentField == null) {
context.logger.e(relationElement,
ProcessorErrors.relationCannotFindParentEntityField(
entityName = element.qualifiedName.toString(),
columnName = parentColumnInput,
availableColumns = myFields.map { it.columnName }))
return null
}
// parse it as an entity.
val asMember = MoreTypes
.asMemberOf(context.processingEnv.typeUtils, container, relationElement)
if (asMember.kind == TypeKind.ERROR) {
context.logger.e(ProcessorErrors.CANNOT_FIND_TYPE, element)
return null
}
val declared = MoreTypes.asDeclared(asMember)
if (!declared.isCollection()) {
context.logger.e(relationElement, ProcessorErrors.RELATION_NOT_COLLECTION)
return null
}
val typeArg = declared.typeArguments.first()
if (typeArg.kind == TypeKind.ERROR) {
context.logger.e(MoreTypes.asTypeElement(typeArg), CANNOT_FIND_TYPE)
return null
}
val typeArgElement = MoreTypes.asTypeElement(typeArg)
val entityClassInput = AnnotationMirrors
.getAnnotationValue(annotation, "entity").toClassType()
val pojo : Pojo
val entity : Entity
if (entityClassInput == null
|| MoreTypes.isTypeOf(Any::class.java, entityClassInput)) {
entity = EntityProcessor(context, typeArgElement).process()
pojo = entity
} else {
entity = EntityProcessor(context, MoreTypes.asTypeElement(entityClassInput)).process()
pojo = PojoProcessor(baseContext = context,
element = typeArgElement,
bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
parent = parent).process()
}
// now find the field in the entity.
val entityColumnInput = AnnotationMirrors.getAnnotationValue(annotation, "entityColumn")
.getAsString() ?: ""
val entityField = entity.fields.firstOrNull {
it.columnName == entityColumnInput
}
if (entityField == null) {
context.logger.e(relationElement,
ProcessorErrors.relationCannotFindEntityField(
entityName = entity.typeName.toString(),
columnName = entityColumnInput,
availableColumns = entity.fields.map { it.columnName }))
return null
}
val field = Field(
element = relationElement,
name = relationElement.simpleName.toString(),
type = context.processingEnv.typeUtils.asMemberOf(container, relationElement),
affinity = null,
parent = parent)
val projection = AnnotationMirrors.getAnnotationValue(annotation, "projection")
.getAsStringList()
if(projection.isNotEmpty()) {
val missingColumns = projection.filterNot { columnName ->
entity.fields.any { columnName == it.columnName }
}
if (missingColumns.isNotEmpty()) {
context.logger.e(relationElement,
ProcessorErrors.relationBadProject(entity.typeName.toString(),
missingColumns, entity.fields.map { it.columnName }))
}
}
// if types don't match, row adapter prints a warning
return android.arch.persistence.room.vo.Relation(
entity = entity,
pojo = pojo,
field = field,
parentField = parentField,
entityField = entityField,
projection = projection
)
}
private fun assignGetters(fields: List<Field>, getterCandidates: List<ExecutableElement>) {
fields.forEach { field ->
assignGetter(field, getterCandidates)
}
}
private fun assignGetter(field: Field, getterCandidates: List<ExecutableElement>) {
val success = chooseAssignment(field = field,
candidates = getterCandidates,
nameVariations = field.getterNameWithVariations,
getType = { method ->
method.returnType
},
assignFromField = {
field.getter = FieldGetter(
name = field.name,
type = field.type,
callType = CallType.FIELD)
},
assignFromMethod = { match ->
field.getter = FieldGetter(
name = match.simpleName.toString(),
type = match.returnType,
callType = CallType.METHOD)
},
reportAmbiguity = { matching ->
context.logger.e(field.element,
ProcessorErrors.tooManyMatchingGetters(field, matching))
})
context.checker.check(success, field.element, CANNOT_FIND_GETTER_FOR_FIELD)
}
private fun assignSetters(fields: List<Field>, setterCandidates: List<ExecutableElement>,
constructor : Constructor?) {
fields.forEach { field ->
assignSetter(field, setterCandidates, constructor)
}
}
private fun assignSetter(field: Field, setterCandidates: List<ExecutableElement>,
constructor: Constructor?) {
if (constructor != null && constructor.hasField(field)) {
field.setter = FieldSetter(field.name, field.type, CallType.CONSTRUCTOR)
return
}
val success = chooseAssignment(field = field,
candidates = setterCandidates,
nameVariations = field.setterNameWithVariations,
getType = { method ->
method.parameters.first().asType()
},
assignFromField = {
field.setter = FieldSetter(
name = field.name,
type = field.type,
callType = CallType.FIELD)
},
assignFromMethod = { match ->
val paramType = match.parameters.first().asType()
field.setter = FieldSetter(
name = match.simpleName.toString(),
type = paramType,
callType = CallType.METHOD)
},
reportAmbiguity = { matching ->
context.logger.e(field.element,
ProcessorErrors.tooManyMatchingSetter(field, matching))
})
context.checker.check(success, field.element, CANNOT_FIND_SETTER_FOR_FIELD)
}
/**
* Finds a setter/getter from available list of methods.
* It returns true if assignment is successful, false otherwise.
* At worst case, it sets to the field as if it is accessible so that the rest of the
* compilation can continue.
*/
private fun chooseAssignment(field: Field, candidates: List<ExecutableElement>,
nameVariations: List<String>,
getType: (ExecutableElement) -> TypeMirror,
assignFromField: () -> Unit,
assignFromMethod: (ExecutableElement) -> Unit,
reportAmbiguity: (List<String>) -> Unit): Boolean {
if (field.element.hasAnyOf(PUBLIC)) {
assignFromField()
return true
}
val types = context.processingEnv.typeUtils
val matching = candidates
.filter {
types.isAssignable(getType(it), field.element.asType())
&& (field.nameWithVariations.contains(it.simpleName.toString())
|| nameVariations.contains(it.simpleName.toString()))
}
.groupBy {
if (it.hasAnyOf(PUBLIC)) PUBLIC else PROTECTED
}
if (matching.isEmpty()) {
// we always assign to avoid NPEs in the rest of the compilation.
assignFromField()
// if field is not private, assume it works (if we are on the same package).
// if not, compiler will tell, we didn't have any better alternative anyways.
return !field.element.hasAnyOf(PRIVATE)
}
val match = verifyAndChooseOneFrom(matching[PUBLIC], reportAmbiguity) ?:
verifyAndChooseOneFrom(matching[PROTECTED], reportAmbiguity)
if (match == null) {
assignFromField()
return false
} else {
assignFromMethod(match)
return true
}
}
private fun verifyAndChooseOneFrom(candidates: List<ExecutableElement>?,
reportAmbiguity: (List<String>) -> Unit)
: ExecutableElement? {
if (candidates == null) {
return null
}
if (candidates.size > 1) {
reportAmbiguity(candidates.map { it.simpleName.toString() })
}
return candidates.first()
}
}