mirror of
https://github.com/apple/pkl.git
synced 2026-05-06 07:03:35 +02:00
Allow renaming Java/Kotlin classes/packages during code generation (#499)
Adds a `rename` field to the Java/Kotlin code generators that allows renaming packages and classes during codegen. * Add `--rename` flag to CLIs * Add `rename` property to Gradle API
This commit is contained in:
@@ -173,6 +173,115 @@ class CliJavaCodeGeneratorTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `custom package names`(@TempDir tempDir: Path) {
|
||||
val module1 =
|
||||
PklModule(
|
||||
"org.foo.Module1",
|
||||
"""
|
||||
module org.foo.Module1
|
||||
|
||||
class Person {
|
||||
name: String
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
val module2 =
|
||||
PklModule(
|
||||
"org.bar.Module2",
|
||||
"""
|
||||
module org.bar.Module2
|
||||
|
||||
import "../../org/foo/Module1.pkl"
|
||||
|
||||
class Group {
|
||||
owner: Module1.Person
|
||||
name: String
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
val module3 =
|
||||
PklModule(
|
||||
"org.baz.Module3",
|
||||
"""
|
||||
module org.baz.Module3
|
||||
|
||||
import "../../org/bar/Module2.pkl"
|
||||
|
||||
class Supergroup {
|
||||
owner: Module2.Group
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
val module1PklFile = module1.writeToDisk(tempDir.resolve("org/foo/Module1.pkl"))
|
||||
val module2PklFile = module2.writeToDisk(tempDir.resolve("org/bar/Module2.pkl"))
|
||||
val module3PklFile = module3.writeToDisk(tempDir.resolve("org/baz/Module3.pkl"))
|
||||
val outputDir = tempDir.resolve("output")
|
||||
|
||||
val generator =
|
||||
CliJavaCodeGenerator(
|
||||
CliJavaCodeGeneratorOptions(
|
||||
CliBaseOptions(listOf(module1PklFile, module2PklFile, module3PklFile).map { it.toUri() }),
|
||||
outputDir,
|
||||
renames = mapOf("org.foo" to "com.foo.x", "org.baz" to "com.baz.a.b")
|
||||
)
|
||||
)
|
||||
|
||||
generator.run()
|
||||
|
||||
val module1JavaFile = outputDir.resolve("java/com/foo/x/Module1.java")
|
||||
module1JavaFile.readString().let {
|
||||
assertContains("package com.foo.x;", it)
|
||||
|
||||
assertContains("public final class Module1 {", it)
|
||||
|
||||
assertContains(
|
||||
"""
|
||||
| public static final class Person {
|
||||
| public final @NonNull String name;
|
||||
""",
|
||||
it
|
||||
)
|
||||
}
|
||||
|
||||
val module2JavaFile = outputDir.resolve("java/org/bar/Module2.java")
|
||||
module2JavaFile.readString().let {
|
||||
assertContains("package org.bar;", it)
|
||||
|
||||
assertContains("import com.foo.x.Module1;", it)
|
||||
|
||||
assertContains("public final class Module2 {", it)
|
||||
|
||||
assertContains(
|
||||
"""
|
||||
| public static final class Group {
|
||||
| public final Module1. @NonNull Person owner;
|
||||
""",
|
||||
it
|
||||
)
|
||||
}
|
||||
|
||||
val module3JavaFile = outputDir.resolve("java/com/baz/a/b/Module3.java")
|
||||
module3JavaFile.readString().let {
|
||||
assertContains("package com.baz.a.b;", it)
|
||||
|
||||
assertContains("import org.bar.Module2;", it)
|
||||
|
||||
assertContains("public final class Module3 {", it)
|
||||
|
||||
assertContains(
|
||||
"""
|
||||
| public static final class Supergroup {
|
||||
| public final Module2. @NonNull Group owner;
|
||||
""",
|
||||
it
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun assertContains(part: String, code: String) {
|
||||
val trimmedPart = part.trim().trimMargin()
|
||||
if (!code.contains(trimmedPart)) {
|
||||
|
||||
@@ -30,7 +30,9 @@ object InMemoryJavaCompiler {
|
||||
val fileManager =
|
||||
InMemoryFileManager(compiler.getStandardFileManager(diagnosticsCollector, null, null))
|
||||
val sourceObjects =
|
||||
sourceFiles.map { (filename, contents) -> ReadableSourceFileObject(filename, contents) }
|
||||
sourceFiles
|
||||
.filter { (filename, _) -> filename.endsWith(".java") }
|
||||
.map { (filename, contents) -> ReadableSourceFileObject(filename, contents) }
|
||||
val task = compiler.getTask(null, fileManager, diagnosticsCollector, null, null, sourceObjects)
|
||||
val result = task.call()
|
||||
if (!result) {
|
||||
|
||||
@@ -33,6 +33,8 @@ import org.pkl.core.util.IoUtils
|
||||
|
||||
class JavaCodeGeneratorTest {
|
||||
companion object {
|
||||
const val MAPPER_PREFIX = "resources/META-INF/org/pkl/config/java/mapper/classes"
|
||||
|
||||
private val simpleClass by lazy {
|
||||
compileJavaCode(
|
||||
generateJavaCode(
|
||||
@@ -101,7 +103,8 @@ class JavaCodeGeneratorTest {
|
||||
generateJavadoc: Boolean = false,
|
||||
generateSpringBootConfig: Boolean = false,
|
||||
nonNullAnnotation: String? = null,
|
||||
implementSerializable: Boolean = false
|
||||
implementSerializable: Boolean = false,
|
||||
renames: Map<String, String> = emptyMap()
|
||||
): String {
|
||||
val module = Evaluator.preconfigured().evaluateSchema(text(pklCode))
|
||||
val generator =
|
||||
@@ -113,6 +116,7 @@ class JavaCodeGeneratorTest {
|
||||
generateSpringBootConfig = generateSpringBootConfig,
|
||||
nonNullAnnotation = nonNullAnnotation,
|
||||
implementSerializable = implementSerializable,
|
||||
renames = renames
|
||||
)
|
||||
)
|
||||
return generator.javaFile
|
||||
@@ -127,6 +131,8 @@ class JavaCodeGeneratorTest {
|
||||
}
|
||||
}
|
||||
|
||||
@TempDir lateinit var tempDir: Path
|
||||
|
||||
@Test
|
||||
fun testEquals() {
|
||||
val ctor = simpleClass.constructors.first()
|
||||
@@ -1420,7 +1426,7 @@ class JavaCodeGeneratorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `import module`(@TempDir tempDir: Path) {
|
||||
fun `import module`() {
|
||||
val library =
|
||||
PklModule(
|
||||
"library",
|
||||
@@ -1449,7 +1455,7 @@ class JavaCodeGeneratorTest {
|
||||
.trimIndent()
|
||||
)
|
||||
|
||||
val javaSourceFiles = generateJavaFiles(tempDir, library, client)
|
||||
val javaSourceFiles = generateFiles(library, client)
|
||||
val javaClientCode =
|
||||
javaSourceFiles.entries.find { (fileName, _) -> fileName.endsWith("Client.java") }!!.value
|
||||
|
||||
@@ -1467,7 +1473,7 @@ class JavaCodeGeneratorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `extend module`(@TempDir tempDir: Path) {
|
||||
fun `extend module`() {
|
||||
val base =
|
||||
PklModule(
|
||||
"base",
|
||||
@@ -1496,7 +1502,7 @@ class JavaCodeGeneratorTest {
|
||||
.trimIndent()
|
||||
)
|
||||
|
||||
val javaSourceFiles = generateJavaFiles(tempDir, base, derived)
|
||||
val javaSourceFiles = generateFiles(base, derived)
|
||||
val javaDerivedCode =
|
||||
javaSourceFiles.entries.find { (filename, _) -> filename.endsWith("Derived.java") }!!.value
|
||||
|
||||
@@ -1520,7 +1526,7 @@ class JavaCodeGeneratorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `extend module that only contains type aliases`(@TempDir tempDir: Path) {
|
||||
fun `extend module that only contains type aliases`() {
|
||||
val base =
|
||||
PklModule(
|
||||
"base",
|
||||
@@ -1545,7 +1551,7 @@ class JavaCodeGeneratorTest {
|
||||
.trimIndent()
|
||||
)
|
||||
|
||||
val javaSourceFiles = generateJavaFiles(tempDir, base, derived)
|
||||
val javaSourceFiles = generateFiles(base, derived)
|
||||
val javaDerivedCode =
|
||||
javaSourceFiles.entries.find { (filename, _) -> filename.endsWith("Derived.java") }!!.value
|
||||
|
||||
@@ -1561,7 +1567,7 @@ class JavaCodeGeneratorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `generated properties files`(@TempDir tempDir: Path) {
|
||||
fun `generated properties files`() {
|
||||
val pklModule =
|
||||
PklModule(
|
||||
"Mod.pkl",
|
||||
@@ -1582,7 +1588,7 @@ class JavaCodeGeneratorTest {
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
val generated = generateFiles(tempDir, pklModule)
|
||||
val generated = generateFiles(pklModule)
|
||||
val expectedPropertyFile =
|
||||
"resources/META-INF/org/pkl/config/java/mapper/classes/org.pkl.Mod.properties"
|
||||
assertThat(generated).containsKey(expectedPropertyFile)
|
||||
@@ -1596,7 +1602,7 @@ class JavaCodeGeneratorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `generated properties files with normalized java name`(@TempDir tempDir: Path) {
|
||||
fun `generated properties files with normalized java name`() {
|
||||
val pklModule =
|
||||
PklModule(
|
||||
"mod.pkl",
|
||||
@@ -1617,7 +1623,7 @@ class JavaCodeGeneratorTest {
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
val generated = generateFiles(tempDir, pklModule)
|
||||
val generated = generateFiles(pklModule)
|
||||
val expectedPropertyFile =
|
||||
"resources/META-INF/org/pkl/config/java/mapper/classes/my.mod.properties"
|
||||
assertThat(generated).containsKey(expectedPropertyFile)
|
||||
@@ -1824,24 +1830,235 @@ class JavaCodeGeneratorTest {
|
||||
assertCompilesSuccessfully(javaCode)
|
||||
}
|
||||
|
||||
private fun generateFiles(tempDir: Path, vararg pklModules: PklModule): Map<String, String> {
|
||||
@Test
|
||||
fun `override names in a standalone module`() {
|
||||
val files =
|
||||
JavaCodegenOptions(
|
||||
renames = mapOf("a.b.c." to "x.y.z.", "d.e.f.AnotherModule" to "u.v.w.RenamedModule")
|
||||
)
|
||||
.generateFiles(
|
||||
"MyModule.pkl" to
|
||||
"""
|
||||
module a.b.c.MyModule
|
||||
|
||||
foo: String = "abc"
|
||||
"""
|
||||
.trimIndent(),
|
||||
"AnotherModule.pkl" to
|
||||
"""
|
||||
module d.e.f.AnotherModule
|
||||
|
||||
bar: Int = 123
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
.toMutableMap()
|
||||
|
||||
files.validateContents(
|
||||
"java/x/y/z/MyModule.java" to listOf("package x.y.z;", "public final class MyModule {"),
|
||||
"$MAPPER_PREFIX/a.b.c.MyModule.properties" to
|
||||
listOf("org.pkl.config.java.mapper.a.b.c.MyModule\\#ModuleClass=x.y.z.MyModule"),
|
||||
// ---
|
||||
"java/u/v/w/RenamedModule.java" to
|
||||
listOf("package u.v.w;", "public final class RenamedModule {"),
|
||||
"$MAPPER_PREFIX/d.e.f.AnotherModule.properties" to
|
||||
listOf("org.pkl.config.java.mapper.d.e.f.AnotherModule\\#ModuleClass=u.v.w.RenamedModule"),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `override names based on the longest prefix`() {
|
||||
val files =
|
||||
JavaCodegenOptions(
|
||||
renames = mapOf("com.foo.bar." to "x.", "com.foo." to "y.", "com." to "z.", "" to "w.")
|
||||
)
|
||||
.generateFiles(
|
||||
"com/foo/bar/Module1" to
|
||||
"""
|
||||
module com.foo.bar.Module1
|
||||
|
||||
bar: String
|
||||
"""
|
||||
.trimIndent(),
|
||||
"com/Module2" to
|
||||
"""
|
||||
module com.Module2
|
||||
|
||||
com: String
|
||||
"""
|
||||
.trimIndent(),
|
||||
"org/baz/Module3" to
|
||||
"""
|
||||
module org.baz.Module3
|
||||
|
||||
baz: String
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
|
||||
files.validateContents(
|
||||
"java/x/Module1.java" to listOf("package x;", "public final class Module1 {"),
|
||||
"$MAPPER_PREFIX/com.foo.bar.Module1.properties" to
|
||||
listOf("org.pkl.config.java.mapper.com.foo.bar.Module1\\#ModuleClass=x.Module1"),
|
||||
// ---
|
||||
"java/z/Module2.java" to listOf("package z;", "public final class Module2 {"),
|
||||
"$MAPPER_PREFIX/com.Module2.properties" to
|
||||
listOf("org.pkl.config.java.mapper.com.Module2\\#ModuleClass=z.Module2"),
|
||||
// ---
|
||||
"java/w/org/baz/Module3.java" to listOf("package w.org.baz;", "public final class Module3 {"),
|
||||
"$MAPPER_PREFIX/org.baz.Module3.properties" to
|
||||
listOf("org.pkl.config.java.mapper.org.baz.Module3\\#ModuleClass=w.org.baz.Module3"),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `override names in multiple modules using each other`() {
|
||||
val files =
|
||||
JavaCodegenOptions(
|
||||
renames =
|
||||
mapOf(
|
||||
"org.foo." to "com.foo.x.",
|
||||
"org.bar.Module2" to "org.bar.RenamedModule",
|
||||
"org.baz." to "com.baz.a.b."
|
||||
)
|
||||
)
|
||||
.generateFiles(
|
||||
"org/foo/Module1" to
|
||||
"""
|
||||
module org.foo.Module1
|
||||
|
||||
class Person {
|
||||
name: String
|
||||
}
|
||||
"""
|
||||
.trimIndent(),
|
||||
"org/bar/Module2" to
|
||||
"""
|
||||
module org.bar.Module2
|
||||
|
||||
import "../../org/foo/Module1.pkl"
|
||||
|
||||
class Group {
|
||||
owner: Module1.Person
|
||||
name: String
|
||||
}
|
||||
"""
|
||||
.trimIndent(),
|
||||
"org/baz/Module3" to
|
||||
"""
|
||||
module org.baz.Module3
|
||||
|
||||
import "../../org/bar/Module2.pkl"
|
||||
|
||||
class Supergroup {
|
||||
owner: Module2.Group
|
||||
}
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
|
||||
files.validateContents(
|
||||
"java/com/foo/x/Module1.java" to listOf("package com.foo.x;", "public final class Module1 {"),
|
||||
"$MAPPER_PREFIX/org.foo.Module1.properties" to
|
||||
listOf(
|
||||
"org.pkl.config.java.mapper.org.foo.Module1\\#ModuleClass=com.foo.x.Module1",
|
||||
"org.pkl.config.java.mapper.org.foo.Module1\\#Person=com.foo.x.Module1${'$'}Person",
|
||||
),
|
||||
// ---
|
||||
"java/org/bar/RenamedModule.java" to
|
||||
listOf(
|
||||
"package org.bar;",
|
||||
"import com.foo.x.Module1;",
|
||||
"public final class RenamedModule {",
|
||||
"public final Module1. @NonNull Person owner;"
|
||||
),
|
||||
"$MAPPER_PREFIX/org.bar.Module2.properties" to
|
||||
listOf(
|
||||
"org.pkl.config.java.mapper.org.bar.Module2\\#ModuleClass=org.bar.RenamedModule",
|
||||
"org.pkl.config.java.mapper.org.bar.Module2\\#Group=org.bar.RenamedModule${'$'}Group",
|
||||
),
|
||||
// ---
|
||||
"java/com/baz/a/b/Module3.java" to
|
||||
listOf(
|
||||
"package com.baz.a.b;",
|
||||
"import org.bar.RenamedModule;",
|
||||
"public final class Module3 {",
|
||||
"public final RenamedModule. @NonNull Group owner;"
|
||||
),
|
||||
"$MAPPER_PREFIX/org.baz.Module3.properties" to
|
||||
listOf(
|
||||
"org.pkl.config.java.mapper.org.baz.Module3\\#ModuleClass=com.baz.a.b.Module3",
|
||||
"org.pkl.config.java.mapper.org.baz.Module3\\#Supergroup=com.baz.a.b.Module3${'$'}Supergroup",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `do not capitalize names of renamed classes`() {
|
||||
val files =
|
||||
JavaCodegenOptions(
|
||||
renames = mapOf("a.b.c.MyModule" to "x.y.z.renamed_module", "d.e.f." to "u.v.w.")
|
||||
)
|
||||
.generateFiles(
|
||||
"MyModule.pkl" to
|
||||
"""
|
||||
module a.b.c.MyModule
|
||||
|
||||
foo: String = "abc"
|
||||
"""
|
||||
.trimIndent(),
|
||||
"lower_module.pkl" to
|
||||
"""
|
||||
module d.e.f.lower_module
|
||||
|
||||
bar: Int = 123
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
|
||||
files.validateContents(
|
||||
"java/x/y/z/renamed_module.java" to
|
||||
listOf("package x.y.z;", "public final class renamed_module {"),
|
||||
"$MAPPER_PREFIX/a.b.c.MyModule.properties" to
|
||||
listOf("org.pkl.config.java.mapper.a.b.c.MyModule\\#ModuleClass=x.y.z.renamed_module"),
|
||||
// ---
|
||||
"java/u/v/w/Lower_module.java" to
|
||||
listOf("package u.v.w;", "public final class Lower_module {"),
|
||||
"$MAPPER_PREFIX/d.e.f.lower_module.properties" to
|
||||
listOf("org.pkl.config.java.mapper.d.e.f.lower_module\\#ModuleClass=u.v.w.Lower_module"),
|
||||
)
|
||||
}
|
||||
|
||||
private fun Map<String, String>.validateContents(
|
||||
vararg assertions: kotlin.Pair<String, List<String>>
|
||||
) {
|
||||
val files = toMutableMap()
|
||||
|
||||
for ((fileName, lines) in assertions) {
|
||||
assertThat(files).containsKey(fileName)
|
||||
assertThat(files.remove(fileName)).describedAs("Contents of $fileName").contains(lines)
|
||||
}
|
||||
|
||||
assertThat(files).isEmpty()
|
||||
}
|
||||
|
||||
private fun JavaCodegenOptions.generateFiles(vararg pklModules: PklModule): Map<String, String> {
|
||||
val pklFiles = pklModules.map { it.writeToDisk(tempDir.resolve("pkl/${it.name}.pkl")) }
|
||||
val evaluator = Evaluator.preconfigured()
|
||||
return pklFiles.fold(mapOf()) { acc, pklFile ->
|
||||
val pklSchema = evaluator.evaluateSchema(path(pklFile))
|
||||
acc + JavaCodeGenerator(pklSchema, JavaCodegenOptions()).output
|
||||
val generator = JavaCodeGenerator(pklSchema, this)
|
||||
acc + generator.output
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateJavaFiles(tempDir: Path, vararg pklModules: PklModule): Map<String, String> {
|
||||
val pklFiles = pklModules.map { it.writeToDisk(tempDir.resolve("pkl/${it.name}.pkl")) }
|
||||
val evaluator = Evaluator.preconfigured()
|
||||
return pklFiles.fold(mapOf()) { acc, pklFile ->
|
||||
val pklSchema = evaluator.evaluateSchema(path(pklFile))
|
||||
val generator = JavaCodeGenerator(pklSchema, JavaCodegenOptions())
|
||||
acc + arrayOf(generator.javaFileName to generator.javaFile)
|
||||
}
|
||||
}
|
||||
private fun JavaCodegenOptions.generateFiles(
|
||||
vararg pklModules: kotlin.Pair<String, String>
|
||||
): Map<String, String> =
|
||||
generateFiles(*pklModules.map { (name, text) -> PklModule(name, text) }.toTypedArray())
|
||||
|
||||
private fun generateFiles(vararg pklModules: PklModule): Map<String, String> =
|
||||
JavaCodegenOptions().generateFiles(*pklModules)
|
||||
|
||||
private fun instantiateOtherAndPropertyTypes(): kotlin.Pair<Any, Any> {
|
||||
val otherCtor = propertyTypesClasses.getValue("my.Mod\$Other").constructors.first()
|
||||
|
||||
Reference in New Issue
Block a user