jvmMain.reflect.GenerateBinarySerializer.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of yass2-generate-jvm Show documentation
Show all versions of yass2-generate-jvm Show documentation
Yet Another Service Solution
The newest version!
package ch.softappeal.yass2.generate.reflect
import ch.softappeal.yass2.generate.CodeWriter
import ch.softappeal.yass2.generate.PropertyKind
import ch.softappeal.yass2.serialize.binary.BaseEncoder
import ch.softappeal.yass2.serialize.binary.BinarySerializer
import ch.softappeal.yass2.serialize.binary.ClassEncoder
import ch.softappeal.yass2.serialize.binary.EnumEncoder
import ch.softappeal.yass2.serialize.binary.FIRST_ENCODER_ID
import ch.softappeal.yass2.serialize.binary.ListEncoderId
import kotlin.reflect.KClass
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.KProperty1
import kotlin.reflect.full.primaryConstructor
import kotlin.reflect.full.valueParameters
private fun List>>.getBaseEncoderTypes() =
map { it.supertypes.first().arguments.first().type!!.classifier as KClass<*> }
public fun CodeWriter.generateBinarySerializer(
baseEncoderClasses: List>>,
enumClasses: List>,
treeConcreteClasses: List>,
graphConcreteClasses: List> = emptyList(),
) {
val baseTypes = baseEncoderClasses.getBaseEncoderTypes()
val baseClasses = baseTypes + enumClasses
val classes = baseClasses + treeConcreteClasses + graphConcreteClasses
require(classes.size == classes.toSet().size) { "class must not be duplicated" }
checkNotEnum(baseTypes + treeConcreteClasses + graphConcreteClasses, "belongs to 'enumClasses'")
enumClasses.forEach {
require(it.isEnum()) { "class '${it.qualifiedName}' in 'enumClasses' must be enum" }
}
class Property(val property: KProperty1) {
var kind: PropertyKind
var encoderId: Int = -1
init {
val type = property.returnType.classifier as KClass<*>
kind = if (property.returnType.isMarkedNullable) PropertyKind.NoIdOptional else PropertyKind.NoIdRequired
if (type == List::class) {
encoderId = ListEncoderId.id
} else {
val index = baseClasses.indexOfFirst { it == type }
if (index >= 0) encoderId = index + FIRST_ENCODER_ID else kind = PropertyKind.WithId
}
}
}
class Properties(klass: KClass<*>) {
val parameter: List
val body: List
val all: List
init {
require(!klass.isAbstract) { "class '${klass.qualifiedName}' must be concrete" }
val properties = klass.getAllPropertiesNotThrowable().map { Property(it) }
parameter = buildList {
val primaryConstructor = klass.primaryConstructor ?: error(
"class '${klass.qualifiedName}' must hava a primary constructor"
)
val parameterNames = primaryConstructor.valueParameters.map { it.name }
val propertyNames = properties.map { it.property.name }
parameterNames.forEach { parameterName ->
require(propertyNames.contains(parameterName)) {
"primary constructor parameter '$parameterName' of class '${klass.qualifiedName}' must be a property"
}
add(properties.first { it.property.name == parameterName })
}
}
body = buildList {
properties
.filter { it !in parameter }
.forEach { property ->
require(property.property is KMutableProperty1) {
"body property '${property.property.name}' of '${klass.qualifiedName}' must be 'var'"
}
add(property)
}
}
all = parameter + body
}
}
enumClasses.forEachIndexed { enumClassIndex, enumClass ->
writeLine()
writeNestedLine("private class EnumEncoder${enumClassIndex + 1} : ${EnumEncoder::class.qualifiedName}<${enumClass.qualifiedName}>(") {
writeNestedLine("${enumClass.qualifiedName}::class, kotlin.enumValues()")
}
writeNestedLine(")")
}
writeLine()
writeNestedLine("public fun createSerializer(): ${BinarySerializer::class.qualifiedName} =") {
writeNestedLine("${BinarySerializer::class.qualifiedName}(listOf(") {
baseEncoderClasses.forEach { type -> writeNestedLine("${type.qualifiedName}(),") }
for (enumEncoderIndex in 1..enumClasses.size) writeNestedLine("EnumEncoder$enumEncoderIndex(),")
fun List>.add(graph: Boolean) = forEach { type ->
fun Property.encoderId(tail: String = "") = if (kind != PropertyKind.WithId) "$encoderId$tail" else ""
writeNestedLine("${ClassEncoder::class.qualifiedName}(${type.qualifiedName}::class, $graph,") {
val properties = Properties(type)
if (properties.all.isEmpty()) {
writeNestedLine("{ _, _ -> },")
} else {
writeNestedLine("{ w, i ->") {
properties.all.forEach { property ->
writeNestedLine("w.write${property.kind}(${property.encoderId(", ")}i.${property.property.name})")
}
}
writeNestedLine("},")
}
writeNestedLine("{${if (graph || properties.all.isNotEmpty()) " r ->" else ""}") {
writeNestedLine("val i = ${if (graph) "r.created(" else ""}${type.qualifiedName}(") {
properties.parameter.forEach { property ->
writeNestedLine("r.read${property.kind}(${property.encoderId()}) as ${property.property.returnType},")
}
}
writeNestedLine(")${if (graph) ")" else ""}")
properties.body.forEach { property ->
writeNestedLine("i.${property.property.name} = r.read${property.kind}(${property.encoderId()}) as ${property.property.returnType}")
}
writeNestedLine("i")
}
writeNestedLine("}")
}
writeNestedLine("),")
}
treeConcreteClasses.add(false)
graphConcreteClasses.add(true)
}
writeNestedLine("))")
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy