Skip to content

Commit 8564565

Browse files
committed
Merge branch '2.13' into 2.14
2 parents 6023113 + 1c112c9 commit 8564565

File tree

9 files changed

+486
-86
lines changed

9 files changed

+486
-86
lines changed

pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,13 @@
208208
TODO Remove after release of 2.14 and update the oldVersion above to 2.14.
209209
-->
210210
<exclude>com.fasterxml.jackson.module.kotlin.ValueClassBoxSerializer</exclude>
211+
<exclude>com.fasterxml.jackson.module.kotlin.ValueClassSerializer$StaticJsonValue</exclude>
212+
<exclude>com.fasterxml.jackson.module.kotlin.ValueClassSerializer$Unbox</exclude>
213+
<exclude>com.fasterxml.jackson.module.kotlin.ValueClassStaticJsonValueSerializer</exclude>
214+
<exclude>com.fasterxml.jackson.module.kotlin.ValueClassUnboxSerializer.serialize(java.lang.Object, com.fasterxml.jackson.core.JsonGenerator, com.fasterxml.jackson.databind.SerializerProvider)</exclude>
215+
<exclude>com.fasterxml.jackson.databind.jsonschema.SchemaAware[com.fasterxml.jackson.databind.jsonschema.SchemaAware]</exclude>
216+
<exclude>com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitable[com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitable]:INTERFACE_REMOVED, java.io.Serializable[java.io.Serializable]</exclude>
217+
<exclude>com.fasterxml.jackson.module.kotlin.ValueClassUnboxSerializer</exclude>
211218
</excludes>
212219
</parameter>
213220
</configuration>
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/KotlinAnnotationIntrospector.kt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import com.fasterxml.jackson.databind.Module
77
import com.fasterxml.jackson.databind.cfg.MapperConfig
88
import com.fasterxml.jackson.databind.introspect.*
99
import com.fasterxml.jackson.databind.jsontype.NamedType
10+
import com.fasterxml.jackson.databind.ser.std.StdSerializer
1011
import java.lang.reflect.AccessibleObject
1112
import java.lang.reflect.Constructor
1213
import java.lang.reflect.Field
@@ -62,7 +63,7 @@ internal class KotlinAnnotationIntrospector(private val context: Module.SetupCon
6263
}
6364

