com.zeoflow.depot.processor.PojoProcessor.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of depot-compiler Show documentation
Show all versions of depot-compiler Show documentation
The Depot persistence library provides an abstraction layer over SQLite to allow for more robust database access while using the full power of SQLite.
The newest version!
/*
* Copyright (C) 2021 ZeoFlow SRL
*
* 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.zeoflow.depot.processor
import com.zeoflow.depot.compiler.processing.XExecutableElement
import com.zeoflow.depot.compiler.processing.XFieldElement
import com.zeoflow.depot.compiler.processing.XType
import com.zeoflow.depot.compiler.processing.XTypeElement
import com.zeoflow.depot.compiler.processing.XVariableElement
import com.zeoflow.depot.compiler.processing.isCollection
import com.zeoflow.depot.compiler.processing.isVoid
import com.zeoflow.depot.ext.isNotVoid
import com.zeoflow.depot.processor.ProcessorErrors.CANNOT_FIND_GETTER_FOR_FIELD
import com.zeoflow.depot.processor.ProcessorErrors.CANNOT_FIND_SETTER_FOR_FIELD
import com.zeoflow.depot.processor.ProcessorErrors.POJO_FIELD_HAS_DUPLICATE_COLUMN_NAME
import com.zeoflow.depot.processor.autovalue.AutoValuePojoProcessorDelegate
import com.zeoflow.depot.processor.cache.Cache
import com.zeoflow.depot.vo.CallType
import com.zeoflow.depot.vo.Constructor
import com.zeoflow.depot.vo.EmbeddedField
import com.zeoflow.depot.vo.Entity
import com.zeoflow.depot.vo.EntityOrView
import com.zeoflow.depot.vo.Field
import com.zeoflow.depot.vo.FieldGetter
import com.zeoflow.depot.vo.FieldSetter
import com.zeoflow.depot.vo.Pojo
import com.zeoflow.depot.vo.PojoMethod
import com.zeoflow.depot.vo.Warning
import com.zeoflow.depot.vo.columnNames
import com.zeoflow.depot.vo.findFieldByColumnName
import com.google.auto.value.AutoValue
/**
* Processes any class as if it is a Pojo.
*/
class PojoProcessor private constructor(
baseContext: Context,
val element: XTypeElement,
val bindingScope: FieldProcessor.BindingScope,
val parent: EmbeddedField?,
val referenceStack: LinkedHashSet = LinkedHashSet(),
private val delegate: Delegate
) {
val context = baseContext.fork(element)
companion object {
val PROCESSED_ANNOTATIONS = listOf(com.zeoflow.depot.ColumnInfo::class, com.zeoflow.depot.Embedded::class, com.zeoflow.depot.Relation::class)
val TARGET_METHOD_ANNOTATIONS = arrayOf(
com.zeoflow.depot.PrimaryKey::class, com.zeoflow.depot.ColumnInfo::class,
com.zeoflow.depot.Embedded::class, com.zeoflow.depot.Relation::class
)
fun createFor(
context: Context,
element: XTypeElement,
bindingScope: FieldProcessor.BindingScope,
parent: EmbeddedField?,
referenceStack: LinkedHashSet = LinkedHashSet()
): PojoProcessor {
val (pojoElement, delegate) = if (element.hasAnnotation(AutoValue::class)) {
val processingEnv = context.processingEnv
val autoValueGeneratedElement = element.let {
val typeName = AutoValuePojoProcessorDelegate.getGeneratedClassName(it)
processingEnv.findTypeElement(typeName) ?: throw MissingTypeException(typeName)
}
autoValueGeneratedElement to AutoValuePojoProcessorDelegate(context, element)
} else {
element to DefaultDelegate(context)
}
return PojoProcessor(
baseContext = context,
element = pojoElement,
bindingScope = bindingScope,
parent = parent,
referenceStack = referenceStack,
delegate = delegate
)
}
}
fun process(): Pojo {
return context.cache.pojos.get(Cache.PojoKey(element, bindingScope, parent)) {
referenceStack.add(element.qualifiedName)
try {
doProcess()
} finally {
referenceStack.remove(element.qualifiedName)
}
}
}
private fun doProcess(): Pojo {
delegate.onPreProcess(element)
val declaredType = element.type
// TODO handle conflicts with super: b/35568142
val allFields = element.getAllFieldsIncludingPrivateSupers()
.filter {
!it.hasAnnotation(com.zeoflow.depot.Ignore::class) &&
!it.isStatic() &&
(
!it.isTransient() ||
it.hasAnyOf(com.zeoflow.depot.ColumnInfo::class, com.zeoflow.depot.Embedded::class, com.zeoflow.depot.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(com.zeoflow.depot.Embedded::class)) {
com.zeoflow.depot.Embedded::class
} else if (field.hasAnnotation(com.zeoflow.depot.Relation::class)) {
com.zeoflow.depot.Relation::class
} else {
null
}
}
val ignoredColumns =
element.getAnnotation(com.zeoflow.depot.Entity::class)?.value?.ignoredColumns?.toSet()
?: emptySet()
val fieldBindingErrors = mutableMapOf()
val unfilteredMyFields = allFields[null]
?.map {
FieldProcessor(
baseContext = context,
containing = declaredType,
element = it,
bindingScope = bindingScope,
fieldParent = parent,
onBindingError = { field, errorMsg ->
fieldBindingErrors[field] = errorMsg
}
).process()
} ?: emptyList()
val myFields = unfilteredMyFields.filterNot { ignoredColumns.contains(it.columnName) }
myFields.forEach { field ->
fieldBindingErrors[field]?.let {
context.logger.e(field.element, it)
}
}
val unfilteredEmbeddedFields =
allFields[com.zeoflow.depot.Embedded::class]
?.mapNotNull {
processEmbeddedField(declaredType, it)
}
?: emptyList()
val embeddedFields =
unfilteredEmbeddedFields.filterNot { ignoredColumns.contains(it.field.columnName) }
val subFields = embeddedFields.flatMap { it.pojo.fields }
val fields = myFields + subFields
val unfilteredCombinedFields =
unfilteredMyFields + unfilteredEmbeddedFields.map { it.field }
val missingIgnoredColumns = ignoredColumns.filterNot { ignoredColumn ->
unfilteredCombinedFields.any { it.columnName == ignoredColumn }
}
context.checker.check(
missingIgnoredColumns.isEmpty(), element,
ProcessorErrors.missingIgnoredColumns(missingIgnoredColumns)
)
val myRelationsList = allFields[com.zeoflow.depot.Relation::class]
?.mapNotNull {
processRelationField(fields, declaredType, it)
}
?: 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 = element.getAllNonPrivateInstanceMethods()
.asSequence()
.filter {
!it.isAbstract() && !it.hasAnnotation(com.zeoflow.depot.Ignore::class)
}.map {
PojoMethodProcessor(
context = context,
element = it,
owner = declaredType
).process()
}.toList()
val getterCandidates = methods.filter {
it.element.parameters.size == 0 && it.resolvedType.returnType.isNotVoid()
}
val setterCandidates = methods.filter {
it.element.parameters.size == 1 && it.resolvedType.returnType.isVoid()
}
// 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, relations)
}
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)
}
return delegate.createPojo(
element, declaredType, fields, embeddedFields, relations,
constructor
)
}
private fun chooseConstructor(
myFields: List,
embedded: List,
relations: List
): Constructor? {
val constructors = delegate.findConstructors(element)
val fieldMap = myFields.associateBy { it.name }
val embeddedMap = embedded.associateBy { it.field.name }
val relationMap = relations.associateBy { it.field.name }
// list of param names -> matched params pairs for each failed constructor
val failedConstructors = arrayListOf()
val goodConstructors = constructors.map { constructor ->
val parameterNames = constructor.parameters.map { it.name }
val params = constructor.parameters.mapIndexed param@{ index, param ->
val paramName = parameterNames[index]
val paramType = param.type
val matches = fun(field: Field?): Boolean {
return if (field == null) {
false
} else if (!field.nameWithVariations.contains(paramName)) {
false
} else {
// see: b/69164099
field.type.isAssignableFromWithoutVariance(paramType)
}
}
val exactFieldMatch = fieldMap[paramName]
if (matches(exactFieldMatch)) {
return@param Constructor.Param.FieldParam(exactFieldMatch!!)
}
val exactEmbeddedMatch = embeddedMap[paramName]
if (matches(exactEmbeddedMatch?.field)) {
return@param Constructor.Param.EmbeddedParam(exactEmbeddedMatch!!)
}
val exactRelationMatch = relationMap[paramName]
if (matches(exactRelationMatch?.field)) {
return@param Constructor.Param.RelationParam(exactRelationMatch!!)
}
val matchingFields = myFields.filter {
matches(it)
}
val embeddedMatches = embedded.filter {
matches(it.field)
}
val relationMatches = relations.filter {
matches(it.field)
}
when (matchingFields.size + embeddedMatches.size + relationMatches.size) {
0 -> null
1 -> when {
matchingFields.isNotEmpty() ->
Constructor.Param.FieldParam(matchingFields.first())
embeddedMatches.isNotEmpty() ->
Constructor.Param.EmbeddedParam(embeddedMatches.first())
else ->
Constructor.Param.RelationParam(relationMatches.first())
}
else -> {
context.logger.e(
param,
ProcessorErrors.ambiguousConstructor(
pojo = element.qualifiedName,
paramName = paramName,
matchingFields = matchingFields.map { it.getPath() } +
embeddedMatches.map { it.field.getPath() } +
relationMatches.map { it.field.getPath() }
)
)
null
}
}
}
if (params.any { it == null }) {
failedConstructors.add(FailedConstructor(constructor, parameterNames, params))
null
} else {
@Suppress("UNCHECKED_CAST")
Constructor(constructor, params as List)
}
}.filterNotNull()
when {
goodConstructors.isEmpty() -> {
if (failedConstructors.isNotEmpty()) {
val failureMsg = failedConstructors.joinToString("\n") { entry ->
entry.log()
}
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
}
goodConstructors.size > 1 -> {
// if the Pojo is a Kotlin data class then pick its primary constructor. This is
// better than picking the no-arg constructor and forcing users to define fields as
// vars.
val primaryConstructor =
element.findPrimaryConstructor()?.let { primary ->
goodConstructors.firstOrNull { candidate ->
candidate.element == primary
}
}
if (primaryConstructor != null) {
return primaryConstructor
}
// if there is a no-arg constructor, pick it. Even though it is weird, easily happens
// with kotlin data classes.
val noArg = goodConstructors.firstOrNull { it.params.isEmpty() }
if (noArg != null) {
context.logger.w(
Warning.DEFAULT_CONSTRUCTOR, element,
ProcessorErrors.TOO_MANY_POJO_CONSTRUCTORS_CHOOSING_NO_ARG
)
return noArg
}
goodConstructors.forEach {
context.logger.e(it.element, ProcessorErrors.TOO_MANY_POJO_CONSTRUCTORS)
}
return null
}
else -> return goodConstructors.first()
}
}
private fun processEmbeddedField(
declaredType: XType,
variableElement: XFieldElement
): EmbeddedField? {
val asMemberType = variableElement.asMemberOf(declaredType)
val asTypeElement = asMemberType.typeElement
if (asTypeElement == null) {
context.logger.e(
variableElement,
ProcessorErrors.EMBEDDED_TYPES_MUST_BE_A_CLASS_OR_INTERFACE
)
return null
}
if (detectReferenceRecursion(asTypeElement)) {
return null
}
val fieldPrefix = variableElement.getAnnotation(com.zeoflow.depot.Embedded::class)?.value?.prefix ?: ""
val inheritedPrefix = parent?.prefix ?: ""
val embeddedField = Field(
variableElement,
variableElement.name,
type = asMemberType,
affinity = null,
parent = parent
)
val subParent = EmbeddedField(
field = embeddedField,
prefix = inheritedPrefix + fieldPrefix,
parent = parent
)
subParent.pojo = createFor(
context = context.fork(variableElement),
element = asTypeElement,
bindingScope = bindingScope,
parent = subParent,
referenceStack = referenceStack
).process()
return subParent
}
private fun processRelationField(
myFields: List,
container: XType?,
relationElement: XFieldElement
): com.zeoflow.depot.vo.Relation? {
val annotation = relationElement.getAnnotation(com.zeoflow.depot.Relation::class)!!
val parentField = myFields.firstOrNull {
it.columnName == annotation.value.parentColumn
}
if (parentField == null) {
context.logger.e(
relationElement,
ProcessorErrors.relationCannotFindParentEntityField(
entityName = element.qualifiedName,
columnName = annotation.value.parentColumn,
availableColumns = myFields.map { it.columnName }
)
)
return null
}
// parse it as an entity.
val asMember = relationElement.asMemberOf(container!!)
if (asMember.isError()) {
context.logger.e(relationElement, ProcessorErrors.CANNOT_FIND_TYPE)
return null
}
val asType = if (asMember.isCollection()) {
asMember.typeArguments.first().extendsBoundOrSelf()
} else {
asMember
}
val typeElement = asType.typeElement
if (typeElement == null) {
context.logger.e(
relationElement,
ProcessorErrors.RELATION_TYPE_MUST_BE_A_CLASS_OR_INTERFACE
)
return null
}
if (asType.isError()) {
context.logger.e(typeElement, ProcessorErrors.CANNOT_FIND_TYPE)
return null
}
val entityClassInput = annotation.getAsType("entity")
// do we need to decide on the entity?
val inferEntity = (entityClassInput == null || entityClassInput.isTypeOf(Any::class))
val entityElement = if (inferEntity) {
typeElement
} else {
entityClassInput!!.typeElement
}
if (entityElement == null) {
// this should not happen as we check for declared above but for compile time
// null safety, it is still good to have this additional check here.
context.logger.e(
typeElement,
ProcessorErrors.RELATION_TYPE_MUST_BE_A_CLASS_OR_INTERFACE
)
return null
}
if (detectReferenceRecursion(entityElement)) {
return null
}
val entity = EntityOrViewProcessor(context, entityElement, referenceStack).process()
// now find the field in the entity.
val entityField = entity.findFieldByColumnName(annotation.value.entityColumn)
if (entityField == null) {
context.logger.e(
relationElement,
ProcessorErrors.relationCannotFindEntityField(
entityName = entity.typeName.toString(),
columnName = annotation.value.entityColumn,
availableColumns = entity.columnNames
)
)
return null
}
// do we have a join entity?
val junctionAnnotation = annotation.getAsAnnotationBox("associateBy")
val junctionClassInput = junctionAnnotation.getAsType("value")
val junctionElement: XTypeElement? = if (junctionClassInput != null &&
!junctionClassInput.isTypeOf(Any::class)
) {
junctionClassInput.typeElement.also {
if (it == null) {
context.logger.e(
relationElement,
ProcessorErrors.NOT_ENTITY_OR_VIEW
)
}
}
} else {
null
}
val junction = junctionElement?.let {
val entityOrView = EntityOrViewProcessor(context, it, referenceStack).process()
fun findAndValidateJunctionColumn(
columnName: String,
onMissingField: () -> Unit
): Field? {
val field = entityOrView.findFieldByColumnName(columnName)
if (field == null) {
onMissingField()
return null
}
if (entityOrView is Entity) {
// warn about not having indices in the junction columns, only considering
// 1st column in composite primary key and indices, since order matters.
val coveredColumns = entityOrView.primaryKey.fields.columnNames.first() +
entityOrView.indices.map { it.columnNames.first() }
if (!coveredColumns.contains(field.columnName)) {
context.logger.w(
Warning.MISSING_INDEX_ON_JUNCTION, field.element,
ProcessorErrors.junctionColumnWithoutIndex(
entityName = entityOrView.typeName.toString(),
columnName = columnName
)
)
}
}
return field
}
val junctionParentColumn = if (junctionAnnotation.value.parentColumn.isNotEmpty()) {
junctionAnnotation.value.parentColumn
} else {
parentField.columnName
}
val junctionParentField = findAndValidateJunctionColumn(
columnName = junctionParentColumn,
onMissingField = {
context.logger.e(
junctionElement,
ProcessorErrors.relationCannotFindJunctionParentField(
entityName = entityOrView.typeName.toString(),
columnName = junctionParentColumn,
availableColumns = entityOrView.columnNames
)
)
}
)
val junctionEntityColumn = if (junctionAnnotation.value.entityColumn.isNotEmpty()) {
junctionAnnotation.value.entityColumn
} else {
entityField.columnName
}
val junctionEntityField = findAndValidateJunctionColumn(
columnName = junctionEntityColumn,
onMissingField = {
context.logger.e(
junctionElement,
ProcessorErrors.relationCannotFindJunctionEntityField(
entityName = entityOrView.typeName.toString(),
columnName = junctionEntityColumn,
availableColumns = entityOrView.columnNames
)
)
}
)
if (junctionParentField == null || junctionEntityField == null) {
return null
}
com.zeoflow.depot.vo.Junction(
entity = entityOrView,
parentField = junctionParentField,
entityField = junctionEntityField
)
}
val field = Field(
element = relationElement,
name = relationElement.name,
type = relationElement.asMemberOf(container),
affinity = null,
parent = parent
)
val projection = if (annotation.value.projection.isEmpty()) {
// we need to infer the projection from inputs.
createRelationshipProjection(inferEntity, asType, entity, entityField, typeElement)
} else {
// make sure projection makes sense
validateRelationshipProjection(annotation.value.projection, entity, relationElement)
annotation.value.projection.asList()
}
// if types don't match, row adapter prints a warning
return com.zeoflow.depot.vo.Relation(
entity = entity,
pojoType = asType,
field = field,
parentField = parentField,
entityField = entityField,
junction = junction,
projection = projection
)
}
private fun validateRelationshipProjection(
projectionInput: Array,
entity: EntityOrView,
relationElement: XVariableElement
) {
val missingColumns = projectionInput.toList() - entity.columnNames
if (missingColumns.isNotEmpty()) {
context.logger.e(
relationElement,
ProcessorErrors.relationBadProject(
entity.typeName.toString(),
missingColumns, entity.columnNames
)
)
}
}
/**
* Create the projection column list based on the relationship args.
*
* if entity field in the annotation is not specified, it is the method return type
* if it is specified in the annotation:
* still check the method return type, if the same, use it
* if not, check to see if we can find a column Adapter, if so use the childField
* last resort, try to parse it as a pojo to infer it.
*/
private fun createRelationshipProjection(
inferEntity: Boolean,
typeArg: XType,
entity: EntityOrView,
entityField: Field,
typeArgElement: XTypeElement
): List {
return if (inferEntity || typeArg.typeName == entity.typeName) {
entity.columnNames
} else {
val columnAdapter = context.typeAdapterStore.findCursorValueReader(typeArg, null)
if (columnAdapter != null) {
// nice, there is a column adapter for this, assume single column response
listOf(entityField.name)
} else {
// last resort, it needs to be a pojo
val pojo = createFor(
context = context,
element = typeArgElement,
bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
parent = parent,
referenceStack = referenceStack
).process()
pojo.columnNames
}
}
}
private fun detectReferenceRecursion(typeElement: XTypeElement): Boolean {
if (referenceStack.contains(typeElement.qualifiedName)) {
context.logger.e(
typeElement,
ProcessorErrors
.RECURSIVE_REFERENCE_DETECTED
.format(computeReferenceRecursionString(typeElement))
)
return true
}
return false
}
private fun computeReferenceRecursionString(typeElement: XTypeElement): String {
val recursiveTailTypeName = typeElement.qualifiedName
val referenceRecursionList = mutableListOf()
with(referenceRecursionList) {
add(recursiveTailTypeName)
addAll(referenceStack.toList().takeLastWhile { it != recursiveTailTypeName })
add(recursiveTailTypeName)
}
return referenceRecursionList.joinToString(" -> ")
}
private fun assignGetters(fields: List, getterCandidates: List) {
fields.forEach { field ->
assignGetter(field, getterCandidates)
}
}
private fun assignGetter(field: Field, getterCandidates: List) {
val success = chooseAssignment(
field = field,
candidates = getterCandidates,
nameVariations = field.getterNameWithVariations,
getType = { method ->
method.resolvedType.returnType
},
assignFromField = {
field.getter = FieldGetter(
name = field.name,
type = field.type,
callType = CallType.FIELD
)
},
assignFromMethod = { match ->
field.getter = FieldGetter(
name = match.name,
type = match.resolvedType.returnType,
callType = CallType.METHOD
)
},
reportAmbiguity = { matching ->
context.logger.e(
field.element,
ProcessorErrors.tooManyMatchingGetters(field, matching)
)
}
)
context.checker.check(
success || bindingScope == FieldProcessor.BindingScope.READ_FROM_CURSOR,
field.element, CANNOT_FIND_GETTER_FOR_FIELD
)
if (success && !field.getter.type.isSameType(field.type)) {
// getter's parameter type is not exactly the same as the field type.
// put a warning and update the value statement binder.
context.logger.w(
warning = Warning.MISMATCHED_GETTER_TYPE,
element = field.element,
msg = ProcessorErrors.mismatchedGetter(
fieldName = field.name,
ownerType = element.type.typeName,
getterType = field.getter.type.typeName,
fieldType = field.typeName
)
)
field.statementBinder = context.typeAdapterStore.findStatementValueBinder(
input = field.getter.type,
affinity = field.affinity
)
}
}
private fun assignSetters(
fields: List,
setterCandidates: List,
constructor: Constructor?
) {
fields.forEach { field ->
assignSetter(field, setterCandidates, constructor)
}
}
private fun assignSetter(
field: Field,
setterCandidates: List,
constructor: Constructor?
) {
if (constructor != null && constructor.hasField(field)) {
field.setter = FieldSetter(
name = field.name,
type = field.type,
callType = CallType.CONSTRUCTOR
)
return
}
val success = chooseAssignment(
field = field,
candidates = setterCandidates,
nameVariations = field.setterNameWithVariations,
getType = { method ->
method.resolvedType.parameterTypes.first()
},
assignFromField = {
field.setter = FieldSetter(
name = field.name,
type = field.type,
callType = CallType.FIELD
)
},
assignFromMethod = { match ->
val paramType = match.resolvedType.parameterTypes.first()
field.setter = FieldSetter(
name = match.name,
type = paramType,
callType = CallType.METHOD
)
},
reportAmbiguity = { matching ->
context.logger.e(
field.element,
ProcessorErrors.tooManyMatchingSetter(field, matching)
)
}
)
context.checker.check(
success || bindingScope == FieldProcessor.BindingScope.BIND_TO_STMT,
field.element, CANNOT_FIND_SETTER_FOR_FIELD
)
if (success && !field.setter.type.isSameType(field.type)) {
// setter's parameter type is not exactly the same as the field type.
// put a warning and update the value reader adapter.
context.logger.w(
warning = Warning.MISMATCHED_SETTER_TYPE,
element = field.element,
msg = ProcessorErrors.mismatchedSetter(
fieldName = field.name,
ownerType = element.type.typeName,
setterType = field.setter.type.typeName,
fieldType = field.typeName
)
)
field.cursorValueReader = context.typeAdapterStore.findCursorValueReader(
output = field.setter.type,
affinity = field.affinity
)
}
}
/**
* 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,
nameVariations: List,
getType: (PojoMethod) -> XType,
assignFromField: () -> Unit,
assignFromMethod: (PojoMethod) -> Unit,
reportAmbiguity: (List) -> Unit
): Boolean {
if (field.element.isPublic()) {
assignFromField()
return true
}
val matching = candidates
.filter {
// b/69164099
field.type.isAssignableFromWithoutVariance(getType(it)) &&
(
field.nameWithVariations.contains(it.name) ||
nameVariations.contains(it.name)
)
}
.groupBy {
it.element.isPublic()
}
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.isPrivate()
}
// first try public ones, then try non-public
val match = verifyAndChooseOneFrom(matching[true], reportAmbiguity)
?: verifyAndChooseOneFrom(matching[false], reportAmbiguity)
if (match == null) {
assignFromField()
return false
} else {
assignFromMethod(match)
return true
}
}
private fun verifyAndChooseOneFrom(
candidates: List?,
reportAmbiguity: (List) -> Unit
): PojoMethod? {
if (candidates == null) {
return null
}
if (candidates.size > 1) {
reportAmbiguity(candidates.map { it.name })
}
return candidates.first()
}
interface Delegate {
fun onPreProcess(element: XTypeElement)
/**
* Constructors are XExecutableElement rather than XConstrcutorElement to account for
* factory methods.
*/
fun findConstructors(element: XTypeElement): List
fun createPojo(
element: XTypeElement,
declaredType: XType,
fields: List,
embeddedFields: List,
relations: List,
constructor: Constructor?
): Pojo
}
private class DefaultDelegate(private val context: Context) : Delegate {
override fun onPreProcess(element: XTypeElement) {
// Check that certain Depot annotations with @Target(METHOD) are not used in the POJO
// since it is not annotated with AutoValue.
element.getAllMethods()
.filter { it.hasAnyOf(*TARGET_METHOD_ANNOTATIONS) }
.forEach { method ->
val annotationName = TARGET_METHOD_ANNOTATIONS
.first { method.hasAnnotation(it) }
.java.simpleName
context.logger.e(
method,
ProcessorErrors.invalidAnnotationTarget(annotationName, method.kindName())
)
}
}
override fun findConstructors(element: XTypeElement) = element.getConstructors().filterNot {
it.hasAnnotation(com.zeoflow.depot.Ignore::class) || it.isPrivate()
}
override fun createPojo(
element: XTypeElement,
declaredType: XType,
fields: List,
embeddedFields: List,
relations: List,
constructor: Constructor?
): Pojo {
return Pojo(
element = element,
type = declaredType,
fields = fields,
embeddedFields = embeddedFields,
relations = relations,
constructor = constructor
)
}
}
private data class FailedConstructor(
val method: XExecutableElement,
val params: List,
val matches: List
) {
fun log(): String {
val logPerParam = params.withIndex().joinToString(", ") {
"param:${it.value} -> matched field:" + (matches[it.index]?.log() ?: "unmatched")
}
return "$method -> [$logPerParam]"
}
}
}