/* * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.serialization.internal import kotlinx.serialization.KSerializer import java.lang.ref.SoftReference import java.util.concurrent.ConcurrentHashMap import kotlin.reflect.KClass import kotlin.reflect.KClassifier import kotlin.reflect.KType import kotlin.reflect.KTypeProjection /* * By default, we use ClassValue-based caches to avoid classloader leaks, * but ClassValue is not available on Android, thus we attempt to check it dynamically * and fallback to ConcurrentHashMap-based cache. */ private val useClassValue = try { Class.forName("java.lang.ClassValue") true } catch (_: Throwable) { false } /** * Creates a **strongly referenced** cache of values associated with [Class]. * Serializers are computed using provided [factory] function. * * `null` values are not supported, though there aren't any technical limitations. */ internal actual fun createCache(factory: (KClass<*>) -> KSerializer?): SerializerCache { return if (useClassValue) ClassValueCache(factory) else ConcurrentHashMapCache(factory) } /** * Creates a **strongly referenced** cache of values associated with [Class]. * Serializers are computed using provided [factory] function. * * `null` values are not supported, though there aren't any technical limitations. */ internal actual fun createParametrizedCache(factory: (KClass, List) -> KSerializer?): ParametrizedSerializerCache { return if (useClassValue) ClassValueParametrizedCache(factory) else ConcurrentHashMapParametrizedCache(factory) } private class ClassValueCache(val compute: (KClass<*>) -> KSerializer?) : SerializerCache { private val classValue = ClassValueReferences>() override fun get(key: KClass): KSerializer? { return classValue .getOrSet(key.java) { CacheEntry(compute(key)) } .serializer } } /** * A class that combines the capabilities of ClassValue and SoftReference. * Softly binds the calculated value to the specified class. * * [SoftReference] used to prevent class loaders from leaking, * since the value can transitively refer to an instance of type [Class], this may prevent the loader from * being collected during garbage collection. * * In the first calculation the value is cached, every time [getOrSet] is called, a pre-calculated value is returned. * * However, the value can be collected during garbage collection (thanks to [SoftReference]) * - in this case, when trying to call the [getOrSet] function, the value will be calculated again and placed in the cache. * * An important requirement for a function generating a value is that it must be stable, so that each time it is called for the same class, the function returns similar values. * In the case of serializers, these should be instances of the same class filled with equivalent values. */ @SuppressAnimalSniffer private class ClassValueReferences : ClassValue>() { override fun computeValue(type: Class<*>): MutableSoftReference { return MutableSoftReference() } inline fun getOrSet(key: Class<*>, crossinline factory: () -> T): T { val ref: MutableSoftReference = get(key) ref.reference.get()?.let { return it } // go to the slow path and create serializer with blocking, also wrap factory block return ref.getOrSetWithLock { factory() } } } /** * Wrapper over `SoftReference`, used to store a mutable value. */ private class MutableSoftReference { // volatile because of situations like https://stackoverflow.com/a/7855774 @JvmField @Volatile var reference: SoftReference = SoftReference(null) /* It is important that the monitor for synchronized is the `MutableSoftReference` of a specific class This way access to reference is blocked only for one serializable class, and not for all */ @Synchronized fun getOrSetWithLock(factory: () -> T): T { // exit function if another thread has already filled in the `reference` with non-null value reference.get()?.let { return it } val value = factory() reference = SoftReference(value) return value } } private class ClassValueParametrizedCache(private val compute: (KClass, List) -> KSerializer?) : ParametrizedSerializerCache { private val classValue = ClassValueReferences>() override fun get(key: KClass, types: List): Result?> { return classValue.getOrSet(key.java) { ParametrizedCacheEntry() } .computeIfAbsent(types) { compute(key, types) } } } /** * We no longer support Java 6, so the only place we use this cache is Android, where there * are no classloader leaks issue, thus we can safely use strong references and do not bother * with WeakReference wrapping. */ private class ConcurrentHashMapCache(private val compute: (KClass<*>) -> KSerializer?) : SerializerCache { private val cache = ConcurrentHashMap, CacheEntry>() override fun get(key: KClass): KSerializer? { return cache.getOrPut(key.java) { CacheEntry(compute(key)) }.serializer } } private class ConcurrentHashMapParametrizedCache(private val compute: (KClass, List) -> KSerializer?) : ParametrizedSerializerCache { private val cache = ConcurrentHashMap, ParametrizedCacheEntry>() override fun get(key: KClass, types: List): Result?> { return cache.getOrPut(key.java) { ParametrizedCacheEntry() } .computeIfAbsent(types) { compute(key, types) } } } /** * Wrapper for cacheable serializer of some type. * Used to store cached serializer or indicates that the serializer is not cacheable. * * If serializer for type is not cacheable then value of [serializer] is `null`. */ private class CacheEntry(@JvmField val serializer: KSerializer?) /** * Workaround of https://youtrack.jetbrains.com/issue/KT-54611 and https://github.com/Kotlin/kotlinx.serialization/issues/2065 */ private class KTypeWrapper(private val origin: KType) : KType { override val annotations: List get() = origin.annotations override val arguments: List get() = origin.arguments override val classifier: KClassifier? get() = origin.classifier override val isMarkedNullable: Boolean get() = origin.isMarkedNullable override fun equals(other: Any?): Boolean { if (other == null) return false if (origin != (other as? KTypeWrapper)?.origin) return false val kClassifier = classifier if (kClassifier is KClass<*>) { val otherClassifier = (other as? KType)?.classifier if (otherClassifier == null || otherClassifier !is KClass<*>) { return false } return kClassifier.java == otherClassifier.java } else { return false } } override fun hashCode(): Int { return origin.hashCode() } override fun toString(): String { return "KTypeWrapper: $origin" } } private class ParametrizedCacheEntry { private val serializers: ConcurrentHashMap, Result?>> = ConcurrentHashMap() inline fun computeIfAbsent(types: List, producer: () -> KSerializer?): Result?> { val wrappedTypes = types.map { KTypeWrapper(it) } return serializers.getOrPut(wrappedTypes) { kotlin.runCatching { producer() } } } }