Skip to content

Commit f879758

Browse files
committed
Merge remote-tracking branch 'FasterXML/2.x' into 3.x
2 parents 32ebe48 + d7f228e commit f879758

File tree

12 files changed

+612
-91
lines changed

12 files changed

+612
-91
lines changed

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.20.0 (not yet released)
1919

2020
WrongWrong (@k163377)
21+
* #1018: Use MethodHandle in processing related to value class
2122
* #969: Cleanup of deprecated contents
2223
* #967: Update settings for 2.20
2324

release-notes/VERSION-2.x

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@ Co-maintainers:
1717
------------------------------------------------------------------------
1818

1919
2.20.0 (not yet released)
20-
20+
#1018: Improved handling of `value class` has improved performance for both serialization and deserialization.
21+
In particular, for serialization, proper caching has improved throughput by a factor of 2 or more in the general cases.
22+
Also, replacing function execution by reflection with `MethodHandle` improved throughput by several percent for both serialization and deserialization.
23+
In cases where the number of properties of a `value class` in the processing target is large, there is a possibility to obtain a larger improvement.
24+
Please note that this modification causes a destructive change in that exceptions thrown during deserialization of
25+
`value class` are no longer wrapped in an `InvocationTargetException`.
2126
#969: Deprecated content has been cleaned up with the version upgrade.
2227
#967: Kotlin has been upgraded to 2.0.21.
2328
- Generate SBOMs [JSTEP-14]

src/main/kotlin/tools/jackson/module/kotlin/Converters.kt

Lines changed: 161 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ import tools.jackson.databind.type.TypeFactory
77
import tools.jackson.databind.util.ClassUtil
88
import tools.jackson.databind.util.StdConverter
99
import kotlin.reflect.KClass
10+
import java.lang.invoke.MethodHandle
11+
import java.lang.invoke.MethodHandles
12+
import java.lang.invoke.MethodType
13+
import java.lang.reflect.Method
14+
import java.lang.reflect.Type
15+
import java.util.UUID
1016
import kotlin.time.toJavaDuration
1117
import kotlin.time.toKotlinDuration
1218
import java.time.Duration as JavaDuration
@@ -23,7 +29,7 @@ internal class SequenceToIteratorConverter(private val input: JavaType) : StdCon
2329
}
2430

2531
internal object KotlinDurationValueToJavaDurationConverter : StdConverter<Long, JavaDuration>() {
26-
private val boxConverter by lazy { ValueClassBoxConverter(Long::class.java, KotlinDuration::class) }
32+
private val boxConverter by lazy { LongValueClassBoxConverter(KotlinDuration::class.java) }
2733

2834
override fun convert(value: Long): JavaDuration = KotlinToJavaDurationConverter.convert(boxConverter.convert(value))
2935
}
@@ -45,18 +51,163 @@ internal object JavaToKotlinDurationConverter : StdConverter<JavaDuration, Kotli
4551
}
4652
}
4753

