Skip to content

Commit

Permalink
Merge pull request #14 from ProjectMapK/fix-to-use-metadata-jvm
Browse files Browse the repository at this point in the history
Fix to use metadata jvm
  • Loading branch information
k163377 authored Jan 9, 2023
2 parents d37f28c + e975c9b commit e908e0c
Show file tree
Hide file tree
Showing 11 changed files with 373 additions and 132 deletions.
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ repositories {
dependencies {
implementation(kotlin("stdlib"))
implementation(kotlin("reflect"))
implementation("org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.5.0")
implementation(platform("com.fasterxml.jackson:jackson-bom:2.13.3"))

// https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ package com.fasterxml.jackson.module.kotlin
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.JsonMappingException
import com.fasterxml.jackson.databind.exc.MismatchedInputException
import kotlin.reflect.KParameter
import com.fasterxml.jackson.module.kotlin.deser.value_instantiator.creator.ValueParameter

/**
* Specialized [JsonMappingException] sub-class used to indicate that a mandatory Kotlin constructor
* parameter was missing or null.
*/
public class MissingKotlinParameterException internal constructor(
public val parameter: KParameter,
internal val parameter: ValueParameter,
processor: JsonParser? = null,
msg: String
) : MismatchedInputException(processor, msg)
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
package com.fasterxml.jackson.module.kotlin

import com.fasterxml.jackson.databind.JsonMappingException
import kotlinx.metadata.KmClass
import kotlinx.metadata.KmValueParameter
import kotlinx.metadata.jvm.JvmMethodSignature
import kotlinx.metadata.jvm.KotlinClassHeader
import kotlinx.metadata.jvm.KotlinClassMetadata
import java.lang.reflect.Constructor
import java.lang.reflect.Method
import java.util.*
import kotlin.reflect.KType
import kotlin.reflect.jvm.jvmErasure
Expand All @@ -26,3 +33,57 @@ internal fun Class<*>.isKotlinClass(): Boolean = declaredAnnotations.any { it is
internal fun Class<*>.isUnboxableValueClass() = annotations.any { it is JvmInline }

internal fun KType.erasedType(): Class<out Any> = this.jvmErasure.java

internal fun Class<*>.toKmClass(): KmClass = annotations
.filterIsInstance<Metadata>()
.first()
.let {
KotlinClassMetadata.read(
KotlinClassHeader(
it.kind,
it.metadataVersion,
it.data1,
it.data2,
it.extraString,
it.packageName,
it.extraInt
)
) as KotlinClassMetadata.Class
}.toKmClass()

private val primitiveClassToDesc by lazy {
mapOf(
Byte::class.javaPrimitiveType to 'B',
Char::class.javaPrimitiveType to 'C',
Double::class.javaPrimitiveType to 'D',
Float::class.javaPrimitiveType to 'F',
Int::class.javaPrimitiveType to 'I',
Long::class.javaPrimitiveType to 'J',
Short::class.javaPrimitiveType to 'S',
Boolean::class.javaPrimitiveType to 'Z',
Void::class.javaPrimitiveType to 'V'
)
}

private val Class<*>.descriptor: String
get() = when {
isPrimitive -> primitiveClassToDesc.getValue(this).toString()
isArray -> "[${componentType.descriptor}"
else -> "L${name.replace('.', '/')};"
}

internal fun Array<Class<*>>.toDescString(): String =
joinToString(separator = "", prefix = "(", postfix = ")") { it.descriptor }

internal fun Constructor<*>.toSignature(): JvmMethodSignature =
JvmMethodSignature("<init>", parameterTypes.toDescString() + "V")

internal fun Method.toSignature(): JvmMethodSignature =
JvmMethodSignature(this.name, parameterTypes.toDescString() + this.returnType.descriptor)

internal fun List<KmValueParameter>.hasVarargParam(): Boolean =
lastOrNull()?.let { it.varargElementType != null } ?: false

internal val defaultConstructorMarker: Class<*> by lazy {
Class.forName("kotlin.jvm.internal.DefaultConstructorMarker")
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,17 @@ internal class ReflectionCache(reflectionCacheSize: Int) {
val constructor = _withArgsCreator.annotated as Constructor<Any>

javaConstructorToValueCreator.get(constructor)
?: kotlinFromJava(constructor)?.let {
val value = ConstructorValueCreator(it)
?: run {
val value = ConstructorValueCreator(constructor)
javaConstructorToValueCreator.putIfAbsent(constructor, value) ?: value
}
}
is AnnotatedMethod -> {
val method = _withArgsCreator.annotated as Method

javaMethodToValueCreator.get(method)
?: kotlinFromJava(method)?.let {
val value = MethodValueCreator.of(it)
?: kotlin.run {
val value = MethodValueCreator<Any?>(method)
javaMethodToValueCreator.putIfAbsent(method, value) ?: value
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,11 @@ import com.fasterxml.jackson.databind.deser.impl.PropertyValueBuffer
import com.fasterxml.jackson.databind.deser.std.StdValueInstantiator
import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException
import com.fasterxml.jackson.module.kotlin.ReflectionCache
import com.fasterxml.jackson.module.kotlin.deser.value_instantiator.creator.ConstructorValueCreator
import com.fasterxml.jackson.module.kotlin.deser.value_instantiator.creator.MethodValueCreator
import com.fasterxml.jackson.module.kotlin.deser.value_instantiator.creator.ValueCreator
import com.fasterxml.jackson.module.kotlin.deser.value_instantiator.creator.ValueParameter
import com.fasterxml.jackson.module.kotlin.isKotlinClass
import com.fasterxml.jackson.module.kotlin.wrapWithPath
import java.lang.reflect.TypeVariable
import kotlin.reflect.KParameter
import kotlin.reflect.KType
import kotlin.reflect.jvm.javaType

internal class KotlinValueInstantiator(
Expand All @@ -29,6 +26,10 @@ internal class KotlinValueInstantiator(
private val nullIsSameAsDefault: Boolean,
private val strictNullChecks: Boolean
) : StdValueInstantiator(src) {
// If the collection type argument cannot be obtained, treat it as nullable
// @see com.fasterxml.jackson.module.kotlin._ported.test.StrictNullChecksTest#testListOfGenericWithNullValue
private fun ValueParameter.isNullishTypeAt(index: Int) = arguments.getOrNull(index)?.isNullable ?: true

override fun createFromObjectWith(
ctxt: DeserializationContext,
props: Array<out SettableBeanProperty>,
Expand All @@ -37,24 +38,7 @@ internal class KotlinValueInstantiator(
val valueCreator: ValueCreator<*> = cache.valueCreatorFromJava(_withArgsCreator)
?: return super.createFromObjectWith(ctxt, props, buffer)

val propCount: Int
var numCallableParameters: Int
val callableParameters: Array<KParameter?>
val jsonParamValueList: Array<Any?>

if (valueCreator is MethodValueCreator) {
propCount = props.size + 1
numCallableParameters = 1
callableParameters = arrayOfNulls<KParameter>(propCount)
.apply { this[0] = valueCreator.instanceParameter }
jsonParamValueList = arrayOfNulls<Any>(propCount)
.apply { this[0] = valueCreator.companionObjectInstance }
} else {
propCount = props.size
numCallableParameters = 0
callableParameters = arrayOfNulls(propCount)
jsonParamValueList = arrayOfNulls(propCount)
}
val bucket = valueCreator.generateBucket()

valueCreator.valueParameters.forEachIndexed { idx, paramDef ->
val jsonProp = props[idx]
Expand All @@ -64,14 +48,14 @@ internal class KotlinValueInstantiator(
return@forEachIndexed
}

var paramVal = if (!isMissing || paramDef.isPrimitive() || jsonProp.hasInjectableValueId()) {
var paramVal = if (!isMissing || paramDef.isPrimitive || jsonProp.hasInjectableValueId()) {
val tempParamVal = buffer.getParameter(jsonProp)
if (nullIsSameAsDefault && tempParamVal == null && paramDef.isOptional) {
return@forEachIndexed
}
tempParamVal
} else {
if (paramDef.type.isMarkedNullable) {
if (paramDef.isNullable) {
// do not try to create any object if it is nullable and the value is missing
null
} else {
Expand All @@ -84,11 +68,8 @@ internal class KotlinValueInstantiator(
paramVal = NullsAsEmptyProvider(jsonProp.valueDeserializer).getNullValue(ctxt)
}

val isGenericTypeVar = paramDef.type.javaType is TypeVariable<*>
val isMissingAndRequired = paramVal == null && isMissing && jsonProp.isRequired
if (isMissingAndRequired ||
(!isGenericTypeVar && paramVal == null && !paramDef.type.isMarkedNullable)
) {
if (isMissingAndRequired || (!paramDef.isGenericType && paramVal == null && !paramDef.isNullable)) {
throw MissingKotlinParameterException(
parameter = paramDef,
processor = ctxt.parser,
Expand All @@ -97,24 +78,17 @@ internal class KotlinValueInstantiator(
}

if (strictNullChecks && paramVal != null) {
var paramType: String? = null
var itemType: KType? = null
if (jsonProp.type.isCollectionLikeType && paramDef.type.arguments.getOrNull(0)?.type?.isMarkedNullable == false && (paramVal as Collection<*>).any { it == null }) {
paramType = "collection"
itemType = paramDef.type.arguments[0].type
}

if (jsonProp.type.isMapLikeType && paramDef.type.arguments.getOrNull(1)?.type?.isMarkedNullable == false && (paramVal as Map<*, *>).any { it.value == null }) {
paramType = "map"
itemType = paramDef.type.arguments[1].type
}

if (jsonProp.type.isArrayType && paramDef.type.arguments.getOrNull(0)?.type?.isMarkedNullable == false && (paramVal as Array<*>).any { it == null }) {
paramType = "array"
itemType = paramDef.type.arguments[0].type
}

if (paramType != null && itemType != null) {
// If an error occurs, Argument.name is always non-null
// @see com.fasterxml.jackson.module.kotlin.deser.value_instantiator.creator.Argument
when {
paramVal is Collection<*> && !paramDef.isNullishTypeAt(0) && paramVal.any { it == null } ->
"collection" to paramDef.arguments[0].name!!
paramVal is Array<*> && !paramDef.isNullishTypeAt(0) && paramVal.any { it == null } ->
"array" to paramDef.arguments[0].name!!
paramVal is Map<*, *> && !paramDef.isNullishTypeAt(1) && paramVal.values.any { it == null } ->
"map" to paramDef.arguments[1].name!!
else -> null
}?.let { (paramType, itemType) ->
throw MissingKotlinParameterException(
parameter = paramDef,
processor = ctxt.parser,
Expand All @@ -123,25 +97,11 @@ internal class KotlinValueInstantiator(
}
}

jsonParamValueList[numCallableParameters] = paramVal
callableParameters[numCallableParameters] = paramDef
numCallableParameters++
bucket[idx] = paramVal
}

return if (numCallableParameters == jsonParamValueList.size && valueCreator is ConstructorValueCreator) {
// we didn't do anything special with default parameters, do a normal call
super.createFromObjectWith(ctxt, jsonParamValueList)
} else {
valueCreator.checkAccessibility(ctxt)

val callableParametersByName = linkedMapOf<KParameter, Any?>()
callableParameters.mapIndexed { idx, paramDef ->
if (paramDef != null) {
callableParametersByName[paramDef] = jsonParamValueList[idx]
}
}
valueCreator.callBy(callableParametersByName)
}
valueCreator.checkAccessibility(ctxt)
return valueCreator.callBy(bucket)
}

private fun KParameter.isPrimitive(): Boolean {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package com.fasterxml.jackson.module.kotlin.deser.value_instantiator.argument_bucket

import java.lang.reflect.Array as ReflectArray

private fun defaultPrimitiveValue(type: Class<*>): Any = when (type) {
Boolean::class.java -> false
Char::class.java -> 0.toChar()
Byte::class.java -> 0.toByte()
Short::class.java -> 0.toShort()
Int::class.java -> 0
Float::class.java -> 0f
Long::class.java -> 0L
Double::class.java -> 0.0
Void.TYPE -> throw IllegalStateException("Parameter with void type is illegal")
else -> throw UnsupportedOperationException("Unknown primitive: $type")
}

private fun defaultEmptyArray(arrayType: Class<*>): Any =
ReflectArray.newInstance(arrayType.componentType, 0)

// @see https://github.com/JetBrains/kotlin/blob/4c925d05883a8073e6732bca95bf575beb031a59/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KCallableImpl.kt#L114
internal class BucketGenerator(
parameterTypes: List<Class<*>>,
hasVarargParam: Boolean
) {
private val valueParameterSize: Int = parameterTypes.size
private val originalAbsentArgs: Array<Any?>

init {
val maskSize = (parameterTypes.size + Integer.SIZE - 1) / Integer.SIZE

// Array containing the actual function arguments, masks, and +1 for DefaultConstructorMarker or MethodHandle.
originalAbsentArgs = arrayOfNulls<Any?>(valueParameterSize + maskSize + 1)

// Set values of primitive arguments to the boxed default values (such as 0, 0.0, false) instead of nulls.
parameterTypes.forEachIndexed { i, clazz ->
if (clazz.isPrimitive) {
originalAbsentArgs[i] = defaultPrimitiveValue(clazz)
}
}

if (hasVarargParam) {
// vararg argument is always at the end of the arguments.
val i = valueParameterSize - 1
originalAbsentArgs[i] = defaultEmptyArray(parameterTypes[i])
}

for (i in 0 until maskSize) {
originalAbsentArgs[valueParameterSize + i] = -1
}
}

fun generate(): ArgumentBucket = ArgumentBucket(valueParameterSize, originalAbsentArgs.clone())
}

internal class ArgumentBucket(
private val valueParameterSize: Int,
private val arguments: Array<Any?>
) {
companion object {
// List of Int with only 1 bit enabled.
private val BIT_FLAGS: List<Int> = IntArray(Int.SIZE_BITS) { (1 shl it).inv() }.asList()
}

private var count = 0

operator fun set(index: Int, arg: Any?) {
// Since there is no multiple initialization in the use case, the key check is omitted.
arguments[index] = arg

val maskIndex = valueParameterSize + (index / Integer.SIZE)
arguments[maskIndex] = (arguments[maskIndex] as Int) and BIT_FLAGS[index % Integer.SIZE]

count++
}

val isFullInitialized: Boolean get() = count == valueParameterSize

fun getArgs(): Array<Any?> = arguments.copyOf(valueParameterSize)
fun getDefaultArgs(): Array<Any?> = arguments
}
Loading

0 comments on commit e908e0c

Please sign in to comment.