Skip to content

Commit c293598

Browse files
committed
Merge remote-tracking branch 'origin/2.13' into github-530/fix
2 parents c0ab038 + c49144b commit c293598

File tree

5 files changed

+161
-66
lines changed

5 files changed

+161
-66
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.fasterxml.jackson.module.kotlin
2+
3+
import kotlin.reflect.KFunction
4+
import kotlin.reflect.jvm.isAccessible
5+
6+
internal class ConstructorValueCreator<T>(override val callable: KFunction<T>) : ValueCreator<T>() {
7+
override val accessible: Boolean = callable.isAccessible
8+
9+
init {
10+
callable.isAccessible = true
11+
}
12+
}

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

Lines changed: 25 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,15 @@ package com.fasterxml.jackson.module.kotlin
33
import com.fasterxml.jackson.databind.BeanDescription
44
import com.fasterxml.jackson.databind.DeserializationConfig
55
import com.fasterxml.jackson.databind.DeserializationContext
6-
import com.fasterxml.jackson.databind.MapperFeature
76
import com.fasterxml.jackson.databind.deser.SettableBeanProperty
87
import com.fasterxml.jackson.databind.deser.ValueInstantiator
98
import com.fasterxml.jackson.databind.deser.ValueInstantiators
109
import com.fasterxml.jackson.databind.deser.impl.NullsAsEmptyProvider
1110
import com.fasterxml.jackson.databind.deser.impl.PropertyValueBuffer
1211
import com.fasterxml.jackson.databind.deser.std.StdValueInstantiator
13-
import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor
14-
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod
15-
import java.lang.reflect.Constructor
16-
import java.lang.reflect.Method
1712
import java.lang.reflect.TypeVariable
1813
import kotlin.reflect.KParameter
1914
import kotlin.reflect.KType
20-
import kotlin.reflect.full.extensionReceiverParameter
21-
import kotlin.reflect.full.instanceParameter
22-
import kotlin.reflect.full.valueParameters
23-
import kotlin.reflect.jvm.isAccessible
2415
import kotlin.reflect.jvm.javaType
2516

