From bd78d0841775b1a1c91ed4eef1defc62b33e9ed5 Mon Sep 17 00:00:00 2001
From: Reinier Zwitserloot
Date: Mon, 16 Nov 2015 23:46:12 +0100
Subject: [builder] Added clearX() functionality for `@Singular` annotations
for javac. Also docs and changelog.
---
doc/changelog.markdown | 1 +
.../singulars/EclipseGuavaSingularizer.java | 2 +-
.../EclipseJavaUtilListSetSingularizer.java | 2 +-
.../singulars/EclipseJavaUtilMapSingularizer.java | 2 +-
.../handlers/singulars/JavacGuavaSingularizer.java | 20 ++++++++++++++
.../JavacJavaUtilListSetSingularizer.java | 24 ++++++++++++++++
.../singulars/JavacJavaUtilMapSingularizer.java | 27 ++++++++++++++++++
.../BuilderSingularGuavaListsSets.java | 24 ++++++++++++++++
.../after-delombok/BuilderSingularGuavaMaps.java | 18 ++++++++++++
.../after-delombok/BuilderSingularLists.java | 18 ++++++++++++
.../after-delombok/BuilderSingularMaps.java | 32 ++++++++++++++++++++++
.../after-delombok/BuilderSingularNoAuto.java | 18 ++++++++++++
.../BuilderSingularRedirectToGuava.java | 18 ++++++++++++
.../after-delombok/BuilderSingularSets.java | 24 ++++++++++++++++
.../after-delombok/BuilderWithToBuilder.java | 6 ++++
usage_examples/BuilderExample_post.jpage | 8 ++++++
website/features/Builder.html | 7 +++--
17 files changed, 245 insertions(+), 6 deletions(-)
diff --git a/doc/changelog.markdown b/doc/changelog.markdown
index da1cefce..52ed1182 100644
--- a/doc/changelog.markdown
+++ b/doc/changelog.markdown
@@ -2,6 +2,7 @@ Lombok Changelog
----------------
### v1.16.7 "Edgy Guinea Pig"
+* FEATURE: The `@Builder` annotation has received many updates: It now generates `clearFieldName()` methods if `@Singular` is used, ..... [Issue #967](https://github.com/rzwitserloot/lombok/issues/967).
* FEATURE: A `lombok.config` key can now be used to make your fields `final` and/or `private`... __everywhere__. We'll be monitoring the performance impact of this for a while. We'll touch every source file if you turn these on, and even if you don't, we have to call into the lombok config system for every file.
* BUGFIX: `@Value` and `@FieldDefaults` no longer make uninitialized static fields final. [Issue #928](https://github.com/rzwitserloot/lombok/issues/928).
* BUGFIX: When using delombok, a source file with only `@NonNull` annotations on parameters as lombok feature would not get properly delomboked. [Issue #950](https://github.com/rzwitserloot/lombok/issues/950).
diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java
index 622451ee..d1c4d53c 100644
--- a/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java
+++ b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java
@@ -124,7 +124,7 @@ abstract class EclipseGuavaSingularizer extends EclipseSingularizer {
thisDotField.receiver = new ThisReference(0, 0);
Assignment a = new Assignment(thisDotField, new NullLiteral(0, 0), 0);
md.selector = HandlerUtil.buildAccessorName("clear", new String(data.getPluralName())).toCharArray();
- md.statements = new Statement[] {a, returnStatement};
+ md.statements = returnStatement != null ? new Statement[] {a, returnStatement} : new Statement[] {a};
md.returnType = returnType;
injectMethod(builderType, md);
}
diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java
index 5efb610f..2d8083d3 100644
--- a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java
+++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java
@@ -120,7 +120,7 @@ abstract class EclipseJavaUtilListSetSingularizer extends EclipseJavaUtilSingula
clearMsg.receiver = thisDotField2;
clearMsg.selector = "clear".toCharArray();
Statement clearStatement = new IfStatement(new EqualExpression(thisDotField, new NullLiteral(0, 0), OperatorIds.NOT_EQUAL), clearMsg, 0, 0);
- md.statements = new Statement[] {clearStatement, returnStatement};
+ md.statements = returnStatement != null ? new Statement[] {clearStatement, returnStatement} : new Statement[] {clearStatement};
md.returnType = returnType;
injectMethod(builderType, md);
}
diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java
index 3aaa9165..ef9e2a76 100644
--- a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java
+++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java
@@ -176,7 +176,7 @@ public class EclipseJavaUtilMapSingularizer extends EclipseJavaUtilSingularizer
Block clearMsgs = new Block(2);
clearMsgs.statements = new Statement[] {clearMsg1, clearMsg2};
Statement clearStatement = new IfStatement(new EqualExpression(thisDotField, new NullLiteral(0, 0), OperatorIds.NOT_EQUAL), clearMsgs, 0, 0);
- md.statements = new Statement[] {clearStatement, returnStatement};
+ md.statements = returnStatement != null ? new Statement[] {clearStatement, returnStatement} : new Statement[] {clearStatement};
md.returnType = returnType;
injectMethod(builderType, md);
}
diff --git a/src/core/lombok/javac/handlers/singulars/JavacGuavaSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacGuavaSingularizer.java
index 41e379f6..97e5f9be 100644
--- a/src/core/lombok/javac/handlers/singulars/JavacGuavaSingularizer.java
+++ b/src/core/lombok/javac/handlers/singulars/JavacGuavaSingularizer.java
@@ -78,6 +78,26 @@ abstract class JavacGuavaSingularizer extends JavacSingularizer {
returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(maker, CTC_VOID));
returnStatement = chain ? maker.Return(maker.Ident(builderType.toName("this"))) : null;
generatePluralMethod(maker, returnType, returnStatement, data, builderType, source, fluent);
+
+ returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(maker, CTC_VOID));
+ returnStatement = chain ? maker.Return(maker.Ident(builderType.toName("this"))) : null;
+ generateClearMethod(maker, returnType, returnStatement, data, builderType, source);
+ }
+
+ private void generateClearMethod(JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source) {
+ JCModifiers mods = maker.Modifiers(Flags.PUBLIC);
+ List typeParams = List.nil();
+ List thrown = List.nil();
+ List params = List.nil();
+
+ JCExpression thisDotField = maker.Select(maker.Ident(builderType.toName("this")), data.getPluralName());
+ JCStatement clearField = maker.Exec(maker.Assign(thisDotField, maker.Literal(CTC_BOT, null)));
+ List statements = returnStatement != null ? List.of(clearField, returnStatement) : List.of(clearField);
+
+ JCBlock body = maker.Block(0, statements);
+ Name methodName = builderType.toName(HandlerUtil.buildAccessorName("clear", data.getPluralName().toString()));
+ JCMethodDecl method = maker.MethodDef(mods, methodName, returnType, typeParams, params, thrown, body, null);
+ injectMethod(builderType, method);
}
void generateSingularMethod(JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source, boolean fluent) {
diff --git a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSetSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSetSingularizer.java
index 8574ddbf..e167c7e2 100644
--- a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSetSingularizer.java
+++ b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSetSingularizer.java
@@ -91,6 +91,30 @@ abstract class JavacJavaUtilListSetSingularizer extends JavacJavaUtilSingularize
returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(maker, CTC_VOID));
returnStatement = chain ? maker.Return(maker.Ident(thisName)) : null;
generatePluralMethod(maker, returnType, returnStatement, data, builderType, source, fluent);
+
+ returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(maker, CTC_VOID));
+ returnStatement = chain ? maker.Return(maker.Ident(thisName)) : null;
+ generateClearMethod(maker, returnType, returnStatement, data, builderType, source);
+ }
+
+ private void generateClearMethod(JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source) {
+ JCModifiers mods = maker.Modifiers(Flags.PUBLIC);
+ List typeParams = List.nil();
+ List thrown = List.nil();
+ List params = List.nil();
+ List jceBlank = List.nil();
+
+ JCExpression thisDotField = maker.Select(maker.Ident(builderType.toName("this")), data.getPluralName());
+ JCExpression thisDotFieldDotClear = maker.Select(maker.Select(maker.Ident(builderType.toName("this")), data.getPluralName()), builderType.toName("clear"));
+ JCStatement clearCall = maker.Exec(maker.Apply(jceBlank, thisDotFieldDotClear, jceBlank));
+ JCExpression cond = maker.Binary(CTC_NOT_EQUAL, thisDotField, maker.Literal(CTC_BOT, null));
+ JCStatement ifSetCallClear = maker.If(cond, clearCall, null);
+ List statements = returnStatement != null ? List.of(ifSetCallClear, returnStatement) : List.of(ifSetCallClear);
+
+ JCBlock body = maker.Block(0, statements);
+ Name methodName = builderType.toName(HandlerUtil.buildAccessorName("clear", data.getPluralName().toString()));
+ JCMethodDecl method = maker.MethodDef(mods, methodName, returnType, typeParams, params, thrown, body, null);
+ injectMethod(builderType, method);
}
void generateSingularMethod(JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source, boolean fluent) {
diff --git a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilMapSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilMapSingularizer.java
index 0830c9c9..1acae7e3 100644
--- a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilMapSingularizer.java
+++ b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilMapSingularizer.java
@@ -115,6 +115,33 @@ public class JavacJavaUtilMapSingularizer extends JavacJavaUtilSingularizer {
returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(maker, CTC_VOID));
returnStatement = chain ? maker.Return(maker.Ident(builderType.toName("this"))) : null;
generatePluralMethod(maker, returnType, returnStatement, data, builderType, source, fluent);
+
+ returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(maker, CTC_VOID));
+ returnStatement = chain ? maker.Return(maker.Ident(builderType.toName("this"))) : null;
+ generateClearMethod(maker, returnType, returnStatement, data, builderType, source);
+ }
+
+ private void generateClearMethod(JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source) {
+ JCModifiers mods = maker.Modifiers(Flags.PUBLIC);
+ List typeParams = List.nil();
+ List thrown = List.nil();
+ List params = List.nil();
+ List jceBlank = List.nil();
+
+ JCExpression thisDotKeyField = chainDots(builderType, "this", data.getPluralName() + "$key");
+ JCExpression thisDotKeyFieldDotClear = chainDots(builderType, "this", data.getPluralName() + "$key", "clear");
+ JCExpression thisDotValueFieldDotClear = chainDots(builderType, "this", data.getPluralName() + "$value", "clear");
+ JCStatement clearKeyCall = maker.Exec(maker.Apply(jceBlank, thisDotKeyFieldDotClear, jceBlank));
+ JCStatement clearValueCall = maker.Exec(maker.Apply(jceBlank, thisDotValueFieldDotClear, jceBlank));
+ JCExpression cond = maker.Binary(CTC_NOT_EQUAL, thisDotKeyField, maker.Literal(CTC_BOT, null));
+ JCBlock clearCalls = maker.Block(0, List.of(clearKeyCall, clearValueCall));
+ JCStatement ifSetCallClear = maker.If(cond, clearCalls, null);
+ List statements = returnStatement != null ? List.of(ifSetCallClear, returnStatement) : List.of(ifSetCallClear);
+
+ JCBlock body = maker.Block(0, statements);
+ Name methodName = builderType.toName(HandlerUtil.buildAccessorName("clear", data.getPluralName().toString()));
+ JCMethodDecl method = maker.MethodDef(mods, methodName, returnType, typeParams, params, thrown, body, null);
+ injectMethod(builderType, method);
}
private void generateSingularMethod(JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source, boolean fluent) {
diff --git a/test/transform/resource/after-delombok/BuilderSingularGuavaListsSets.java b/test/transform/resource/after-delombok/BuilderSingularGuavaListsSets.java
index f8e0579d..3339d809 100644
--- a/test/transform/resource/after-delombok/BuilderSingularGuavaListsSets.java
+++ b/test/transform/resource/after-delombok/BuilderSingularGuavaListsSets.java
@@ -51,6 +51,12 @@ class BuilderSingularGuavaListsSets {
}
@java.lang.SuppressWarnings("all")
@javax.annotation.Generated("lombok")
+ public BuilderSingularGuavaListsSetsBuilder clearCards() {
+ this.cards = null;
+ return this;
+ }
+ @java.lang.SuppressWarnings("all")
+ @javax.annotation.Generated("lombok")
public BuilderSingularGuavaListsSetsBuilder frog(final Number frog) {
if (this.frogs == null) this.frogs = com.google.common.collect.ImmutableList.builder();
this.frogs.add(frog);
@@ -65,6 +71,12 @@ class BuilderSingularGuavaListsSets {
}
@java.lang.SuppressWarnings("all")
@javax.annotation.Generated("lombok")
+ public BuilderSingularGuavaListsSetsBuilder clearFrogs() {
+ this.frogs = null;
+ return this;
+ }
+ @java.lang.SuppressWarnings("all")
+ @javax.annotation.Generated("lombok")
public BuilderSingularGuavaListsSetsBuilder rawSet(final java.lang.Object rawSet) {
if (this.rawSet == null) this.rawSet = com.google.common.collect.ImmutableSet.builder();
this.rawSet.add(rawSet);
@@ -79,6 +91,12 @@ class BuilderSingularGuavaListsSets {
}
@java.lang.SuppressWarnings("all")
@javax.annotation.Generated("lombok")
+ public BuilderSingularGuavaListsSetsBuilder clearRawSet() {
+ this.rawSet = null;
+ return this;
+ }
+ @java.lang.SuppressWarnings("all")
+ @javax.annotation.Generated("lombok")
public BuilderSingularGuavaListsSetsBuilder pass(final String pass) {
if (this.passes == null) this.passes = com.google.common.collect.ImmutableSortedSet.naturalOrder();
this.passes.add(pass);
@@ -93,6 +111,12 @@ class BuilderSingularGuavaListsSets {
}
@java.lang.SuppressWarnings("all")
@javax.annotation.Generated("lombok")
+ public BuilderSingularGuavaListsSetsBuilder clearPasses() {
+ this.passes = null;
+ return this;
+ }
+ @java.lang.SuppressWarnings("all")
+ @javax.annotation.Generated("lombok")
public BuilderSingularGuavaListsSets build() {
com.google.common.collect.ImmutableList cards = this.cards == null ? com.google.common.collect.ImmutableList.of() : this.cards.build();
com.google.common.collect.ImmutableCollection frogs = this.frogs == null ? com.google.common.collect.ImmutableList.of() : this.frogs.build();
diff --git a/test/transform/resource/after-delombok/BuilderSingularGuavaMaps.java b/test/transform/resource/after-delombok/BuilderSingularGuavaMaps.java
index 0cb0001b..10545a8f 100644
--- a/test/transform/resource/after-delombok/BuilderSingularGuavaMaps.java
+++ b/test/transform/resource/after-delombok/BuilderSingularGuavaMaps.java
@@ -45,6 +45,12 @@ class BuilderSingularGuavaMaps {
}
@java.lang.SuppressWarnings("all")
@javax.annotation.Generated("lombok")
+ public BuilderSingularGuavaMapsBuilder clearBattleaxes() {
+ this.battleaxes = null;
+ return this;
+ }
+ @java.lang.SuppressWarnings("all")
+ @javax.annotation.Generated("lombok")
public BuilderSingularGuavaMapsBuilder vertex(final Integer vertex$key, final V vertex$value) {
if (this.vertices == null) this.vertices = com.google.common.collect.ImmutableSortedMap.naturalOrder();
this.vertices.put(vertex$key, vertex$value);
@@ -59,6 +65,12 @@ class BuilderSingularGuavaMaps {
}
@java.lang.SuppressWarnings("all")
@javax.annotation.Generated("lombok")
+ public BuilderSingularGuavaMapsBuilder clearVertices() {
+ this.vertices = null;
+ return this;
+ }
+ @java.lang.SuppressWarnings("all")
+ @javax.annotation.Generated("lombok")
public BuilderSingularGuavaMapsBuilder rawMap(final java.lang.Object rawMap$key, final java.lang.Object rawMap$value) {
if (this.rawMap == null) this.rawMap = com.google.common.collect.ImmutableBiMap.builder();
this.rawMap.put(rawMap$key, rawMap$value);
@@ -73,6 +85,12 @@ class BuilderSingularGuavaMaps {
}
@java.lang.SuppressWarnings("all")
@javax.annotation.Generated("lombok")
+ public BuilderSingularGuavaMapsBuilder clearRawMap() {
+ this.rawMap = null;
+ return this;
+ }
+ @java.lang.SuppressWarnings("all")
+ @javax.annotation.Generated("lombok")
public BuilderSingularGuavaMaps build() {
com.google.common.collect.ImmutableMap battleaxes = this.battleaxes == null ? com.google.common.collect.ImmutableMap.of() : this.battleaxes.build();
com.google.common.collect.ImmutableSortedMap vertices = this.vertices == null ? com.google.common.collect.ImmutableSortedMap.of() : this.vertices.build();
diff --git a/test/transform/resource/after-delombok/BuilderSingularLists.java b/test/transform/resource/after-delombok/BuilderSingularLists.java
index f58934d4..9b409404 100644
--- a/test/transform/resource/after-delombok/BuilderSingularLists.java
+++ b/test/transform/resource/after-delombok/BuilderSingularLists.java
@@ -44,6 +44,12 @@ class BuilderSingularLists {
}
@java.lang.SuppressWarnings("all")
@javax.annotation.Generated("lombok")
+ public BuilderSingularListsBuilder clearChildren() {
+ if (this.children != null) this.children.clear();
+ return this;
+ }
+ @java.lang.SuppressWarnings("all")
+ @javax.annotation.Generated("lombok")
public BuilderSingularListsBuilder scarf(final Number scarf) {
if (this.scarves == null) this.scarves = new java.util.ArrayList();
this.scarves.add(scarf);
@@ -58,6 +64,12 @@ class BuilderSingularLists {
}
@java.lang.SuppressWarnings("all")
@javax.annotation.Generated("lombok")
+ public BuilderSingularListsBuilder clearScarves() {
+ if (this.scarves != null) this.scarves.clear();
+ return this;
+ }
+ @java.lang.SuppressWarnings("all")
+ @javax.annotation.Generated("lombok")
public BuilderSingularListsBuilder rawList(final java.lang.Object rawList) {
if (this.rawList == null) this.rawList = new java.util.ArrayList();
this.rawList.add(rawList);
@@ -72,6 +84,12 @@ class BuilderSingularLists {
}
@java.lang.SuppressWarnings("all")
@javax.annotation.Generated("lombok")
+ public BuilderSingularListsBuilder clearRawList() {
+ if (this.rawList != null) this.rawList.clear();
+ return this;
+ }
+ @java.lang.SuppressWarnings("all")
+ @javax.annotation.Generated("lombok")
public BuilderSingularLists build() {
java.util.List children;
switch (this.children == null ? 0 : this.children.size()) {
diff --git a/test/transform/resource/after-delombok/BuilderSingularMaps.java b/test/transform/resource/after-delombok/BuilderSingularMaps.java
index 212ece26..257a2ec2 100644
--- a/test/transform/resource/after-delombok/BuilderSingularMaps.java
+++ b/test/transform/resource/after-delombok/BuilderSingularMaps.java
@@ -57,6 +57,14 @@ class BuilderSingularMaps {
return this;
}
@SuppressWarnings("all")
+ public BuilderSingularMapsBuilder clearWomen() {
+ if (this.women$key != null) {
+ this.women$key.clear();
+ this.women$value.clear();
+ }
+ return this;
+ }
+ @SuppressWarnings("all")
public BuilderSingularMapsBuilder man(K manKey, Number manValue) {
if (this.men$key == null) {
this.men$key = new java.util.ArrayList();
@@ -79,6 +87,14 @@ class BuilderSingularMaps {
return this;
}
@SuppressWarnings("all")
+ public BuilderSingularMapsBuilder clearMen() {
+ if (this.men$key != null) {
+ this.men$key.clear();
+ this.men$value.clear();
+ }
+ return this;
+ }
+ @SuppressWarnings("all")
public BuilderSingularMapsBuilder rawMap(Object rawMapKey, Object rawMapValue) {
if (this.rawMap$key == null) {
this.rawMap$key = new java.util.ArrayList
@Builder gained @Singular support and was promoted to the main lombok package since lombok v1.16.0.
-
+
+ @Builder with @Singular adds a clear method since lombok v1.16.8.
Overview
@@ -82,11 +83,11 @@
By annotating one of the parameters (if annotating a static method or constructor with @Builder) or fields (if annotating a class with @Builder) with the
@Singular annotation, lombok will treat that builder node as a collection, and it generates 2 'adder' methods instead of a 'setter' method. One which adds a single element to the collection, and one
- which adds all elements of another collection to the collection. No setter to just set the collection (replacing whatever was already added) will be generated. These 'singular' builders
+ which adds all elements of another collection to the collection. No setter to just set the collection (replacing whatever was already added) will be generated. A 'clear' method is also generated. These 'singular' builders
are very complicated in order to guarantee the following properties:
When invoking build(), the produced collection will be immutable.
-
Calling one of the 'adder' methods after invoking build() does not modify any already generated objects, and, if build() is later called again, another collection with all the elements added since the creation of the builder is generated.
+
Calling one of the 'adder' methods, or the 'clear' method, after invoking build() does not modify any already generated objects, and, if build() is later called again, another collection with all the elements added since the creation of the builder is generated.
The produced collection will be compacted to the smallest feasible format while remaining efficient.