Skip to content

Commit

Permalink
[fixes #2046] you can now suppress the builder() method, useful if yo…
Browse files Browse the repository at this point in the history
…u only want toBuilder(). Also suppresses the warnings about any missing Builder.Default annotations.
  • Loading branch information
rzwitserloot committed Mar 25, 2019
1 parent 535e209 commit 228e99f
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 7 deletions.
1 change: 1 addition & 0 deletions doc/changelog.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ Lombok Changelog

### v1.18.7 "Edgy Guinea Pig"
* BUGFIX: var/val on methods that return an intersection type would now work in Eclipse [Issue #1986](https://github.com/rzwitserloot/lombok/issues/1986)
* FEATURE: You can now suppress generation of the `builder` method when using `@Builder`; usually because you're only interested in the `toBuilder` method. As a convenience we won't emit warnings about missing `@Builder.Default` annotations when you do this. [Issue #2046](https://github.com/rzwitserloot/lombok/issues/2046)

### v1.18.6 (February 12th, 2019)
* FEATURE: Javadoc on fields will now also be copied to the Builders' setters. Thanks for the contribution, Emil Lundberg. [Issue #2008](https://github.com/rzwitserloot/lombok/issues/2008)
Expand Down
24 changes: 21 additions & 3 deletions src/core/lombok/eclipse/handlers/HandleBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,15 @@ private static final char[] prefixWith(char[] prefix, char[] name) {
if (buildMethodName == null) builderMethodName = "build";
if (builderClassName == null) builderClassName = "";

if (!checkName("builderMethodName", builderMethodName, annotationNode)) return;
boolean generateBuilderMethod;
if (builderMethodName.isEmpty()) {
generateBuilderMethod = false;
} else if (!checkName("builderMethodName", builderMethodName, annotationNode)) {
return;
} else {
generateBuilderMethod = true;
}

if (!checkName("buildMethodName", buildMethodName, annotationNode)) return;
if (!builderClassName.isEmpty()) {
if (!checkName("builderClassName", builderClassName, annotationNode)) return;
Expand All @@ -192,6 +200,8 @@ private static final char[] prefixWith(char[] prefix, char[] name) {
boolean addCleaning = false;
boolean isStatic = true;

List<EclipseNode> nonFinalNonDefaultedFields = null;

if (parent.get() instanceof TypeDeclaration) {
tdParent = parent;
TypeDeclaration td = (TypeDeclaration) tdParent.get();
Expand Down Expand Up @@ -225,7 +235,8 @@ private static final char[] prefixWith(char[] prefix, char[] name) {

if (fd.initialization != null && isDefault == null) {
if (isFinal) continue;
fieldNode.addWarning("@Builder will ignore the initializing expression entirely. If you want the initializing expression to serve as default, add @Builder.Default. If it is not supposed to be settable during building, make the field final.");
if (nonFinalNonDefaultedFields == null) nonFinalNonDefaultedFields = new ArrayList<EclipseNode>();
nonFinalNonDefaultedFields.add(fieldNode);
}

if (isDefault != null) {
Expand Down Expand Up @@ -486,7 +497,8 @@ private static final char[] prefixWith(char[] prefix, char[] name) {
if (cleanMethod != null) injectMethod(builderType, cleanMethod);
}

if (methodExists(builderMethodName, tdParent, -1) == MemberExistsResult.NOT_EXISTS) {
if (generateBuilderMethod && methodExists(builderMethodName, tdParent, -1) != MemberExistsResult.NOT_EXISTS) generateBuilderMethod = false;
if (generateBuilderMethod) {
MethodDeclaration md = generateBuilderMethod(isStatic, builderMethodName, builderClassName, tdParent, typeParams, ast);
if (md != null) injectMethod(tdParent, md);
}
Expand All @@ -508,6 +520,12 @@ private static final char[] prefixWith(char[] prefix, char[] name) {

if (md != null) injectMethod(tdParent, md);
}

if (nonFinalNonDefaultedFields != null && generateBuilderMethod) {
for (EclipseNode fieldNode : nonFinalNonDefaultedFields) {
fieldNode.addWarning("@Builder will ignore the initializing expression entirely. If you want the initializing expression to serve as default, add @Builder.Default. If it is not supposed to be settable during building, make the field final.");
}
}
}

private static final char[] BUILDER_TEMP_VAR = {'b', 'u', 'i', 'l', 'd', 'e', 'r'};
Expand Down
24 changes: 21 additions & 3 deletions src/core/lombok/javac/handlers/HandleBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,15 @@ static class BuilderFieldData {
if (buildMethodName == null) buildMethodName = "build";
if (builderClassName == null) builderClassName = "";

if (!checkName("builderMethodName", builderMethodName, annotationNode)) return;
boolean generateBuilderMethod;
if (builderMethodName.isEmpty()) {
generateBuilderMethod = false;
} else if (!checkName("builderMethodName", builderMethodName, annotationNode)) {
return;
} else {
generateBuilderMethod = true;
}

if (!checkName("buildMethodName", buildMethodName, annotationNode)) return;
if (!builderClassName.isEmpty()) {
if (!checkName("builderClassName", builderClassName, annotationNode)) return;
Expand All @@ -140,6 +148,8 @@ static class BuilderFieldData {
boolean addCleaning = false;
boolean isStatic = true;

ArrayList<JavacNode> nonFinalNonDefaultedFields = null;

if (parent.get() instanceof JCClassDecl) {
tdParent = parent;
JCClassDecl td = (JCClassDecl) tdParent.get();
Expand Down Expand Up @@ -172,7 +182,8 @@ static class BuilderFieldData {

if (fd.init != null && isDefault == null) {
if (isFinal) continue;
fieldNode.addWarning("@Builder will ignore the initializing expression entirely. If you want the initializing expression to serve as default, add @Builder.Default. If it is not supposed to be settable during building, make the field final.");
if (nonFinalNonDefaultedFields == null) nonFinalNonDefaultedFields = new ArrayList<JavacNode>();
nonFinalNonDefaultedFields.add(fieldNode);
}

if (isDefault != null) {
Expand Down Expand Up @@ -431,7 +442,8 @@ static class BuilderFieldData {

if (addCleaning) injectMethod(builderType, generateCleanMethod(builderFields, builderType, ast));

if (methodExists(builderMethodName, tdParent, -1) == MemberExistsResult.NOT_EXISTS) {
if (generateBuilderMethod && methodExists(builderMethodName, tdParent, -1) != MemberExistsResult.NOT_EXISTS) generateBuilderMethod = false;
if (generateBuilderMethod) {
JCMethodDecl md = generateBuilderMethod(isStatic, builderMethodName, builderClassName, annotationNode, tdParent, typeParams);
recursiveSetGeneratedBy(md, ast, annotationNode.getContext());
if (md != null) injectMethod(tdParent, md);
Expand Down Expand Up @@ -459,6 +471,12 @@ static class BuilderFieldData {
}
}
}

if (nonFinalNonDefaultedFields != null && generateBuilderMethod) {
for (JavacNode fieldNode : nonFinalNonDefaultedFields) {
fieldNode.addWarning("@Builder will ignore the initializing expression entirely. If you want the initializing expression to serve as default, add @Builder.Default. If it is not supposed to be settable during building, make the field final.");
}
}
}

private static String unpack(JCExpression expr) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
class BuilderWithNoBuilderMethod {
private String a = "";
@java.lang.SuppressWarnings("all")
BuilderWithNoBuilderMethod(final String a) {
this.a = a;
}
@java.lang.SuppressWarnings("all")
public static class BuilderWithNoBuilderMethodBuilder {
@java.lang.SuppressWarnings("all")
private String a;
@java.lang.SuppressWarnings("all")
BuilderWithNoBuilderMethodBuilder() {
}
@java.lang.SuppressWarnings("all")
public BuilderWithNoBuilderMethodBuilder a(final String a) {
this.a = a;
return this;
}
@java.lang.SuppressWarnings("all")
public BuilderWithNoBuilderMethod build() {
return new BuilderWithNoBuilderMethod(a);
}
@java.lang.Override
@java.lang.SuppressWarnings("all")
public java.lang.String toString() {
return "BuilderWithNoBuilderMethod.BuilderWithNoBuilderMethodBuilder(a=" + this.a + ")";
}
}
@java.lang.SuppressWarnings("all")
public BuilderWithNoBuilderMethodBuilder toBuilder() {
return new BuilderWithNoBuilderMethodBuilder().a(this.a);
}
}
27 changes: 27 additions & 0 deletions test/transform/resource/after-ecj/BuilderWithNoBuilderMethod.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import lombok.Builder;
@Builder(toBuilder = true,builderMethodName = "") class BuilderWithNoBuilderMethod {
public static @java.lang.SuppressWarnings("all") class BuilderWithNoBuilderMethodBuilder {
private @java.lang.SuppressWarnings("all") String a;
@java.lang.SuppressWarnings("all") BuilderWithNoBuilderMethodBuilder() {
super();
}
public @java.lang.SuppressWarnings("all") BuilderWithNoBuilderMethodBuilder a(final String a) {
this.a = a;
return this;
}
public @java.lang.SuppressWarnings("all") BuilderWithNoBuilderMethod build() {
return new BuilderWithNoBuilderMethod(a);
}
public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() {
return (("BuilderWithNoBuilderMethod.BuilderWithNoBuilderMethodBuilder(a=" + this.a) + ")");
}
}
private String a = "";
@java.lang.SuppressWarnings("all") BuilderWithNoBuilderMethod(final String a) {
super();
this.a = a;
}
public @java.lang.SuppressWarnings("all") BuilderWithNoBuilderMethodBuilder toBuilder() {
return new BuilderWithNoBuilderMethodBuilder().a(this.a);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import lombok.Builder;
@Builder(toBuilder = true, builderMethodName = "")
class BuilderWithNoBuilderMethod {
private String a = "";
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
13 @Builder.Default requires @Builder on the class for it to mean anything.
6 @Builder will ignore the initializing expression entirely. If you want the initializing expression to serve as default, add @Builder.Default. If it is not supposed to be settable during building, make the field final.
8 @Builder.Default requires an initializing expression (' = something;').
9 @Builder.Default and @Singular cannot be mixed.
6 @Builder will ignore the initializing expression entirely. If you want the initializing expression to serve as default, add @Builder.Default. If it is not supposed to be settable during building, make the field final.
9 changes: 9 additions & 0 deletions website/templates/features/Builder.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
<code>@Builder</code> with <code>@Singular</code> adds a clear method since lombok v1.16.8.
</p><p>
<code>@Builder.Default</code> functionality was added in lombok v1.16.16.
</p><p>
<code>@Builder(builderMethodName = "")</code> is legal (and will suppress generation of the builder method) starting with lombok v1.18.8.
</p>
</@f.history>

Expand Down Expand Up @@ -183,6 +185,13 @@ <h3 id="jackson"><a name="jackson">With Jackson</a></h3>
The initializer on a <code>@Builder.Default</code> field is removed and stored in a static method, in order to guarantee that this initializer won't be executed at all if a value is specified in the build. This does mean the initializer cannot refer to <code>this</code>, <code>super</code> or any non-static member. If lombok generates a constructor for you, it'll also initialize this field with the initializer.
</p><p>
Various well known annotations about nullity cause null checks to be inserted and will be copied to parameter of the builder's 'setter' method. See <a href="/features/GetterSetter">Getter/Setter</a> documentation's small print for more information.
</p><p>
You can suppress the generation of the <code>builder()</code> method, for example because you <em>just</em> want the <code>toBuilder()</code> functionality, by using:
<code>@Builder(builderMethodName = "")</code>. Any warnings about missing <code>@Builder.Default</code> annotations will disappear when you do this, as such warnings
are not relevant when only using <code>toBuilder()</code> to make builder instances.
</p><p>
You can use <code>@Builder</code> for copy constructors: <code>foo.toBuilder().build()</code> makes a shallow clone. Consider suppressing the generating of the
<code>builder</code> method if you just want this functionality, by using: <code>@Builder(toBuilder = true, builderMethodName = "")</code>.
</p><p>
Due to a peculiar way javac processes static imports, trying to do a non-star static import of the static <code>builder()</code> method won't work. Either use a star static import: `import static TypeThatHasABuilder.*;` or don't statically import the <code>builder</code> method.
</p>
Expand Down

0 comments on commit 228e99f

Please sign in to comment.