From 228e99fe5203e92c7297325fec69a82abc1a4bd7 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Mon, 25 Mar 2019 23:11:48 +0100 Subject: [fixes #2046] you can now suppress the builder() method, useful if you only want toBuilder(). Also suppresses the warnings about any missing Builder.Default annotations. --- doc/changelog.markdown | 1 + .../lombok/eclipse/handlers/HandleBuilder.java | 24 ++++++++++++++-- src/core/lombok/javac/handlers/HandleBuilder.java | 24 ++++++++++++++-- .../after-delombok/BuilderWithNoBuilderMethod.java | 33 ++++++++++++++++++++++ .../after-ecj/BuilderWithNoBuilderMethod.java | 27 ++++++++++++++++++ .../before/BuilderWithNoBuilderMethod.java | 5 ++++ .../BuilderDefaultsWarnings.java.messages | 2 +- website/templates/features/Builder.html | 9 ++++++ 8 files changed, 118 insertions(+), 7 deletions(-) create mode 100644 test/transform/resource/after-delombok/BuilderWithNoBuilderMethod.java create mode 100644 test/transform/resource/after-ecj/BuilderWithNoBuilderMethod.java create mode 100644 test/transform/resource/before/BuilderWithNoBuilderMethod.java diff --git a/doc/changelog.markdown b/doc/changelog.markdown index 17326a58..4f8d203e 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -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) diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java index 3391b99d..3adbc27c 100755 --- a/src/core/lombok/eclipse/handlers/HandleBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java @@ -173,7 +173,15 @@ public class HandleBuilder extends EclipseAnnotationHandler { 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; @@ -192,6 +200,8 @@ public class HandleBuilder extends EclipseAnnotationHandler { boolean addCleaning = false; boolean isStatic = true; + List nonFinalNonDefaultedFields = null; + if (parent.get() instanceof TypeDeclaration) { tdParent = parent; TypeDeclaration td = (TypeDeclaration) tdParent.get(); @@ -225,7 +235,8 @@ public class HandleBuilder extends EclipseAnnotationHandler { 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(); + nonFinalNonDefaultedFields.add(fieldNode); } if (isDefault != null) { @@ -486,7 +497,8 @@ public class HandleBuilder extends EclipseAnnotationHandler { 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); } @@ -508,6 +520,12 @@ public class HandleBuilder extends EclipseAnnotationHandler { 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'}; diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java index 609adbd6..2ef2fac2 100644 --- a/src/core/lombok/javac/handlers/HandleBuilder.java +++ b/src/core/lombok/javac/handlers/HandleBuilder.java @@ -119,7 +119,15 @@ public class HandleBuilder extends JavacAnnotationHandler { 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; @@ -140,6 +148,8 @@ public class HandleBuilder extends JavacAnnotationHandler { boolean addCleaning = false; boolean isStatic = true; + ArrayList nonFinalNonDefaultedFields = null; + if (parent.get() instanceof JCClassDecl) { tdParent = parent; JCClassDecl td = (JCClassDecl) tdParent.get(); @@ -172,7 +182,8 @@ public class HandleBuilder extends JavacAnnotationHandler { 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(); + nonFinalNonDefaultedFields.add(fieldNode); } if (isDefault != null) { @@ -431,7 +442,8 @@ public class HandleBuilder extends JavacAnnotationHandler { 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); @@ -459,6 +471,12 @@ public class HandleBuilder extends JavacAnnotationHandler { } } } + + 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) { diff --git a/test/transform/resource/after-delombok/BuilderWithNoBuilderMethod.java b/test/transform/resource/after-delombok/BuilderWithNoBuilderMethod.java new file mode 100644 index 00000000..35e2c79e --- /dev/null +++ b/test/transform/resource/after-delombok/BuilderWithNoBuilderMethod.java @@ -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); + } +} diff --git a/test/transform/resource/after-ecj/BuilderWithNoBuilderMethod.java b/test/transform/resource/after-ecj/BuilderWithNoBuilderMethod.java new file mode 100644 index 00000000..624b14b9 --- /dev/null +++ b/test/transform/resource/after-ecj/BuilderWithNoBuilderMethod.java @@ -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); + } +} diff --git a/test/transform/resource/before/BuilderWithNoBuilderMethod.java b/test/transform/resource/before/BuilderWithNoBuilderMethod.java new file mode 100644 index 00000000..3f2b21ac --- /dev/null +++ b/test/transform/resource/before/BuilderWithNoBuilderMethod.java @@ -0,0 +1,5 @@ +import lombok.Builder; +@Builder(toBuilder = true, builderMethodName = "") +class BuilderWithNoBuilderMethod { + private String a = ""; +} diff --git a/test/transform/resource/messages-delombok/BuilderDefaultsWarnings.java.messages b/test/transform/resource/messages-delombok/BuilderDefaultsWarnings.java.messages index 694511f8..c6590132 100644 --- a/test/transform/resource/messages-delombok/BuilderDefaultsWarnings.java.messages +++ b/test/transform/resource/messages-delombok/BuilderDefaultsWarnings.java.messages @@ -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. diff --git a/website/templates/features/Builder.html b/website/templates/features/Builder.html index 082b97ed..0818f9d2 100644 --- a/website/templates/features/Builder.html +++ b/website/templates/features/Builder.html @@ -10,6 +10,8 @@ @Builder with @Singular adds a clear method since lombok v1.16.8.

@Builder.Default functionality was added in lombok v1.16.16. +

+ @Builder(builderMethodName = "") is legal (and will suppress generation of the builder method) starting with lombok v1.18.8.

@@ -183,6 +185,13 @@ public class JacksonExample { The initializer on a @Builder.Default 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 this, super or any non-static member. If lombok generates a constructor for you, it'll also initialize this field with the initializer.

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 Getter/Setter documentation's small print for more information. +

+ You can suppress the generation of the builder() method, for example because you just want the toBuilder() functionality, by using: + @Builder(builderMethodName = ""). Any warnings about missing @Builder.Default annotations will disappear when you do this, as such warnings + are not relevant when only using toBuilder() to make builder instances. +

+ You can use @Builder for copy constructors: foo.toBuilder().build() makes a shallow clone. Consider suppressing the generating of the + builder method if you just want this functionality, by using: @Builder(toBuilder = true, builderMethodName = "").

Due to a peculiar way javac processes static imports, trying to do a non-star static import of the static builder() method won't work. Either use a star static import: `import static TypeThatHasABuilder.*;` or don't statically import the builder method.

-- cgit