This is a KSP processor that generates builders for Kotlin classes. It is intended to provide just enough functionality to help migrate away from Immutables and ditch KAPT annotation processing.
See the integration-tests subproject for a working setup.
You have to bring in the KSP plugin
plugins {
id("com.google.devtools.ksp") version "1.6.10-1.0.2"
}
and add the processor as a ksp
dependency and the annotations as an implementation
dependency:
dependencies {
implementation("com.toasttab.ksp.builder:ksp-builder-gen-annotations:${version}")
ksp("com.toasttab.ksp.builder:ksp-builder-gen-processor:${version}")
}
Unlike Immutables, you start with a simple Kotlin data-ish class. A more precise definition of a data-ish class is a class whose primary constructor's parameters are all backed by public properties. To generate a builder for a class, annotate the class with @GenerateBuilder
.
@GenerateBuilder
class User(
val name: String,
val email: String?
)
The generated code will look like this
public class UserBuilder() {
private var name: String? = null
private var email: String? = null
public constructor(o: User) : this() {
this.name = o.name
this.email = o.email
}
public fun name(name: String): UserBuilder {
this.name = name
return this
}
public fun email(email: String?): UserBuilder {
this.email = email
return this
}
public fun build(): User = User(name!!, email)
}
For basic collections (Collection
, List
, Set
, Map
), convenience mutators will be generated. For example,
@GenerateBuilder
class Container(
val map: Map<String, Long>,
val list: List<String>
)
will yield
public class ContainerBuilder() {
public putMap(k: String, v: Long): ContainerBuilder // { ... }
public putAllMap(Map<String, Long> map): ContainerBuilder // { ... }
public addList(o: String): ContainerBuilder // { ... }
public addAllList(Iterable<String>): ContainerBuilder // { ... }
}
The name of the builder class is "${className}Builder"
by default. It is customizable via the name
annotation attribute.
Defaults are supported via a custom annotation. Unfortunately, the code generator does not have access to parameters' default values, but it knows whether a default exists. For the sake of consistency, if the @Default
annotation is present, the property must also have a Kotlin default.
@GenerateBuilder
class User(
@GenerateBuilder.Default("true")
val active = true
)
The @Default
annotation is not supported for collection properties. You can do really bad things if you put complex expressions into the annotation; so don't.
For callsites written in Kotlin, it is typically desirable to use the constructor directly instead of calling the builder. Generated builders can be marked @Deprecated
via the deprecated
attribute.
- Convert the Immutables interface spec to a concrete Kotlin class, add
@GenerateBuilder
, and adapt existing callsites to the new builder. - Add
deprecated = true
to the@GenerateBuilder
annotation when all callsites are converted to Kotlin. - ?
- Profit