Skip to content

Commit 016a441

Browse files
authored
Merge pull request #818 from k163377/feat/creator
Optimize the search process for creators
2 parents e48cfb4 + 2955fac commit 016a441

File tree

5 files changed

+42
-51
lines changed

5 files changed

+42
-51
lines changed

pom.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,9 @@
273273
<exclude>
274274
com.fasterxml.jackson.module.kotlin.KotlinNamesAnnotationIntrospector#KotlinNamesAnnotationIntrospector(com.fasterxml.jackson.module.kotlin.ReflectionCache,java.util.Set,boolean)
275275
</exclude>
276+
<exclude>
277+
com.fasterxml.jackson.module.kotlin.ReflectionCache#checkConstructorIsCreatorAnnotated(com.fasterxml.jackson.databind.introspect.AnnotatedConstructor,kotlin.jvm.functions.Function1)
278+
</exclude>
276279

277280
</excludes>
278281
</parameter>

release-notes/CREDITS-2.x

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Contributors:
1818
# 2.18.0 (not yet released)
1919

2020
WrongWrong (@k163377)
21+
* #818: Optimize the search process for creators
2122
* #817: Fixed nullability of convertValue function argument
2223
* #782: Organize deprecated contents
2324
* #542: Remove meaningless checks and properties in KNAI

release-notes/VERSION-2.x

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ Co-maintainers:
1818

1919
2.18.0 (not yet released)
2020

21+
#818: The implementation of the search process for the `JsonCreator` (often the primary constructor)
22+
used by default for deserialization has been changed to `AnnotationIntrospector#findDefaultCreator`.
23+
This has improved first-time processing performance and memory usage.
24+
It also solves the problem of `findCreatorAnnotation` results by `AnnotationIntrospector` registered by the user
25+
being ignored depending on the order in which modules are registered.
2126
#817: The convertValue extension function now accepts null
2227
#803: Kotlin has been upgraded to 1.8.10.
2328
The reason 1.8.22 is not used is to avoid KT-65156.

src/main/kotlin/com/fasterxml/jackson/module/kotlin/KotlinNamesAnnotationIntrospector.kt

Lines changed: 32 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@ import com.fasterxml.jackson.annotation.JsonProperty
55
import com.fasterxml.jackson.databind.JavaType
66
import com.fasterxml.jackson.databind.cfg.MapperConfig
77
import com.fasterxml.jackson.databind.introspect.Annotated
8+
import com.fasterxml.jackson.databind.introspect.AnnotatedClass
89
import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor
910
import com.fasterxml.jackson.databind.introspect.AnnotatedMember
1011
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod
1112
import com.fasterxml.jackson.databind.introspect.AnnotatedParameter
1213
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector
14+
import com.fasterxml.jackson.databind.introspect.PotentialCreator
15+
import java.lang.reflect.Constructor
1316
import java.util.Locale
1417
import kotlin.reflect.KClass
1518
import kotlin.reflect.KFunction
@@ -18,6 +21,7 @@ import kotlin.reflect.full.declaredFunctions
1821
import kotlin.reflect.full.hasAnnotation
1922
import kotlin.reflect.full.memberProperties
2023
import kotlin.reflect.full.primaryConstructor
24+
import kotlin.reflect.jvm.javaConstructor
2125
import kotlin.reflect.jvm.javaGetter
2226
import kotlin.reflect.jvm.javaType
2327

@@ -84,62 +88,44 @@ internal class KotlinNamesAnnotationIntrospector(
8488
}
8589
} ?: baseType
8690

