diff options
8 files changed, 118 insertions, 7 deletions
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<Builder> { 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<Builder> { boolean addCleaning = false; boolean isStatic = true; + List<EclipseNode> nonFinalNonDefaultedFields = null; + if (parent.get() instanceof TypeDeclaration) { tdParent = parent; TypeDeclaration td = (TypeDeclaration) tdParent.get(); @@ -225,7 +235,8 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { 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) { @@ -486,7 +497,8 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { 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<Builder> { 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<Builder> { 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<Builder> { boolean addCleaning = false; boolean isStatic = true; + ArrayList<JavacNode> nonFinalNonDefaultedFields = null; + if (parent.get() instanceof JCClassDecl) { tdParent = parent; JCClassDecl td = (JCClassDecl) tdParent.get(); @@ -172,7 +182,8 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { 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) { @@ -431,7 +442,8 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { 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<Builder> { } } } + + 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 @@ <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> @@ -184,6 +186,13 @@ public class JacksonExample { </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> </@f.smallPrint> |