48-
// S is nullable because value corresponds to a nullable value class
49-
// @see KotlinNamesAnnotationIntrospector.findNullSerializer
50-
internal class ValueClassBoxConverter<S : Any?, D : Any>(
51-
unboxedClass: Class<S>,
52-
val boxedClass: KClass<D>
53-
) : StdConverter<S, D>() {
54-
private val boxMethod = boxedClass.java.getDeclaredMethod("box-impl", unboxedClass).apply {
55-
ClassUtil.checkAndFixAccess(this, false)
54+
internal sealed class ValueClassBoxConverter<S : Any?, D : Any> : StdConverter<S, D>() {
55+
abstract val boxedClass: Class<D>
56+
abstract val boxHandle: MethodHandle
57+
58+
protected fun rawBoxHandle(
59+
unboxedClass: Class<*>,
60+
): MethodHandle = MethodHandles.lookup().findStatic(
61+
boxedClass,
62+
"box-impl",
63+
MethodType.methodType(boxedClass, unboxedClass),
64+
)
65+
66+
val delegatingSerializer: StdDelegatingSerializer by lazy { StdDelegatingSerializer(this) }
67+
68+
companion object {
69+
fun create(
70+
unboxedClass: Class<*>,
71+
valueClass: Class<*>,
72+
): ValueClassBoxConverter<*, *> = when (unboxedClass) {
73+
Int::class.java -> IntValueClassBoxConverter(valueClass)
74+
Long::class.java -> LongValueClassBoxConverter(valueClass)
75+
String::class.java -> StringValueClassBoxConverter(valueClass)
76+
UUID::class.java -> JavaUuidValueClassBoxConverter(valueClass)
77+
else -> GenericValueClassBoxConverter(unboxedClass, valueClass)
78+
}
5679
}
5780

81+
// If the wrapped type is explicitly specified, it is inherited for the sake of distinction
82+
internal sealed class Specified<S : Any?, D : Any> : ValueClassBoxConverter<S, D>()
83+
}
84+
85+
// region: Converters for common classes as wrapped values, add as needed.
86+
internal class IntValueClassBoxConverter<D : Any>(
87+
override val boxedClass: Class<D>,
88+
) : ValueClassBoxConverter.Specified<Int, D>() {
89+
override val boxHandle: MethodHandle = rawBoxHandle(Int::class.java).asType(INT_TO_ANY_METHOD_TYPE)
90+
91+
@Suppress("UNCHECKED_CAST")
92+
override fun convert(value: Int): D = boxHandle.invokeExact(value) as D
93+
}
94+
95+
internal class LongValueClassBoxConverter<D : Any>(
96+
override val boxedClass: Class<D>,
97+
) : ValueClassBoxConverter.Specified<Long, D>() {
98+
override val boxHandle: MethodHandle = rawBoxHandle(Long::class.java).asType(LONG_TO_ANY_METHOD_TYPE)
99+
100+
@Suppress("UNCHECKED_CAST")
101+
override fun convert(value: Long): D = boxHandle.invokeExact(value) as D
102+
}
103+
104+
internal class StringValueClassBoxConverter<D : Any>(
105+
override val boxedClass: Class<D>,
106+
) : ValueClassBoxConverter.Specified<String?, D>() {
107+
override val boxHandle: MethodHandle = rawBoxHandle(String::class.java).asType(STRING_TO_ANY_METHOD_TYPE)
108+
109+
@Suppress("UNCHECKED_CAST")
110+
override fun convert(value: String?): D = boxHandle.invokeExact(value) as D
111+
}
112+
113+
internal class JavaUuidValueClassBoxConverter<D : Any>(
114+
override val boxedClass: Class<D>,
115+
) : ValueClassBoxConverter.Specified<UUID?, D>() {
116+
override val boxHandle: MethodHandle = rawBoxHandle(UUID::class.java).asType(JAVA_UUID_TO_ANY_METHOD_TYPE)
117+
118+
@Suppress("UNCHECKED_CAST")
119+
override fun convert(value: UUID?): D = boxHandle.invokeExact(value) as D
120+
}
121+
// endregion
122+
123+
/**
124+
* A converter that only performs box processing for the value class.
125+
* Note that constructor-impl is not called.
126+
* @param S is nullable because value corresponds to a nullable value class.
127+
* see [io.github.projectmapk.jackson.module.kogera.annotationIntrospector.KotlinFallbackAnnotationIntrospector.findNullSerializer]
128+
*/
129+
internal class GenericValueClassBoxConverter<S : Any?, D : Any>(
130+
unboxedClass: Class<S>,
131+
override val boxedClass: Class<D>,
132+
) : ValueClassBoxConverter<S, D>() {
133+
override val boxHandle: MethodHandle = rawBoxHandle(unboxedClass).asType(ANY_TO_ANY_METHOD_TYPE)
134+
58135
@Suppress("UNCHECKED_CAST")
59-
override fun convert(value: S): D = boxMethod.invoke(null, value) as D
136+
override fun convert(value: S): D = boxHandle.invokeExact(value) as D
137+
}
138+
139+
internal sealed class ValueClassUnboxConverter<S : Any, D : Any?> : StdConverter<S, D>() {
140+
abstract val valueClass: Class<S>
141+
abstract val unboxedType: Type
142+
abstract val unboxHandle: MethodHandle
143+
144+
final override fun getInputType(typeFactory: TypeFactory): JavaType = typeFactory.constructType(valueClass)
145+
final override fun getOutputType(typeFactory: TypeFactory): JavaType = typeFactory.constructType(unboxedType)
60146

61147
val delegatingSerializer: StdDelegatingSerializer by lazy { StdDelegatingSerializer(this) }
148+
149+
companion object {
150+
fun create(valueClass: Class<*>): ValueClassUnboxConverter<*, *> {
151+
val unboxMethod = valueClass.getDeclaredMethod("unbox-impl")
152+
val unboxedType = unboxMethod.genericReturnType
153+
154+
return when (unboxedType) {
155+
Int::class.java -> IntValueClassUnboxConverter(valueClass, unboxMethod)
156+
Long::class.java -> LongValueClassUnboxConverter(valueClass, unboxMethod)
157+
String::class.java -> StringValueClassUnboxConverter(valueClass, unboxMethod)
158+
UUID::class.java -> JavaUuidValueClassUnboxConverter(valueClass, unboxMethod)
159+
else -> GenericValueClassUnboxConverter(valueClass, unboxedType, unboxMethod)
160+
}
161+
}
162+
}
163+
}
164+
165+
internal class IntValueClassUnboxConverter<T : Any>(
166+
override val valueClass: Class<T>,
167+
unboxMethod: Method,
168+
) : ValueClassUnboxConverter<T, Int>() {
169+
override val unboxedType: Type get() = Int::class.java
170+
override val unboxHandle: MethodHandle = unreflectAsType(unboxMethod, ANY_TO_INT_METHOD_TYPE)
171+
172+
override fun convert(value: T): Int = unboxHandle.invokeExact(value) as Int
173+
}
174+
175+
internal class LongValueClassUnboxConverter<T : Any>(
176+
override val valueClass: Class<T>,
177+
unboxMethod: Method,
178+
) : ValueClassUnboxConverter<T, Long>() {
179+
override val unboxedType: Type get() = Long::class.java
180+
override val unboxHandle: MethodHandle = unreflectAsType(unboxMethod, ANY_TO_LONG_METHOD_TYPE)
181+
182+
override fun convert(value: T): Long = unboxHandle.invokeExact(value) as Long
183+
}
184+
185+
internal class StringValueClassUnboxConverter<T : Any>(
186+
override val valueClass: Class<T>,
187+
unboxMethod: Method,
188+
) : ValueClassUnboxConverter<T, String?>() {
189+
override val unboxedType: Type get() = String::class.java
190+
override val unboxHandle: MethodHandle = unreflectAsType(unboxMethod, ANY_TO_STRING_METHOD_TYPE)
191+
192+
override fun convert(value: T): String? = unboxHandle.invokeExact(value) as String?
193+
}
194+
195+
internal class JavaUuidValueClassUnboxConverter<T : Any>(
196+
override val valueClass: Class<T>,
197+
unboxMethod: Method,
198+
) : ValueClassUnboxConverter<T, UUID?>() {
199+
override val unboxedType: Type get() = UUID::class.java
200+
override val unboxHandle: MethodHandle = unreflectAsType(unboxMethod, ANY_TO_JAVA_UUID_METHOD_TYPE)
201+
202+
override fun convert(value: T): UUID? = unboxHandle.invokeExact(value) as UUID?
203+
}
204+
205+
internal class GenericValueClassUnboxConverter<T : Any>(
206+
override val valueClass: Class<T>,
207+
override val unboxedType: Type,
208+
unboxMethod: Method,
209+
) : ValueClassUnboxConverter<T, Any?>() {
210+
override val unboxHandle: MethodHandle = unreflectAsType(unboxMethod, ANY_TO_ANY_METHOD_TYPE)
211+
212+
override fun convert(value: T): Any? = unboxHandle.invokeExact(value)
62213
}

src/main/kotlin/tools/jackson/module/kotlin/InternalCommons.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ package tools.jackson.module.kotlin
22

33
import com.fasterxml.jackson.annotation.JsonCreator
44
import tools.jackson.databind.DatabindException
5+
import java.lang.invoke.MethodHandle
6+
import java.lang.invoke.MethodHandles
7+
import java.lang.invoke.MethodType
58
import java.lang.reflect.AnnotatedElement
9+
import java.lang.reflect.Method
610
import java.util.*
711
import kotlin.reflect.KClass
812
import kotlin.reflect.KType
@@ -46,3 +50,16 @@ internal fun AnnotatedElement.hasCreatorAnnotation(): Boolean = getAnnotation(Js
4650
// Determine if the unbox result of value class is nullable
4751
internal fun KClass<*>.wrapsNullable(): Boolean =
4852
this.memberProperties.first { it.javaField != null }.returnType.isMarkedNullable
53+
54+
internal val ANY_TO_ANY_METHOD_TYPE by lazy {MethodType.methodType(Any::class.java, Any::class.java) }
55+
internal val ANY_TO_INT_METHOD_TYPE by lazy {MethodType.methodType(Int::class.java, Any::class.java) }
56+
internal val ANY_TO_LONG_METHOD_TYPE by lazy {MethodType.methodType(Long::class.java, Any::class.java) }
57+
internal val ANY_TO_STRING_METHOD_TYPE by lazy {MethodType.methodType(String::class.java, Any::class.java) }
58+
internal val ANY_TO_JAVA_UUID_METHOD_TYPE by lazy {MethodType.methodType(UUID::class.java, Any::class.java) }
59+
internal val INT_TO_ANY_METHOD_TYPE by lazy {MethodType.methodType(Any::class.java, Int::class.java) }
60+
internal val LONG_TO_ANY_METHOD_TYPE by lazy {MethodType.methodType(Any::class.java, Long::class.java) }
61+
internal val STRING_TO_ANY_METHOD_TYPE by lazy {MethodType.methodType(Any::class.java, String::class.java) }
62+
internal val JAVA_UUID_TO_ANY_METHOD_TYPE by lazy {MethodType.methodType(Any::class.java, UUID::class.java) }
63+
64+
internal fun unreflect(method: Method): MethodHandle = MethodHandles.lookup().unreflect(method)
65+
internal fun unreflectAsType(method: Method, type: MethodType): MethodHandle = unreflect(method).asType(type)

0 commit comments

Comments
 (0)