Coverage Summary for Class: GhostSerializerContext (com.ghost.serialization.compiler)
| Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| GhostSerializerContext |
100%
(4/4)
|
68.8%
(22/32)
|
96.2%
(50/52)
|
89.1%
(246/276)
|
| GhostSerializerContext$Companion |
100%
(2/2)
|
74.2%
(98/132)
|
98.9%
(92/93)
|
96.2%
(684/711)
|
| Total |
100%
(6/6)
|
73.2%
(120/164)
|
97.9%
(142/145)
|
94.2%
(930/987)
|
package com.ghost.serialization.compiler
import com.google.devtools.ksp.symbol.ClassKind
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.Modifier
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.ksp.toClassName
import com.ghost.serialization.compiler.GhostEmitterConstants as C
/**
* Immutable metadata for generating a single [GhostSerializer] companion object.
*/
internal class GhostSerializerContext private constructor(
val properties: List<GhostPropertyModel>,
val classDeclaration: KSClassDeclaration,
val textChannel: Boolean,
val envelopeModel: GhostEnvelopeModel?,
val fullPaths: List<List<String>>,
val isSealed: Boolean,
val isValue: Boolean,
val isEnum: Boolean,
val isObject: Boolean,
val isResilient: Boolean,
val isProto: Boolean,
val isInferred: Boolean,
val sealedSubclasses: List<KSClassDeclaration>,
val packageName: String,
val originalClassName: ClassName,
val baseClassName: String,
val finalTypeName: String,
val discriminator: String?,
val sealedDiscriminatorKey: String,
val enumValues: Map<String, String>?,
val hasFallbackEnum: Boolean,
val serializerInterface: ClassName,
val streamingWriterClass: ClassName,
val flatWriterClass: ClassName,
val stringWriterClass: ClassName,
val streamingReaderClass: ClassName,
val flatReaderClass: ClassName,
val stringReaderClass: ClassName,
) {
val readerClass: ClassName = streamingReaderClass
val serializerName: String = baseClassName + C.STR_SERIALIZER_SUFFIX
fun needsObjectParsingImports(): Boolean {
if (isEnum || isValue) return false
if (isObject) return true
if (isSealed) return isInferred
return properties.isNotEmpty()
}
fun needsCachedByteStringHeaders(): Boolean {
if (isEnum || isValue || isSealed || isObject) return false
return getAllJsonNames().isNotEmpty()
}
fun getAllJsonNames(): List<String> {
val names = mutableSetOf<String>()
val allProps = if (isSealed && isInferred) {
properties.firstOrNull()?.inferredSubclasses?.flatMap { it.properties } ?: emptyList()
} else {
properties
}
allProps.forEach { prop ->
if (prop.wrappedSourceKeys != null) {
names.addAll(prop.wrappedSourceKeys)
} else if (prop.flattenPath != null) {
names.addAll(prop.flattenPath)
} else if (prop.wrapPath != null) {
names.addAll(prop.wrapPath)
}
if (prop.wrappedSourceKeys == null) {
names.add(prop.jsonName)
}
}
return names.toList().sorted()
}
companion object {
fun from(
properties: List<GhostPropertyModel>,
classDeclaration: KSClassDeclaration,
textChannel: Boolean,
envelopeModel: GhostEnvelopeModel?,
): GhostSerializerContext {
val fullPaths = properties.map {
it.flattenPath
?: (it.wrapPath?.let { path -> path + it.jsonName }
?: listOf(it.jsonName))
}
val isSealed = classDeclaration.modifiers.contains(Modifier.SEALED)
val isValue = classDeclaration.modifiers.contains(Modifier.VALUE) ||
classDeclaration.modifiers.contains(Modifier.INLINE)
val isEnum = classDeclaration.classKind == ClassKind.ENUM_CLASS
val isObject = classDeclaration.classKind == ClassKind.OBJECT
val isResilient =
classDeclaration.annotations.any { it.shortName.asString() == C.GHOST_RESILIENT }
val isProto =
classDeclaration.annotations.any { it.shortName.asString() == C.ANNOTATION_GHOST_PROTO_SERIALIZATION }
val sealedSubclasses = if (isSealed) {
classDeclaration.getSealedSubclasses().toList()
} else {
emptyList()
}
val originalClassName = classDeclaration.toClassName()
val baseClassName = originalClassName.simpleNames.joinToString(C.STR_UNDERSCORE)
val parentSealedClass = resolveParentSealedClass(
classDeclaration,
isSealed,
isValue,
isEnum
)
val discriminator = if (parentSealedClass != null) {
val customName = classDeclaration.annotations
.find { it.shortName.asString() == C.ANNOTATION_GHOST_SERIALIZATION }
?.arguments?.find { it.name?.asString() == C.ARG_NAME }?.value as? String
if (!customName.isNullOrEmpty()) customName else classDeclaration.simpleName.asString()
} else {
null
}
val sealedDiscriminatorKey = run {
val annotationSource = parentSealedClass ?: classDeclaration
annotationSource.annotations
.find { it.shortName.asString() == C.ANNOTATION_GHOST_SERIALIZATION }
?.arguments
?.find { it.name?.asString() == C.ARG_DISCRIMINATOR }
?.value as? String
?: C.STR_DEFAULT_DISCRIMINATOR
}
val customTypeName = classDeclaration.annotations
.find { it.shortName.asString() == C.ANNOTATION_GHOST_SERIALIZATION }
?.arguments
?.find { it.name?.asString() == C.ARG_NAME }
?.value as? String ?: C.STR_EMPTY
val isInferred = (if (discriminator != null) {
classDeclaration.parentDeclaration as? KSClassDeclaration
} else {
classDeclaration
})?.annotations
?.find { it.shortName.asString() == C.ANNOTATION_GHOST_SERIALIZATION }
?.arguments
?.find { it.name?.asString() == C.ARG_INFERRED }
?.value as? Boolean
?: false
val finalTypeName = customTypeName.ifEmpty { classDeclaration.simpleName.asString() }
val enumValues = properties.firstOrNull { it.isEnum }?.enumValues
val hasFallbackEnum = classDeclaration.annotations.any {
it.shortName.asString() == C.STR_FALLBACK_ANNOTATION
}
return GhostSerializerContext(
properties = properties,
classDeclaration = classDeclaration,
textChannel = textChannel,
envelopeModel = envelopeModel,
fullPaths = fullPaths,
isSealed = isSealed,
isValue = isValue,
isEnum = isEnum,
isObject = isObject,
isResilient = isResilient,
isProto = isProto,
isInferred = isInferred,
sealedSubclasses = sealedSubclasses,
packageName = classDeclaration.packageName.asString(),
originalClassName = originalClassName,
baseClassName = baseClassName,
finalTypeName = finalTypeName,
discriminator = discriminator,
sealedDiscriminatorKey = sealedDiscriminatorKey,
enumValues = enumValues,
hasFallbackEnum = hasFallbackEnum,
serializerInterface = ClassName(C.PKG_CONTRACT, C.STR_GHOST_SERIALIZER),
streamingWriterClass = ClassName(C.PKG_WRITER, C.STR_GHOST_JSON_WRITER),
flatWriterClass = ClassName(C.PKG_WRITER, C.STR_GHOST_JSON_FLAT_WRITER),
stringWriterClass = ClassName(C.PKG_WRITER, C.STR_GHOST_JSON_STRING_WRITER),
streamingReaderClass = ClassName(C.PKG_PARSER, C.STR_GHOST_JSON_READER),
flatReaderClass = ClassName(C.PKG_PARSER, C.STR_GHOST_JSON_FLAT_READER),
stringReaderClass = ClassName(C.PKG_PARSER, C.STR_GHOST_JSON_STRING_READER),
)
}
private fun resolveParentSealedClass(
classDeclaration: KSClassDeclaration,
isSealed: Boolean,
isValue: Boolean,
isEnum: Boolean,
): KSClassDeclaration? {
if (isSealed || isValue || isEnum) return null
val parentDecl = classDeclaration.parentDeclaration as? KSClassDeclaration
if (parentDecl != null && parentDecl.modifiers.contains(Modifier.SEALED)) {
return parentDecl
}
for (superType in classDeclaration.superTypes) {
val resolved = superType.resolve().declaration as? KSClassDeclaration
if (resolved != null && resolved.modifiers.contains(Modifier.SEALED)) {
return resolved
}
}
return null
}
}
}