Skip to content

Commit

Permalink
Merge pull request #223 from ProjectMapK/opt-jc-cache
Browse files Browse the repository at this point in the history
Optimization for Java Class retrieval.
  • Loading branch information
k163377 authored Mar 20, 2024
2 parents e02c9b1 + b2cfcb9 commit 6e4007d
Show file tree
Hide file tree
Showing 9 changed files with 47 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ internal object ClosedRangeResolver : SimpleAbstractTypeResolver() {
}

fun findClosedFloatingPointRangeRef(contentType: Class<*>): Class<*>? = when (contentType) {
Double::class.javaPrimitiveType, Double::class.javaObjectType -> closedDoubleRangeRef
Float::class.javaPrimitiveType, Float::class.javaObjectType -> closedFloatRangeRef
Double::class.java, Double::class.javaObjectType -> closedDoubleRangeRef
Float::class.java, Float::class.javaObjectType -> closedFloatRangeRef
else -> null
}

Expand All @@ -67,7 +67,7 @@ internal object ClosedRangeResolver : SimpleAbstractTypeResolver() {
override fun findTypeMapping(config: DeserializationConfig, type: JavaType): JavaType? {
val rawClass = type.rawClass

return if (rawClass == ClosedRange::class.java || rawClass == ClosedFloatingPointRange::class.java) {
return if (rawClass == ClosedRange::class.java || rawClass == CLOSED_FLOATING_POINT_RANGE_CLASS) {
type.bindings.typeParameters.firstOrNull()
?.let { typeParam ->
findClosedFloatingPointRangeRef(typeParam.rawClass)?.let {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package io.github.projectmapk.jackson.module.kogera

import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonProperty
import io.github.projectmapk.jackson.module.kogera.annotation.JsonKUnbox
import kotlinx.metadata.KmClass
import kotlinx.metadata.KmClassifier
import kotlinx.metadata.KmType
Expand All @@ -14,25 +16,25 @@ import java.lang.reflect.Method
internal typealias JavaDuration = java.time.Duration
internal typealias KotlinDuration = kotlin.time.Duration

internal fun Class<*>.toKmClass(): KmClass? = getAnnotation(Metadata::class.java)?.let {
internal fun Class<*>.toKmClass(): KmClass? = getAnnotation(METADATA_CLASS)?.let {
(KotlinClassMetadata.readStrict(it) as KotlinClassMetadata.Class).kmClass
}

internal fun Class<*>.isUnboxableValueClass() = this.isAnnotationPresent(JvmInline::class.java)
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.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'
Byte::class.java to 'B',
Char::class.java to 'C',
Double::class.java to 'D',
Float::class.java to 'F',
Int::class.java to 'I',
Long::class.java to 'J',
Short::class.java to 'S',
Boolean::class.java to 'Z',
Void.TYPE to 'V'
)

// -> this.name.replace(".", "/")
Expand Down Expand Up @@ -88,6 +90,19 @@ internal fun String.reconstructClass(): Class<*> {
internal fun KmType.reconstructClassOrNull(): Class<*>? = (classifier as? KmClassifier.Class)
?.let { kotlin.runCatching { it.name.reconstructClass() }.getOrNull() }

internal fun AnnotatedElement.hasCreatorAnnotation(): Boolean = getAnnotation(JsonCreator::class.java)
internal fun AnnotatedElement.hasCreatorAnnotation(): Boolean = getAnnotation(JSON_CREATOR_CLASS)
?.let { it.mode != JsonCreator.Mode.DISABLED }
?: false

// Instantiating Java Class as a static property is expected to improve first-time execution performance.
// However, maybe this improvement is limited to Java Classes that are not used to initialize static content.
// Also, for classes that are read at the time of initialization of static content or module initialization,
// optimization seems unnecessary because caching is effective.
internal val METADATA_CLASS = Metadata::class.java
internal val JVM_INLINE_CLASS = JvmInline::class.java
internal val JSON_CREATOR_CLASS = JsonCreator::class.java
internal val JSON_PROPERTY_CLASS = JsonProperty::class.java
internal val JSON_K_UNBOX_CLASS = JsonKUnbox::class.java
internal val KOTLIN_DURATION_CLASS = KotlinDuration::class.java
internal val CLOSED_FLOATING_POINT_RANGE_CLASS = ClosedFloatingPointRange::class.java
internal val ANY_CLASS = Any::class.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ internal object KotlinClassIntrospector : BasicClassIntrospector() {
?: run {
val coll = collectProperties(config, type, r, true)

if (type.rawClass.isAnnotationPresent(Metadata::class.java)) {
if (type.rawClass.isAnnotationPresent(METADATA_CLASS)) {
KotlinBeanDescription(coll)
} else {
BasicBeanDescription.forDeserialization(coll)
Expand All @@ -71,7 +71,7 @@ internal object KotlinClassIntrospector : BasicClassIntrospector() {
?: run {
val coll = collectProperties(config, type, r, false)

if (type.rawClass.isAnnotationPresent(Metadata::class.java)) {
if (type.rawClass.isAnnotationPresent(METADATA_CLASS)) {
KotlinBeanDescription(coll)
} else {
BasicBeanDescription.forDeserialization(coll)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ internal class ReflectionCache(initialCacheSize: Int, maxCacheSize: Int) : Seria
val superJmClass = if (!clazz.isInterface) {
clazz.superclass?.let {
// Stop parsing when `Object` is reached
if (it != Any::class.java) getJmClass(it) else null
if (it != ANY_CLASS) getJmClass(it) else null
}
} else {
null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import com.fasterxml.jackson.databind.introspect.AnnotatedMethod
import com.fasterxml.jackson.databind.introspect.AnnotatedParameter
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector
import com.fasterxml.jackson.databind.util.Converter
import io.github.projectmapk.jackson.module.kogera.KotlinDuration
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.annotation.JsonKUnbox
import io.github.projectmapk.jackson.module.kogera.isUnboxableValueClass
import io.github.projectmapk.jackson.module.kogera.reconstructClassOrNull
import io.github.projectmapk.jackson.module.kogera.ser.KotlinDurationValueToJavaDurationConverter
Expand Down Expand Up @@ -74,15 +74,15 @@ internal class KotlinFallbackAnnotationIntrospector(
override fun findSerializationConverter(a: Annotated): Converter<*, *>? = when (a) {
// Find a converter to handle the case where the getter returns an unboxed value from the value class.
is AnnotatedMethod -> cache.findBoxedReturnType(a.member)?.let {
if (useJavaDurationConversion && it == KotlinDuration::class.java) {
if (a.rawReturnType == KotlinDuration::class.java) {
if (useJavaDurationConversion && it == KOTLIN_DURATION_CLASS) {
if (a.rawReturnType == KOTLIN_DURATION_CLASS) {
KotlinToJavaDurationConverter
} else {
KotlinDurationValueToJavaDurationConverter
}
} else {
// If JsonUnbox is specified, the unboxed getter is used as is.
if (a.hasAnnotation(JsonKUnbox::class.java) || it.isAnnotationPresent(JsonKUnbox::class.java)) {
if (a.hasAnnotation(JSON_K_UNBOX_CLASS) || it.isAnnotationPresent(JSON_K_UNBOX_CLASS)) {
null
} else {
cache.getValueClassBoxConverter(a.rawReturnType, it)
Expand All @@ -95,7 +95,7 @@ internal class KotlinFallbackAnnotationIntrospector(

private fun lookupKotlinTypeConverter(a: AnnotatedClass) = when {
Sequence::class.java.isAssignableFrom(a.rawType) -> SequenceToIteratorConverter(a.type)
KotlinDuration::class.java == a.rawType -> KotlinToJavaDurationConverter.takeIf { useJavaDurationConversion }
KOTLIN_DURATION_CLASS == a.rawType -> KotlinToJavaDurationConverter.takeIf { useJavaDurationConversion }
else -> null
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.fasterxml.jackson.databind.introspect.AnnotatedMethod
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
Expand Down Expand Up @@ -42,7 +43,7 @@ internal class KotlinPrimaryAnnotationIntrospector(
// If JsonProperty.required is true, the behavior is clearly specified and the result is paramount.
// Otherwise, the required is determined from the configuration and the definition on Kotlin.
override fun hasRequiredMarker(m: AnnotatedMember): Boolean? {
val byAnnotation = _findAnnotation(m, JsonProperty::class.java)
val byAnnotation = _findAnnotation(m, JSON_PROPERTY_CLASS)
?.let { if (it.required) return true else false }

return cache.getJmClass(m.member.declaringClass)?.let {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ internal class ConstructorValueCreator<T : Any>(
val parameterSize = it.size
val temp = it.copyOf(parameterSize + maskSize + 1)
for (i in 0 until maskSize) {
temp[it.size + i] = Int::class.javaPrimitiveType
temp[it.size + i] = Int::class.java
}
temp[parameterSize + maskSize] = defaultConstructorMarker

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
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
Expand Down Expand Up @@ -50,9 +51,9 @@ internal class MethodValueCreator<T>(
temp[0] = companionObjectClass // companion object
parameterTypes.copyInto(temp, 1) // parameter types
for (i in (valueParameterSize + 1)..(valueParameterSize + maskSize)) { // masks
temp[i] = Int::class.javaPrimitiveType
temp[i] = Int::class.java
}
temp[valueParameterSize + maskSize + 1] = Object::class.java // maker
temp[valueParameterSize + maskSize + 1] = ANY_CLASS // maker
temp
} as Array<Class<*>>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.JavaType
import com.fasterxml.jackson.databind.type.TypeFactory
import com.fasterxml.jackson.databind.util.StdConverter
import io.github.projectmapk.jackson.module.kogera.JavaDuration
import io.github.projectmapk.jackson.module.kogera.KOTLIN_DURATION_CLASS
import io.github.projectmapk.jackson.module.kogera.KotlinDuration
import io.github.projectmapk.jackson.module.kogera.ValueClassBoxConverter
import kotlin.time.toJavaDuration
Expand All @@ -20,7 +21,7 @@ internal class SequenceToIteratorConverter(private val input: JavaType) : StdCon
}

internal object KotlinDurationValueToJavaDurationConverter : StdConverter<Long, JavaDuration>() {
private val boxConverter by lazy { ValueClassBoxConverter(Long::class.java, KotlinDuration::class.java) }
private val boxConverter by lazy { ValueClassBoxConverter(Long::class.java, KOTLIN_DURATION_CLASS) }

override fun convert(value: Long): JavaDuration = KotlinToJavaDurationConverter.convert(boxConverter.convert(value))
}
Expand Down

0 comments on commit 6e4007d

Please sign in to comment.