6465
// Find a serializer to handle the case where the getter returns an unboxed value from the value class.
65-
override fun findSerializer(am: Annotated): ValueClassBoxSerializer<*>? = when (am) {
66+
override fun findSerializer(am: Annotated): StdSerializer<*>? = when (am) {
6667
is AnnotatedMethod -> {
6768
val getter = am.member.apply {
6869
// If the return value of the getter is a value class,
@@ -87,8 +88,10 @@ internal class KotlinAnnotationIntrospector(private val context: Module.SetupCon
8788
?.takeIf { it.isValue }
8889
?.java
8990
?.let { outerClazz ->
90-
@Suppress("UNCHECKED_CAST")
91-
ValueClassBoxSerializer(outerClazz, getter.returnType)
91+
val innerClazz = getter.returnType
92+
93+
ValueClassStaticJsonValueSerializer.createdOrNull(outerClazz, innerClazz)
94+
?: @Suppress("UNCHECKED_CAST") ValueClassBoxSerializer(outerClazz, innerClazz)
9295
}
9396
}
9497
// Ignore the case of AnnotatedField, because JvmField cannot be set in the field of value class.

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

Lines changed: 78 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.fasterxml.jackson.module.kotlin
22

3+
import com.fasterxml.jackson.annotation.JsonValue
34
import com.fasterxml.jackson.core.JsonGenerator
45
import com.fasterxml.jackson.databind.BeanDescription
56
import com.fasterxml.jackson.databind.JavaType
@@ -8,8 +9,9 @@ import com.fasterxml.jackson.databind.SerializationConfig
89
import com.fasterxml.jackson.databind.SerializerProvider
910
import com.fasterxml.jackson.databind.ser.Serializers
1011
import com.fasterxml.jackson.databind.ser.std.StdSerializer
12+
import java.lang.reflect.Method
13+
import java.lang.reflect.Modifier
1114
import java.math.BigInteger
12-
import kotlin.reflect.KClass
1315

1416
object SequenceSerializer : StdSerializer<Sequence<*>>(Sequence::class.java) {
1517
override fun serialize(value: Sequence<*>, gen: JsonGenerator, provider: SerializerProvider) {
@@ -43,16 +45,47 @@ object ULongSerializer : StdSerializer<ULong>(ULong::class.java) {
4345
}
4446
}
4547

46-
object ValueClassUnboxSerializer : StdSerializer<Any>(Any::class.java) {
47-
override fun serialize(value: Any, gen: JsonGenerator, provider: SerializerProvider) {
48-
val unboxed = value::class.java.getMethod("unbox-impl").invoke(value)
48+
// Class must be UnboxableValueClass.
49+
private fun Class<*>.getStaticJsonValueGetter(): Method? = this.declaredMethods
50+
.find { method -> Modifier.isStatic(method.modifiers) && method.annotations.any { it is JsonValue } }
4951

50-
if (unboxed == null) {
51-
provider.findNullValueSerializer(null).serialize(unboxed, gen, provider)
52-
return
52+
internal sealed class ValueClassSerializer<T : Any>(t: Class<T>) : StdSerializer<T>(t) {
53+
object Unbox : ValueClassSerializer<Any>(Any::class.java) {
54+
override fun serialize(value: Any, gen: JsonGenerator, provider: SerializerProvider) {
55+
val unboxed = value::class.java.getMethod("unbox-impl").invoke(value)
56+
57+
if (unboxed == null) {
58+
provider.findNullValueSerializer(null).serialize(unboxed, gen, provider)
59+
return
60+
}
61+
62+
provider.findValueSerializer(unboxed::class.java).serialize(unboxed, gen, provider)
63+
}
64+
}
65+
66+
class StaticJsonValue<T : Any>(
67+
t: Class<T>, private val staticJsonValueGetter: Method
68+
) : ValueClassSerializer<T>(t) {
69+
private val unboxMethod: Method = t.getMethod("unbox-impl")
70+
71+
override fun serialize(value: T, gen: JsonGenerator, provider: SerializerProvider) {
72+
val unboxed = unboxMethod.invoke(value)
73+
// As shown in the processing of the factory function, jsonValueGetter is always a static method.
74+
val jsonValue: Any? = staticJsonValueGetter.invoke(null, unboxed)
75+
jsonValue
76+
?.let { provider.findValueSerializer(it::class.java).serialize(it, gen, provider) }
77+
?: provider.findNullValueSerializer(null).serialize(null, gen, provider)
5378
}
79+
}
5480

55-
provider.findValueSerializer(unboxed::class.java).serialize(unboxed, gen, provider)
81+
companion object {
82+
// `t` must be UnboxableValueClass.
83+
// If create a function with a JsonValue in the value class,
84+
// it will be compiled as a static method (= cannot be processed properly by Jackson),
85+
// so use a ValueClassSerializer.StaticJsonValue to handle this.
86+
fun from(t: Class<*>): ValueClassSerializer<*> = t.getStaticJsonValueGetter()
87+
?.let { StaticJsonValue(t, it) }
88+
?: Unbox
5689
}
5790
}
5891

@@ -61,15 +94,19 @@ internal class KotlinSerializers : Serializers.Base() {
6194
config: SerializationConfig?,
6295
type: JavaType,
6396
beanDesc: BeanDescription?
64-
): JsonSerializer<*>? = when {
65-
Sequence::class.java.isAssignableFrom(type.rawClass) -> SequenceSerializer
66-
UByte::class.java.isAssignableFrom(type.rawClass) -> UByteSerializer
67-
UShort::class.java.isAssignableFrom(type.rawClass) -> UShortSerializer
68-
UInt::class.java.isAssignableFrom(type.rawClass) -> UIntSerializer
69-
ULong::class.java.isAssignableFrom(type.rawClass) -> ULongSerializer
70-
// The priority of Unboxing needs to be lowered so as not to break the serialization of Unsigned Integers.
71-
type.rawClass.isUnboxableValueClass() -> ValueClassUnboxSerializer
72-
else -> null
97+
): JsonSerializer<*>? {
98+
val rawClass = type.rawClass
99+
100+
return when {
101+
Sequence::class.java.isAssignableFrom(rawClass) -> SequenceSerializer
102+
UByte::class.java.isAssignableFrom(rawClass) -> UByteSerializer
103+
UShort::class.java.isAssignableFrom(rawClass) -> UShortSerializer
104+
UInt::class.java.isAssignableFrom(rawClass) -> UIntSerializer
105+
ULong::class.java.isAssignableFrom(rawClass) -> ULongSerializer
106+
// The priority of Unboxing needs to be lowered so as not to break the serialization of Unsigned Integers.
107+
rawClass.isUnboxableValueClass() -> ValueClassSerializer.from(rawClass)
108+
else -> null
109+
}
73110
}
74111
}
75112

@@ -89,3 +126,27 @@ internal class ValueClassBoxSerializer<T : Any>(
89126
provider.findValueSerializer(outerClazz).serialize(boxed, gen, provider)
90127
}
91128
}
129+
130+
internal class ValueClassStaticJsonValueSerializer<T> private constructor(
131+
innerClazz: Class<T>,
132+
private val staticJsonValueGetter: Method
133+
) : StdSerializer<T>(innerClazz) {
134+
override fun serialize(value: T?, gen: JsonGenerator, provider: SerializerProvider) {
135+
// As shown in the processing of the factory function, jsonValueGetter is always a static method.
136+
val jsonValue: Any? = staticJsonValueGetter.invoke(null, value)
137+
jsonValue
138+
?.let { provider.findValueSerializer(it::class.java).serialize(it, gen, provider) }
139+
?: provider.findNullValueSerializer(null).serialize(null, gen, provider)
140+
}
141+
142+
// Since JsonValue can be processed correctly if it is given to a non-static getter/field,
143+
// this class will only process if it is a `static` method.
144+
companion object {
145+
fun <T> createdOrNull(
146+
outerClazz: Class<out Any>,
147+
innerClazz: Class<T>
148+
): ValueClassStaticJsonValueSerializer<T>? = outerClazz
149+
.getStaticJsonValueGetter()
150+
?.let { ValueClassStaticJsonValueSerializer(innerClazz, it) }
151+
}
152+
}

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+
}

0 commit comments

Comments
 (0)