summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Shanshin <sergey.shanshin@jetbrains.com>2022-09-07 12:47:47 +0300
committerGitHub <noreply@github.com>2022-09-07 12:47:47 +0300
commit2825e213ac584cb3fb8c4329b9782020be0bef7b (patch)
treec119b0b58b134051bccdf3e9bf3ac9c2a12cc694
parent49dad8635d31d3105478807637b868c04c150f7e (diff)
downloadkotlinx.serialization-2825e213ac584cb3fb8c4329b9782020be0bef7b.tar.gz
Implemented serializers caching for lookup
-rw-r--r--benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/LookupOverheadBenchmark.kt69
-rw-r--r--build.gradle2
-rw-r--r--core/commonMain/src/kotlinx/serialization/Serializers.kt123
-rw-r--r--core/commonMain/src/kotlinx/serialization/SerializersCache.kt74
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/Platform.common.kt33
-rw-r--r--core/jsMain/src/kotlinx/serialization/internal/Platform.kt16
-rw-r--r--core/jvmMain/src/kotlinx/serialization/internal/Caching.kt112
-rw-r--r--core/jvmMain/src/kotlinx/serialization/internal/Platform.kt18
-rw-r--r--core/jvmMain/src/kotlinx/serialization/internal/SuppressAnimalSniffer.kt13
-rw-r--r--core/nativeMain/src/kotlinx/serialization/internal/Platform.kt17
10 files changed, 424 insertions, 53 deletions
diff --git a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/LookupOverheadBenchmark.kt b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/LookupOverheadBenchmark.kt
new file mode 100644
index 00000000..ddfc5792
--- /dev/null
+++ b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/LookupOverheadBenchmark.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.benchmarks.json
+
+import kotlinx.serialization.*
+import kotlinx.serialization.builtins.*
+import kotlinx.serialization.json.*
+import org.openjdk.jmh.annotations.*
+import java.util.concurrent.*
+
+@Warmup(iterations = 7, time = 1)
+@Measurement(iterations = 7, time = 1)
+@BenchmarkMode(Mode.Throughput)
+@OutputTimeUnit(TimeUnit.MICROSECONDS)
+@State(Scope.Benchmark)
+@Fork(2)
+open class LookupOverheadBenchmark {
+
+ @Serializable
+ class Holder(val a: String)
+
+ @Serializable
+ class Generic<T>(val a: T)
+
+ @Serializable
+ class DoubleGeneric<T1, T2>(val a: T1, val b: T2)
+
+ @Serializable
+ class PentaGeneric<T1, T2, T3, T4, T5>(val a: T1, val b: T2, val c: T3, val d: T4, val e: T5)
+
+ private val data = """{"a":""}"""
+ private val doubleData = """{"a":"","b":0}"""
+ private val pentaData = """{"a":"","b":0,"c":1,"d":true,"e":" "}"""
+
+ @Serializable
+ object Object
+
+ @Benchmark
+ fun dataReified() = Json.decodeFromString<Holder>(data)
+
+ @Benchmark
+ fun dataPlain() = Json.decodeFromString(Holder.serializer(), data)
+
+ @Benchmark
+ fun genericReified() = Json.decodeFromString<Generic<String>>(data)
+
+ @Benchmark
+ fun genericPlain() = Json.decodeFromString(Generic.serializer(String.serializer()), data)
+
+ @Benchmark
+ fun doubleGenericReified() = Json.decodeFromString<DoubleGeneric<String, Int>>(doubleData)
+
+ @Benchmark
+ fun doubleGenericPlain() = Json.decodeFromString(DoubleGeneric.serializer(String.serializer(), Int.serializer()), doubleData)
+
+ @Benchmark
+ fun pentaGenericReified() = Json.decodeFromString<PentaGeneric<String, Int, Long, Boolean, Char>>(pentaData)
+
+ @Benchmark
+ fun pentaGenericPlain() = Json.decodeFromString(PentaGeneric.serializer(String.serializer(), Int.serializer(), Long.serializer(), Boolean.serializer(), Char.serializer()), pentaData)
+
+ @Benchmark
+ fun objectReified() = Json.decodeFromString<Object>("{}")
+
+ @Benchmark
+ fun objectPlain() = Json.decodeFromString(Object.serializer(), "{}")
+}
diff --git a/build.gradle b/build.gradle
index 0d21932a..f363e1d2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -173,7 +173,7 @@ subprojects {
afterEvaluate { // Can be applied only when the project is evaluated
animalsniffer {
sourceSets = [sourceSets.main]
- annotation = "kotlinx.serialization.json.internal.SuppressAnimalSniffer"
+ annotation = (name == "kotlinx-serialization-core")? "kotlinx.serialization.internal.SuppressAnimalSniffer" : "kotlinx.serialization.json.internal.SuppressAnimalSniffer"
}
dependencies {
signature 'net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature'
diff --git a/core/commonMain/src/kotlinx/serialization/Serializers.kt b/core/commonMain/src/kotlinx/serialization/Serializers.kt
index ff35a9f1..8063e1a8 100644
--- a/core/commonMain/src/kotlinx/serialization/Serializers.kt
+++ b/core/commonMain/src/kotlinx/serialization/Serializers.kt
@@ -66,9 +66,8 @@ public fun SerializersModule.serializer(type: KType): KSerializer<Any?> =
* Returns `null` if serializer cannot be created (provided [type] or its type argument is not serializable and is not registered in [this] module).
*/
@OptIn(ExperimentalSerializationApi::class)
-public fun SerializersModule.serializerOrNull(type: KType): KSerializer<Any?>? {
- return serializerByKTypeImpl(type, failOnMissingTypeArgSerializer = false)
-}
+public fun SerializersModule.serializerOrNull(type: KType): KSerializer<Any?>? =
+ serializerByKTypeImpl(type, failOnMissingTypeArgSerializer = false)
@OptIn(ExperimentalSerializationApi::class)
private fun SerializersModule.serializerByKTypeImpl(
@@ -79,54 +78,47 @@ private fun SerializersModule.serializerByKTypeImpl(
val isNullable = type.isMarkedNullable
val typeArguments = type.arguments
.map { requireNotNull(it.type) { "Star projections in type arguments are not allowed, but had $type" } }
- val result: KSerializer<Any>? = when {
- typeArguments.isEmpty() -> rootClass.serializerOrNull() ?: getContextual(rootClass)
- else -> builtinSerializer(typeArguments, rootClass, failOnMissingTypeArgSerializer)
- }?.cast()
- return result?.nullable(isNullable)
-}
-@OptIn(ExperimentalSerializationApi::class)
-private fun SerializersModule.builtinSerializer(
- typeArguments: List<KType>,
- rootClass: KClass<Any>,
- failOnMissingTypeArgSerializer: Boolean
-): KSerializer<out Any>? {
- val serializers = if (failOnMissingTypeArgSerializer)
- typeArguments.map(::serializer)
- else {
- typeArguments.map { serializerOrNull(it) ?: return null }
+ val cachedSerializer = if (typeArguments.isEmpty()) {
+ findCachedSerializer(rootClass, isNullable)
+ } else {
+ val cachedResult = findParametrizedCachedSerializer(rootClass, typeArguments, isNullable)
+ if (failOnMissingTypeArgSerializer) {
+ cachedResult.getOrNull()
+ } else {
+ // return null if error occurred - serializer for parameter(s) was not found
+ cachedResult.getOrElse { return null }
+ }
}
- // Array is not supported, see KT-32839
- return when (rootClass) {
- Collection::class, List::class, MutableList::class, ArrayList::class -> ArrayListSerializer(serializers[0])
- HashSet::class -> HashSetSerializer(serializers[0])
- Set::class, MutableSet::class, LinkedHashSet::class -> LinkedHashSetSerializer(serializers[0])
- HashMap::class -> HashMapSerializer(serializers[0], serializers[1])
- Map::class, MutableMap::class, LinkedHashMap::class -> LinkedHashMapSerializer(
- serializers[0],
- serializers[1]
+ cachedSerializer?.let { return it }
+
+ // slow path to find contextual serializers in serializers module
+ val contextualSerializer: KSerializer<out Any?>? = if (typeArguments.isEmpty()) {
+ getContextual(rootClass)
+ } else {
+ val serializers = serializersForParameters(typeArguments, failOnMissingTypeArgSerializer) ?: return null
+ // first, we look among the built-in serializers, because the parameter could be contextual
+ rootClass.parametrizedSerializerOrNull(typeArguments, serializers) ?: getContextual(
+ rootClass,
+ serializers
)
- Map.Entry::class -> MapEntrySerializer(serializers[0], serializers[1])
- Pair::class -> PairSerializer(serializers[0], serializers[1])
- Triple::class -> TripleSerializer(serializers[0], serializers[1], serializers[2])
- else -> {
- if (isReferenceArray(rootClass)) {
- return ArraySerializer<Any, Any?>(typeArguments[0].classifier as KClass<Any>, serializers[0]).cast()
- }
- val args = serializers.toTypedArray()
- rootClass.constructSerializerForGivenTypeArgs(*args)
- ?: reflectiveOrContextual(rootClass, serializers)
- }
}
+ return contextualSerializer?.cast<Any>()?.nullable(isNullable)
}
-@OptIn(ExperimentalSerializationApi::class)
-internal fun <T : Any> SerializersModule.reflectiveOrContextual(
- kClass: KClass<T>,
- typeArgumentsSerializers: List<KSerializer<Any?>>
-): KSerializer<T>? {
- return kClass.serializerOrNull() ?: getContextual(kClass, typeArgumentsSerializers)
+/**
+ * Returns null only if `failOnMissingTypeArgSerializer == false` and at least one parameter serializer not found.
+ */
+internal fun SerializersModule.serializersForParameters(
+ typeArguments: List<KType>,
+ failOnMissingTypeArgSerializer: Boolean
+): List<KSerializer<Any?>>? {
+ val serializers = if (failOnMissingTypeArgSerializer) {
+ typeArguments.map { serializer(it) }
+ } else {
+ typeArguments.map { serializerOrNull(it) ?: return null }
+ }
+ return serializers
}
/**
@@ -179,6 +171,47 @@ public fun <T : Any> KClass<T>.serializer(): KSerializer<T> = serializerOrNull()
public fun <T : Any> KClass<T>.serializerOrNull(): KSerializer<T>? =
compiledSerializerImpl() ?: builtinSerializerOrNull()
+internal fun KClass<Any>.parametrizedSerializerOrNull(
+ types: List<KType>,
+ serializers: List<KSerializer<Any?>>
+): KSerializer<out Any>? {
+ // builtin first because some standard parametrized interfaces (e.g. Map) must use builtin serializer but not polymorphic
+ return builtinParametrizedSerializer(types, serializers) ?: compiledParametrizedSerializer(serializers)
+}
+
+
+private fun KClass<Any>.compiledParametrizedSerializer(serializers: List<KSerializer<Any?>>): KSerializer<out Any>? {
+ return constructSerializerForGivenTypeArgs(*serializers.toTypedArray())
+}
+
+@OptIn(ExperimentalSerializationApi::class)
+private fun KClass<Any>.builtinParametrizedSerializer(
+ typeArguments: List<KType>,
+ serializers: List<KSerializer<Any?>>,
+): KSerializer<out Any>? {
+ // Array is not supported, see KT-32839
+ return when (this) {
+ Collection::class, List::class, MutableList::class, ArrayList::class -> ArrayListSerializer(serializers[0])
+ HashSet::class -> HashSetSerializer(serializers[0])
+ Set::class, MutableSet::class, LinkedHashSet::class -> LinkedHashSetSerializer(serializers[0])
+ HashMap::class -> HashMapSerializer(serializers[0], serializers[1])
+ Map::class, MutableMap::class, LinkedHashMap::class -> LinkedHashMapSerializer(
+ serializers[0],
+ serializers[1]
+ )
+ Map.Entry::class -> MapEntrySerializer(serializers[0], serializers[1])
+ Pair::class -> PairSerializer(serializers[0], serializers[1])
+ Triple::class -> TripleSerializer(serializers[0], serializers[1], serializers[2])
+ else -> {
+ if (isReferenceArray(this)) {
+ ArraySerializer(typeArguments[0].classifier as KClass<Any>, serializers[0])
+ } else {
+ null
+ }
+ }
+ }
+}
+
private fun <T : Any> KSerializer<T>.nullable(shouldBeNullable: Boolean): KSerializer<T?> {
if (shouldBeNullable) return nullable
return this as KSerializer<T?>
diff --git a/core/commonMain/src/kotlinx/serialization/SerializersCache.kt b/core/commonMain/src/kotlinx/serialization/SerializersCache.kt
new file mode 100644
index 00000000..b1481884
--- /dev/null
+++ b/core/commonMain/src/kotlinx/serialization/SerializersCache.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization
+
+import kotlinx.serialization.builtins.nullable
+import kotlinx.serialization.internal.cast
+import kotlinx.serialization.internal.createCache
+import kotlinx.serialization.internal.createParametrizedCache
+import kotlinx.serialization.modules.EmptySerializersModule
+import kotlin.native.concurrent.ThreadLocal
+import kotlin.reflect.KClass
+import kotlin.reflect.KType
+
+
+/**
+ * Cache for non-null non-parametrized and non-contextual serializers.
+ */
+@ThreadLocal
+private val SERIALIZERS_CACHE = createCache { it.serializerOrNull() }
+
+/**
+ * Cache for nullable non-parametrized and non-contextual serializers.
+ */
+@ThreadLocal
+private val SERIALIZERS_CACHE_NULLABLE = createCache<Any?> { it.serializerOrNull()?.nullable?.cast() }
+
+/**
+ * Cache for non-null parametrized and non-contextual serializers.
+ */
+@ThreadLocal
+private val PARAMETRIZED_SERIALIZERS_CACHE = createParametrizedCache { clazz, types ->
+ val serializers = EmptySerializersModule().serializersForParameters(types, true)!!
+ clazz.parametrizedSerializerOrNull(types, serializers)
+}
+
+/**
+ * Cache for nullable parametrized and non-contextual serializers.
+ */
+@ThreadLocal
+private val PARAMETRIZED_SERIALIZERS_CACHE_NULLABLE = createParametrizedCache<Any?> { clazz, types ->
+ val serializers = EmptySerializersModule().serializersForParameters(types, true)!!
+ clazz.parametrizedSerializerOrNull(types, serializers)?.nullable?.cast()
+}
+
+/**
+ * Find cacheable serializer in the cache.
+ * If serializer is cacheable but missed in cache - it will be created, placed into the cache and returned.
+ */
+internal fun findCachedSerializer(clazz: KClass<Any>, isNullable: Boolean): KSerializer<Any?>? {
+ return if (!isNullable) {
+ SERIALIZERS_CACHE.get(clazz)?.cast()
+ } else {
+ SERIALIZERS_CACHE_NULLABLE.get(clazz)
+ }
+}
+
+/**
+ * Find cacheable parametrized serializer in the cache.
+ * If serializer is cacheable but missed in cache - it will be created, placed into the cache and returned.
+ */
+internal fun findParametrizedCachedSerializer(
+ clazz: KClass<Any>,
+ types: List<KType>,
+ isNullable: Boolean
+): Result<KSerializer<Any?>?> {
+ return if (!isNullable) {
+ @Suppress("UNCHECKED_CAST")
+ PARAMETRIZED_SERIALIZERS_CACHE.get(clazz, types) as Result<KSerializer<Any?>?>
+ } else {
+ PARAMETRIZED_SERIALIZERS_CACHE_NULLABLE.get(clazz, types)
+ }
+}
diff --git a/core/commonMain/src/kotlinx/serialization/internal/Platform.common.kt b/core/commonMain/src/kotlinx/serialization/internal/Platform.common.kt
index fde2c36a..9c4bad7c 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/Platform.common.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/Platform.common.kt
@@ -130,6 +130,18 @@ internal expect fun BooleanArray.getChecked(index: Int): Boolean
internal expect fun <T : Any> KClass<T>.compiledSerializerImpl(): KSerializer<T>?
+/**
+ * Create serializers cache for non-parametrized and non-contextual serializers.
+ * The activity and type of cache is determined for a specific platform and a specific environment.
+ */
+internal expect fun <T> createCache(factory: (KClass<*>) -> KSerializer<T>?): SerializerCache<T>
+
+/**
+ * Create serializers cache for parametrized and non-contextual serializers. Parameters also non-contextual.
+ * The activity and type of cache is determined for a specific platform and a specific environment.
+ */
+internal expect fun <T> createParametrizedCache(factory: (KClass<Any>, List<KType>) -> KSerializer<T>?): ParametrizedSerializerCache<T>
+
internal expect fun <T : Any, E : T?> ArrayList<E>.toNativeArrayImpl(eClass: KClass<T>): Array<E>
/**
@@ -145,3 +157,24 @@ internal expect fun Any.isInstanceOf(kclass: KClass<*>): Boolean
internal inline fun <T, K> Iterable<T>.elementsHashCodeBy(selector: (T) -> K): Int {
return fold(1) { hash, element -> 31 * hash + selector(element).hashCode() }
}
+
+/**
+ * Cache class for non-parametrized and non-contextual serializers.
+ */
+internal interface SerializerCache<T> {
+ /**
+ * Returns cached serializer or `null` if serializer not found.
+ */
+ fun get(key: KClass<Any>): KSerializer<T>?
+}
+
+/**
+ * Cache class for parametrized and non-contextual serializers.
+ */
+internal interface ParametrizedSerializerCache<T> {
+ /**
+ * Returns successful result with cached serializer or `null` if root serializer not found.
+ * If no serializer was found for the parameters, then result contains an exception.
+ */
+ fun get(key: KClass<Any>, types: List<KType> = emptyList()): Result<KSerializer<T>?>
+}
diff --git a/core/jsMain/src/kotlinx/serialization/internal/Platform.kt b/core/jsMain/src/kotlinx/serialization/internal/Platform.kt
index 25c48146..a9534dda 100644
--- a/core/jsMain/src/kotlinx/serialization/internal/Platform.kt
+++ b/core/jsMain/src/kotlinx/serialization/internal/Platform.kt
@@ -20,6 +20,22 @@ internal actual fun BooleanArray.getChecked(index: Int): Boolean {
internal actual fun <T : Any> KClass<T>.compiledSerializerImpl(): KSerializer<T>? =
this.constructSerializerForGivenTypeArgs() ?: this.js.asDynamic().Companion?.serializer() as? KSerializer<T>
+internal actual fun <T> createCache(factory: (KClass<*>) -> KSerializer<T>?): SerializerCache<T> {
+ return object: SerializerCache<T> {
+ override fun get(key: KClass<Any>): KSerializer<T>? {
+ return factory(key)
+ }
+ }
+}
+
+internal actual fun <T> createParametrizedCache(factory: (KClass<Any>, List<KType>) -> KSerializer<T>?): ParametrizedSerializerCache<T> {
+ return object: ParametrizedSerializerCache<T> {
+ override fun get(key: KClass<Any>, types: List<KType>): Result<KSerializer<T>?> {
+ return kotlin.runCatching { factory(key, types) }
+ }
+ }
+}
+
internal actual fun <T : Any, E : T?> ArrayList<E>.toNativeArrayImpl(eClass: KClass<T>): Array<E> = toTypedArray()
internal actual fun Any.isInstanceOf(kclass: KClass<*>): Boolean = kclass.isInstance(this)
diff --git a/core/jvmMain/src/kotlinx/serialization/internal/Caching.kt b/core/jvmMain/src/kotlinx/serialization/internal/Caching.kt
new file mode 100644
index 00000000..39cec936
--- /dev/null
+++ b/core/jvmMain/src/kotlinx/serialization/internal/Caching.kt
@@ -0,0 +1,112 @@
+/*
+ * 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.util.concurrent.ConcurrentHashMap
+import kotlin.reflect.KClass
+import kotlin.reflect.KType
+
+/*
+ * 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 = runCatching {
+ Class.forName("java.lang.ClassValue")
+}.map { true }.getOrDefault(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 <T> createCache(factory: (KClass<*>) -> KSerializer<T>?): SerializerCache<T> {
+ 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 <T> createParametrizedCache(factory: (KClass<Any>, List<KType>) -> KSerializer<T>?): ParametrizedSerializerCache<T> {
+ return if (useClassValue) ClassValueParametrizedCache(factory) else ConcurrentHashMapParametrizedCache(factory)
+}
+
+@SuppressAnimalSniffer
+private class ClassValueCache<T>(private val compute: (KClass<*>) -> KSerializer<T>?) : SerializerCache<T> {
+ private val classValue = initClassValue()
+
+ private fun initClassValue() = object : ClassValue<CacheEntry<T>>() {
+ /*
+ * Since during the computing of the value for the `ClassValue` entry, we do not know whether a nullable
+ * serializer is needed, so we may need to differentiate nullable/non-null caches by a level higher
+ */
+ override fun computeValue(type: Class<*>): CacheEntry<T> {
+ return CacheEntry(compute(type.kotlin))
+ }
+ }
+
+ override fun get(key: KClass<Any>): KSerializer<T>? = classValue[key.java].serializer
+}
+
+@SuppressAnimalSniffer
+private class ClassValueParametrizedCache<T>(private val compute: (KClass<Any>, List<KType>) -> KSerializer<T>?) : ParametrizedSerializerCache<T> {
+ private val classValue = initClassValue()
+
+ private fun initClassValue() = object : ClassValue<ParametrizedCacheEntry<T>>() {
+ /*
+ * Since during the computing of the value for the `ClassValue` entry, we do not know whether a nullable
+ * serializer is needed, so we may need to differentiate nullable/non-null caches by a level higher
+ */
+ override fun computeValue(type: Class<*>): ParametrizedCacheEntry<T> {
+ return ParametrizedCacheEntry()
+ }
+ }
+
+ override fun get(key: KClass<Any>, types: List<KType>): Result<KSerializer<T>?> =
+ classValue[key.java].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<T>(private val compute: (KClass<*>) -> KSerializer<T>?) : SerializerCache<T> {
+ private val cache = ConcurrentHashMap<Class<*>, CacheEntry<T>>()
+
+ override fun get(key: KClass<Any>): KSerializer<T>? {
+ return cache.getOrPut(key.java) {
+ CacheEntry(compute(key))
+ }.serializer
+ }
+}
+
+
+
+private class ConcurrentHashMapParametrizedCache<T>(private val compute: (KClass<Any>, List<KType>) -> KSerializer<T>?) : ParametrizedSerializerCache<T> {
+ private val cache = ConcurrentHashMap<Class<*>, ParametrizedCacheEntry<T>>()
+
+ override fun get(key: KClass<Any>, types: List<KType>): Result<KSerializer<T>?> {
+ return cache.getOrPut(key.java) { ParametrizedCacheEntry() }
+ .computeIfAbsent(types) { compute(key, types) }
+ }
+}
+
+private class CacheEntry<T>(@JvmField val serializer: KSerializer<T>?)
+
+private class ParametrizedCacheEntry<T> {
+ private val serializers: ConcurrentHashMap<List<KType>, Result<KSerializer<T>?>> = ConcurrentHashMap()
+ inline fun computeIfAbsent(types: List<KType>, producer: () -> KSerializer<T>?): Result<KSerializer<T>?> {
+ return serializers.getOrPut(types) {
+ kotlin.runCatching { producer() }
+ }
+ }
+}
+
diff --git a/core/jvmMain/src/kotlinx/serialization/internal/Platform.kt b/core/jvmMain/src/kotlinx/serialization/internal/Platform.kt
index 7c16650b..1feb842e 100644
--- a/core/jvmMain/src/kotlinx/serialization/internal/Platform.kt
+++ b/core/jvmMain/src/kotlinx/serialization/internal/Platform.kt
@@ -61,7 +61,11 @@ internal fun <T: Any> Class<T>.constructSerializerForGivenTypeArgs(vararg args:
}
if (fromNamedCompanion != null) return fromNamedCompanion
// Check for polymorphic
- return polymorphicSerializer()
+ return if (isPolymorphicSerializer()) {
+ PolymorphicSerializer(this.kotlin)
+ } else {
+ null
+ }
}
private fun <T: Any> Class<T>.isNotAnnotated(): Boolean {
@@ -72,19 +76,19 @@ private fun <T: Any> Class<T>.isNotAnnotated(): Boolean {
getAnnotation(Polymorphic::class.java) == null
}
-private fun <T: Any> Class<T>.polymorphicSerializer(): KSerializer<T>? {
+private fun <T: Any> Class<T>.isPolymorphicSerializer(): Boolean {
/*
* Last resort: check for @Polymorphic or Serializable(with = PolymorphicSerializer::class)
* annotations.
*/
if (getAnnotation(Polymorphic::class.java) != null) {
- return PolymorphicSerializer(this.kotlin)
+ return true
}
val serializable = getAnnotation(Serializable::class.java)
if (serializable != null && serializable.with == PolymorphicSerializer::class) {
- return PolymorphicSerializer(this.kotlin)
+ return true
}
- return null
+ return false
}
private fun <T: Any> Class<T>.interfaceSerializer(): KSerializer<T>? {
@@ -125,9 +129,9 @@ private fun Class<*>.companionOrNull() =
}
@Suppress("UNCHECKED_CAST")
-private fun <T : Any> Class<T>.createEnumSerializer(): KSerializer<T>? {
+private fun <T : Any> Class<T>.createEnumSerializer(): KSerializer<T> {
val constants = enumConstants
- return EnumSerializer(canonicalName, constants as Array<out Enum<*>>) as? KSerializer<T>
+ return EnumSerializer(canonicalName, constants as Array<out Enum<*>>) as KSerializer<T>
}
private fun <T : Any> Class<T>.findObjectSerializer(): KSerializer<T>? {
diff --git a/core/jvmMain/src/kotlinx/serialization/internal/SuppressAnimalSniffer.kt b/core/jvmMain/src/kotlinx/serialization/internal/SuppressAnimalSniffer.kt
new file mode 100644
index 00000000..7b3cc310
--- /dev/null
+++ b/core/jvmMain/src/kotlinx/serialization/internal/SuppressAnimalSniffer.kt
@@ -0,0 +1,13 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.internal
+
+/**
+ * Suppresses Animal Sniffer plugin errors for certain classes.
+ * Such classes are not available in Android API, but used only for JVM.
+ */
+@Retention(AnnotationRetention.BINARY)
+@Target(AnnotationTarget.CLASS)
+internal annotation class SuppressAnimalSniffer
diff --git a/core/nativeMain/src/kotlinx/serialization/internal/Platform.kt b/core/nativeMain/src/kotlinx/serialization/internal/Platform.kt
index e24c1820..1c0d5ab3 100644
--- a/core/nativeMain/src/kotlinx/serialization/internal/Platform.kt
+++ b/core/nativeMain/src/kotlinx/serialization/internal/Platform.kt
@@ -45,6 +45,23 @@ internal actual fun <T : Any> KClass<T>.constructSerializerForGivenTypeArgs(vara
internal actual fun <T : Any> KClass<T>.compiledSerializerImpl(): KSerializer<T>? =
this.constructSerializerForGivenTypeArgs()
+
+internal actual fun <T> createCache(factory: (KClass<*>) -> KSerializer<T>?): SerializerCache<T> {
+ return object: SerializerCache<T> {
+ override fun get(key: KClass<Any>): KSerializer<T>? {
+ return factory(key)
+ }
+ }
+}
+
+internal actual fun <T> createParametrizedCache(factory: (KClass<Any>, List<KType>) -> KSerializer<T>?): ParametrizedSerializerCache<T> {
+ return object: ParametrizedSerializerCache<T> {
+ override fun get(key: KClass<Any>, types: List<KType>): Result<KSerializer<T>?> {
+ return kotlin.runCatching { factory(key, types) }
+ }
+ }
+}
+
internal actual fun <T : Any, E : T?> ArrayList<E>.toNativeArrayImpl(eClass: KClass<T>): Array<E> {
val result = arrayOfAnyNulls<E>(size)
var index = 0