mirror of
https://github.com/apple/pkl.git
synced 2026-01-18 09:27:32 +01:00
codegen-kotlin: Support Java serialization of module classes (#721)
Motivation: - Java serialization makes as much sense for module classes as it does for regular classes. - Offer same Java serialization support as codegen-java. Changes: - Move generation of `appendProperty` method and serialization code into new method `generateCompanionRelatedCode`. - Improve/fix generation of serialization code. Result: Java serialization is also supported for module classes.
This commit is contained in:
@@ -138,50 +138,86 @@ class KotlinCodeGenerator(
|
||||
|
||||
val pModuleClass = moduleSchema.moduleClass
|
||||
|
||||
val hasProperties = pModuleClass.properties.any { !it.value.isHidden }
|
||||
val isGenerateClass = hasProperties || pModuleClass.isOpen || pModuleClass.isAbstract
|
||||
val hasModuleProperties = pModuleClass.properties.any { !it.value.isHidden }
|
||||
val isGenerateModuleClass =
|
||||
hasModuleProperties || pModuleClass.isOpen || pModuleClass.isAbstract
|
||||
|
||||
fun generateCompanionRelatedCode(
|
||||
builder: TypeSpec.Builder,
|
||||
isModuleType: Boolean = false
|
||||
): TypeSpec.Builder {
|
||||
// ensure that at most one companion object is generated for this type
|
||||
val companionObjectBuilder: Lazy<TypeSpec.Builder> = lazy {
|
||||
TypeSpec.companionObjectBuilder()
|
||||
}
|
||||
|
||||
// generate append method for module classes w/o parent class;
|
||||
// reuse in subclasses and nested classes
|
||||
val isGenerateAppendPropertyMethod =
|
||||
isModuleType &&
|
||||
// check if we inherit another module's append method
|
||||
pModuleClass.superclass!!.info == PClassInfo.Module &&
|
||||
// check if anyone is (potentially) going to use our append method
|
||||
(pModuleClass.isOpen ||
|
||||
pModuleClass.isAbstract ||
|
||||
(isGenerateModuleClass && !builder.modifiers.contains(KModifier.DATA)) ||
|
||||
builder.typeSpecs.any { !it.modifiers.contains(KModifier.DATA) })
|
||||
|
||||
if (isGenerateAppendPropertyMethod) {
|
||||
val appendPropertyMethodModifier =
|
||||
if (pModuleClass.isOpen || pModuleClass.isAbstract) {
|
||||
// alternative is `@JvmStatic protected`
|
||||
// (`protected` alone isn't sufficient as of Kotlin 1.6)
|
||||
KModifier.PUBLIC
|
||||
} else KModifier.PRIVATE
|
||||
if (isGenerateModuleClass) {
|
||||
companionObjectBuilder.value.addFunction(
|
||||
appendPropertyMethod().addModifiers(appendPropertyMethodModifier).build()
|
||||
)
|
||||
} else { // kotlin object
|
||||
builder.addFunction(
|
||||
appendPropertyMethod().addModifiers(appendPropertyMethodModifier).build()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// generate serialization code
|
||||
if (
|
||||
options.implementSerializable &&
|
||||
(!isModuleType || isGenerateModuleClass) &&
|
||||
!builder.modifiers.contains(KModifier.ABSTRACT)
|
||||
) {
|
||||
builder.addSuperinterface(java.io.Serializable::class.java)
|
||||
companionObjectBuilder.value.addProperty(
|
||||
PropertySpec.builder(
|
||||
"serialVersionUID",
|
||||
Long::class.java,
|
||||
KModifier.PRIVATE,
|
||||
KModifier.CONST
|
||||
)
|
||||
.initializer("0L")
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
if (companionObjectBuilder.isInitialized()) {
|
||||
builder.addType(companionObjectBuilder.value.build())
|
||||
}
|
||||
|
||||
return builder
|
||||
}
|
||||
|
||||
val moduleType =
|
||||
if (isGenerateClass) {
|
||||
if (isGenerateModuleClass) {
|
||||
generateTypeSpec(pModuleClass, moduleSchema)
|
||||
} else {
|
||||
generateObjectSpec(pModuleClass)
|
||||
}
|
||||
|
||||
for (pClass in moduleSchema.classes.values) {
|
||||
moduleType.addType(generateTypeSpec(pClass, moduleSchema).ensureSerializable().build())
|
||||
}
|
||||
|
||||
// generate append method for module classes w/o parent class; reuse in subclasses and nested
|
||||
// classes
|
||||
val isGenerateAppendPropertyMethod =
|
||||
// check if we can inherit someone else's append method
|
||||
pModuleClass.superclass!!.info == PClassInfo.Module &&
|
||||
// check if anyone is (potentially) going to use our append method
|
||||
(pModuleClass.isOpen ||
|
||||
pModuleClass.isAbstract ||
|
||||
(hasProperties && !moduleType.modifiers.contains(KModifier.DATA)) ||
|
||||
moduleType.typeSpecs.any { !it.modifiers.contains(KModifier.DATA) })
|
||||
|
||||
if (isGenerateAppendPropertyMethod) {
|
||||
val appendPropertyMethodModifier =
|
||||
if (pModuleClass.isOpen || pModuleClass.isAbstract) {
|
||||
// alternative is `@JvmStatic protected`
|
||||
// (`protected` alone isn't sufficient as of Kotlin 1.6)
|
||||
KModifier.PUBLIC
|
||||
} else KModifier.PRIVATE
|
||||
if (isGenerateClass) {
|
||||
moduleType.addType(
|
||||
TypeSpec.companionObjectBuilder()
|
||||
.addFunction(
|
||||
appendPropertyMethod().addModifiers(appendPropertyMethodModifier).build()
|
||||
)
|
||||
.build()
|
||||
)
|
||||
} else { // kotlin object
|
||||
moduleType.addFunction(
|
||||
appendPropertyMethod().addModifiers(appendPropertyMethodModifier).build()
|
||||
)
|
||||
}
|
||||
moduleType.addType(
|
||||
generateCompanionRelatedCode(generateTypeSpec(pClass, moduleSchema)).build()
|
||||
)
|
||||
}
|
||||
|
||||
val moduleName = moduleSchema.moduleName
|
||||
@@ -206,7 +242,7 @@ class KotlinCodeGenerator(
|
||||
}
|
||||
}
|
||||
|
||||
fileSpec.addType(moduleType.build())
|
||||
fileSpec.addType(generateCompanionRelatedCode(moduleType, isModuleType = true).build())
|
||||
return fileSpec.build().toString()
|
||||
}
|
||||
|
||||
@@ -530,44 +566,6 @@ class KotlinCodeGenerator(
|
||||
else generateRegularClass()
|
||||
}
|
||||
|
||||
private fun TypeSpec.Builder.ensureSerializable(): TypeSpec.Builder {
|
||||
if (!options.implementSerializable || modifiers.contains(KModifier.ABSTRACT)) {
|
||||
return this
|
||||
}
|
||||
|
||||
if (!this.superinterfaces.containsKey(java.io.Serializable::class.java.asTypeName())) {
|
||||
this.addSuperinterface(java.io.Serializable::class.java)
|
||||
}
|
||||
|
||||
var useExistingCompanionBuilder = false
|
||||
val companionBuilder =
|
||||
this.typeSpecs
|
||||
.find { it.isCompanion }
|
||||
?.let {
|
||||
useExistingCompanionBuilder = true
|
||||
it.toBuilder(TypeSpec.Kind.OBJECT)
|
||||
}
|
||||
?: TypeSpec.companionObjectBuilder()
|
||||
|
||||
if (!companionBuilder.propertySpecs.any { it.name == "serialVersionUID" })
|
||||
companionBuilder.addProperty(
|
||||
PropertySpec.builder(
|
||||
"serialVersionUID",
|
||||
Long::class.java,
|
||||
KModifier.PRIVATE,
|
||||
KModifier.CONST
|
||||
)
|
||||
.initializer("0L")
|
||||
.build()
|
||||
)
|
||||
|
||||
if (!useExistingCompanionBuilder) {
|
||||
this.addType(companionBuilder.build())
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
private fun generateEnumTypeSpec(
|
||||
typeAlias: TypeAlias,
|
||||
stringLiterals: Set<String>
|
||||
|
||||
@@ -1760,6 +1760,45 @@ class KotlinCodeGeneratorTest {
|
||||
confirmSerDe(bigStruct)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `generates serializable module classes`() {
|
||||
val kotlinCode =
|
||||
generateKotlinCode(
|
||||
"""
|
||||
module Person
|
||||
|
||||
address: Address
|
||||
|
||||
class Address {
|
||||
street: String
|
||||
}
|
||||
""",
|
||||
implementSerializable = true
|
||||
)
|
||||
|
||||
assertThat(kotlinCode)
|
||||
.contains(
|
||||
"""
|
||||
|data class Person(
|
||||
| val address: Address
|
||||
|) : Serializable {
|
||||
| data class Address(
|
||||
| val street: String
|
||||
| ) : Serializable {
|
||||
| companion object {
|
||||
| private const val serialVersionUID: Long = 0L
|
||||
| }
|
||||
| }
|
||||
|
|
||||
| companion object {
|
||||
| private const val serialVersionUID: Long = 0L
|
||||
| }
|
||||
|}
|
||||
"""
|
||||
.trimMargin()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `encoded file paths`() {
|
||||
val kotlinCode =
|
||||
|
||||
Reference in New Issue
Block a user