in generator/src/main/kotlin/space/jetbrains/api/generator/GenerateStructures.kt [66:283]
fun generateStructures(model: HttpApiEntitiesById): List<FileSpec> {
val fieldDescriptorsByDtoId = model.buildFieldsByDtoId()
return model.dtoAndUrlParams.values.mapNotNull { root ->
if (root.extends != null) return@mapNotNull null
val rootClassName = root.getClassName()
if (rootClassName == batchInfoType) return@mapNotNull null
val rootStructureClassName = rootClassName.getStructureClassName()
FileSpec.builder(rootStructureClassName.packageName, rootStructureClassName.simpleName).apply {
indent(INDENT)
addAnnotation(
AnnotationSpec.builder(Suppress::class)
.addMember("%S", "ClassName")
.addMember("%S", "UnusedImport")
.addMember("%S", "REDUNDANT_ELSE_IN_WHEN")
.addMember("%S", "RemoveExplicitTypeArguments")
.addMember("%S", "KotlinRedundantDiagnosticSuppress")
.build()
)
addAnnotation(
AnnotationSpec.builder(ClassName("kotlin", "OptIn")).also { ann ->
model.featureFlags.values.forEach {
ann.addMember("%T::class", it.annotationClassName())
}
}.build()
)
root.subclasses(model).forEach { dto ->
val dtoClassName = dto.getClassName()
val dtoStructureClassName = dtoClassName.getStructureClassName()
Log.info {
"Generating structure for '${dto.name}'"
}
addType(TypeSpec.objectBuilder(dtoStructureClassName).also { typeBuilder ->
typeBuilder.superclass(typeStructureType.parameterizedBy(dtoClassName))
typeBuilder.addSuperclassConstructorParameter(dto.record.toString())
val fields = fieldDescriptorsByDtoId.getValue(dto.id)
typeBuilder.addProperties(fields.map {
PropertySpec.builder(
name = it.dtoField.name,
type = propertyType.importNested().parameterizedBy(it.dtoField.field.fieldKotlinPoetType(model)),
modifiers = listOf(KModifier.PRIVATE),
).initializer(buildCodeBlock { appendPropertyProvider(it.dtoField, model) })
.build()
})
typeBuilder.addFunction(FunSpec.builder("deserialize").also func@{ funcBuilder ->
funcBuilder.addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
funcBuilder.addParameter("context", deserializationContextType)
funcBuilder.returns(dtoClassName)
if (dto.id in model.urlParams) {
funcBuilder.beginControlFlow(
"context.json?.%M()?.let·{ compactId ->",
asStringOrNullFunction
)
val urlParam = model.urlParams.getValue(dto.id)
funcBuilder.addCode("val (fieldNames, json) = compactIdToFieldNamesAndJson(compactId)\n")
funcBuilder.addCode("val newContext = context.copy(json = json)\n")
funcBuilder.beginControlFlow("return·when (fieldNames) {")
urlParam.options.forEach { option ->
when (option) {
is HA_UrlParameterOption.Const -> {
funcBuilder.addCode("setOf(%S", option.value)
}
is HA_UrlParameterOption.Var -> {
funcBuilder.addCode("setOf(")
option.parameters.forEachIndexed { i, param ->
if (i != 0) funcBuilder.addCode(", ")
funcBuilder.addCode("%S", param.name)
}
}
}
funcBuilder.addCode(
") -> %T.deserialize(newContext)\n",
option.getClassName().getStructureClassName()
)
}
funcBuilder.addCode("else -> ")
urlParam.options.firstNotNullOfOrNull { it as? HA_UrlParameterOption.Var }?.let {
funcBuilder.addCode(
"%T.deserialize(newContext)\n",
it.getClassName().getStructureClassName()
)
} ?: funcBuilder.addCode(
"minorDeserializationError(\"Unsupported parameter set: '\$fieldNames'\", context.link)\n"
)
funcBuilder.endControlFlow()
funcBuilder.endControlFlow()
}
val codeReferences = mutableListOf<Any>()
val createInstance = buildString {
when {
dto.isObject -> append("%T")
fields.isNotEmpty() -> {
append("%T(\n$INDENT")
fields.forEachIndexed { i, field ->
if (i != 0) append(",\n$INDENT")
append("${field.dtoField.name} = this.${field.dtoField.name}.deserialize(context)")
}
append("\n)")
}
else -> append("%T()")
}
}
val toReturn = if (dto.inheritors.isEmpty() && !dto.hierarchyRole2.isAbstract) {
codeReferences += dtoClassName
createInstance
} else {
"when (val className = context.className()) {" +
dto.inheritors.joinToString("\n$INDENT", "\n$INDENT", "\n") {
val inheritor = model.resolveDto(it)
val inheritorClassName = inheritor.getClassName()
codeReferences += inheritorClassName.getStructureClassName()
val condition = "\"${inheritor.name}\"" + if (inheritor.inheritors.isNotEmpty()) {
codeReferences += inheritorClassName.getStructureClassName()
", in %T.childClassNames"
} else ""
val withActualTypeCall = if (inheritor.inheritors.isEmpty()) ".withActualType(className)" else ""
"$condition -> %T.deserialize(context$withActualTypeCall)"
} +
(if (!dto.hierarchyRole2.isAbstract) {
codeReferences += dtoClassName
"$INDENT\"${dto.name}\" -> " +
createInstance.indentNonFirst() + "\n"
} else "") +
"${INDENT}else -> minorDeserializationError(\"Unsupported class name: '\$className'\", context.link)\n}"
}
if (dto.id in model.urlParams) {
funcBuilder.addCode("return·$toReturn", *codeReferences.toTypedArray())
} else {
funcBuilder.addCode("return $toReturn", *codeReferences.toTypedArray())
}
}.build())
typeBuilder.addFunction(FunSpec.builder("serialize").also func@{ func ->
func.addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE)
func.addParameter("value", dtoClassName)
func.returns(jsonValueType)
if (dto.id in model.urlParams) {
func.addCode("return %M(value.compactId)", jsonStringFunction)
return@func
}
val codeReferences = mutableListOf<Any>()
val createJson = "%M(listOfNotNull(" + (fields.takeIf { it.isNotEmpty() }
?.joinToString(",\n$INDENT", "\n$INDENT", "\n") {
if (it.dtoField.field.requiresAddedNullability) {
"value.${it.dtoField.name}?.let·{ this.${it.dtoField.name}.serialize(it) }"
} else {
"this.${it.dtoField.name}.serialize(value.${it.dtoField.name})"
}
} ?: "") + "))"
val toReturn = if (dto.inheritors.isEmpty() && !dto.hierarchyRole2.isAbstract) {
codeReferences += jsonObjectFunction
createJson
} else {
"when (value) {" +
dto.inheritors.joinToString("\n$INDENT", "\n$INDENT", "\n") {
val inheritor = model.resolveDto(it)
val inheritorClassName = inheritor.getClassName()
codeReferences += inheritorClassName
codeReferences += inheritorClassName.getStructureClassName()
"is %T -> %T.serialize(value).withClassName(\"${inheritor.name}\")"
} +
"${INDENT}else -> " +
if (!dto.hierarchyRole2.isAbstract) {
codeReferences += jsonObjectFunction
createJson.indentNonFirst() + ".withClassName(\"${dto.name}\")"
} else {
"error(\"Unsupported class: '\${value::class.simpleName}'\")"
} +
"\n}"
}
func.addCode("return $toReturn", *codeReferences.toTypedArray())
}.build())
if (dto.inheritors.isNotEmpty()) {
typeBuilder.addProperty(
PropertySpec.builder("childClassNames", SET.parameterizedBy(STRING), KModifier.OVERRIDE)
.initializer(buildCodeBlock {
add("setOf(")
dto.inheritors.forEachIndexed { i, it ->
if (i != 0) add(", ")
add("%S", model.resolveDto(it).name)
}
add(")")
dto.inheritors.forEach {
val inheritor = model.resolveDto(it)
if (inheritor.inheritors.isNotEmpty()) {
add("·+ %T.childClassNames", inheritor.getClassName().getStructureClassName())
}
}
})
.build()
)
}
}.build())
}
}.build()
}
}