-
-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathReflectionCache.kt
More file actions
103 lines (84 loc) · 4.32 KB
/
ReflectionCache.kt
File metadata and controls
103 lines (84 loc) · 4.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
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
// For ease of testing, maxCacheSize is limited only in KotlinModule.
internal class ReflectionCache(initialCacheSize: Int, maxCacheSize: Int) : Serializable {
companion object {
// Increment is required when properties that use LRUMap are changed.
private const val serialVersionUID = 4L
}
/**
* For frequently used JmClass and BoxedReturnType, reduce overhead by using Class and Method directly as key.
* For other caches, if the key type overlaps, wrap it.
*/
private sealed class OtherCacheKey<K : Any, V : Any> {
abstract val key: K
// The comparison was implemented directly because the decompiled results showed subtle efficiency.
final override fun equals(other: Any?): Boolean = (other as? OtherCacheKey<*, *>)
?.let { it::class == this::class && it.key == key } == true
// If the hashCode matches the raw key, the search efficiency is reduced, so it is displaced.
final override fun hashCode(): Int = key.hashCode() * 31
final override fun toString(): String = key.toString()
class ValueClassBoxConverter(
override val key: Class<*>,
) : OtherCacheKey<Class<*>, io.github.projectmapk.jackson.module.kogera.ValueClassBoxConverter<*, *>>()
class ValueClassUnboxConverter(
override val key: Class<*>,
) : OtherCacheKey<Class<*>, io.github.projectmapk.jackson.module.kogera.ValueClassUnboxConverter<*>>()
}
private val cache = LRUMap<Any, Any>(initialCacheSize, maxCacheSize)
private fun <T : Any> find(key: Any): T? = cache[key]?.let {
@Suppress("UNCHECKED_CAST")
it as T
}
@Suppress("UNCHECKED_CAST")
private fun <T : Any> putIfAbsent(key: Any, value: T): T = cache.putIfAbsent(key, value) as T? ?: value
fun getJmClass(clazz: Class<*>): JmClass? {
return find(clazz) ?: run {
val kmClass = clazz.toKmClass() ?: return null
// Do not parse super class for interfaces.
val superJmClass = if (!clazz.isInterface) {
clazz.superclass?.let {
// Stop parsing when `Object` is reached
if (it != ANY_CLASS) getJmClass(it) else null
}
} else {
null
}
val interfaceJmClasses = clazz.interfaces.mapNotNull { getJmClass(it) }
val value = JmClass(clazz, kmClass, superJmClass, interfaceJmClasses)
putIfAbsent(clazz, value)
}
}
private fun Method.getValueClassReturnType(): Class<*>? {
val kotlinProperty = getJmClass(declaringClass)?.findPropertyByGetter(this)
// Since there was no way to directly determine whether returnType is a value class or not,
// Class is restored and processed.
return kotlinProperty?.returnType?.reconstructClassOrNull()?.takeIf { it.isUnboxableValueClass() }
}
// Return boxed type on Kotlin for unboxed getters
fun findBoxedReturnType(getter: Method): Class<*>? {
val optional = find<Optional<Class<*>>>(getter)
return if (optional != null) {
optional
} else {
// If the return value of the getter is a value class,
// it will be serialized properly without doing anything.
// TODO: Verify the case where a value class encompasses another value class.
if (getter.returnType.isUnboxableValueClass()) return null
val value = Optional.ofNullable(getter.getValueClassReturnType())
putIfAbsent(getter, value)
}.orElse(null)
}
fun getValueClassBoxConverter(unboxedClass: Class<*>, valueClass: Class<*>): ValueClassBoxConverter<*, *> {
val key = OtherCacheKey.ValueClassBoxConverter(valueClass)
return find(key) ?: putIfAbsent(key, ValueClassBoxConverter(unboxedClass, valueClass))
}
fun getValueClassUnboxConverter(valueClass: Class<*>): ValueClassUnboxConverter<*> {
val key = OtherCacheKey.ValueClassUnboxConverter(valueClass)
return find(key) ?: putIfAbsent(key, ValueClassUnboxConverter(valueClass))
}
}