Code Generation
kUML ships three code generators out of the box. They share a CodeGenRegistry
infrastructure, so authoring a new generator follows the same pattern as a theme or
profile — META-INF/services registration, done.
Built-in generators
| Generator | Output |
|---|---|
|
Kotlin data classes for each |
|
Java POJOs, records, or Lombok-annotated classes. Honours JavaEE / JPA stereotypes
for |
|
DDL for PostgreSQL (default), MySQL, H2, or SQLite. Topologically sorted
|
Generating Kotlin
kuml generate --input model.kuml.kts \
--plugin kotlin \
--package com.example.domain \
--output gen/
Output structure:
gen/
└── com/
└── example/
└── domain/
├── Order.kt
├── OrderItem.kt
└── OrderStatus.kt
The Kotlin generator produces idiomatic data classes with no framework dependencies.
Operations become method stubs marked TODO() — kUML generates signatures, not
implementations. That’s a deliberate choice: model the contract, write the body in Kotlin.
Generating Java
kuml generate --input model.kuml.kts \
--plugin java \
--package com.example.domain \
--options java-style=records \
--output gen/
java-style options:
-
pojo(default) — classic class with private fields and getters/setters -
records— Java 16+ record types -
lombok—@Data-annotated POJO (requires the Lombok dependency)
The Java generator inspects applied JavaEE stereotypes:
classOf("Order") {
stereotype("Entity", "tableName" to "orders")
attribute("id", "UUID") { stereotype("Id") }
attribute("notes", "String") { stereotype("Transient") }
}
produces:
@jakarta.persistence.Entity(name = "orders")
public class Order {
@jakarta.persistence.Id
private UUID id;
@jakarta.persistence.Transient
private String notes;
// getters/setters omitted
}
JavaEE annotations are emitted fully qualified — that side-steps import collisions and makes the output self-documenting. Lombok-mode is the exception (mainstream pattern, short forms expected).
Generating SQL DDL
kuml generate --input model.kuml.kts \
--plugin sql \
--options sql-dialect=postgres,sql-drop=true \
--output gen/
sql-dialect options: postgres (default), mysql, h2, sqlite.
sql-drop=true prepends DROP TABLE IF EXISTS … statements (handy for migrations).
The output is a single schema.sql with:
-
Optional
DROP TABLEblock -
Enum types emitted as
VARCHARwith aCHECKconstraint on all dialects -
CREATE TABLEstatements, topologically sorted so referenced tables exist before referrers -
ALTER TABLE … ADD CONSTRAINT … FOREIGN KEY …block at the end (avoids circular FK problems during deployment)
Multiplicity translates to constraints:
| UML multiplicity | SQL |
|---|---|
|
|
|
|
|
Real junction table with a composite primary key (emitted via the ERM path) |
Stereotype-driven mapping:
-
Entity{tableName="users"}→CREATE TABLE users (…) -
Id→PRIMARY KEY -
Column{name="created_at"}→ renames the column
Under the hood, --plugin sql runs on an ERM model, not directly on the UML
model — a UML script is first transformed to ERM (UML → ERM), then ErmSqlEmitter
turns the ERM model into DDL (ERM → SQL). This intermediate step is transparent for
the UML-direct path above, but it is also directly accessible: author an
ermModel(…) { … } script yourself and generate from it with the same
--plugin sql, or with --plugin exposed for Kotlin Exposed tables. See
ERM DSL for the full model, all four rendering notations, and the
UML→ERM transform.
|
Generating from Gradle
The Gradle plugin’s kumlGenerate task takes the same options through the extension:
kuml {
generator.set("java")
generatePackage.set("com.example.domain")
generateOptions.put("java-style", "records")
}
kumlGenerate writes to ${buildDir}/kuml/generated/ by default. Configure
outputDir.dir("generated") to override.
C++ and C# code generation
kUML ships two optional codegen plugins for native languages:
| Plugin id | Module | Output |
|---|---|---|
|
|
ISO C++ 17 header ( |
|
|
C# 10 |
kuml generate model.kuml.kts \
--plugin cpp \
--namespace com::example::domain \
--output gen/
kuml generate model.kuml.kts \
--plugin csharp \
--namespace Example.Domain \
--output gen/
The C++ generator produces one .hpp / .cpp pair per UmlClass. Forward declarations
handle circular includes; enum class maps to UmlEnumeration; virtual methods with
= 0 stub operations on abstract classes. The C# generator emits idiomatic C# 10:
record for value objects (no mutable attributes), class otherwise; List<T> for
1..* multiplicities; nullable reference types (T?) for optional attributes.
C++ and C# reverse engineering
kuml reverse reconstructs a UML class diagram from existing C++ or C# source code:
# C# — reads all *.cs files in the directory tree
kuml reverse --format csharp ./src/ --output model.kuml.kts
# C++ — reads all *.hpp / *.h / *.cpp files
kuml reverse --format cpp ./include/ --output model.kuml.kts
Both plugins ship as example plugins under kuml-plugin-examples/:
| Plugin | Module |
|---|---|
|
|
|
|
The C# reverse engine (CsharpReverseEngine) is a handwritten four-phase structural
parser — no ANTLR dependency. It handles:
-
File-scoped namespaces (
namespace X.Y;) and block namespaces. -
class,abstract class,sealed class,static class,interface,struct,record,enum. -
Auto-properties (
T Name { get; set; }), methods, fields, readonly fields. -
Generic types:
List<T>,IEnumerable<T>,T[],Dictionary<K,V>,HashSet<T>. -
Base list classification: exact known-interface set first, then
I-prefix heuristic.
Safety limits: 10 MB per file, 2000 files per run, symlink-escape guard.
The C++ reverse engine uses the same structural approach, parsing headers for class and struct declarations, method signatures, and inheritance lists.
Register the plugins via META-INF/services in the standard plugin loader way, or
pass --plugin-jar plugin-reverse-csharp.jar on the CLI.
Authoring a custom generator
Implement KumlCodeGenerator and provide a KumlCodeGeneratorProvider:
class TypeScriptCodeGenerator : KumlCodeGenerator {
override fun generate(
diagram: KumlDiagram,
outputDir: File,
options: Map<String, String>,
): List<File> {
// emit .ts files, return the list of written files
}
}
class TypeScriptCodeGeneratorProvider : KumlCodeGeneratorProvider {
override val name: String = "typescript"
override fun create(): KumlCodeGenerator = TypeScriptCodeGenerator()
}
Register via META-INF/services/dev.kuml.codegen.api.KumlCodeGeneratorProvider. CLI/Gradle
pick it up via --plugin typescript / generator.set("typescript").
The KumlCodeGenerator API is intentionally minimal — diagram + outputDir + options in,
list of written files out. Generators decide their own directory layout.