Skip to content

Commit

Permalink
EnumStringSerializer support
Browse files Browse the repository at this point in the history
  • Loading branch information
CharlieTap committed Mar 22, 2023
1 parent 3ad80b1 commit e0907e8
Show file tree
Hide file tree
Showing 11 changed files with 111 additions and 24 deletions.
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -44,7 +45,7 @@ internal data class EncoderContext(
val serializerMap by lazy {
serializers.fold(mutableMapOf<KSType, Pair<TypeName, Boolean>>()) { 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()
Expand All @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -36,18 +37,8 @@ internal class SynkSymbols(resolver: Resolver) {
val mapEncoder by lazy { resolver.getClassDeclarationByName<MapEncoder<*>>()!!.asType() }
val synkAdapter by lazy { resolver.getClassDeclarationByName<SynkAdapter<*>>()!!.asType() }
val stringSerializer by lazy { resolver.getClassDeclarationByName<StringSerializer<*>>()!!.asType() }
val enumStringSerializer by lazy { resolver.getClassDeclarationByName<EnumStringSerializer<*>>()!!.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<T>
//
// }
}

fun isString(type: KSType): Boolean {
return type == stringType
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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()
Expand All @@ -108,6 +122,8 @@ private fun deriveParameters(): List<EncoderParameter> {
deriveSubEncoderParameter(param)
} else if (param.hasProvidedSerializer) {
deriveSerializerParameter(param)
} else if(param.isEnum) {
deriveEnumSerializerParameter(param)
} else null
}

Expand Down Expand Up @@ -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)
}
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,26 +46,35 @@ 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,
val concreteTypeName: TypeName,
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
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,12 @@ private fun encoderParameters(paramEncoders: List<EncoderParameter>): List<Param
defaultValue(encoderDefaultType(encoderData))
}.build()
}
is EncoderParameter.Serializer -> {
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()
Expand Down Expand Up @@ -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)
Expand All @@ -179,6 +184,14 @@ private fun encoderDefaultType(paramEncoder: EncoderParameter.Serializer): CodeB
}.build()
}

/**
* EnumStringSerializer<Enum>()
*/
private fun encoderDefaultType(paramEncoder: EncoderParameter.EnumSerializer): CodeBlock {
return CodeBlock.builder().apply {
add("EnumStringSerializer<%T>()", paramEncoder.enumType)
}.build()
}

private fun constructor(parameterSpecs: List<ParameterSpec>): FunSpec? {
return if (parameterSpecs.isNotEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -494,25 +494,29 @@ 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
public class FooMapEncoder(
private val barSerializer: StringSerializer<Bar> = BarSerializer,
private val barNullSerializer: StringSerializer<Bar> = BarSerializer,
private val bazSerializer: EnumStringSerializer<Baz> = EnumStringSerializer<Baz>(),
) : MapEncoder<Foo> {
public override fun encode(crdt: Foo): Map<String, String> {
val map = mutableMapOf<String, String>()
map["bar"] = barSerializer.serialize(crdt.bar)
crdt.barNull?.let { map["barNull"] = barNullSerializer.serialize(crdt.barNull) }
map["baz"] = bazSerializer.serialize(crdt.baz)
return map
}
public override fun decode(map: Map<String, String>): Foo {
val crdt = Foo(
barSerializer.deserialize(map["bar"]!!),
map["barNull"]?.let { barNullSerializer.deserialize(map["barNull"]!!) },
bazSerializer.deserialize(map["baz"]!!),
)
return crdt
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,13 +232,25 @@ 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",
"""
package com.test.processor
data class Foo(
private val bar: Bar,
private val barNull: Bar?,
private val baz: Baz,
)
"""
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.tap.synk.serialize

class EnumStringSerializer<T: Enum<T>>(
private val values: Array<out T>
): StringSerializer<T> {
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 <reified T : Enum<T>> EnumStringSerializer(): EnumStringSerializer<T> {
return EnumStringSerializer(enumValues())
}

0 comments on commit e0907e8

Please sign in to comment.