87-
private fun hasCreatorAnnotation(member: AnnotatedConstructor): Boolean {
88-
// don't add a JsonCreator to any constructor if one is declared already
89-
90-
val kClass = member.declaringClass.kotlin
91-
val kConstructor = cache.kotlinFromJava(member.annotated) ?: return false
92-
93-
// TODO: should we do this check or not? It could cause failures if we miss another way a property could be set
94-
// val requiredProperties = kClass.declaredMemberProperties.filter {!it.returnType.isMarkedNullable }.map { it.name }.toSet()
95-
// val areAllRequiredParametersInConstructor = kConstructor.parameters.all { requiredProperties.contains(it.name) }
91+
override fun findDefaultCreator(
92+
config: MapperConfig<*>,
93+
valueClass: AnnotatedClass,
94+
declaredConstructors: List<PotentialCreator>,
95+
declaredFactories: List<PotentialCreator>
96+
): PotentialCreator? {
97+
val kClass = valueClass.creatableKotlinClass() ?: return null
9698

9799
val propertyNames = kClass.memberProperties.map { it.name }.toSet()
98100

99-
return when {
100-
kConstructor.isPossibleSingleString(propertyNames) -> false
101-
kConstructor.parameters.any { it.name == null } -> false
102-
!kClass.isPrimaryConstructor(kConstructor) -> false
103-
else -> {
104-
val anyConstructorHasJsonCreator = kClass.constructors
105-
.filterOutSingleStringCallables(propertyNames)
106-
.any { it.hasAnnotation<JsonCreator>() }
107-
108-
val anyCompanionMethodIsJsonCreator = member.type.rawClass.kotlin.companionObject?.declaredFunctions
109-
?.filterOutSingleStringCallables(propertyNames)
110-
?.any { it.hasAnnotation<JsonCreator>() && it.hasAnnotation<JvmStatic>() }
111-
?: false
112-
113-
!(anyConstructorHasJsonCreator || anyCompanionMethodIsJsonCreator)
114-
}
101+
val defaultCreator = kClass.let { _ ->
102+
// By default, the primary constructor or the only publicly available constructor may be used
103+
val ctor = kClass.primaryConstructor ?: kClass.constructors.takeIf { it.size == 1 }?.single()
104+
ctor?.takeIf { it.isPossibleCreator(propertyNames) }
115105
}
116-
}
117-
118-
// TODO: possible work around for JsonValue class that requires the class constructor to have the JsonCreator(DELEGATED) set?
119-
// since we infer the creator at times for these methods, the wrong mode could be implied.
120-
override fun findCreatorAnnotation(config: MapperConfig<*>, ann: Annotated): JsonCreator.Mode? {
121-
if (ann !is AnnotatedConstructor || !ann.isKotlinConstructorWithParameters()) return null
106+
?: return null
122107

123-
return JsonCreator.Mode.DEFAULT.takeIf {
124-
cache.checkConstructorIsCreatorAnnotated(ann) { hasCreatorAnnotation(it) }
108+
return declaredConstructors.find {
109+
// To avoid problems with constructors that include `value class` as an argument,
110+
// convert to `KFunction` and compare
111+
cache.kotlinFromJava(it.creator().annotated as Constructor<*>) == defaultCreator
125112
}
126113
}
127114

128115
private fun findKotlinParameterName(param: AnnotatedParameter): String? = cache.findKotlinParameter(param)?.name
129116
}
130117

131-
// if has parameters, is a Kotlin class, and the parameters all have parameter annotations, then pretend we have a JsonCreator
132-
private fun AnnotatedConstructor.isKotlinConstructorWithParameters(): Boolean =
133-
parameterCount > 0 && declaringClass.isKotlinClass() && !declaringClass.isEnum
118+
// If it is not a Kotlin class or an Enum, Creator is not used
119+
private fun AnnotatedClass.creatableKotlinClass(): KClass<*>? = annotated
120+
.takeIf { it.isKotlinClass() && !it.isEnum }
121+
?.kotlin
134122

135-
private fun KFunction<*>.isPossibleSingleString(propertyNames: Set<String>): Boolean = parameters.size == 1 &&
136-
parameters[0].name !in propertyNames &&
137-
parameters[0].type.javaType == String::class.java &&
138-
!parameters[0].hasAnnotation<JsonProperty>()
123+
private fun KFunction<*>.isPossibleCreator(propertyNames: Set<String>): Boolean = 0 < parameters.size
124+
&& !isPossibleSingleString(propertyNames)
125+
&& parameters.none { it.name == null }
139126

140-
private fun Collection<KFunction<*>>.filterOutSingleStringCallables(propertyNames: Set<String>): Collection<KFunction<*>> =
141-
this.filter { !it.isPossibleSingleString(propertyNames) }
142-
143-
private fun KClass<*>.isPrimaryConstructor(kConstructor: KFunction<*>) = this.primaryConstructor.let {
144-
it == kConstructor || (it == null && this.constructors.size == 1)
145-
}
127+
private fun KFunction<*>.isPossibleSingleString(propertyNames: Set<String>): Boolean = parameters.singleOrNull()?.let {
128+
it.name !in propertyNames
129+
&& it.type.javaType == String::class.java
130+
&& !it.hasAnnotation<JsonProperty>()
131+
} == true

src/main/kotlin/com/fasterxml/jackson/module/kotlin/ReflectionCache.kt

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import kotlin.reflect.jvm.kotlinFunction
2222
internal class ReflectionCache(reflectionCacheSize: Int) : Serializable {
2323
companion object {
2424
// Increment is required when properties that use LRUMap are changed.
25-
private const val serialVersionUID = 2L
25+
private const val serialVersionUID = 3L
2626
}
2727

2828
sealed class BooleanTriState(val value: Boolean?) {
@@ -47,7 +47,6 @@ internal class ReflectionCache(reflectionCacheSize: Int) : Serializable {
4747

4848
private val javaExecutableToKotlin = LRUMap<Executable, KFunction<*>>(reflectionCacheSize, reflectionCacheSize)
4949
private val javaExecutableToValueCreator = LRUMap<Executable, ValueCreator<*>>(reflectionCacheSize, reflectionCacheSize)
50-
private val javaConstructorIsCreatorAnnotated = LRUMap<AnnotatedConstructor, Boolean>(reflectionCacheSize, reflectionCacheSize)
5150
private val javaMemberIsRequired = LRUMap<AnnotatedMember, BooleanTriState?>(reflectionCacheSize, reflectionCacheSize)
5251

5352
// Initial size is 0 because the value class is not always used
@@ -103,9 +102,6 @@ internal class ReflectionCache(reflectionCacheSize: Int) : Serializable {
103102
)
104103
} // we cannot reflect this method so do the default Java-ish behavior
105104

106-
fun checkConstructorIsCreatorAnnotated(key: AnnotatedConstructor, calc: (AnnotatedConstructor) -> Boolean): Boolean = javaConstructorIsCreatorAnnotated.get(key)
107-
?: calc(key).let { javaConstructorIsCreatorAnnotated.putIfAbsent(key, it) ?: it }
108-
109105
fun javaMemberIsRequired(key: AnnotatedMember, calc: (AnnotatedMember) -> Boolean?): Boolean? = javaMemberIsRequired.get(key)?.value
110106
?: calc(key).let { javaMemberIsRequired.putIfAbsent(key, BooleanTriState.fromBoolean(it))?.value ?: it }
111107

0 commit comments

Comments
 (0)