2617
internal class KotlinValueInstantiator(
@@ -31,62 +22,34 @@ internal class KotlinValueInstantiator(
3122
private val nullIsSameAsDefault: Boolean,
3223
private val strictNullChecks: Boolean
3324
) : StdValueInstantiator(src) {
34-
@Suppress("UNCHECKED_CAST")
3525
override fun createFromObjectWith(
3626
ctxt: DeserializationContext,
3727
props: Array<out SettableBeanProperty>,
3828
buffer: PropertyValueBuffer
3929
): Any? {
40-
val callable = when (_withArgsCreator) {
41-
is AnnotatedConstructor -> cache.kotlinFromJava(_withArgsCreator.annotated as Constructor<Any>)
42-
is AnnotatedMethod -> cache.kotlinFromJava(_withArgsCreator.annotated as Method)
43-
else -> throw IllegalStateException("Expected a constructor or method to create a Kotlin object, instead found ${_withArgsCreator.annotated.javaClass.name}")
44-
} ?: return super.createFromObjectWith(
45-
ctxt,
46-
props,
47-
buffer
48-
) // we cannot reflect this method so do the default Java-ish behavior
49-
50-
if (callable.extensionReceiverParameter != null) {
51-
// we shouldn't have an instance or receiver parameter and if we do, just go with default Java-ish behavior
52-
return super.createFromObjectWith(ctxt, props, buffer)
53-
}
54-
55-
val propCount = props.size + if (callable.instanceParameter != null) 1 else 0
56-
57-
var numCallableParameters = 0
58-
val callableParameters = arrayOfNulls<KParameter>(propCount)
59-
val jsonParamValueList = arrayOfNulls<Any>(propCount)
60-
61-
if (callable.instanceParameter != null) {
62-
val possibleCompanion = callable.instanceParameter!!.type.erasedType().kotlin
63-
64-
if (!possibleCompanion.isCompanion) {
65-
// abort, we have some unknown case here
66-
return super.createFromObjectWith(ctxt, props, buffer)
67-
}
68-
69-
// TODO: cache this lookup since the exception throwing/catching can be expensive
70-
jsonParamValueList[numCallableParameters] = try {
71-
possibleCompanion.objectInstance
72-
} catch (ex: IllegalAccessException) {
73-
// fallback for when an odd access exception happens through Kotlin reflection
74-
val companionField = possibleCompanion.java.enclosingClass.fields.firstOrNull { it.type.kotlin.isCompanion }
75-
?: throw ex
76-
val accessible = companionField.isAccessible
77-
if ((!accessible && ctxt.config.isEnabled(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)) ||
78-
(accessible && ctxt.config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS))
79-
) {
80-
companionField.isAccessible = true
81-
}
82-
companionField.get(null) ?: throw ex
83-
}
84-
85-
callableParameters[numCallableParameters] = callable.instanceParameter
86-
numCallableParameters++
30+
val valueCreator: ValueCreator<*> = cache.valueCreatorFromJava(_withArgsCreator)
31+
?: return super.createFromObjectWith(ctxt, props, buffer)
32+
33+
val propCount: Int
34+
var numCallableParameters: Int
35+
val callableParameters: Array<KParameter?>
36+
val jsonParamValueList: Array<Any?>
37+
38+
if (valueCreator is MethodValueCreator) {
39+
propCount = props.size + 1
40+
numCallableParameters = 1
41+
callableParameters = arrayOfNulls<KParameter>(propCount)
42+
.apply { this[0] = valueCreator.instanceParameter }
43+
jsonParamValueList = arrayOfNulls<Any>(propCount)
44+
.apply { this[0] = valueCreator.companionObjectInstance }
45+
} else {
46+
propCount = props.size
47+
numCallableParameters = 0
48+
callableParameters = arrayOfNulls(propCount)
49+
jsonParamValueList = arrayOfNulls(propCount)
8750
}
8851

89-
callable.valueParameters.forEachIndexed { idx, paramDef ->
52+
valueCreator.valueParameters.forEachIndexed { idx, paramDef ->
9053
val jsonProp = props[idx]
9154
val isMissing = !buffer.hasParameter(jsonProp)
9255

@@ -157,23 +120,19 @@ internal class KotlinValueInstantiator(
157120
numCallableParameters++
158121
}
159122

160-
return if (numCallableParameters == jsonParamValueList.size && callable.instanceParameter == null) {
123+
return if (numCallableParameters == jsonParamValueList.size && valueCreator is ConstructorValueCreator) {
161124
// we didn't do anything special with default parameters, do a normal call
162125
super.createFromObjectWith(ctxt, jsonParamValueList)
163126
} else {
164-
val accessible = callable.isAccessible
165-
if ((!accessible && ctxt.config.isEnabled(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)) ||
166-
(accessible && ctxt.config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS))
167-
) {
168-
callable.isAccessible = true
169-
}
127+
valueCreator.checkAccessibility(ctxt)
128+
170129
val callableParametersByName = linkedMapOf<KParameter, Any?>()
171130
callableParameters.mapIndexed { idx, paramDef ->
172131
if (paramDef != null) {
173132
callableParametersByName[paramDef] = jsonParamValueList[idx]
174133
}
175134
}
176-
callable.callBy(callableParametersByName)
135+
valueCreator.callBy(callableParametersByName)
177136
}
178137

179138
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.fasterxml.jackson.module.kotlin
2+
3+
import kotlin.reflect.KFunction
4+
import kotlin.reflect.KParameter
5+
import kotlin.reflect.full.extensionReceiverParameter
6+
import kotlin.reflect.full.instanceParameter
7+
import kotlin.reflect.jvm.isAccessible
8+
9+
internal class MethodValueCreator<T> private constructor(
10+
override val callable: KFunction<T>,
11+
override val accessible: Boolean,
12+
val companionObjectInstance: Any
13+
) : ValueCreator<T>() {
14+
val instanceParameter: KParameter = callable.instanceParameter!!
15+
16+
companion object {
17+
fun <T> of(callable: KFunction<T>): MethodValueCreator<T>? {
18+
// we shouldn't have an instance or receiver parameter and if we do, just go with default Java-ish behavior
19+
if (callable.extensionReceiverParameter != null) return null
20+
21+
val possibleCompanion = callable.instanceParameter!!.type.erasedType().kotlin
22+
23+
// abort, we have some unknown case here
24+
if (!possibleCompanion.isCompanion) return null
25+
26+
val (companionObjectInstance: Any, accessible: Boolean) = try {
27+
// throws ex
28+
val instance = possibleCompanion.objectInstance!!
29+
// If an instance of the companion object can be obtained, accessibility depends on the KFunction
30+
instance to callable.isAccessible
31+
} catch (ex: IllegalAccessException) {
32+
// fallback for when an odd access exception happens through Kotlin reflection
33+
possibleCompanion.java.enclosingClass.fields
34+
.firstOrNull { it.type.kotlin.isCompanion }
35+
?.let {
36+
it.isAccessible = true
37+
38+
// If the instance of the companion object cannot be obtained, accessibility will always be false
39+
it.get(null) to false
40+
} ?: throw ex
41+
}
42+
43+
return MethodValueCreator(callable, accessible, companionObjectInstance)
44+
}
45+
}
46+
}

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.fasterxml.jackson.module.kotlin
33
import com.fasterxml.jackson.databind.introspect.AnnotatedConstructor
44
import com.fasterxml.jackson.databind.introspect.AnnotatedMember
55
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod
6+
import com.fasterxml.jackson.databind.introspect.AnnotatedWithParams
67
import com.fasterxml.jackson.databind.util.LRUMap
78
import java.lang.reflect.Constructor
89
import java.lang.reflect.Method
@@ -35,6 +36,8 @@ internal class ReflectionCache(reflectionCacheSize: Int) {
3536
private val javaClassToKotlin = LRUMap<Class<Any>, KClass<Any>>(reflectionCacheSize, reflectionCacheSize)
3637
private val javaConstructorToKotlin = LRUMap<Constructor<Any>, KFunction<Any>>(reflectionCacheSize, reflectionCacheSize)
3738
private val javaMethodToKotlin = LRUMap<Method, KFunction<*>>(reflectionCacheSize, reflectionCacheSize)
39+
private val javaConstructorToValueCreator = LRUMap<Constructor<Any>, ConstructorValueCreator<*>>(reflectionCacheSize, reflectionCacheSize)
40+
private val javaMethodToValueCreator = LRUMap<Method, MethodValueCreator<*>>(reflectionCacheSize, reflectionCacheSize)
3841
private val javaConstructorIsCreatorAnnotated = LRUMap<AnnotatedConstructor, Boolean>(reflectionCacheSize, reflectionCacheSize)
3942
private val javaMemberIsRequired = LRUMap<AnnotatedMember, BooleanTriState?>(reflectionCacheSize, reflectionCacheSize)
4043
private val kotlinGeneratedMethod = LRUMap<AnnotatedMethod, Boolean>(reflectionCacheSize, reflectionCacheSize)
@@ -49,6 +52,35 @@ internal class ReflectionCache(reflectionCacheSize: Int) {
4952
fun kotlinFromJava(key: Method): KFunction<*>? = javaMethodToKotlin.get(key)
5053
?: key.kotlinFunction?.let { javaMethodToKotlin.putIfAbsent(key, it) ?: it }
5154

55+
/**
56+
* return null if...
57+
* - can't get kotlinFunction
58+
* - contains extensionReceiverParameter
59+
* - instance parameter is not companion object or can't get
60+
*/
61+
@Suppress("UNCHECKED_CAST")
62+
fun valueCreatorFromJava(_withArgsCreator: AnnotatedWithParams): ValueCreator<*>? = when (_withArgsCreator) {
63+
is AnnotatedConstructor -> {
64+
val constructor = _withArgsCreator.annotated as Constructor<Any>
65+
66+
javaConstructorToValueCreator.get(constructor)
67+
?: kotlinFromJava(constructor)?.let {
68+
val value = ConstructorValueCreator(it)
69+
javaConstructorToValueCreator.putIfAbsent(constructor, value) ?: value
70+
}
71+
}
72+
is AnnotatedMethod -> {
73+
val method = _withArgsCreator.annotated as Method
74+
75+
javaMethodToValueCreator.get(method)
76+
?: kotlinFromJava(method)?.let {
77+
val value = MethodValueCreator.of(it)
78+
javaMethodToValueCreator.putIfAbsent(method, value) ?: value
79+
}
80+
}
81+
else -> throw IllegalStateException("Expected a constructor or method to create a Kotlin object, instead found ${_withArgsCreator.annotated.javaClass.name}")
82+
} // we cannot reflect this method so do the default Java-ish behavior
83+
5284
fun checkConstructorIsCreatorAnnotated(key: AnnotatedConstructor, calc: (AnnotatedConstructor) -> Boolean): Boolean = javaConstructorIsCreatorAnnotated.get(key)
5385
?: calc(key).let { javaConstructorIsCreatorAnnotated.putIfAbsent(key, it) ?: it }
5486

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.fasterxml.jackson.module.kotlin
2+
3+
import com.fasterxml.jackson.databind.DeserializationContext
4+
import com.fasterxml.jackson.databind.MapperFeature
5+
import kotlin.reflect.KFunction
6+
import kotlin.reflect.KParameter
7+
import kotlin.reflect.full.valueParameters
8+
9+
/**
10+
* A class that abstracts the creation of instances by calling KFunction.
11+
* @see KotlinValueInstantiator
12+
*/
13+
internal sealed class ValueCreator<T> {
14+
/**
15+
* Function to be call.
16+
*/
17+
protected abstract val callable: KFunction<T>
18+
19+
/**
20+
* Initial value for accessibility by reflection.
21+
*/
22+
protected abstract val accessible: Boolean
23+
24+
/**
25+
* ValueParameters of the KFunction to be called.
26+
*/
27+
val valueParameters: List<KParameter> by lazy { callable.valueParameters }
28+
29+
/**
30+
* Checking process to see if access from context is possible.
31+
* @throws IllegalAccessException
32+
*/
33+
fun checkAccessibility(ctxt: DeserializationContext) {
34+
if ((!accessible && ctxt.config.isEnabled(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)) ||
35+
(accessible && ctxt.config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS))) {
36+
return
37+
}
38+
39+
throw IllegalAccessException("Cannot access to function or companion object instance, target: $callable")
40+
}
41+
42+
/**
43+
* Function call with default values enabled.
44+
*/
45+
fun callBy(args: Map<KParameter, Any?>): T = callable.callBy(args)
46+
}

0 commit comments

Comments
 (0)