Skip to content

Commit

Permalink
Merge pull request #234 from ProjectMapK/reduce-props
Browse files Browse the repository at this point in the history
Reduce the amount of information that continues to be held in memory
  • Loading branch information
k163377 authored Jun 9, 2024
2 parents f572264 + 685c589 commit 0f8c5fc
Show file tree
Hide file tree
Showing 17 changed files with 170 additions and 101 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import io.github.projectmapk.jackson.module.kogera.annotation.JsonKUnbox
import kotlinx.metadata.KmClass
import kotlinx.metadata.KmClassifier
import kotlinx.metadata.KmType
import kotlinx.metadata.isNullable
import kotlinx.metadata.jvm.JvmMethodSignature
import kotlinx.metadata.jvm.KotlinClassMetadata
import java.lang.reflect.AnnotatedElement
Expand All @@ -22,9 +21,6 @@ internal fun Class<*>.toKmClass(): KmClass? = getAnnotation(METADATA_CLASS)?.let

internal fun Class<*>.isUnboxableValueClass() = this.isAnnotationPresent(JVM_INLINE_CLASS)

// JmClass must be value class.
internal fun JmClass.wrapsNullValueClass() = inlineClassUnderlyingType!!.isNullable

private val primitiveClassToDesc = mapOf(
Byte::class.java to 'B',
Char::class.java to 'C',
Expand Down Expand Up @@ -87,8 +83,9 @@ internal fun String.reconstructClass(): Class<*> {
return Class.forName(String(replaced))
}

internal fun KmType.reconstructClassOrNull(): Class<*>? = (classifier as? KmClassifier.Class)
?.let { kotlin.runCatching { it.name.reconstructClass() }.getOrNull() }
internal fun KmType.reconstructClassOrNull(): Class<*>? = (classifier as? KmClassifier.Class)?.reconstructClassOrNull()
internal fun KmClassifier.Class.reconstructClassOrNull(): Class<*>? =
runCatching { name.reconstructClass() }.getOrNull()

internal fun AnnotatedElement.hasCreatorAnnotation(): Boolean = getAnnotation(JSON_CREATOR_CLASS)
?.let { it.mode != JsonCreator.Mode.DISABLED }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.github.projectmapk.jackson.module.kogera

import com.fasterxml.jackson.databind.util.LRUMap
import io.github.projectmapk.jackson.module.kogera.jmClass.JmClass
import java.io.Serializable
import java.lang.reflect.Method
import java.util.Optional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,11 @@ import io.github.projectmapk.jackson.module.kogera.JSON_K_UNBOX_CLASS
import io.github.projectmapk.jackson.module.kogera.KOTLIN_DURATION_CLASS
import io.github.projectmapk.jackson.module.kogera.ReflectionCache
import io.github.projectmapk.jackson.module.kogera.isUnboxableValueClass
import io.github.projectmapk.jackson.module.kogera.reconstructClassOrNull
import io.github.projectmapk.jackson.module.kogera.jmClass.JmValueParameter
import io.github.projectmapk.jackson.module.kogera.ser.KotlinDurationValueToJavaDurationConverter
import io.github.projectmapk.jackson.module.kogera.ser.KotlinToJavaDurationConverter
import io.github.projectmapk.jackson.module.kogera.ser.SequenceToIteratorConverter
import io.github.projectmapk.jackson.module.kogera.wrapsNullValueClass
import kotlinx.metadata.KmTypeProjection
import kotlinx.metadata.KmValueParameter
import kotlinx.metadata.isNullable
import java.lang.reflect.Constructor
import java.lang.reflect.Method
Expand All @@ -36,9 +34,9 @@ internal class KotlinFallbackAnnotationIntrospector(
private val useJavaDurationConversion: Boolean,
private val cache: ReflectionCache
) : NopAnnotationIntrospector() {
private fun findKotlinParameter(param: AnnotatedParameter): KmValueParameter? =
private fun findKotlinParameter(param: AnnotatedParameter): JmValueParameter? =
when (val owner = param.owner.member) {
is Constructor<*> -> cache.getJmClass(param.declaringClass)?.findKmConstructor(owner)?.valueParameters
is Constructor<*> -> cache.getJmClass(param.declaringClass)?.findJmConstructor(owner)?.valueParameters
is Method -> if (Modifier.isStatic(owner.modifiers)) {
cache.getJmClass(param.declaringClass)
?.companion
Expand All @@ -49,7 +47,7 @@ internal class KotlinFallbackAnnotationIntrospector(
else -> null
}?.let { it[param.index] }

private fun findKotlinParameter(param: Annotated): KmValueParameter? =
private fun findKotlinParameter(param: Annotated): JmValueParameter? =
(param as? AnnotatedParameter)?.let { findKotlinParameter(it) }

// since 2.4
Expand All @@ -66,7 +64,7 @@ internal class KotlinFallbackAnnotationIntrospector(
override fun refineDeserializationType(config: MapperConfig<*>, a: Annotated, baseType: JavaType): JavaType =
findKotlinParameter(a)?.let { param ->
val rawType = a.rawType
param.type.reconstructClassOrNull()
param.reconstructedClassOrNull
?.takeIf { it.isUnboxableValueClass() && it != rawType }
?.let { config.constructType(it) }
} ?: baseType
Expand Down Expand Up @@ -101,7 +99,7 @@ internal class KotlinFallbackAnnotationIntrospector(

// Determine if the unbox result of value class is nullable
// @see findNullSerializer
private fun Class<*>.requireRebox(): Boolean = cache.getJmClass(this)!!.wrapsNullValueClass()
private fun Class<*>.requireRebox(): Boolean = cache.getJmClass(this)!!.wrapsNullableIfValue

// Perform proper serialization even if the value wrapped by the value class is null.
// If value is a non-null object type, it must not be reboxing.
Expand All @@ -124,11 +122,11 @@ internal class KotlinFallbackAnnotationIntrospector(
?: super.findSetterInfo(ann)
}

private fun KmValueParameter.isNullishTypeAt(index: Int): Boolean = type.arguments.getOrNull(index)?.let {
private fun JmValueParameter.isNullishTypeAt(index: Int): Boolean = arguments.getOrNull(index)?.let {
// If it is not a StarProjection, type is not null
it === KmTypeProjection.STAR || it.type!!.isNullable
} ?: true // If a type argument cannot be taken, treat it as nullable to avoid unexpected failure.

private fun KmValueParameter.requireStrictNullCheck(type: JavaType): Boolean =
private fun JmValueParameter.requireStrictNullCheck(type: JavaType): Boolean =
((type.isArrayType || type.isCollectionLikeType) && !this.isNullishTypeAt(0)) ||
(type.isMapLikeType && !this.isNullishTypeAt(1))
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,15 @@ import com.fasterxml.jackson.databind.introspect.AnnotatedParameter
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector
import com.fasterxml.jackson.databind.jsontype.NamedType
import io.github.projectmapk.jackson.module.kogera.JSON_PROPERTY_CLASS
import io.github.projectmapk.jackson.module.kogera.JmClass
import io.github.projectmapk.jackson.module.kogera.ReflectionCache
import io.github.projectmapk.jackson.module.kogera.hasCreatorAnnotation
import io.github.projectmapk.jackson.module.kogera.jmClass.JmClass
import io.github.projectmapk.jackson.module.kogera.jmClass.JmProperty
import io.github.projectmapk.jackson.module.kogera.jmClass.JmValueParameter
import io.github.projectmapk.jackson.module.kogera.reconstructClass
import io.github.projectmapk.jackson.module.kogera.toSignature
import kotlinx.metadata.KmClassifier
import kotlinx.metadata.KmProperty
import kotlinx.metadata.KmValueParameter
import kotlinx.metadata.declaresDefaultValue
import kotlinx.metadata.isNullable
import kotlinx.metadata.isSecondary
import kotlinx.metadata.jvm.getterSignature
import kotlinx.metadata.jvm.setterSignature
import kotlinx.metadata.jvm.signature
import java.lang.reflect.Constructor
import java.lang.reflect.Executable
import java.lang.reflect.Method
Expand Down Expand Up @@ -71,11 +66,11 @@ internal class KotlinPrimaryAnnotationIntrospector(
// only a check for the existence of a getter is performed.
// https://youtrack.jetbrains.com/issue/KT-6519
?.let {
if (it.getterSignature == null) !(it.returnType.isNullable || type.hasDefaultEmptyValue()) else null
if (it.getterName == null) !(it.returnType.isNullable || type.hasDefaultEmptyValue()) else null
}
}

private fun KmProperty.isRequiredByNullability(): Boolean = !this.returnType.isNullable
private fun JmProperty.isRequiredByNullability(): Boolean = !this.returnType.isNullable

private fun AnnotatedMethod.getRequiredMarkerFromCorrespondingAccessor(jmClass: JmClass): Boolean? =
when (parameterCount) {
Expand All @@ -93,19 +88,19 @@ internal class KotlinPrimaryAnnotationIntrospector(

private fun AnnotatedParameter.hasRequiredMarker(jmClass: JmClass): Boolean? {
val paramDef = when (val member = member) {
is Constructor<*> -> jmClass.findKmConstructor(member)?.valueParameters
is Constructor<*> -> jmClass.findJmConstructor(member)?.valueParameters
is Method -> jmClass.companion?.findFunctionByMethod(member)?.valueParameters
else -> null
}?.let { it[index] } ?: return null // Return null if function on Kotlin cannot be determined

// non required if...
return when {
// Argument definition is nullable
paramDef.type.isNullable -> false
paramDef.isNullable -> false
// Default argument are defined
paramDef.declaresDefaultValue -> false
paramDef.isOptional -> false
// vararg is treated as an empty array because undefined input is allowed
paramDef.varargElementType != null -> false
paramDef.isVararg -> false
// The conversion in case of null is defined.
type.hasDefaultEmptyValue() -> false
else -> true
Expand Down Expand Up @@ -141,18 +136,18 @@ internal class KotlinPrimaryAnnotationIntrospector(
}
}

private fun Constructor<*>.isPrimarilyConstructorOf(jmClass: JmClass): Boolean = jmClass.findKmConstructor(this)
private fun Constructor<*>.isPrimarilyConstructorOf(jmClass: JmClass): Boolean = jmClass.findJmConstructor(this)
?.let { !it.isSecondary || jmClass.constructors.size == 1 }
?: false

private fun KmClassifier.isString(): Boolean = this is KmClassifier.Class && this.name == "kotlin/String"

private fun isPossibleSingleString(
kotlinParams: List<KmValueParameter>,
kotlinParams: List<JmValueParameter>,
javaFunction: Executable,
propertyNames: Set<String>
): Boolean = kotlinParams.size == 1 &&
kotlinParams[0].let { it.name !in propertyNames && it.type.classifier.isString() } &&
kotlinParams[0].let { it.name !in propertyNames && it.isString } &&
javaFunction.parameters[0].annotations.none { it is JsonProperty }

private fun hasCreatorConstructor(clazz: Class<*>, jmClass: JmClass, propertyNames: Set<String>): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException
import com.fasterxml.jackson.databind.module.SimpleDeserializers
import io.github.projectmapk.jackson.module.kogera.JmClass
import io.github.projectmapk.jackson.module.kogera.KotlinDuration
import io.github.projectmapk.jackson.module.kogera.ReflectionCache
import io.github.projectmapk.jackson.module.kogera.ValueClassBoxConverter
import io.github.projectmapk.jackson.module.kogera.deser.JavaToKotlinDurationConverter
import io.github.projectmapk.jackson.module.kogera.deser.WrapsNullableValueClassDeserializer
import io.github.projectmapk.jackson.module.kogera.hasCreatorAnnotation
import io.github.projectmapk.jackson.module.kogera.isUnboxableValueClass
import io.github.projectmapk.jackson.module.kogera.jmClass.JmClass
import io.github.projectmapk.jackson.module.kogera.toSignature
import kotlinx.metadata.isSecondary
import kotlinx.metadata.jvm.signature
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import io.github.projectmapk.jackson.module.kogera.deser.WrapsNullableValueClass
import io.github.projectmapk.jackson.module.kogera.deser.valueInstantiator.creator.ConstructorValueCreator
import io.github.projectmapk.jackson.module.kogera.deser.valueInstantiator.creator.MethodValueCreator
import io.github.projectmapk.jackson.module.kogera.deser.valueInstantiator.creator.ValueCreator
import io.github.projectmapk.jackson.module.kogera.wrapsNullValueClass
import java.lang.reflect.Constructor
import java.lang.reflect.Executable
import java.lang.reflect.Method
Expand Down Expand Up @@ -48,7 +47,7 @@ internal class KotlinValueInstantiator(
valueDeserializer: JsonDeserializer<*>?
): Boolean = !isNullableParam &&
valueDeserializer is WrapsNullableValueClassDeserializer<*> &&
cache.getJmClass(valueDeserializer.handledType())!!.wrapsNullValueClass()
cache.getJmClass(valueDeserializer.handledType())!!.wrapsNullableIfValue

private val valueCreator: ValueCreator<*>? by ReflectProperties.lazySoft {
val creator = _withArgsCreator.annotated as Executable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package io.github.projectmapk.jackson.module.kogera.deser.valueInstantiator.argu

import io.github.projectmapk.jackson.module.kogera.ValueClassUnboxConverter
import io.github.projectmapk.jackson.module.kogera.deser.valueInstantiator.calcMaskSize
import io.github.projectmapk.jackson.module.kogera.deser.valueInstantiator.creator.ValueParameter
import io.github.projectmapk.jackson.module.kogera.jmClass.JmValueParameter
import java.lang.reflect.Array as ReflectArray

private fun defaultPrimitiveValue(type: Class<*>): Any = when (type) {
Expand Down Expand Up @@ -47,7 +47,7 @@ private fun IntArray.update(index: Int, operation: MaskOperation) {
// @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<*>>,
valueParameters: List<ValueParameter>,
valueParameters: List<JmValueParameter>,
private val converters: List<ValueClassUnboxConverter<Any>?>
) {
private val valueParameterSize: Int = parameterTypes.size
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package io.github.projectmapk.jackson.module.kogera.deser.valueInstantiator.creator

import io.github.projectmapk.jackson.module.kogera.JmClass
import io.github.projectmapk.jackson.module.kogera.ReflectionCache
import io.github.projectmapk.jackson.module.kogera.call
import io.github.projectmapk.jackson.module.kogera.defaultConstructorMarker
import io.github.projectmapk.jackson.module.kogera.deser.valueInstantiator.argumentBucket.ArgumentBucket
import io.github.projectmapk.jackson.module.kogera.deser.valueInstantiator.argumentBucket.BucketGenerator
import io.github.projectmapk.jackson.module.kogera.deser.valueInstantiator.calcMaskSize
import io.github.projectmapk.jackson.module.kogera.getDeclaredConstructorBy
import io.github.projectmapk.jackson.module.kogera.jmClass.JmClass
import io.github.projectmapk.jackson.module.kogera.jmClass.JmValueParameter
import java.lang.reflect.Constructor

internal class ConstructorValueCreator<T : Any>(
Expand All @@ -19,21 +20,19 @@ internal class ConstructorValueCreator<T : Any>(

override val isAccessible: Boolean = constructor.isAccessible
override val callableName: String = constructor.name
override val valueParameters: List<ValueParameter>
override val valueParameters: List<JmValueParameter>
override val bucketGenerator: BucketGenerator

init {
// To prevent the call from failing, save the initial value and then rewrite the flag.
if (!isAccessible) constructor.isAccessible = true

val constructorParameters = declaringJmClass.findKmConstructor(constructor)!!.valueParameters

valueParameters = constructorParameters.map { ValueParameter(it) }
valueParameters = declaringJmClass.findJmConstructor(constructor)!!.valueParameters
val rawTypes = constructor.parameterTypes.asList()
bucketGenerator = BucketGenerator(
rawTypes,
valueParameters,
constructorParameters.mapToConverters(rawTypes, cache)
valueParameters.mapToConverters(rawTypes, cache)
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package io.github.projectmapk.jackson.module.kogera.deser.valueInstantiator.creator

import io.github.projectmapk.jackson.module.kogera.ANY_CLASS
import io.github.projectmapk.jackson.module.kogera.JmClass
import io.github.projectmapk.jackson.module.kogera.ReflectionCache
import io.github.projectmapk.jackson.module.kogera.call
import io.github.projectmapk.jackson.module.kogera.deser.valueInstantiator.argumentBucket.ArgumentBucket
import io.github.projectmapk.jackson.module.kogera.deser.valueInstantiator.argumentBucket.BucketGenerator
import io.github.projectmapk.jackson.module.kogera.deser.valueInstantiator.calcMaskSize
import io.github.projectmapk.jackson.module.kogera.getDeclaredMethodBy
import kotlinx.metadata.KmFunction
import io.github.projectmapk.jackson.module.kogera.jmClass.JmClass
import io.github.projectmapk.jackson.module.kogera.jmClass.JmValueParameter
import java.lang.reflect.Method

internal class MethodValueCreator<T>(
Expand All @@ -19,22 +19,21 @@ internal class MethodValueCreator<T>(
private val companion: JmClass.CompanionObject = declaringJmClass.companion!!
override val isAccessible: Boolean = method.isAccessible && companion.isAccessible
override val callableName: String = method.name
override val valueParameters: List<ValueParameter>
override val valueParameters: List<JmValueParameter>
override val bucketGenerator: BucketGenerator

init {
// To prevent the call from failing, save the initial value and then rewrite the flag.
if (!method.isAccessible) method.isAccessible = true

val kmFunction: KmFunction = companion.findFunctionByMethod(method)!!
val kmParameters = kmFunction.valueParameters
val function = companion.findFunctionByMethod(method)!!

valueParameters = kmParameters.map { ValueParameter(it) }
valueParameters = function.valueParameters
val rawTypes = method.parameterTypes.asList()
bucketGenerator = BucketGenerator(
rawTypes,
valueParameters,
kmParameters.mapToConverters(rawTypes, cache)
valueParameters.mapToConverters(rawTypes, cache)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ import io.github.projectmapk.jackson.module.kogera.ValueClassUnboxConverter
import io.github.projectmapk.jackson.module.kogera.deser.valueInstantiator.argumentBucket.ArgumentBucket
import io.github.projectmapk.jackson.module.kogera.deser.valueInstantiator.argumentBucket.BucketGenerator
import io.github.projectmapk.jackson.module.kogera.isUnboxableValueClass
import io.github.projectmapk.jackson.module.kogera.reconstructClassOrNull
import kotlinx.metadata.KmValueParameter
import io.github.projectmapk.jackson.module.kogera.jmClass.JmValueParameter

/**
* A class that abstracts the creation of instances by calling KFunction.
Expand All @@ -28,7 +27,7 @@ internal sealed class ValueCreator<T> {
/**
* ValueParameters of the KFunction to be called.
*/
abstract val valueParameters: List<ValueParameter>
abstract val valueParameters: List<JmValueParameter>

protected abstract val bucketGenerator: BucketGenerator

Expand Down Expand Up @@ -61,12 +60,12 @@ internal sealed class ValueCreator<T> {
}

@Suppress("UNCHECKED_CAST")
internal fun List<KmValueParameter>.mapToConverters(
internal fun List<JmValueParameter>.mapToConverters(
rawTypes: List<Class<*>>,
cache: ReflectionCache
): List<ValueClassUnboxConverter<Any>?> =
mapIndexed { i, param ->
param.type.reconstructClassOrNull()
param.reconstructedClassOrNull
?.takeIf { it.isUnboxableValueClass() && rawTypes[i] != it }
?.let { cache.getValueClassUnboxConverter(it) }
} as List<ValueClassUnboxConverter<Any>?> // Cast to cheat generics

This file was deleted.

Loading

0 comments on commit 0f8c5fc

Please sign in to comment.