Skip to content

Commit e908e0c

Browse files
authored
Merge pull request #14 from ProjectMapK/fix-to-use-metadata-jvm
Fix to use metadata jvm
2 parents d37f28c + e975c9b commit e908e0c

File tree

11 files changed

+373
-132
lines changed

11 files changed

+373
-132
lines changed

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ repositories {
1414
dependencies {
1515
implementation(kotlin("stdlib"))
1616
implementation(kotlin("reflect"))
17+
implementation("org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.5.0")
1718
implementation(platform("com.fasterxml.jackson:jackson-bom:2.13.3"))
1819

1920
// https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ package com.fasterxml.jackson.module.kotlin
33
import com.fasterxml.jackson.core.JsonParser
44
import com.fasterxml.jackson.databind.JsonMappingException
55
import com.fasterxml.jackson.databind.exc.MismatchedInputException
6-
import kotlin.reflect.KParameter
6+
import com.fasterxml.jackson.module.kotlin.deser.value_instantiator.creator.ValueParameter
77

88
/**
99
* Specialized [JsonMappingException] sub-class used to indicate that a mandatory Kotlin constructor
1010
* parameter was missing or null.
1111
*/
1212
public class MissingKotlinParameterException internal constructor(
13-
public val parameter: KParameter,
13+
internal val parameter: ValueParameter,
1414
processor: JsonParser? = null,
1515
msg: String
1616
) : MismatchedInputException(processor, msg)

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

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

33
import com.fasterxml.jackson.databind.JsonMappingException
4+
import kotlinx.metadata.KmClass
5+
import kotlinx.metadata.KmValueParameter
6+
import kotlinx.metadata.jvm.JvmMethodSignature
7+
import kotlinx.metadata.jvm.KotlinClassHeader
8+
import kotlinx.metadata.jvm.KotlinClassMetadata
9+
import java.lang.reflect.Constructor
10+
import java.lang.reflect.Method
411
import java.util.*
512
import kotlin.reflect.KType
613
import kotlin.reflect.jvm.jvmErasure
@@ -26,3 +33,57 @@ internal fun Class<*>.isKotlinClass(): Boolean = declaredAnnotations.any { it is
2633
internal fun Class<*>.isUnboxableValueClass() = annotations.any { it is JvmInline }
2734

2835
internal fun KType.erasedType(): Class<out Any> = this.jvmErasure.java
36+
37+
internal fun Class<*>.toKmClass(): KmClass = annotations
38+
.filterIsInstance<Metadata>()
39+
.first()
40+
.let {
41+
KotlinClassMetadata.read(
42+
KotlinClassHeader(
43+
it.kind,
44+
it.metadataVersion,
45+
it.data1,
46+
it.data2,
47+
it.extraString,
48+
it.packageName,
49+
it.extraInt
50+
)
51+
) as KotlinClassMetadata.Class
52+
}.toKmClass()
53+
54+
private val primitiveClassToDesc by lazy {
55+
mapOf(
56+
Byte::class.javaPrimitiveType to 'B',
57+
Char::class.javaPrimitiveType to 'C',
58+
Double::class.javaPrimitiveType to 'D',
59+
Float::class.javaPrimitiveType to 'F',
60+
Int::class.javaPrimitiveType to 'I',
61+
Long::class.javaPrimitiveType to 'J',
62+
Short::class.javaPrimitiveType to 'S',
63+
Boolean::class.javaPrimitiveType to 'Z',
64+
Void::class.javaPrimitiveType to 'V'
65+
)
66+
}
67+
68+
private val Class<*>.descriptor: String
69+
get() = when {
70+
isPrimitive -> primitiveClassToDesc.getValue(this).toString()
71+
isArray -> "[${componentType.descriptor}"
72+
else -> "L${name.replace('.', '/')};"
73+
}
74+
75+
internal fun Array<Class<*>>.toDescString(): String =
76+
joinToString(separator = "", prefix = "(", postfix = ")") { it.descriptor }
77+
78+
internal fun Constructor<*>.toSignature(): JvmMethodSignature =
79+
JvmMethodSignature("<init>", parameterTypes.toDescString() + "V")
80+
81+
internal fun Method.toSignature(): JvmMethodSignature =
82+
JvmMethodSignature(this.name, parameterTypes.toDescString() + this.returnType.descriptor)
83+
84+
internal fun List<KmValueParameter>.hasVarargParam(): Boolean =
85+
lastOrNull()?.let { it.varargElementType != null } ?: false
86+
87+
internal val defaultConstructorMarker: Class<*> by lazy {
88+
Class.forName("kotlin.jvm.internal.DefaultConstructorMarker")
89+
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,17 +65,17 @@ internal class ReflectionCache(reflectionCacheSize: Int) {
6565
val constructor = _withArgsCreator.annotated as Constructor<Any>
6666

6767
javaConstructorToValueCreator.get(constructor)
68-
?: kotlinFromJava(constructor)?.let {
69-
val value = ConstructorValueCreator(it)
68+
?: run {
69+
val value = ConstructorValueCreator(constructor)
7070
javaConstructorToValueCreator.putIfAbsent(constructor, value) ?: value
7171
}
7272
}
7373
is AnnotatedMethod -> {
7474
val method = _withArgsCreator.annotated as Method
7575

7676
javaMethodToValueCreator.get(method)
77-
?: kotlinFromJava(method)?.let {
78-
val value = MethodValueCreator.of(it)
77+
?: kotlin.run {
78+
val value = MethodValueCreator<Any?>(method)
7979
javaMethodToValueCreator.putIfAbsent(method, value) ?: value
8080
}
8181
}

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

Lines changed: 23 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,11 @@ import com.fasterxml.jackson.databind.deser.impl.PropertyValueBuffer
1111
import com.fasterxml.jackson.databind.deser.std.StdValueInstantiator
1212
import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException
1313
import com.fasterxml.jackson.module.kotlin.ReflectionCache
14-
import com.fasterxml.jackson.module.kotlin.deser.value_instantiator.creator.ConstructorValueCreator
15-
import com.fasterxml.jackson.module.kotlin.deser.value_instantiator.creator.MethodValueCreator
1614
import com.fasterxml.jackson.module.kotlin.deser.value_instantiator.creator.ValueCreator
15+
import com.fasterxml.jackson.module.kotlin.deser.value_instantiator.creator.ValueParameter
1716
import com.fasterxml.jackson.module.kotlin.isKotlinClass
1817
import com.fasterxml.jackson.module.kotlin.wrapWithPath
19-
import java.lang.reflect.TypeVariable
2018
import kotlin.reflect.KParameter
21-
import kotlin.reflect.KType
2219
import kotlin.reflect.jvm.javaType
2320

2421
internal class KotlinValueInstantiator(
@@ -29,6 +26,10 @@ internal class KotlinValueInstantiator(
2926
private val nullIsSameAsDefault: Boolean,
3027
private val strictNullChecks: Boolean
3128
) : StdValueInstantiator(src) {
29+
// If the collection type argument cannot be obtained, treat it as nullable
30+
// @see com.fasterxml.jackson.module.kotlin._ported.test.StrictNullChecksTest#testListOfGenericWithNullValue
31+
private fun ValueParameter.isNullishTypeAt(index: Int) = arguments.getOrNull(index)?.isNullable ?: true
32+
3233
override fun createFromObjectWith(
3334
ctxt: DeserializationContext,
3435
props: Array<out SettableBeanProperty>,
@@ -37,24 +38,7 @@ internal class KotlinValueInstantiator(
3738
val valueCreator: ValueCreator<*> = cache.valueCreatorFromJava(_withArgsCreator)
3839
?: return super.createFromObjectWith(ctxt, props, buffer)
3940

40-
val propCount: Int
41-
var numCallableParameters: Int
42-
val callableParameters: Array<KParameter?>
43-
val jsonParamValueList: Array<Any?>
44-
45-
if (valueCreator is MethodValueCreator) {
46-
propCount = props.size + 1
47-
numCallableParameters = 1
48-
callableParameters = arrayOfNulls<KParameter>(propCount)
49-
.apply { this[0] = valueCreator.instanceParameter }
50-
jsonParamValueList = arrayOfNulls<Any>(propCount)
51-
.apply { this[0] = valueCreator.companionObjectInstance }
52-
} else {
53-
propCount = props.size
54-
numCallableParameters = 0
55-
callableParameters = arrayOfNulls(propCount)
56-
jsonParamValueList = arrayOfNulls(propCount)
57-
}
41+
val bucket = valueCreator.generateBucket()
5842

5943
valueCreator.valueParameters.forEachIndexed { idx, paramDef ->
6044
val jsonProp = props[idx]
@@ -64,14 +48,14 @@ internal class KotlinValueInstantiator(
6448
return@forEachIndexed
6549
}
6650

67-
var paramVal = if (!isMissing || paramDef.isPrimitive() || jsonProp.hasInjectableValueId()) {
51+
var paramVal = if (!isMissing || paramDef.isPrimitive || jsonProp.hasInjectableValueId()) {
6852
val tempParamVal = buffer.getParameter(jsonProp)
6953
if (nullIsSameAsDefault && tempParamVal == null && paramDef.isOptional) {
7054
return@forEachIndexed
7155
}
7256
tempParamVal
7357
} else {
74-
if (paramDef.type.isMarkedNullable) {
58+
if (paramDef.isNullable) {
7559
// do not try to create any object if it is nullable and the value is missing
7660
null
7761
} else {
@@ -84,11 +68,8 @@ internal class KotlinValueInstantiator(
8468
paramVal = NullsAsEmptyProvider(jsonProp.valueDeserializer).getNullValue(ctxt)
8569
}
8670

87-
val isGenericTypeVar = paramDef.type.javaType is TypeVariable<*>
8871
val isMissingAndRequired = paramVal == null && isMissing && jsonProp.isRequired
89-
if (isMissingAndRequired ||
90-
(!isGenericTypeVar && paramVal == null && !paramDef.type.isMarkedNullable)
91-
) {
72+
if (isMissingAndRequired || (!paramDef.isGenericType && paramVal == null && !paramDef.isNullable)) {
9273
throw MissingKotlinParameterException(
9374
parameter = paramDef,
9475
processor = ctxt.parser,
@@ -97,24 +78,17 @@ internal class KotlinValueInstantiator(
9778
}
9879

9980
if (strictNullChecks && paramVal != null) {
100-
var paramType: String? = null
101-
var itemType: KType? = null
102-
if (jsonProp.type.isCollectionLikeType && paramDef.type.arguments.getOrNull(0)?.type?.isMarkedNullable == false && (paramVal as Collection<*>).any { it == null }) {
103-
paramType = "collection"
104-
itemType = paramDef.type.arguments[0].type
105-
}
106-
107-
if (jsonProp.type.isMapLikeType && paramDef.type.arguments.getOrNull(1)?.type?.isMarkedNullable == false && (paramVal as Map<*, *>).any { it.value == null }) {
108-
paramType = "map"
109-
itemType = paramDef.type.arguments[1].type
110-
}
111-
112-
if (jsonProp.type.isArrayType && paramDef.type.arguments.getOrNull(0)?.type?.isMarkedNullable == false && (paramVal as Array<*>).any { it == null }) {
113-
paramType = "array"
114-
itemType = paramDef.type.arguments[0].type
115-
}
116-
117-
if (paramType != null && itemType != null) {
81+
// If an error occurs, Argument.name is always non-null
82+
// @see com.fasterxml.jackson.module.kotlin.deser.value_instantiator.creator.Argument
83+
when {
84+
paramVal is Collection<*> && !paramDef.isNullishTypeAt(0) && paramVal.any { it == null } ->
85+
"collection" to paramDef.arguments[0].name!!
86+
paramVal is Array<*> && !paramDef.isNullishTypeAt(0) && paramVal.any { it == null } ->
87+
"array" to paramDef.arguments[0].name!!
88+
paramVal is Map<*, *> && !paramDef.isNullishTypeAt(1) && paramVal.values.any { it == null } ->
89+
"map" to paramDef.arguments[1].name!!
90+
else -> null
91+
}?.let { (paramType, itemType) ->
11892
throw MissingKotlinParameterException(
11993
parameter = paramDef,
12094
processor = ctxt.parser,
@@ -123,25 +97,11 @@ internal class KotlinValueInstantiator(
12397
}
12498
}
12599

126-
jsonParamValueList[numCallableParameters] = paramVal
127-
callableParameters[numCallableParameters] = paramDef
128-
numCallableParameters++
100+
bucket[idx] = paramVal
129101
}
130102

131-
return if (numCallableParameters == jsonParamValueList.size && valueCreator is ConstructorValueCreator) {
132-
// we didn't do anything special with default parameters, do a normal call
133-
super.createFromObjectWith(ctxt, jsonParamValueList)
134-
} else {
135-
valueCreator.checkAccessibility(ctxt)
136-
137-
val callableParametersByName = linkedMapOf<KParameter, Any?>()
138-
callableParameters.mapIndexed { idx, paramDef ->
139-
if (paramDef != null) {
140-
callableParametersByName[paramDef] = jsonParamValueList[idx]
141-
}
142-
}
143-
valueCreator.callBy(callableParametersByName)
144-
}
103+
valueCreator.checkAccessibility(ctxt)
104+
return valueCreator.callBy(bucket)
145105
}
146106

147107
private fun KParameter.isPrimitive(): Boolean {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package com.fasterxml.jackson.module.kotlin.deser.value_instantiator.argument_bucket
2+
3+
import java.lang.reflect.Array as ReflectArray
4+
5+
private fun defaultPrimitiveValue(type: Class<*>): Any = when (type) {
6+
Boolean::class.java -> false
7+
Char::class.java -> 0.toChar()
8+
Byte::class.java -> 0.toByte()
9+
Short::class.java -> 0.toShort()
10+
Int::class.java -> 0
11+
Float::class.java -> 0f
12+
Long::class.java -> 0L
13+
Double::class.java -> 0.0
14+
Void.TYPE -> throw IllegalStateException("Parameter with void type is illegal")
15+
else -> throw UnsupportedOperationException("Unknown primitive: $type")
16+
}
17+
18+
private fun defaultEmptyArray(arrayType: Class<*>): Any =
19+
ReflectArray.newInstance(arrayType.componentType, 0)
20+
21+
// @see https://github.com/JetBrains/kotlin/blob/4c925d05883a8073e6732bca95bf575beb031a59/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KCallableImpl.kt#L114
22+
internal class BucketGenerator(
23+
parameterTypes: List<Class<*>>,
24+
hasVarargParam: Boolean
25+
) {
26+
private val valueParameterSize: Int = parameterTypes.size
27+
private val originalAbsentArgs: Array<Any?>
28+
29+
init {
30+
val maskSize = (parameterTypes.size + Integer.SIZE - 1) / Integer.SIZE
31+
32+
// Array containing the actual function arguments, masks, and +1 for DefaultConstructorMarker or MethodHandle.
33+
originalAbsentArgs = arrayOfNulls<Any?>(valueParameterSize + maskSize + 1)
34+
35+
// Set values of primitive arguments to the boxed default values (such as 0, 0.0, false) instead of nulls.
36+
parameterTypes.forEachIndexed { i, clazz ->
37+
if (clazz.isPrimitive) {
38+
originalAbsentArgs[i] = defaultPrimitiveValue(clazz)
39+
}
40+
}
41+
42+
if (hasVarargParam) {
43+
// vararg argument is always at the end of the arguments.
44+
val i = valueParameterSize - 1
45+
originalAbsentArgs[i] = defaultEmptyArray(parameterTypes[i])
46+
}
47+
48+
for (i in 0 until maskSize) {
49+
originalAbsentArgs[valueParameterSize + i] = -1
50+
}
51+
}
52+
53+
fun generate(): ArgumentBucket = ArgumentBucket(valueParameterSize, originalAbsentArgs.clone())
54+
}
55+
56+
internal class ArgumentBucket(
57+
private val valueParameterSize: Int,
58+
private val arguments: Array<Any?>
59+
) {
60+
companion object {
61+
// List of Int with only 1 bit enabled.
62+
private val BIT_FLAGS: List<Int> = IntArray(Int.SIZE_BITS) { (1 shl it).inv() }.asList()
63+
}
64+
65+
private var count = 0
66+
67+
operator fun set(index: Int, arg: Any?) {
68+
// Since there is no multiple initialization in the use case, the key check is omitted.
69+
arguments[index] = arg
70+
71+
val maskIndex = valueParameterSize + (index / Integer.SIZE)
72+
arguments[maskIndex] = (arguments[maskIndex] as Int) and BIT_FLAGS[index % Integer.SIZE]
73+
74+
count++
75+
}
76+
77+
val isFullInitialized: Boolean get() = count == valueParameterSize
78+
79+
fun getArgs(): Array<Any?> = arguments.copyOf(valueParameterSize)
80+
fun getDefaultArgs(): Array<Any?> = arguments
81+
}

0 commit comments

Comments
 (0)