Consider you want to have
two immutable value classes that should have a
bidirectional association between them; let's call them
Container and
Content:
public class Container {
private final Content content;
// ... more fields ...
}
public class Content {
private final Container container;
// ... more fields ...
}
Using
@AllArgsConstructors for these classes, you have a problem: You need a reference to the other instance as argument for both constructors, but one instance has to be created before the other, so that's impossible.
If you manually write one of your constructors, you could create the other instance within this constructor. However, then you have to put
all fields of both classes into that constructor. That may lead to a large number of parameters, and could quickly get confusing (which parameter belongs to which class?).
A possible solution is to use builders. As a builder represents the future state of the object to be, you can simply pass a builder as argument to one of the constructors. For instance, the constructor for
Container gets a
ContentBuilder as parameter. The constructor fills in the "back reference" in that builder, calls
build() to create the
Content instance, and set the result as its "forward reference".
I propose the following syntax (all names preliminary and subject to discussion):
@SuperBuilder
public class Container {
@SuperBuilder.BidirectionalReference(backReferenceSetterName = "container")
private final Content content;
}
@SuperBuilder
public class Content {
private final Container container;
}
Lombok will generate the following code for
Container (for simplicity, I ommitted the type params and
ContainerBuilderImpl class stuff):
public class Container {
public static class ContainerBuilder {
private Content.ContentBuilder contentBuilder;
public ContainerBuilder content(Content.ContentBuilder contentBuilder) {
this.contentBuilder = contentBuilder;
return this;
}
public Container build() {
return new Container(this);
}
}
public Container(ContainerBuilder builder) {
this.content = builder.contentBuilder != null ? builder.contentBuilder.container(this).build() : null;
}
private final Content content;
}
The generated builder code for
Content remains as it is right now.
What's happening is that the builder's setter method for
content now has a
ContentBuilder as parameter. (As
@SuperBuilder builder classes cannot be renamed, lombok knows the name of the builder class without resolution.)
In the constructor, the instance-in-creation (
this) is set as "back reference" within the
ContentBuilder. Because lombok cannot look into the other class, it cannot know the name of this back reference. Thus, this needs to be a parameter of the new
@SuperBuilder.BidirectionalReference annotation.
Finally, the
Container constructor creates the new
Content instance using the
build() method and sets the result as "forward reference".
This approach could also be extended to support 1-to-n relationships. The builder's setter will then get a list of
ContentBuilders instead of a list of
Contents. It may also be combinable with
@Singular, but this will certainly be a later stage of development. Note that implementing it for
@Builder will be problematic though, mainly because it would require changing from an all-args constructor to a custom constructor, which could easily break existing code.
What do you think? Is that a use-case worth supporting? Are there any problems with it I don't see?