Coverage Summary for Class: SerializerSetupEmitter (com.ghost.serialization.compiler)

Class Class, % Method, % Branch, % Line, % Instruction, %
SerializerSetupEmitter 100% (1/1) 100% (7/7) 85.2% (46/54) 92.8% (116/125) 93.1% (620/666)


 package com.ghost.serialization.compiler
 
 import com.squareup.kotlinpoet.ClassName
 import com.squareup.kotlinpoet.CodeBlock
 import com.squareup.kotlinpoet.FunSpec
 import com.squareup.kotlinpoet.KModifier
 import com.squareup.kotlinpoet.PropertySpec
 import com.squareup.kotlinpoet.TypeSpec
 import com.ghost.serialization.compiler.GhostEmitterConstants as C
 
 /**
  * Emits serializer companion setup: perfect-hash OPTIONS, cached field headers, enum tables, warmUp.
  */
 internal class SerializerSetupEmitter(
     private val ctx: GhostSerializerContext,
 ) {
 
     fun addPerfectHashOptions(typeSpecBuilder: TypeSpec.Builder) {
         val names = if (ctx.isSealed && ctx.isInferred) {
             ctx.properties
                 .firstOrNull()
                 ?.inferredSubclasses
                 ?.flatMap { it.properties }
                 ?.map { it.jsonName }?.distinct()
                 ?: emptyList()
         } else {
             DispatchNamesResolver.topLevelNames(ctx.properties)
         }
         val hashConfig = PerfectHashFinder.findPerfectHash(names)
         val optionsClass = ctx.readerClass.peerClass(C.STR_OPTIONS_CLASS)
 
         typeSpecBuilder.addProperty(
             PropertySpec.builder(C.STR_OPTIONS, optionsClass)
                 .addModifiers(KModifier.PRIVATE)
                 .initializer(
                     buildReaderOptionsInitializer(
                         optionsClass = optionsClass,
                         hashConfig = hashConfig,
                         names = names,
                     )
                 )
                 .build()
         )
     }
 
     fun addCachedHeaderProperties(typeSpecBuilder: TypeSpec.Builder) {
         for (name in ctx.getAllJsonNames()) {
             val cleanName = name.replace(C.STR_DOT, C.STR_UNDERSCORE).uppercase()
 
             typeSpecBuilder.addProperty(
                 PropertySpec.builder(C.STR_H_VAL_PREFIX + cleanName, C.BYTE_STRING_CLASS, KModifier.PRIVATE)
                     .initializer(C.TEMPLATE_ENCODE_UTF8, C.FMT_JSON_FIELD.format(name))
                     .build()
             )
 
             if (ctx.textChannel) {
                 typeSpecBuilder.addProperty(
                     PropertySpec.builder(C.STR_HS_PREFIX + cleanName, String::class, KModifier.PRIVATE)
                         .initializer(C.STR_FORMAT_S, C.FMT_JSON_FIELD.format(name))
                         .build()
                 )
             }
         }
     }
 
     fun addEnumOptions(typeSpecBuilder: TypeSpec.Builder) {
         val values = ctx.enumValues!!.values.toList()
         val hashConfig = PerfectHashFinder.findPerfectHash(values)
         val optionsClass = ctx.readerClass.peerClass(C.STR_OPTIONS_CLASS)
 
         typeSpecBuilder.addProperty(
             PropertySpec.builder(C.STR_ENUM_OPTIONS, optionsClass)
                 .addModifiers(KModifier.PRIVATE)
                 .initializer(
                     buildReaderOptionsInitializer(
                         optionsClass = optionsClass,
                         hashConfig = hashConfig,
                         names = values,
                     )
                 )
                 .build()
         )
     }
 
     fun buildWarmUpMethod(): FunSpec {
         val warmupJson = generateMinimalJson()
         val warmUpBlock = CodeBlock.builder()
             .beginControlFlow(C.STR_TRY)
             .addStatement(
                 C.TEMPLATE_WARM_UP_READER_INIT,
                 C.STR_READER1,
                 ctx.streamingReaderClass,
                 warmupJson
             )
             .addStatement(C.TEMPLATE_WARM_UP_DESERIALIZE, C.STR_READER1)
             .nextControlFlow(C.STR_CATCH_EXCEPTION)
             .endControlFlow()
             .beginControlFlow(C.STR_TRY)
             .addStatement(
                 C.TEMPLATE_WARM_UP_READER_INIT,
                 C.STR_READER2,
                 ctx.flatReaderClass,
                 warmupJson
             )
             .addStatement(C.TEMPLATE_WARM_UP_DESERIALIZE, C.STR_READER2)
             .nextControlFlow(C.STR_CATCH_EXCEPTION)
             .endControlFlow()
         if (ctx.textChannel) {
             warmUpBlock
                 .beginControlFlow(C.STR_TRY)
                 .addStatement(
                     C.TEMPLATE_WARM_UP_STRING_READER_INIT,
                     C.STR_READER3,
                     ctx.stringReaderClass,
                     warmupJson
                 )
                 .addStatement(C.TEMPLATE_WARM_UP_DESERIALIZE, C.STR_READER3)
                 .nextControlFlow(C.STR_CATCH_EXCEPTION)
                 .endControlFlow()
         }
         return FunSpec.builder(C.STR_WARM_UP)
             .addModifiers(KModifier.OVERRIDE)
             .addCode(warmUpBlock.build())
             .build()
     }
 
     private fun buildReaderOptionsInitializer(
         optionsClass: ClassName,
         hashConfig: PerfectHashConfig,
         names: List<String>,
     ): CodeBlock {
         val optionsBuilder = CodeBlock.builder()
         if (hashConfig.extendedKeyHash) {
             optionsBuilder.add(
                 C.TEMPLATE_OPTIONS_OF_SEEDS_EXTENDED_START,
                 optionsClass,
                 hashConfig.shift,
                 hashConfig.multiplier,
                 hashConfig.tableSize,
                 ctx.textChannel,
                 true
             )
         } else {
             optionsBuilder.add(
                 C.TEMPLATE_OPTIONS_OF_SEEDS_START,
                 optionsClass,
                 hashConfig.shift,
                 hashConfig.multiplier,
                 hashConfig.tableSize,
                 ctx.textChannel
             )
         }
         names.forEach { name ->
             optionsBuilder.add(C.TEMPLATE_COMMA_FORMAT_S, name)
         }
         optionsBuilder.add(C.STR_PAREN_CLOSE)
         return optionsBuilder.build()
     }
 
     private fun generateMinimalJson(): String {
         if (ctx.isSealed || ctx.isEnum || ctx.isValue) {
             return C.STR_EMPTY_JSON
         }
         val sb = StringBuilder()
         sb.append(C.STR_CURLY_OPEN)
         val entries = mutableListOf<String>()
         ctx.properties.forEach { prop ->
             if (!prop.isNullable && !prop.hasDefaultValue) {
                 val key = C.STR_DOUBLE_QUOTE + prop.jsonName + C.STR_DOUBLE_QUOTE
                 val value = when {
                     prop.type.isPrimitiveInt() || prop.type.isPrimitiveLong() ||
                         prop.type.isPrimitiveByte() || prop.type.isPrimitiveShort() -> C.STR_ZERO
                     prop.type.isPrimitiveDouble() || prop.type.isPrimitiveFloat() -> C.STR_ZERO_D
                     prop.type.isPrimitiveBoolean() -> C.STR_FALSE
                     prop.type.isPrimitiveChar() -> C.STR_JSON_CHAR_NULL
                     prop.type.isString() -> C.STR_EMPTY_STRING
                     prop.type.isList() || prop.type.isSet() -> C.STR_EMPTY_ARRAY
                     prop.type.isMap() -> C.STR_EMPTY_JSON
                     prop.type.isGhost() -> C.STR_EMPTY_JSON
                     else -> C.STR_NULL
                 }
                 entries.add(key + C.STR_COLON + value)
             }
         }
         sb.append(entries.joinToString(C.STR_COMMA))
         sb.append(C.STR_CURLY_CLOSE)
         return sb.toString()
     }
 }