From d02d26428ea4cfb3a6987deb1e360a119a8fb0e6 Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Tue, 16 Jan 2024 12:35:30 +0000 Subject: [PATCH 1/2] Handle kotlin / JVM erasure of types It has been observed that the Kotlin can behave differently during reflection and return Type where normally it would report the correct type. Checking for erasure allows the bson-kotlin library to handle these cases and return the expected type. JAVA-5292 --- .../org/bson/codecs/kotlin/DataClassCodec.kt | 11 ++++++- .../kotlin/DataClassCodecProviderTest.kt | 30 +++++++++++++++++++ .../bson/codecs/kotlin/samples/DataClasses.kt | 3 ++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/bson-kotlin/src/main/kotlin/org/bson/codecs/kotlin/DataClassCodec.kt b/bson-kotlin/src/main/kotlin/org/bson/codecs/kotlin/DataClassCodec.kt index 9027bec4574..21252c141bb 100644 --- a/bson-kotlin/src/main/kotlin/org/bson/codecs/kotlin/DataClassCodec.kt +++ b/bson-kotlin/src/main/kotlin/org/bson/codecs/kotlin/DataClassCodec.kt @@ -23,12 +23,14 @@ import kotlin.reflect.KParameter import kotlin.reflect.KProperty1 import kotlin.reflect.KType import kotlin.reflect.KTypeParameter +import kotlin.reflect.KTypeProjection import kotlin.reflect.full.createType import kotlin.reflect.full.findAnnotation import kotlin.reflect.full.findAnnotations import kotlin.reflect.full.hasAnnotation import kotlin.reflect.full.primaryConstructor import kotlin.reflect.jvm.javaType +import kotlin.reflect.jvm.jvmErasure import org.bson.BsonReader import org.bson.BsonType import org.bson.BsonWriter @@ -199,7 +201,7 @@ internal data class DataClassCodec( codecRegistry.getCodec( kParameter, (kParameter.type.classifier as KClass).javaObjectType, - kParameter.type.arguments.mapNotNull { typeMap[it.type] ?: it.type?.javaType }.toList()) + kParameter.type.arguments.mapNotNull { typeMap[it.type] ?: computeJavaType(it) }.toList()) } is KTypeParameter -> { when (val pType = typeMap[kParameter.type] ?: kParameter.type.javaType) { @@ -219,6 +221,13 @@ internal data class DataClassCodec( "Could not find codec for ${kParameter.name} with type ${kParameter.type}") } + private fun computeJavaType(kTypeProjection: KTypeProjection): Type? { + val javaType: Type = kTypeProjection.type?.javaType!! + return if (javaType == Any().javaClass) { + kTypeProjection.type?.jvmErasure?.javaObjectType + } else javaType + } + @Suppress("UNCHECKED_CAST") private fun CodecRegistry.getCodec(kParameter: KParameter, clazz: Class, types: List): Codec { val codec = diff --git a/bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/DataClassCodecProviderTest.kt b/bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/DataClassCodecProviderTest.kt index e0c7f9d1d1b..7b9e0bbb2ba 100644 --- a/bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/DataClassCodecProviderTest.kt +++ b/bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/DataClassCodecProviderTest.kt @@ -20,11 +20,22 @@ import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertTrue +import kotlin.time.Duration +import org.bson.BsonReader +import org.bson.BsonWriter +import org.bson.codecs.Codec +import org.bson.codecs.DecoderContext +import org.bson.codecs.EncoderContext import org.bson.codecs.configuration.CodecConfigurationException +import org.bson.codecs.configuration.CodecRegistries.fromCodecs +import org.bson.codecs.configuration.CodecRegistries.fromProviders +import org.bson.codecs.configuration.CodecRegistries.fromRegistries import org.bson.codecs.kotlin.samples.DataClassParameterized +import org.bson.codecs.kotlin.samples.DataClassWithJVMErasure import org.bson.codecs.kotlin.samples.DataClassWithSimpleValues import org.bson.conversions.Bson import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertThrows class DataClassCodecProviderTest { @@ -59,4 +70,23 @@ class DataClassCodecProviderTest { assertTrue { codec is DataClassCodec } assertEquals(DataClassWithSimpleValues::class.java, codec.encoderClass) } + + @Test + fun shouldBeAbleHandleDataClassWithJVMErasure() { + + class DurationCodec : Codec { + override fun encode(writer: BsonWriter, value: Duration, encoderContext: EncoderContext) = TODO() + override fun getEncoderClass(): Class = Duration::class.java + override fun decode(reader: BsonReader, decoderContext: DecoderContext): Duration = TODO() + } + + val registry = + fromRegistries( + fromCodecs(DurationCodec()), fromProviders(DataClassCodecProvider()), Bson.DEFAULT_CODEC_REGISTRY) + + val codec = assertDoesNotThrow { registry.get(DataClassWithJVMErasure::class.java) } + assertNotNull(codec) + assertTrue { codec is DataClassCodec } + assertEquals(DataClassWithJVMErasure::class.java, codec.encoderClass) + } } diff --git a/bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/samples/DataClasses.kt b/bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/samples/DataClasses.kt index eaa87ca603b..029b0814118 100644 --- a/bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/samples/DataClasses.kt +++ b/bson-kotlin/src/test/kotlin/org/bson/codecs/kotlin/samples/DataClasses.kt @@ -15,6 +15,7 @@ */ package org.bson.codecs.kotlin.samples +import kotlin.time.Duration import org.bson.BsonDocument import org.bson.BsonMaxKey import org.bson.BsonType @@ -159,3 +160,5 @@ data class DataClassWithFailingInit(val id: String) { } data class DataClassWithSequence(val value: Sequence) + +data class DataClassWithJVMErasure(val duration: Duration, val ints: List) From d3f3a55151f1d9ed6a68b05dba62f80f252e1450 Mon Sep 17 00:00:00 2001 From: Ross Lawley Date: Tue, 16 Jan 2024 14:57:15 +0000 Subject: [PATCH 2/2] Fix spotbugs --- .../src/main/kotlin/org/bson/codecs/kotlin/DataClassCodec.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bson-kotlin/src/main/kotlin/org/bson/codecs/kotlin/DataClassCodec.kt b/bson-kotlin/src/main/kotlin/org/bson/codecs/kotlin/DataClassCodec.kt index 21252c141bb..412a0483231 100644 --- a/bson-kotlin/src/main/kotlin/org/bson/codecs/kotlin/DataClassCodec.kt +++ b/bson-kotlin/src/main/kotlin/org/bson/codecs/kotlin/DataClassCodec.kt @@ -223,7 +223,7 @@ internal data class DataClassCodec( private fun computeJavaType(kTypeProjection: KTypeProjection): Type? { val javaType: Type = kTypeProjection.type?.javaType!! - return if (javaType == Any().javaClass) { + return if (javaType == Any::class.java) { kTypeProjection.type?.jvmErasure?.javaObjectType } else javaType }