Skip to content

Commit

Permalink
Optimize the search process for creators
Browse files Browse the repository at this point in the history
Changed to implement findDefaultCreator.
Fixes FasterXML#805.
  • Loading branch information
k163377 committed Jul 14, 2024
1 parent e48cfb4 commit 7d90528
Showing 1 changed file with 28 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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<PotentialCreator>,
declaredFactories: List<PotentialCreator>
): 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<JsonCreator>() }

val anyCompanionMethodIsJsonCreator = member.type.rawClass.kotlin.companionObject?.declaredFunctions
?.filterOutSingleStringCallables(propertyNames)
?.any { it.hasAnnotation<JsonCreator>() && it.hasAnnotation<JvmStatic>() }
?: 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<String>): Boolean = parameters.size == 1 &&
parameters[0].name !in propertyNames &&
parameters[0].type.javaType == String::class.java &&
!parameters[0].hasAnnotation<JsonProperty>()
// 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<KFunction<*>>.filterOutSingleStringCallables(propertyNames: Set<String>): Collection<KFunction<*>> =
this.filter { !it.isPossibleSingleString(propertyNames) }
private fun KFunction<*>.isPossibleCreator(propertyNames: Set<String>): 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<String>): Boolean = parameters.size == 1 &&
parameters[0].name !in propertyNames &&
parameters[0].type.javaType == String::class.java &&
!parameters[0].hasAnnotation<JsonProperty>()

0 comments on commit 7d90528

Please sign in to comment.