From e0907e86192a25237bc5200fe22362fc29c82efd Mon Sep 17 00:00:00 2001 From: CharlieTap Date: Wed, 22 Mar 2023 13:31:32 +0000 Subject: [PATCH] EnumStringSerializer support --- gradle/libs.versions.toml | 4 +-- .../synk/processor/context/EncoderContext.kt | 9 +++++-- .../synk/processor/context/SynkPoetTypes.kt | 8 ++++++ .../tap/synk/processor/context/SynkSymbols.kt | 13 ++-------- .../processor/ext/KSClassDeclarationExt.kt | 9 +++++++ .../encoder/ClassDeclarationConverter.kt | 25 ++++++++++++++++--- .../processor/filespec/encoder/MapEncoder.kt | 15 ++++++++--- .../filespec/encoder/MapEncoderFileSpec.kt | 17 +++++++++++-- .../processor/SynkAdapterProcessorTest.kt | 6 ++++- .../kotlin/com/tap/synk/processor/fixtures.kt | 12 +++++++++ .../synk/serialize/EnumStringSerializer.kt | 17 +++++++++++++ 11 files changed, 111 insertions(+), 24 deletions(-) create mode 100644 synk/src/commonMain/kotlin/com/tap/synk/serialize/EnumStringSerializer.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 23e9e91..97c1e32 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,12 +6,12 @@ compile-sdk = "32" target-sdk = "32" min-sdk = "24" -version-name = "0.37" +version-name = "0.38" java-compiler-version = "19" java-bytecode-version = "11" -android-build-tools-plugin = "7.4.0-beta02" +android-build-tools-plugin = "8.0.0-alpha08" versions-plugin = "0.45.0" androidx-core = "1.8.0-alpha05" diff --git a/plugins/ksp-adapter-codegen/src/main/kotlin/com/tap/synk/processor/context/EncoderContext.kt b/plugins/ksp-adapter-codegen/src/main/kotlin/com/tap/synk/processor/context/EncoderContext.kt index 1f60f74..a76b9bf 100644 --- a/plugins/ksp-adapter-codegen/src/main/kotlin/com/tap/synk/processor/context/EncoderContext.kt +++ b/plugins/ksp-adapter-codegen/src/main/kotlin/com/tap/synk/processor/context/EncoderContext.kt @@ -1,7 +1,6 @@ package com.tap.synk.processor.context import com.google.devtools.ksp.innerArguments -import com.google.devtools.ksp.symbol.ClassKind import com.google.devtools.ksp.symbol.KSClassDeclaration import com.google.devtools.ksp.symbol.KSType import com.google.devtools.ksp.symbol.KSValueParameter @@ -11,6 +10,8 @@ import com.squareup.kotlinpoet.TypeName import com.squareup.kotlinpoet.ksp.toClassName import com.squareup.kotlinpoet.ksp.toTypeName import com.tap.synk.processor.ext.asType +import com.tap.synk.processor.ext.isEnum +import com.tap.synk.processor.ext.isObject internal data class EncoderContext( private val processorContext: ProcessorContext, @@ -44,7 +45,7 @@ internal data class EncoderContext( val serializerMap by lazy { serializers.fold(mutableMapOf>()) { acc, serializerDeclaration -> - val requiresInstantiation = serializerDeclaration.classKind != ClassKind.OBJECT + val requiresInstantiation = serializerDeclaration.isObject().not() acc.apply { val genericType = serializerDeclaration.superTypes.first().resolve().innerArguments.first().type!!.resolve() @@ -70,6 +71,10 @@ internal data class EncoderContext( serializerMap.contains(type.makeNotNullable()) } + val isEnum by lazy { + (type.declaration as KSClassDeclaration).isEnum() + } + val innerType by lazy { type.innerArguments.first().type?.resolve() ?: run { throw IllegalStateException("Inner type was not resolved for parameter $name") diff --git a/plugins/ksp-adapter-codegen/src/main/kotlin/com/tap/synk/processor/context/SynkPoetTypes.kt b/plugins/ksp-adapter-codegen/src/main/kotlin/com/tap/synk/processor/context/SynkPoetTypes.kt index 218931f..93d0d4f 100644 --- a/plugins/ksp-adapter-codegen/src/main/kotlin/com/tap/synk/processor/context/SynkPoetTypes.kt +++ b/plugins/ksp-adapter-codegen/src/main/kotlin/com/tap/synk/processor/context/SynkPoetTypes.kt @@ -61,6 +61,10 @@ internal class SynkPoetTypes( symbols.stringSerializer.toClassName() } + val enumSerializer by lazy { + symbols.enumStringSerializer.toClassName() + } + fun parameterizedMapEncoder(genericTypeName: TypeName): ParameterizedTypeName { return mapEncoderTypeName.parameterizedBy(genericTypeName) } @@ -69,6 +73,10 @@ internal class SynkPoetTypes( return stringSerializer.parameterizedBy(genericTypeName) } + fun parameterizedEnumStringSerializer(genericTypeName: TypeName) : ParameterizedTypeName { + return enumSerializer.parameterizedBy(genericTypeName) + } + fun primitiveEncoder(type: KSType): TypeName { return when (type) { symbols.boolType -> booleanEncoderTypeName diff --git a/plugins/ksp-adapter-codegen/src/main/kotlin/com/tap/synk/processor/context/SynkSymbols.kt b/plugins/ksp-adapter-codegen/src/main/kotlin/com/tap/synk/processor/context/SynkSymbols.kt index b85ed49..877b557 100644 --- a/plugins/ksp-adapter-codegen/src/main/kotlin/com/tap/synk/processor/context/SynkSymbols.kt +++ b/plugins/ksp-adapter-codegen/src/main/kotlin/com/tap/synk/processor/context/SynkSymbols.kt @@ -9,6 +9,7 @@ import com.tap.synk.adapter.SynkAdapter import com.tap.synk.encode.MapEncoder import com.tap.synk.processor.ext.asType import com.tap.synk.resolver.IDResolver +import com.tap.synk.serialize.EnumStringSerializer import com.tap.synk.serialize.StringSerializer internal class SynkSymbols(resolver: Resolver) { @@ -36,18 +37,8 @@ internal class SynkSymbols(resolver: Resolver) { val mapEncoder by lazy { resolver.getClassDeclarationByName>()!!.asType() } val synkAdapter by lazy { resolver.getClassDeclarationByName>()!!.asType() } val stringSerializer by lazy { resolver.getClassDeclarationByName>()!!.asType() } + val enumStringSerializer by lazy { resolver.getClassDeclarationByName>()!!.asType() } - val availableSerializers by lazy { -// val annotated = resolver.getSymbolsWithAnnotation(SynkSerializer::class.qualifiedName!!) -// annotated.fold(mutableMapOf()) { acc, annotated -> -// -// //verify extends string serializer -// annotated. -// -// // map T to concrete type knowing concrete = StringSerializer -// -// } - } fun isString(type: KSType): Boolean { return type == stringType diff --git a/plugins/ksp-adapter-codegen/src/main/kotlin/com/tap/synk/processor/ext/KSClassDeclarationExt.kt b/plugins/ksp-adapter-codegen/src/main/kotlin/com/tap/synk/processor/ext/KSClassDeclarationExt.kt index 647d6ef..79e60f5 100644 --- a/plugins/ksp-adapter-codegen/src/main/kotlin/com/tap/synk/processor/ext/KSClassDeclarationExt.kt +++ b/plugins/ksp-adapter-codegen/src/main/kotlin/com/tap/synk/processor/ext/KSClassDeclarationExt.kt @@ -1,6 +1,7 @@ package com.tap.synk.processor.ext import com.google.devtools.ksp.innerArguments +import com.google.devtools.ksp.symbol.ClassKind import com.google.devtools.ksp.symbol.KSClassDeclaration import com.google.devtools.ksp.symbol.KSDeclaration import com.google.devtools.ksp.symbol.Modifier @@ -18,6 +19,14 @@ internal fun KSClassDeclaration.isValueClass(): Boolean { return modifiers.contains(Modifier.VALUE) } +internal fun KSClassDeclaration.isObject(): Boolean { + return classKind == ClassKind.OBJECT +} + +internal fun KSClassDeclaration.isEnum(): Boolean { + return classKind == ClassKind.ENUM_CLASS +} + internal fun KSClassDeclaration.containsNestedClasses(symbols: SynkSymbols) : Boolean { return primaryConstructor?.parameters?.any { param -> val parameterType = param.type.resolve() diff --git a/plugins/ksp-adapter-codegen/src/main/kotlin/com/tap/synk/processor/filespec/encoder/ClassDeclarationConverter.kt b/plugins/ksp-adapter-codegen/src/main/kotlin/com/tap/synk/processor/filespec/encoder/ClassDeclarationConverter.kt index 8447861..c915b84 100644 --- a/plugins/ksp-adapter-codegen/src/main/kotlin/com/tap/synk/processor/filespec/encoder/ClassDeclarationConverter.kt +++ b/plugins/ksp-adapter-codegen/src/main/kotlin/com/tap/synk/processor/filespec/encoder/ClassDeclarationConverter.kt @@ -71,14 +71,14 @@ private fun deriveSubEncoderParameter(parameter: EncoderContext.DerivedParameter } context(EncoderContext) -private fun deriveSerializerParameter(parameter: EncoderContext.DerivedParameter): EncoderParameter.Serializer { +private fun deriveSerializerParameter(parameter: EncoderContext.DerivedParameter): EncoderParameter.CustomSerializer { val genericType = parameter.type.makeNotNullable() val parameterizedStringSerializer = poetTypes.parameterizedStringSerializer(genericType.toTypeName()) val (concreteType, requiresInstantiation) = serializerMap[genericType]!! - return EncoderParameter.Serializer( + return EncoderParameter.CustomSerializer( parameter.name, parameter.name + "Serializer", parameterizedStringSerializer, @@ -87,6 +87,20 @@ private fun deriveSerializerParameter(parameter: EncoderContext.DerivedParameter ) } +context(EncoderContext) +private fun deriveEnumSerializerParameter(parameter: EncoderContext.DerivedParameter): EncoderParameter.EnumSerializer { + + val genericType = parameter.type + val parameterizedEnumSerializer = poetTypes.parameterizedEnumStringSerializer(genericType.toTypeName()) + + return EncoderParameter.EnumSerializer( + parameter.name, + parameter.name + "Serializer", + parameter.type.toTypeName(), + parameterizedEnumSerializer + ) +} + context(EncoderContext) private fun deriveCompositeSubEncoderParameter(subClassDeclaration: KSClassDeclaration): EncoderParameter.CompositeSubEncoder { val name = subClassDeclaration.simpleName.asString() @@ -108,6 +122,8 @@ private fun deriveParameters(): List { deriveSubEncoderParameter(param) } else if (param.hasProvidedSerializer) { deriveSerializerParameter(param) + } else if(param.isEnum) { + deriveEnumSerializerParameter(param) } else null } @@ -174,6 +190,8 @@ private fun deriveEncodeFunction(): EncoderFunction { EncoderFunctionCodeBlockStandardEncodable.NestedClass(param.name, param.name + "MapEncoder") } else if(param.hasProvidedSerializer) { EncoderFunctionCodeBlockStandardEncodable.Serializable(param.name, param.name + "Serializer", param.type.isMarkedNullable) + } else if(param.isEnum) { + EncoderFunctionCodeBlockStandardEncodable.Serializable(param.name, param.name + "Serializer", param.type.isMarkedNullable) } else { deriveStandardEncodablePrimitive(param) } @@ -196,13 +214,14 @@ private fun deriveDecodeFunction(): EncoderFunction { deriveDelegateEncoderFunctionCodeBlock() } else { val encodables = derivedParameters.map { param -> - if (param.isInstanceOfCollection) { deriveStandardEncodableCollectionNestedClass(param) } else if(param.isInstanceOfDataClass) { EncoderFunctionCodeBlockStandardEncodable.NestedClass(param.name, param.name + "MapEncoder") } else if(param.hasProvidedSerializer) { EncoderFunctionCodeBlockStandardEncodable.Serializable(param.name, param.name + "Serializer", param.type.isMarkedNullable) + } else if(param.isEnum) { + EncoderFunctionCodeBlockStandardEncodable.Serializable(param.name, param.name + "Serializer", param.type.isMarkedNullable) } else { val conversion = if (!symbols.isString(param.type)) { symbols.stringDecodeFunction(param.type) diff --git a/plugins/ksp-adapter-codegen/src/main/kotlin/com/tap/synk/processor/filespec/encoder/MapEncoder.kt b/plugins/ksp-adapter-codegen/src/main/kotlin/com/tap/synk/processor/filespec/encoder/MapEncoder.kt index d3d396e..901b972 100644 --- a/plugins/ksp-adapter-codegen/src/main/kotlin/com/tap/synk/processor/filespec/encoder/MapEncoder.kt +++ b/plugins/ksp-adapter-codegen/src/main/kotlin/com/tap/synk/processor/filespec/encoder/MapEncoder.kt @@ -46,7 +46,7 @@ internal sealed interface EncoderParameter { ) : EncoderParameter // Value class and Custom Class serializer - data class Serializer( + data class CustomSerializer( val parameterName: String, val serializerVariableName: String, val genericTypeName: ParameterizedTypeName, @@ -54,18 +54,27 @@ internal sealed interface EncoderParameter { val instantiateSerializer: Boolean = false, ) : EncoderParameter + data class EnumSerializer( + val parameterName: String, + val serializerVariableName: String, + val enumType: TypeName, + val parameterizedStringSerializer: ParameterizedTypeName + ): EncoderParameter + fun variableName(): String = when (this) { is CompositeSubEncoder -> name is ParameterizedCollectionEncoder -> collectionEncoderVariableName is SubEncoder -> customEncoderVariableName - is Serializer -> serializerVariableName + is CustomSerializer -> serializerVariableName + is EnumSerializer -> serializerVariableName } fun variableType(): TypeName = when (this) { is CompositeSubEncoder -> genericType is ParameterizedCollectionEncoder -> genericMapEncoderTypeName is SubEncoder -> genericTypeName - is Serializer -> genericTypeName + is CustomSerializer -> genericTypeName + is EnumSerializer -> parameterizedStringSerializer } } diff --git a/plugins/ksp-adapter-codegen/src/main/kotlin/com/tap/synk/processor/filespec/encoder/MapEncoderFileSpec.kt b/plugins/ksp-adapter-codegen/src/main/kotlin/com/tap/synk/processor/filespec/encoder/MapEncoderFileSpec.kt index bdab7d2..6cbcde3 100644 --- a/plugins/ksp-adapter-codegen/src/main/kotlin/com/tap/synk/processor/filespec/encoder/MapEncoderFileSpec.kt +++ b/plugins/ksp-adapter-codegen/src/main/kotlin/com/tap/synk/processor/filespec/encoder/MapEncoderFileSpec.kt @@ -117,7 +117,12 @@ private fun encoderParameters(paramEncoders: List): List { + is EncoderParameter.CustomSerializer -> { + ParameterSpec.builder(encoderData.variableName(), encoderData.variableType()).apply { + defaultValue(encoderDefaultType(encoderData)) + }.build() + } + is EncoderParameter.EnumSerializer -> { ParameterSpec.builder(encoderData.variableName(), encoderData.variableType()).apply { defaultValue(encoderDefaultType(encoderData)) }.build() @@ -169,7 +174,7 @@ private fun encoderDefaultType(paramEncoder: EncoderParameter.SubEncoder): CodeB /** * FooBarMapEncoder() */ -private fun encoderDefaultType(paramEncoder: EncoderParameter.Serializer): CodeBlock { +private fun encoderDefaultType(paramEncoder: EncoderParameter.CustomSerializer): CodeBlock { return CodeBlock.builder().apply { if(paramEncoder.instantiateSerializer) { add("%T()", paramEncoder.concreteTypeName) @@ -179,6 +184,14 @@ private fun encoderDefaultType(paramEncoder: EncoderParameter.Serializer): CodeB }.build() } +/** + * EnumStringSerializer() + */ +private fun encoderDefaultType(paramEncoder: EncoderParameter.EnumSerializer): CodeBlock { + return CodeBlock.builder().apply { + add("EnumStringSerializer<%T>()", paramEncoder.enumType) + }.build() +} private fun constructor(parameterSpecs: List): FunSpec? { return if (parameterSpecs.isNotEmpty()) { diff --git a/plugins/ksp-adapter-codegen/src/test/kotlin/com/tap/synk/processor/SynkAdapterProcessorTest.kt b/plugins/ksp-adapter-codegen/src/test/kotlin/com/tap/synk/processor/SynkAdapterProcessorTest.kt index e6e6cf4..86ceda4 100644 --- a/plugins/ksp-adapter-codegen/src/test/kotlin/com/tap/synk/processor/SynkAdapterProcessorTest.kt +++ b/plugins/ksp-adapter-codegen/src/test/kotlin/com/tap/synk/processor/SynkAdapterProcessorTest.kt @@ -468,7 +468,7 @@ internal class SynkAdapterProcessorTest { @Test fun `compilation succeeds when target has a serializable property`() { - val compilationResult = compile(BAR_VALUE_CLASS, BAR_VALUE_CLASS_SERIALIZER, FOO_VALUE_CLASS, FOO_RESOLVER) + val compilationResult = compile(BAR_VALUE_CLASS, BAR_VALUE_CLASS_SERIALIZER, BAZ_ENUM_CLASS, FOO_VALUE_CLASS, FOO_RESOLVER) assertEquals(KotlinCompilation.ExitCode.OK, compilationResult.exitCode) assertSourceEquals( @@ -494,6 +494,7 @@ internal class SynkAdapterProcessorTest { package com.test.processor import com.tap.synk.encode.MapEncoder + import com.tap.synk.serialize.EnumStringSerializer import com.tap.synk.serialize.StringSerializer import kotlin.String import kotlin.collections.Map @@ -501,11 +502,13 @@ internal class SynkAdapterProcessorTest { public class FooMapEncoder( private val barSerializer: StringSerializer = BarSerializer, private val barNullSerializer: StringSerializer = BarSerializer, + private val bazSerializer: EnumStringSerializer = EnumStringSerializer(), ) : MapEncoder { public override fun encode(crdt: Foo): Map { val map = mutableMapOf() map["bar"] = barSerializer.serialize(crdt.bar) crdt.barNull?.let { map["barNull"] = barNullSerializer.serialize(crdt.barNull) } + map["baz"] = bazSerializer.serialize(crdt.baz) return map } @@ -513,6 +516,7 @@ internal class SynkAdapterProcessorTest { val crdt = Foo( barSerializer.deserialize(map["bar"]!!), map["barNull"]?.let { barNullSerializer.deserialize(map["barNull"]!!) }, + bazSerializer.deserialize(map["baz"]!!), ) return crdt } diff --git a/plugins/ksp-adapter-codegen/src/test/kotlin/com/tap/synk/processor/fixtures.kt b/plugins/ksp-adapter-codegen/src/test/kotlin/com/tap/synk/processor/fixtures.kt index d12ee82..35b0db8 100644 --- a/plugins/ksp-adapter-codegen/src/test/kotlin/com/tap/synk/processor/fixtures.kt +++ b/plugins/ksp-adapter-codegen/src/test/kotlin/com/tap/synk/processor/fixtures.kt @@ -232,6 +232,17 @@ internal val BAR_VALUE_CLASS_SERIALIZER = SourceFile.kotlin( """ ) +internal val BAZ_ENUM_CLASS = SourceFile.kotlin( + "Baz.kt", + """ + package com.test.processor + enum class Baz { + BIM, + BAM, + } + """ +) + internal val FOO_VALUE_CLASS = SourceFile.kotlin( "Foo.kt", """ @@ -239,6 +250,7 @@ internal val FOO_VALUE_CLASS = SourceFile.kotlin( data class Foo( private val bar: Bar, private val barNull: Bar?, + private val baz: Baz, ) """ ) diff --git a/synk/src/commonMain/kotlin/com/tap/synk/serialize/EnumStringSerializer.kt b/synk/src/commonMain/kotlin/com/tap/synk/serialize/EnumStringSerializer.kt new file mode 100644 index 0000000..1b375cd --- /dev/null +++ b/synk/src/commonMain/kotlin/com/tap/synk/serialize/EnumStringSerializer.kt @@ -0,0 +1,17 @@ +package com.tap.synk.serialize + +class EnumStringSerializer>( + private val values: Array +): StringSerializer { + override fun serialize(serializable: T): String { + return serializable.ordinal.toString() + } + + override fun deserialize(serialized: String): T { + return values.first { it.ordinal.toString() == serialized } + } +} + +inline fun > EnumStringSerializer(): EnumStringSerializer { + return EnumStringSerializer(enumValues()) +} \ No newline at end of file