From 7d90528a6679719d8db5dc639572850a7314675c Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 14 Jul 2024 12:27:09 +0900 Subject: [PATCH] Optimize the search process for creators Changed to implement findDefaultCreator. Fixes #805. --- .../KotlinNamesAnnotationIntrospector.kt | 75 +++++++------------ 1 file changed, 28 insertions(+), 47 deletions(-) diff --git a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt index 665f57b5..789894b6 100644 --- a/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt +++ b/src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt @@ -5,11 +5,13 @@ import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.databind.JavaType import com.fasterxml.jackson.databind.cfg.MapperConfig import com.fasterxml.jackson.databind.introspect.Annotated +import com.fasterxml.jackson.databind.introspect.AnnotatedClass import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor import com.fasterxml.jackson.databind.introspect.AnnotatedMember import com.fasterxml.jackson.databind.introspect.AnnotatedMethod import com.fasterxml.jackson.databind.introspect.AnnotatedParameter import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector +import com.fasterxml.jackson.databind.introspect.PotentialCreator import java.util.Locale import kotlin.reflect.KClass import kotlin.reflect.KFunction @@ -18,6 +20,7 @@ import kotlin.reflect.full.declaredFunctions import kotlin.reflect.full.hasAnnotation import kotlin.reflect.full.memberProperties import kotlin.reflect.full.primaryConstructor +import kotlin.reflect.jvm.javaConstructor import kotlin.reflect.jvm.javaGetter import kotlin.reflect.jvm.javaType @@ -84,62 +87,40 @@ internal class KotlinNamesAnnotationIntrospector( } } ?: baseType - private fun hasCreatorAnnotation(member: AnnotatedConstructor): Boolean { - // don't add a JsonCreator to any constructor if one is declared already - - val kClass = member.declaringClass.kotlin - val kConstructor = cache.kotlinFromJava(member.annotated) ?: return false - - // TODO: should we do this check or not? It could cause failures if we miss another way a property could be set - // val requiredProperties = kClass.declaredMemberProperties.filter {!it.returnType.isMarkedNullable }.map { it.name }.toSet() - // val areAllRequiredParametersInConstructor = kConstructor.parameters.all { requiredProperties.contains(it.name) } + override fun findDefaultCreator( + config: MapperConfig<*>, + valueClass: AnnotatedClass, + declaredConstructors: List, + declaredFactories: List + ): PotentialCreator? { + val kClass = valueClass.creatableKotlinClass() ?: return null val propertyNames = kClass.memberProperties.map { it.name }.toSet() - return when { - kConstructor.isPossibleSingleString(propertyNames) -> false - kConstructor.parameters.any { it.name == null } -> false - !kClass.isPrimaryConstructor(kConstructor) -> false - else -> { - val anyConstructorHasJsonCreator = kClass.constructors - .filterOutSingleStringCallables(propertyNames) - .any { it.hasAnnotation() } - - val anyCompanionMethodIsJsonCreator = member.type.rawClass.kotlin.companionObject?.declaredFunctions - ?.filterOutSingleStringCallables(propertyNames) - ?.any { it.hasAnnotation() && it.hasAnnotation() } - ?: false - - !(anyConstructorHasJsonCreator || anyCompanionMethodIsJsonCreator) - } + val defaultCreator = kClass.let { _ -> + // By default, the primary constructor or the only publicly available constructor may be used + val ctor = kClass.primaryConstructor ?: kClass.constructors.takeIf { it.size == 1 }?.single() + ctor?.takeIf { it.isPossibleCreator(propertyNames) } } - } - - // TODO: possible work around for JsonValue class that requires the class constructor to have the JsonCreator(DELEGATED) set? - // since we infer the creator at times for these methods, the wrong mode could be implied. - override fun findCreatorAnnotation(config: MapperConfig<*>, ann: Annotated): JsonCreator.Mode? { - if (ann !is AnnotatedConstructor || !ann.isKotlinConstructorWithParameters()) return null + ?.javaConstructor + ?: return null - return JsonCreator.Mode.DEFAULT.takeIf { - cache.checkConstructorIsCreatorAnnotated(ann) { hasCreatorAnnotation(it) } - } + return declaredConstructors.find { it.creator().annotated == defaultCreator } } private fun findKotlinParameterName(param: AnnotatedParameter): String? = cache.findKotlinParameter(param)?.name } -// if has parameters, is a Kotlin class, and the parameters all have parameter annotations, then pretend we have a JsonCreator -private fun AnnotatedConstructor.isKotlinConstructorWithParameters(): Boolean = - parameterCount > 0 && declaringClass.isKotlinClass() && !declaringClass.isEnum - -private fun KFunction<*>.isPossibleSingleString(propertyNames: Set): Boolean = parameters.size == 1 && - parameters[0].name !in propertyNames && - parameters[0].type.javaType == String::class.java && - !parameters[0].hasAnnotation() +// If it is not a Kotlin class or an Enum, Creator is not used +private fun AnnotatedClass.creatableKotlinClass(): KClass<*>? = annotated + .takeIf { it.isKotlinClass() && !it.isEnum } + ?.kotlin -private fun Collection>.filterOutSingleStringCallables(propertyNames: Set): Collection> = - this.filter { !it.isPossibleSingleString(propertyNames) } +private fun KFunction<*>.isPossibleCreator(propertyNames: Set): Boolean = 0 < parameters.size + && !isPossibleSingleString(propertyNames) + && parameters.none { it.name == null } -private fun KClass<*>.isPrimaryConstructor(kConstructor: KFunction<*>) = this.primaryConstructor.let { - it == kConstructor || (it == null && this.constructors.size == 1) -} +private fun KFunction<*>.isPossibleSingleString(propertyNames: Set): Boolean = parameters.size == 1 && + parameters[0].name !in propertyNames && + parameters[0].type.javaType == String::class.java && + !parameters[0].hasAnnotation()