Even though builder is a very common pattern and all modern IDEs are able to generate them on the fly, they tend to get messy when concepts like inheritance or nesting get into the picture.
All the generated builders implement the Builder
interface which looks like:
public interface Builder<T> {
T build();
}
For usability and readability builders are in most cases fluent
interfaces and use method cascading. This means that all but the
build method return the builder instance itself. This project makes
a distinction between the Builder and Fluent part, by generating
both a Builder
and a Fluent
class (with the Builder
extending
the Fluent
).
The fluent part implements the following interface.
public interface Fluent<F extends Fluent<F>> {
}
Obviously a single class can't implement Builder
s with different
type parameters and thus it's not possible to have a builder extend an
other build (unless of course they use the same or generic type
parameters and abuse generics).
To keep things as simple as possible and maximize re-usability of the
generated bits, this project allows inheritance between Fluent
implementations (which contain all the reusable bits) and then each
builder just extends the corresponding Fluent
implementation. This
minimizes boilerplate and also allows the use of Fluent
, outside the
context of the builder (see below).
On any POJO you can add the @Buildable
annotation on a single
constructor. On compile time a builder will get generated that will
contain methods for all of the constructor arguments. For example:
@Buildable
public Circle(int x, int y, int radius)
This will generate a builder that you can then use:
Circle myCircle = new CircleBuilder().withX(0).withY(0).withRadius(10).build();
An other example could be the:
@Buildable
public Square(int x, int y, int size)
Which you could use like:
Square mySquare = new SquareBuilder().withX(0).withY(0).withSize(10).build();
In the examples above, both Circle
and Square
are actually
Shapes. We could have the class Shape
as a superclass of Circle
and Square
and move fields x and y there.
@Buildable
public Shape(int x, int y)
In this case the CircleFluent
and SquareFluent
will extend the
ShapeFluent
and each builder will extend the corresponding Fluent
.
As shown below:
Fluent Builder
------ ----------------
^ ^ ^ ^
| | | |
| | | |
ShapeFluent | | |
----------- | | |
^ ^ ^ | | |
| | | | | |
| | +-- ShapeBuilder | |
| | ------------ | |
| | | |
+----+ +-------+ | |
| | | |
CircleFluent SquareFluent | |
------------ ------------ | |
^ ^ | |
| | | |
| SquareBuilder----+ |
| |
| |
| +-----------------------------+
| |
CircleBuilder
The generated builders can validate the objects before returning them
(if a validation provider is available). To enable this feature just
set the validationEnabled
flag to true
:
package my.demo
import javax.validation.constraints.NotNull;
public class Demo {
@NotNull
private String value;
@Buildable(validationEnabled=true)
public Demo(....) {
// ...
}
}
By default the objects created by the builder are editable. This
means that the created object automatically implement a method
edit()
which, if called, returns a new builder initialized with the
data of the current object.
Consider for example the Circle
object then you can use the
following code to easily create a modified clone of it:
Circle circle = new CircleBuilder()
.withX(10)
.withY(10)
.withRadius(100)
.build();
// Create a new CircleBuilder with edit(), set props and the build
Circle clone = ((Editable) circle).edit().withRadius(120).build();
You can disable this behaviour by using @Buildable(editableEnabled = false)
In case you need to avoid having any kind of runtime dependency to
this tools interfaces like Builder
or Fluent
interfaces, you can
set the generateBuilderPackage
flag on the @Buildable
annotation
to true
. This will result in generating the base interfaces
themselves in the generated source directory, eliminating all runtime
dependencies to this too. The target package can be set with
builderPackage
. If not given, the default io.sundr.builder
is
used.
package my.demo
public class Demo {
@Buildable(generateBuilderPackage=true,builderPackage="my.pkg")
public Demo(....) {
// ...
}
}
In case you want to generate Builders and Fluents for 3rd party
classes you can use the @ExternalBuilder
annotation. This
annotation can be added on top of any Class. Inside the annotation you
can specify the fully qualified names of the types that for which you
want to generate builders.
@ExternalBuilders({"some.ExternalClass", "yet.another.thirdPartyClass"})
public class MyExternalCase {
}