From 849aa4fddadb40439a30967131d2847b90fb139f Mon Sep 17 00:00:00 2001 From: Jan Rieke Date: Sun, 9 Sep 2018 19:47:51 +0200 Subject: [PATCH 01/19] SuperBuilder toBuilder; test cases --- .../lombok/experimental/SuperBuilder.java | 23 ++- .../SuperBuilderBasicToBuilder.java | 157 ++++++++++++++++++ .../after-ecj/SuperBuilderBasicToBuilder.java | 133 +++++++++++++++ .../before/SuperBuilderBasicToBuilder.java | 18 ++ 4 files changed, 319 insertions(+), 12 deletions(-) create mode 100644 test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java create mode 100644 test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java create mode 100644 test/transform/resource/before/SuperBuilderBasicToBuilder.java diff --git a/src/core/lombok/experimental/SuperBuilder.java b/src/core/lombok/experimental/SuperBuilder.java index 26127a5f10..be6ea304a7 100644 --- a/src/core/lombok/experimental/SuperBuilder.java +++ b/src/core/lombok/experimental/SuperBuilder.java @@ -1,16 +1,16 @@ /* * Copyright (C) 2018 The Project Lombok Authors. - * + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - * + * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -43,7 +43,7 @@ * The builder also has a build() method which returns a completed instance of the original type. *

* Complete documentation is found at the project lombok features page for @SuperBuilder. - * + * * @see Singular */ @Target(TYPE) @@ -51,16 +51,15 @@ public @interface SuperBuilder { /** @return Name of the method that creates a new builder instance. Default: {@code builder}. */ String builderMethodName() default "builder"; - + /** @return Name of the method in the builder class that creates an instance of your {@code @Builder}-annotated class. */ String buildMethodName() default "build"; - - // toBuilder also requires a two-stage system where each class gets its own toBuilder but calls on a second method (and also calls parentclass's method) - // to fill the builder, as this class does not know what fields to pass on to the builder. Let's consider this, but only for milestone 2. - /* - * If true, generate an instance method to obtain a builder that is initialized with the values of this instance. - * + + /** + * If true, generate an instance method to obtain a builder that is initialized with the values of this instance. + * In this case, all superclasses must also have toBuilder=true. + * * @return Whether to generate a {@code toBuilder()} method. */ -// boolean toBuilder() default false; + boolean toBuilder() default false; } diff --git a/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java b/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java new file mode 100644 index 0000000000..2e097c1a5a --- /dev/null +++ b/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java @@ -0,0 +1,157 @@ +import java.util.List; + +import manual.model.Superclass.SuperclassBuilder; +import manual.model.Superclass.SuperclassBuilderImpl; +public class SuperBuilderBasicToBuilder { + public static class Parent { + int field1; + List items; + @java.lang.SuppressWarnings("all") + public static abstract class ParentBuilder> { + @java.lang.SuppressWarnings("all") + private int field1; + @java.lang.SuppressWarnings("all") + private java.util.ArrayList items; + @java.lang.SuppressWarnings("all") + protected B $fillValuesFrom(final C instance) { + this.field1(instance.field1); + this.items(instance.items); + return self(); + } + @java.lang.SuppressWarnings("all") + protected abstract B self(); + @java.lang.SuppressWarnings("all") + public abstract C build(); + @java.lang.SuppressWarnings("all") + public B field1(final int field1) { + this.field1 = field1; + return self(); + } + @java.lang.SuppressWarnings("all") + public B item(final String item) { + if (this.items == null) this.items = new java.util.ArrayList(); + this.items.add(item); + return self(); + } + @java.lang.SuppressWarnings("all") + public B items(final java.util.Collection items) { + if (this.items == null) this.items = new java.util.ArrayList(); + this.items.addAll(items); + return self(); + } + @java.lang.SuppressWarnings("all") + public B clearItems() { + if (this.items != null) this.items.clear(); + return self(); + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "SuperBuilderBasicToBuilder.Parent.ParentBuilder(field1=" + this.field1 + ", items=" + this.items + ")"; + } + } + @java.lang.SuppressWarnings("all") + private static final class ParentBuilderImpl extends ParentBuilder { + @java.lang.SuppressWarnings("all") + private ParentBuilderImpl() { + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + protected ParentBuilderImpl self() { + return this; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public Parent build() { + return new Parent(this); + } + } + @java.lang.SuppressWarnings("all") + protected Parent(final ParentBuilder b) { + this.field1 = b.field1; + java.util.List items; + switch (b.items == null ? 0 : b.items.size()) { + case 0: + items = java.util.Collections.emptyList(); + break; + case 1: + items = java.util.Collections.singletonList(b.items.get(0)); + break; + default: + items = java.util.Collections.unmodifiableList(new java.util.ArrayList(b.items)); + } + this.items = items; + } + @java.lang.SuppressWarnings("all") + public static ParentBuilder builder() { + return new ParentBuilderImpl(); + } + @java.lang.SuppressWarnings("all") + public ParentBuilder toBuilder() { + return new ParentBuilderImpl().$fillValuesFrom(this); + } + } + public static class Child extends Parent { + double field3; + @java.lang.SuppressWarnings("all") + public static abstract class ChildBuilder> extends Parent.ParentBuilder { + @java.lang.SuppressWarnings("all") + private double field3; + @java.lang.Override + @java.lang.SuppressWarnings("all") + protected B $fillValuesFrom(C instance) { + super.$fillValuesFrom(instance); + this.field3(instance.field3); + return self(); + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + protected abstract B self(); + @java.lang.Override + @java.lang.SuppressWarnings("all") + public abstract C build(); + @java.lang.SuppressWarnings("all") + public B field3(final double field3) { + this.field3 = field3; + return self(); + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "SuperBuilderBasicToBuilder.Child.ChildBuilder(super=" + super.toString() + ", field3=" + this.field3 + ")"; + } + } + @java.lang.SuppressWarnings("all") + private static final class ChildBuilderImpl extends ChildBuilder { + @java.lang.SuppressWarnings("all") + private ChildBuilderImpl() { + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + protected ChildBuilderImpl self() { + return this; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public Child build() { + return new Child(this); + } + } + @java.lang.SuppressWarnings("all") + protected Child(final ChildBuilder b) { + super(b); + this.field3 = b.field3; + } + @java.lang.SuppressWarnings("all") + public static ChildBuilder builder() { + return new ChildBuilderImpl(); + } + @java.lang.SuppressWarnings("all") + public ChildBuilder toBuilder() { + return new ChildBuilderImpl().$fillValuesFrom(this); + } + } + public static void test() { + Child x = Child.builder().field3(0.0).field1(5).item("").build().toBuilder().build(); + } +} diff --git a/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java b/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java new file mode 100644 index 0000000000..03fc86d146 --- /dev/null +++ b/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java @@ -0,0 +1,133 @@ +import java.util.List; + +import SuperBuilderBasicToBuilder.Child.ChildBuilder; +import SuperBuilderBasicToBuilder.Child.ChildBuilderImpl; +import SuperBuilderBasicToBuilder.Parent.ParentBuilder; +import SuperBuilderBasicToBuilder.Parent.ParentBuilderImpl; +public class SuperBuilderBasic { + public static @lombok.experimental.SuperBuilder class Parent { + public static abstract @java.lang.SuppressWarnings("all") class ParentBuilder> { + private @java.lang.SuppressWarnings("all") int field1; + private @java.lang.SuppressWarnings("all") java.util.ArrayList items; + public ParentBuilder() { + super(); + } + protected @java.lang.SuppressWarnings("all") B $fillValuesFrom(final C instance) { + this.field1(instance.field1); + this.items(instance.items); + return self(); + } + protected abstract @java.lang.SuppressWarnings("all") B self(); + public abstract @java.lang.SuppressWarnings("all") C build(); + public @java.lang.SuppressWarnings("all") B field1(final int field1) { + this.field1 = field1; + return self(); + } + public @java.lang.SuppressWarnings("all") B item(String item) { + if ((this.items == null)) + this.items = new java.util.ArrayList(); + this.items.add(item); + return self(); + } + public @java.lang.SuppressWarnings("all") B items(java.util.Collection items) { + if ((this.items == null)) + this.items = new java.util.ArrayList(); + this.items.addAll(items); + return self(); + } + public @java.lang.SuppressWarnings("all") B clearItems() { + if ((this.items != null)) + this.items.clear(); + return self(); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (((("SuperBuilderBasic.Parent.ParentBuilder(field1=" + this.field1) + ", items=") + this.items) + ")"); + } + } + private static final @java.lang.SuppressWarnings("all") class ParentBuilderImpl extends ParentBuilder { + private ParentBuilderImpl() { + super(); + } + protected @java.lang.Override @java.lang.SuppressWarnings("all") ParentBuilderImpl self() { + return this; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") Parent build() { + return new Parent(this); + } + } + int field1; + @lombok.Singular List items; + protected @java.lang.SuppressWarnings("all") Parent(final ParentBuilder b) { + super(); + this.field1 = b.field1; + java.util.List items; + switch (((b.items == null) ? 0 : b.items.size())) { + case 0 : + items = java.util.Collections.emptyList(); + break; + case 1 : + items = java.util.Collections.singletonList(b.items.get(0)); + break; + default : + items = java.util.Collections.unmodifiableList(new java.util.ArrayList(b.items)); + } + this.items = items; + } + public static @java.lang.SuppressWarnings("all") ParentBuilder builder() { + return new ParentBuilderImpl(); + } + public @java.lang.SuppressWarnings("all") ParentBuilder toBuilder() { + return new ParentBuilderImpl().$fillValuesFrom(this); + } + } + public static @lombok.experimental.SuperBuilder class Child extends Parent { + public static abstract @java.lang.SuppressWarnings("all") class ChildBuilder> extends Parent.ParentBuilder { + private @java.lang.SuppressWarnings("all") double field3; + public ChildBuilder() { + super(); + } + protected @java.lang.Override @java.lang.SuppressWarnings("all") B $fillValuesFrom(C instance) { + super.$fillValuesFrom(instance); + this.field3(instance.field3); + return self(); + } + protected abstract @java.lang.Override @java.lang.SuppressWarnings("all") B self(); + public abstract @java.lang.Override @java.lang.SuppressWarnings("all") C build(); + public @java.lang.SuppressWarnings("all") B field3(final double field3) { + this.field3 = field3; + return self(); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (((("SuperBuilderBasic.Child.ChildBuilder(super=" + super.toString()) + ", field3=") + this.field3) + ")"); + } + } + private static final @java.lang.SuppressWarnings("all") class ChildBuilderImpl extends ChildBuilder { + private ChildBuilderImpl() { + super(); + } + protected @java.lang.Override @java.lang.SuppressWarnings("all") ChildBuilderImpl self() { + return this; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") Child build() { + return new Child(this); + } + } + double field3; + protected @java.lang.SuppressWarnings("all") Child(final ChildBuilder b) { + super(b); + this.field3 = b.field3; + } + public static @java.lang.SuppressWarnings("all") ChildBuilder builder() { + return new ChildBuilderImpl(); + } + public @java.lang.SuppressWarnings("all") ChildBuilder toBuilder() { + return new ChildBuilderImpl().$fillValuesFrom(this); + } + } + public SuperBuilderBasic() { + super(); + } + public static void test() { + Child x = Child.builder().field3(0.0).field1(5).item("").build(); + } +} diff --git a/test/transform/resource/before/SuperBuilderBasicToBuilder.java b/test/transform/resource/before/SuperBuilderBasicToBuilder.java new file mode 100644 index 0000000000..bc3578f5ef --- /dev/null +++ b/test/transform/resource/before/SuperBuilderBasicToBuilder.java @@ -0,0 +1,18 @@ +import java.util.List; + +public class SuperBuilderBasicToBuilder { + @lombok.experimental.SuperBuilder(toBuilder=true) + public static class Parent { + int field1; + @lombok.Singular List items; + } + + @lombok.experimental.SuperBuilder(toBuilder=true) + public static class Child extends Parent { + double field3; + } + + public static void test() { + Child x = Child.builder().field3(0.0).field1(5).item("").build().toBuilder().build(); + } +} From b42ce5325afbe6202d5d5815a9c74dd64a0ce3de Mon Sep 17 00:00:00 2001 From: Jan Rieke Date: Tue, 11 Sep 2018 10:32:50 +0200 Subject: [PATCH 02/19] SuperBuilder: generate toBuilder method (javac) --- .../lombok/javac/handlers/HandleBuilder.java | 2 +- .../javac/handlers/HandleSuperBuilder.java | 67 ++++++++++++++----- .../SuperBuilderBasicToBuilder.java | 2 - .../after-ecj/SuperBuilderBasicToBuilder.java | 4 -- 4 files changed, 53 insertions(+), 22 deletions(-) diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java index 63697691db..91cd4abb69 100644 --- a/src/core/lombok/javac/handlers/HandleBuilder.java +++ b/src/core/lombok/javac/handlers/HandleBuilder.java @@ -84,7 +84,7 @@ private static final boolean toBoolean(Object expr, boolean defaultValue) { return ((Boolean) expr).booleanValue(); } - private static class BuilderFieldData { + public static class BuilderFieldData { JCExpression type; Name rawName; Name name; diff --git a/src/core/lombok/javac/handlers/HandleSuperBuilder.java b/src/core/lombok/javac/handlers/HandleSuperBuilder.java index b8f572d521..62dc93c106 100644 --- a/src/core/lombok/javac/handlers/HandleSuperBuilder.java +++ b/src/core/lombok/javac/handlers/HandleSuperBuilder.java @@ -68,6 +68,7 @@ import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; import lombok.javac.JavacTreeMaker; +import lombok.javac.handlers.HandleBuilder.BuilderFieldData; import lombok.javac.handlers.JavacHandlerUtil.MemberExistsResult; import lombok.javac.handlers.JavacSingularsRecipes.ExpressionMaker; import lombok.javac.handlers.JavacSingularsRecipes.StatementMaker; @@ -78,21 +79,9 @@ @HandlerPriority(-1024) //-2^10; to ensure we've picked up @FieldDefault's changes (-2048) but @Value hasn't removed itself yet (-512), so that we can error on presence of it on the builder classes. public class HandleSuperBuilder extends JavacAnnotationHandler { private static final String SELF_METHOD = "self"; - - private static class BuilderFieldData { - JCExpression type; - Name rawName; - Name name; - Name nameOfDefaultProvider; - Name nameOfSetFlag; - SingularData singularData; - ObtainVia obtainVia; - JavacNode obtainViaNode; - JavacNode originalFieldNode; - - java.util.List createdFields = new ArrayList(); - } - + private static final String TO_BUILDER_METHOD_NAME = "toBuilder"; + private static final String FILL_VALUES_METHOD_NAME = "$fillValuesFrom"; + @Override public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) { handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.SUPERBUILDER_FLAG_USAGE, "@SuperBuilder"); @@ -109,6 +98,8 @@ public void handle(AnnotationValues annotation, JCAnnotation ast, if (!checkName("builderMethodName", builderMethodName, annotationNode)) return; if (!checkName("buildMethodName", buildMethodName, annotationNode)) return; + boolean toBuilder = superbuilderAnnotation.toBuilder(); + JavacNode tdParent = annotationNode.up(); java.util.List builderFields = new ArrayList(); @@ -307,6 +298,19 @@ public void handle(AnnotationValues annotation, JCAnnotation ast, if (builderMethod != null) injectMethod(tdParent, builderMethod); } } + + if (toBuilder) { + switch (methodExists(TO_BUILDER_METHOD_NAME, tdParent, 0)) { + case EXISTS_BY_USER: + annotationNode.addWarning("Not generating toBuilder() as it already exists."); + return; + case NOT_EXISTS: + JCMethodDecl md = generateToBuilderMethod(builderClassName, builderImplClassName, annotationNode, tdParent, typeParams); + if (md != null) { + injectMethod(tdParent, md); + } + } + } } /** @@ -495,6 +499,39 @@ private JCMethodDecl generateBuilderMethod(String builderMethodName, String buil return maker.MethodDef(maker.Modifiers(modifiers), type.toName(builderMethodName), returnType, copyTypeParams(source, typeParams), List.nil(), List.nil(), body, null); } + /** + * Generates a toBuilder() method that looks like this: + *

+	 * public ParentBuilder<?, ?> toBuilder() {
+	 *     return new FoobarBuilderImpl().$fillValuesFrom(this);
+	 * }
+	 * 
+ */ + private JCMethodDecl generateToBuilderMethod(String builderClassName, String builderImplClassName, JavacNode source, JavacNode type, List typeParams) { + JavacTreeMaker maker = type.getTreeMaker(); + + ListBuffer typeArgs = new ListBuffer(); + for (JCTypeParameter typeParam : typeParams) typeArgs.append(maker.Ident(typeParam.name)); + + JCExpression newClass = maker.NewClass(null, List.nil(), namePlusTypeParamsToTypeReference(maker, type.toName(builderImplClassName), typeParams), List.nil(), null); + JCMethodInvocation invokeFillMethod = maker.Apply(List.nil(), maker.Select(newClass, type.toName(FILL_VALUES_METHOD_NAME)), List.nil()); + JCStatement statement = maker.Return(invokeFillMethod); + + JCBlock body = maker.Block(0, List.of(statement)); + int modifiers = Flags.PUBLIC; + + // Add any type params of the annotated class to the return type. + ListBuffer typeParameterNames = new ListBuffer(); + typeParameterNames.addAll(typeParameterNames(maker, typeParams)); + // Now add the . + JCWildcard wildcard = maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null); + typeParameterNames.add(wildcard); + typeParameterNames.add(wildcard); + JCTypeApply returnType = maker.TypeApply(maker.Ident(type.toName(builderClassName)), typeParameterNames.toList()); + + return maker.MethodDef(maker.Modifiers(modifiers), type.toName(TO_BUILDER_METHOD_NAME), returnType, copyTypeParams(source, typeParams), List.nil(), List.nil(), body, null); + } + private JCMethodDecl generateAbstractSelfMethod(JavacNode type, boolean override, String builderGenericName) { JavacTreeMaker maker = type.getTreeMaker(); List annotations = List.nil(); diff --git a/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java b/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java index 2e097c1a5a..ae3f5f3139 100644 --- a/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java +++ b/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java @@ -1,7 +1,5 @@ import java.util.List; -import manual.model.Superclass.SuperclassBuilder; -import manual.model.Superclass.SuperclassBuilderImpl; public class SuperBuilderBasicToBuilder { public static class Parent { int field1; diff --git a/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java b/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java index 03fc86d146..a2299a9cc2 100644 --- a/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java +++ b/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java @@ -1,9 +1,5 @@ import java.util.List; -import SuperBuilderBasicToBuilder.Child.ChildBuilder; -import SuperBuilderBasicToBuilder.Child.ChildBuilderImpl; -import SuperBuilderBasicToBuilder.Parent.ParentBuilder; -import SuperBuilderBasicToBuilder.Parent.ParentBuilderImpl; public class SuperBuilderBasic { public static @lombok.experimental.SuperBuilder class Parent { public static abstract @java.lang.SuppressWarnings("all") class ParentBuilder> { From abb0d2374e0ca6cda55c5ea63f51235111701081 Mon Sep 17 00:00:00 2001 From: Jan Rieke Date: Tue, 11 Sep 2018 14:52:15 +0200 Subject: [PATCH 03/19] SuperBuilder: generate fillValuesFrom method (javac) --- .../javac/handlers/HandleSuperBuilder.java | 95 ++++++++++++++++++- .../SuperBuilderBasicToBuilder.java | 5 +- .../after-ecj/SuperBuilderBasicToBuilder.java | 2 +- 3 files changed, 95 insertions(+), 7 deletions(-) diff --git a/src/core/lombok/javac/handlers/HandleSuperBuilder.java b/src/core/lombok/javac/handlers/HandleSuperBuilder.java index 62dc93c106..01ac279466 100644 --- a/src/core/lombok/javac/handlers/HandleSuperBuilder.java +++ b/src/core/lombok/javac/handlers/HandleSuperBuilder.java @@ -37,11 +37,13 @@ import com.sun.tools.javac.tree.JCTree.JCBlock; import com.sun.tools.javac.tree.JCTree.JCClassDecl; import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCExpressionStatement; import com.sun.tools.javac.tree.JCTree.JCFieldAccess; import com.sun.tools.javac.tree.JCTree.JCIdent; import com.sun.tools.javac.tree.JCTree.JCMethodDecl; import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; import com.sun.tools.javac.tree.JCTree.JCModifiers; +import com.sun.tools.javac.tree.JCTree.JCReturn; import com.sun.tools.javac.tree.JCTree.JCStatement; import com.sun.tools.javac.tree.JCTree.JCTypeApply; import com.sun.tools.javac.tree.JCTree.JCTypeParameter; @@ -234,6 +236,9 @@ public void handle(AnnotationValues annotation, JCAnnotation ast, injectFieldAndMarkGenerated(builderType, uncleanField); } + // Generate $fillValuesFrom() method in the abstract builder. + injectMethod(builderType, generateFillValuesMethod(tdParent, superclassBuilderClassExpression != null, builderGenericName, classGenericName, builderFields)); + // Generate abstract self() and build() methods in the abstract builder. injectMethod(builderType, generateAbstractSelfMethod(tdParent, superclassBuilderClassExpression != null, builderGenericName)); injectMethod(builderType, generateAbstractBuildMethod(tdParent, buildMethodName, superclassBuilderClassExpression != null, classGenericName)); @@ -500,7 +505,7 @@ private JCMethodDecl generateBuilderMethod(String builderMethodName, String buil } /** - * Generates a toBuilder() method that looks like this: + * Generates a toBuilder() method in the annotated class that looks like this: *
 	 * public ParentBuilder<?, ?> toBuilder() {
 	 *     return new FoobarBuilderImpl().$fillValuesFrom(this);
@@ -514,7 +519,9 @@ private JCMethodDecl generateToBuilderMethod(String builderClassName, String bui
 		for (JCTypeParameter typeParam : typeParams) typeArgs.append(maker.Ident(typeParam.name));
 		
 		JCExpression newClass = maker.NewClass(null, List.nil(), namePlusTypeParamsToTypeReference(maker, type.toName(builderImplClassName), typeParams), List.nil(), null);
-		JCMethodInvocation invokeFillMethod = maker.Apply(List.nil(), maker.Select(newClass, type.toName(FILL_VALUES_METHOD_NAME)), List.nil());
+		List methodArgs = List.of(maker.Ident(type.toName("this")));
+		JCMethodInvocation invokeFillMethod = maker.Apply(List.nil(), maker.Select(newClass, type.toName(FILL_VALUES_METHOD_NAME)), methodArgs);
+//		JCMethodInvocation invokeFillMethod = maker.Apply(List.nil(), maker.Select(newClass, type.toName(FILL_VALUES_METHOD_NAME)), List.nil());
 		JCStatement statement = maker.Return(invokeFillMethod);
 		
 		JCBlock body = maker.Block(0, List.of(statement));
@@ -531,6 +538,88 @@ private JCMethodDecl generateToBuilderMethod(String builderClassName, String bui
 		
 		return maker.MethodDef(maker.Modifiers(modifiers), type.toName(TO_BUILDER_METHOD_NAME), returnType, copyTypeParams(source, typeParams), List.nil(), List.nil(), body, null);
 	}
+
+	/**
+	 * Generates a $fillValuesFrom() method in the abstract builder class that looks
+	 * like this:
+	 * 
+	 * protected B $fillValuesFrom(final C instance) {
+	 *     super.$fillValuesFrom(instance);
+	 *     this.field(instance.field);
+	 *     return self();
+	 * }
+	 * 
+ */ + private JCMethodDecl generateFillValuesMethod(JavacNode type, boolean inherited, String builderGenericName, String classGenericName, java.util.List builderFields) { + JavacTreeMaker maker = type.getTreeMaker(); + List annotations = List.nil(); + if (inherited) { + JCAnnotation overrideAnnotation = maker.Annotation(genJavaLangTypeRef(type, "Override"), List.nil()); + annotations = List.of(overrideAnnotation); + } + JCModifiers modifiers = maker.Modifiers(Flags.PROTECTED, annotations); + Name name = type.toName(FILL_VALUES_METHOD_NAME); + JCExpression returnType = maker.Ident(type.toName(builderGenericName)); + + final String instanceVariableName = "instance"; + JCExpression classGenericNameExpr = maker.Ident(type.toName(classGenericName)); + JCVariableDecl param = maker.VarDef(maker.Modifiers(Flags.LocalVarFlags), type.toName(instanceVariableName), classGenericNameExpr, null); + + ListBuffer body = new ListBuffer(); + + if (inherited) { + // Call super. + JCMethodInvocation callToSuper = maker.Apply(List.nil(), + maker.Select(maker.Ident(type.toName("super")), name), + List.of(maker.Ident(type.toName(instanceVariableName)))); + body.append(maker.Exec(callToSuper)); + } + + // Call the builder's setter methods to fill the values from the instance. + for (BuilderFieldData bfd : builderFields) { + JCExpressionStatement exec = createSetterCallWithInstanceValue(bfd, instanceVariableName, type, maker); + body.append(exec); + } + + JCReturn returnStatement = maker.Return(maker.Apply(List.nil(), maker.Ident(type.toName(SELF_METHOD)), List.nil())); + body.append(returnStatement); + JCBlock bodyBlock = maker.Block(0, body.toList()); + + return maker.MethodDef(modifiers, name, returnType, List.nil(), List.of(param), List.nil(), bodyBlock, null); + } + + private JCExpressionStatement createSetterCallWithInstanceValue(BuilderFieldData bfd, final String instanceVariableName, JavacNode type, JavacTreeMaker maker) { + JCExpression[] tgt = new JCExpression[bfd.singularData == null ? 1 : 2]; + if (bfd.obtainVia == null || !bfd.obtainVia.field().isEmpty()) { + for (int i = 0; i < tgt.length; i++) { + tgt[i] = maker.Select(maker.Ident(type.toName(instanceVariableName)), bfd.obtainVia == null ? bfd.rawName : type.toName(bfd.obtainVia.field())); + } + } else { + if (bfd.obtainVia.isStatic()) { + for (int i = 0; i < tgt.length; i++) { + JCExpression c = maker.Select(maker.Ident(type.toName(type.getName())), type.toName(bfd.obtainVia.method())); + tgt[i] = maker.Apply(List.nil(), c, List.of(maker.Ident(type.toName(instanceVariableName)))); + } + } else { + for (int i = 0; i < tgt.length; i++) { + JCExpression c = maker.Select(maker.Ident(type.toName(instanceVariableName)), type.toName(bfd.obtainVia.method())); + tgt[i] = maker.Apply(List.nil(), c, List.nil()); + } + } + } + + JCExpression arg; + if (bfd.singularData == null) { + arg = tgt[0]; + } else { + JCExpression eqNull = maker.Binary(CTC_EQUAL, tgt[0], maker.Literal(CTC_BOT, null)); + JCExpression emptyList = maker.Apply(List.nil(), chainDots(type, "java", "util", "Collections", "emptyList"), List.nil()); + arg = maker.Conditional(eqNull, emptyList, tgt[1]); + } + JCMethodInvocation apply = maker.Apply(List.nil(), maker.Select(maker.Ident(type.toName("this")), bfd.name), List.of(arg)); + JCExpressionStatement exec = maker.Exec(apply); + return exec; + } private JCMethodDecl generateAbstractSelfMethod(JavacNode type, boolean override, String builderGenericName) { JavacTreeMaker maker = type.getTreeMaker(); @@ -542,7 +631,7 @@ private JCMethodDecl generateAbstractSelfMethod(JavacNode type, boolean override JCModifiers modifiers = maker.Modifiers(Flags.PROTECTED | Flags.ABSTRACT, annotations); Name name = type.toName(SELF_METHOD); JCExpression returnType = maker.Ident(type.toName(builderGenericName)); - + return maker.MethodDef(modifiers, name, returnType, List.nil(), List.nil(), List.nil(), null, null); } diff --git a/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java b/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java index ae3f5f3139..404984b961 100644 --- a/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java +++ b/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java @@ -1,5 +1,4 @@ import java.util.List; - public class SuperBuilderBasicToBuilder { public static class Parent { int field1; @@ -13,7 +12,7 @@ public static abstract class ParentBuilder Date: Tue, 11 Sep 2018 16:36:25 +0200 Subject: [PATCH 04/19] SuperBuilder: generate fillValuesFrom method (ecj) --- .../eclipse/handlers/HandleBuilder.java | 2 +- .../eclipse/handlers/HandleSuperBuilder.java | 127 +++++++++++++++--- .../javac/handlers/HandleSuperBuilder.java | 6 +- .../after-ecj/SuperBuilderBasicToBuilder.java | 16 +-- 4 files changed, 121 insertions(+), 30 deletions(-) diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java index 9a069c588d..5d417bdeae 100644 --- a/src/core/lombok/eclipse/handlers/HandleBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java @@ -105,7 +105,7 @@ private static final boolean toBoolean(Object expr, boolean defaultValue) { return ((Boolean) expr).booleanValue(); } - private static class BuilderFieldData { + public static class BuilderFieldData { TypeReference type; char[] rawName; char[] name; diff --git a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java index 3c07ac55c9..6c1b2cac85 100644 --- a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java @@ -39,7 +39,9 @@ import org.eclipse.jdt.internal.compiler.ast.Argument; import org.eclipse.jdt.internal.compiler.ast.Assignment; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ConditionalExpression; import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; +import org.eclipse.jdt.internal.compiler.ast.EqualExpression; import org.eclipse.jdt.internal.compiler.ast.ExplicitConstructorCall; import org.eclipse.jdt.internal.compiler.ast.Expression; import org.eclipse.jdt.internal.compiler.ast.FalseLiteral; @@ -48,6 +50,8 @@ import org.eclipse.jdt.internal.compiler.ast.IfStatement; import org.eclipse.jdt.internal.compiler.ast.MessageSend; import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.NullLiteral; +import org.eclipse.jdt.internal.compiler.ast.OperatorIds; import org.eclipse.jdt.internal.compiler.ast.ParameterizedQualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference; import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference; @@ -56,6 +60,7 @@ import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.SuperReference; import org.eclipse.jdt.internal.compiler.ast.ThisReference; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.jdt.internal.compiler.ast.TypeParameter; @@ -87,6 +92,7 @@ import lombok.eclipse.handlers.EclipseSingularsRecipes.SingularData; import lombok.eclipse.handlers.EclipseSingularsRecipes.StatementMaker; import lombok.eclipse.handlers.EclipseSingularsRecipes.TypeReferenceMaker; +import lombok.eclipse.handlers.HandleBuilder.BuilderFieldData; import lombok.experimental.NonFinal; import lombok.experimental.SuperBuilder; @@ -98,40 +104,31 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler { private static final char[] DEFAULT_PREFIX = "$default$".toCharArray(); private static final char[] SET_PREFIX = "$set".toCharArray(); private static final char[] SELF_METHOD_NAME = "self".toCharArray(); + private static final char[] TO_BUILDER_METHOD_NAME = "toBuilder".toCharArray(); + private static final char[] FILL_VALUES_METHOD_NAME = "$fillValuesFrom".toCharArray(); + private static final char[] EMPTY_LIST = "emptyList".toCharArray(); private static final AbstractMethodDeclaration[] EMPTY_METHODS = {}; - private static class BuilderFieldData { - TypeReference type; - char[] rawName; - char[] name; - char[] nameOfDefaultProvider; - char[] nameOfSetFlag; - SingularData singularData; - ObtainVia obtainVia; - EclipseNode obtainViaNode; - EclipseNode originalFieldNode; - - List createdFields = new ArrayList(); - } - @Override public void handle(AnnotationValues annotation, Annotation ast, EclipseNode annotationNode) { handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.SUPERBUILDER_FLAG_USAGE, "@SuperBuilder"); long p = (long) ast.sourceStart << 32 | ast.sourceEnd; - SuperBuilder builderInstance = annotation.getInstance(); + SuperBuilder superbuilderAnnotation = annotation.getInstance(); - String builderMethodName = builderInstance.builderMethodName(); - String buildMethodName = builderInstance.buildMethodName(); + String builderMethodName = superbuilderAnnotation.builderMethodName(); + String buildMethodName = superbuilderAnnotation.buildMethodName(); if (builderMethodName == null) builderMethodName = "builder"; if (buildMethodName == null) buildMethodName = "build"; if (!checkName("builderMethodName", builderMethodName, annotationNode)) return; if (!checkName("buildMethodName", buildMethodName, annotationNode)) return; - + + boolean toBuilder = superbuilderAnnotation.toBuilder(); + EclipseNode tdParent = annotationNode.up(); java.util.List builderFields = new ArrayList(); @@ -293,7 +290,12 @@ public void handle(AnnotationValues annotation, Annotation ast, Ec cleanDecl.type = TypeReference.baseTypeReference(TypeIds.T_boolean, 0); injectFieldAndMarkGenerated(builderType, cleanDecl); } - + + if (toBuilder) { + // Generate $fillValuesFrom() method in the abstract builder. + injectMethod(builderType, generateFillValuesMethod(tdParent, superclassBuilderClass != null, builderGenericName, classGenericName, typeParams, builderFields, ast)); + } + // Generate abstract self() and build() methods in the abstract builder. injectMethod(builderType, generateAbstractSelfMethod(tdParent, superclassBuilderClass != null, builderGenericName)); injectMethod(builderType, generateAbstractBuildMethod(tdParent, buildMethodName, superclassBuilderClass != null, classGenericName, ast)); @@ -532,6 +534,93 @@ private MethodDeclaration generateBuilderMethod(String builderMethodName, String return out; } + /** + * Generates a $fillValuesFrom() method in the abstract builder class that looks + * like this: + *
+	 * protected B $fillValuesFrom(final C instance) {
+	 *     super.$fillValuesFrom(instance);
+	 *     this.field(instance.field);
+	 *     return self();
+	 * }
+	 * 
+ */ + private MethodDeclaration generateFillValuesMethod(EclipseNode tdParent, boolean inherited, String builderGenericName, String classGenericName, TypeParameter[] typeParams, java.util.List builderFields, ASTNode source) { + MethodDeclaration out = new MethodDeclaration(((CompilationUnitDeclaration) tdParent.top().get()).compilationResult); + out.selector = FILL_VALUES_METHOD_NAME; + out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + out.modifiers = ClassFileConstants.AccProtected; + if (inherited) out.annotations = new Annotation[] {makeMarkerAnnotation(TypeConstants.JAVA_LANG_OVERRIDE, tdParent.get())}; + out.returnType = new SingleTypeReference(builderGenericName.toCharArray(), 0); + + final String instanceVariableName = "instance"; + TypeReference builderType = createTypeReferenceWithTypeParameters(classGenericName, typeParams); + out.arguments = new Argument[] {new Argument(instanceVariableName.toCharArray(), 0, builderType, Modifier.FINAL)}; + + List body = new ArrayList(); + + if (inherited) { + // Call super. + MessageSend callToSuper = new MessageSend(); + callToSuper.receiver = new SuperReference(0, 0); + callToSuper.selector = FILL_VALUES_METHOD_NAME; + callToSuper.arguments = new Expression[] {new SingleNameReference(instanceVariableName.toCharArray(), 0)}; + body.add(callToSuper); + } + + // Call the builder's setter methods to fill the values from the instance. + for (BuilderFieldData bfd : builderFields) { + MessageSend exec = createSetterCallWithInstanceValue(bfd, instanceVariableName, tdParent, source); + body.add(exec); + } + + MessageSend returnCall = new MessageSend(); + returnCall.receiver = ThisReference.implicitThis(); + returnCall.selector = SELF_METHOD_NAME; + body.add(new ReturnStatement(returnCall, 0, 0)); + + out.statements = body.isEmpty() ? null : body.toArray(new Statement[body.size()]); + + return out; + } + + private MessageSend createSetterCallWithInstanceValue(BuilderFieldData bfd, final String instanceVariableName, EclipseNode type, ASTNode source) { + char[] setterName = bfd.name; + MessageSend ms = new MessageSend(); + Expression[] tgt = new Expression[bfd.singularData == null ? 1 : 2]; + + if (bfd.obtainVia == null || !bfd.obtainVia.field().isEmpty()) { + char[] fieldName = bfd.obtainVia == null ? bfd.rawName : bfd.obtainVia.field().toCharArray(); + for (int i = 0; i < tgt.length; i++) { + FieldReference fr = new FieldReference(fieldName, 0); + fr.receiver = new SingleNameReference(instanceVariableName.toCharArray(), 0); + tgt[i] = fr; + } + } else { + String obtainName = bfd.obtainVia.method(); + boolean obtainIsStatic = bfd.obtainVia.isStatic(); + for (int i = 0; i < tgt.length; i++) { + MessageSend obtainExpr = new MessageSend(); + obtainExpr.receiver = obtainIsStatic ? new SingleNameReference(type.getName().toCharArray(), 0) : new ThisReference(0, 0); + obtainExpr.selector = obtainName.toCharArray(); + if (obtainIsStatic) obtainExpr.arguments = new Expression[] {new ThisReference(0, 0)}; + tgt[i] = obtainExpr; + } + } + if (bfd.singularData == null) { + ms.arguments = tgt; + } else { + Expression ifNull = new EqualExpression(tgt[0], new NullLiteral(0, 0), OperatorIds.EQUAL_EQUAL); + MessageSend emptyList = new MessageSend(); + emptyList.receiver = generateQualifiedNameRef(source, TypeConstants.JAVA, TypeConstants.UTIL, "Collections".toCharArray()); + emptyList.selector = EMPTY_LIST; + ms.arguments = new Expression[] {new ConditionalExpression(ifNull, emptyList, tgt[1])}; + } + ms.receiver = ThisReference.implicitThis(); + ms.selector = setterName; + return ms; + } + private MethodDeclaration generateAbstractSelfMethod(EclipseNode tdParent, boolean override, String builderGenericName) { MethodDeclaration out = new MethodDeclaration(((CompilationUnitDeclaration) tdParent.top().get()).compilationResult); out.selector = SELF_METHOD_NAME; diff --git a/src/core/lombok/javac/handlers/HandleSuperBuilder.java b/src/core/lombok/javac/handlers/HandleSuperBuilder.java index 01ac279466..c8727f93a2 100644 --- a/src/core/lombok/javac/handlers/HandleSuperBuilder.java +++ b/src/core/lombok/javac/handlers/HandleSuperBuilder.java @@ -236,8 +236,10 @@ public void handle(AnnotationValues annotation, JCAnnotation ast, injectFieldAndMarkGenerated(builderType, uncleanField); } - // Generate $fillValuesFrom() method in the abstract builder. - injectMethod(builderType, generateFillValuesMethod(tdParent, superclassBuilderClassExpression != null, builderGenericName, classGenericName, builderFields)); + if (toBuilder) { + // Generate $fillValuesFrom() method in the abstract builder. + injectMethod(builderType, generateFillValuesMethod(tdParent, superclassBuilderClassExpression != null, builderGenericName, classGenericName, builderFields)); + } // Generate abstract self() and build() methods in the abstract builder. injectMethod(builderType, generateAbstractSelfMethod(tdParent, superclassBuilderClassExpression != null, builderGenericName)); diff --git a/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java b/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java index 5e5f9760f6..6dd06fb709 100644 --- a/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java +++ b/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java @@ -1,7 +1,7 @@ import java.util.List; -public class SuperBuilderBasic { - public static @lombok.experimental.SuperBuilder class Parent { +public class SuperBuilderBasicToBuilder { + public static @lombok.experimental.SuperBuilder(toBuilder = true) class Parent { public static abstract @java.lang.SuppressWarnings("all") class ParentBuilder> { private @java.lang.SuppressWarnings("all") int field1; private @java.lang.SuppressWarnings("all") java.util.ArrayList items; @@ -9,8 +9,8 @@ public ParentBuilder() { super(); } protected @java.lang.SuppressWarnings("all") B $fillValuesFrom(final C instance) { - this.field1(instance.field1); - this.items(instance.items == null ? java.util.Collections.emptyList() : instance.items); + field1(instance.field1); + items(((instance.items == null) ? java.util.Collections.emptyList() : instance.items)); return self(); } protected abstract @java.lang.SuppressWarnings("all") B self(); @@ -37,7 +37,7 @@ public ParentBuilder() { return self(); } public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { - return (((("SuperBuilderBasic.Parent.ParentBuilder(field1=" + this.field1) + ", items=") + this.items) + ")"); + return (((("SuperBuilderBasicToBuilder.Parent.ParentBuilder(field1=" + this.field1) + ", items=") + this.items) + ")"); } } private static final @java.lang.SuppressWarnings("all") class ParentBuilderImpl extends ParentBuilder { @@ -76,7 +76,7 @@ private ParentBuilderImpl() { return new ParentBuilderImpl().$fillValuesFrom(this); } } - public static @lombok.experimental.SuperBuilder class Child extends Parent { + public static @lombok.experimental.SuperBuilder(toBuilder = true) class Child extends Parent { public static abstract @java.lang.SuppressWarnings("all") class ChildBuilder> extends Parent.ParentBuilder { private @java.lang.SuppressWarnings("all") double field3; public ChildBuilder() { @@ -84,7 +84,7 @@ public ChildBuilder() { } protected @java.lang.Override @java.lang.SuppressWarnings("all") B $fillValuesFrom(C instance) { super.$fillValuesFrom(instance); - this.field3(instance.field3); + field3(instance.field3); return self(); } protected abstract @java.lang.Override @java.lang.SuppressWarnings("all") B self(); @@ -94,7 +94,7 @@ public ChildBuilder() { return self(); } public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { - return (((("SuperBuilderBasic.Child.ChildBuilder(super=" + super.toString()) + ", field3=") + this.field3) + ")"); + return (((("SuperBuilderBasicToBuilder.Child.ChildBuilder(super=" + super.toString()) + ", field3=") + this.field3) + ")"); } } private static final @java.lang.SuppressWarnings("all") class ChildBuilderImpl extends ChildBuilder { From da59d2d6375afcfcf6cb43288adba61cb99a49e8 Mon Sep 17 00:00:00 2001 From: Jan Rieke Date: Tue, 11 Sep 2018 16:59:29 +0200 Subject: [PATCH 05/19] SuperBuilder: generate toBuilder method (ecj) --- .../eclipse/handlers/HandleSuperBuilder.java | 47 ++++++++++++++++++- .../javac/handlers/HandleSuperBuilder.java | 1 - .../after-ecj/SuperBuilderBasicToBuilder.java | 6 +-- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java index 6c1b2cac85..1bbc514ee8 100644 --- a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java @@ -104,7 +104,8 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler { private static final char[] DEFAULT_PREFIX = "$default$".toCharArray(); private static final char[] SET_PREFIX = "$set".toCharArray(); private static final char[] SELF_METHOD_NAME = "self".toCharArray(); - private static final char[] TO_BUILDER_METHOD_NAME = "toBuilder".toCharArray(); + private static final String TO_BUILDER_METHOD_NAME_STRING = "toBuilder"; + private static final char[] TO_BUILDER_METHOD_NAME = TO_BUILDER_METHOD_NAME_STRING.toCharArray(); private static final char[] FILL_VALUES_METHOD_NAME = "$fillValuesFrom".toCharArray(); private static final char[] EMPTY_LIST = "emptyList".toCharArray(); @@ -345,6 +346,15 @@ public void handle(AnnotationValues annotation, Annotation ast, Ec MethodDeclaration md = generateBuilderMethod(builderMethodName, builderClassName, builderImplClassName, tdParent, typeParams, ast); if (md != null) injectMethod(tdParent, md); } + + if (toBuilder) switch (methodExists(TO_BUILDER_METHOD_NAME_STRING, tdParent, 0)) { + case EXISTS_BY_USER: + annotationNode.addWarning("Not generating toBuilder() as it already exists."); + break; + case NOT_EXISTS: + MethodDeclaration md = generateToBuilderMethod(builderClassName, builderImplClassName, tdParent, typeParams, ast); + if (md != null) injectMethod(tdParent, md); + } } private EclipseNode generateBuilderAbstractClass(EclipseNode tdParent, String builderClass, @@ -534,6 +544,41 @@ private MethodDeclaration generateBuilderMethod(String builderMethodName, String return out; } + /** + * Generates a toBuilder() method in the annotated class that looks like this: + *
+	 * public ParentBuilder<?, ?> toBuilder() {
+	 *     return new FoobarBuilderImpl().$fillValuesFrom(this);
+	 * }
+	 * 
+ */ + private MethodDeclaration generateToBuilderMethod(String builderClassName, String builderImplClassName, EclipseNode type, TypeParameter[] typeParams, ASTNode source) { + int pS = source.sourceStart, pE = source.sourceEnd; + long p = (long) pS << 32 | pE; + + MethodDeclaration out = new MethodDeclaration(((CompilationUnitDeclaration) type.top().get()).compilationResult); + out.selector = TO_BUILDER_METHOD_NAME; + out.modifiers = ClassFileConstants.AccPublic; + out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + + // Add type params if there are any. + if (typeParams != null && typeParams.length > 0) out.typeParameters = copyTypeParams(typeParams, source); + + TypeReference[] wildcards = new TypeReference[] {new Wildcard(Wildcard.UNBOUND), new Wildcard(Wildcard.UNBOUND) }; + out.returnType = new ParameterizedSingleTypeReference(builderClassName.toCharArray(), mergeToTypeReferences(typeParams, wildcards), 0, p); + + AllocationExpression newClass = new AllocationExpression(); + newClass.type = namePlusTypeParamsToTypeReference(builderImplClassName.toCharArray(), typeParams, p); + MessageSend invokeFillMethod = new MessageSend(); + invokeFillMethod.receiver = newClass; + invokeFillMethod.selector = FILL_VALUES_METHOD_NAME; + invokeFillMethod.arguments = new Expression[] {new ThisReference(0, 0)}; + out.statements = new Statement[] {new ReturnStatement(invokeFillMethod, pS, pE)}; + + out.traverse(new SetGeneratedByVisitor(source), ((TypeDeclaration) type.get()).scope); + return out; + } + /** * Generates a $fillValuesFrom() method in the abstract builder class that looks * like this: diff --git a/src/core/lombok/javac/handlers/HandleSuperBuilder.java b/src/core/lombok/javac/handlers/HandleSuperBuilder.java index c8727f93a2..bdcd043a36 100644 --- a/src/core/lombok/javac/handlers/HandleSuperBuilder.java +++ b/src/core/lombok/javac/handlers/HandleSuperBuilder.java @@ -523,7 +523,6 @@ private JCMethodDecl generateToBuilderMethod(String builderClassName, String bui JCExpression newClass = maker.NewClass(null, List.nil(), namePlusTypeParamsToTypeReference(maker, type.toName(builderImplClassName), typeParams), List.nil(), null); List methodArgs = List.of(maker.Ident(type.toName("this"))); JCMethodInvocation invokeFillMethod = maker.Apply(List.nil(), maker.Select(newClass, type.toName(FILL_VALUES_METHOD_NAME)), methodArgs); -// JCMethodInvocation invokeFillMethod = maker.Apply(List.nil(), maker.Select(newClass, type.toName(FILL_VALUES_METHOD_NAME)), List.nil()); JCStatement statement = maker.Return(invokeFillMethod); JCBlock body = maker.Block(0, List.of(statement)); diff --git a/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java b/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java index 6dd06fb709..2b294d6501 100644 --- a/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java +++ b/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java @@ -82,7 +82,7 @@ private ParentBuilderImpl() { public ChildBuilder() { super(); } - protected @java.lang.Override @java.lang.SuppressWarnings("all") B $fillValuesFrom(C instance) { + protected @java.lang.Override @java.lang.SuppressWarnings("all") B $fillValuesFrom(final C instance) { super.$fillValuesFrom(instance); field3(instance.field3); return self(); @@ -120,10 +120,10 @@ private ChildBuilderImpl() { return new ChildBuilderImpl().$fillValuesFrom(this); } } - public SuperBuilderBasic() { + public SuperBuilderBasicToBuilder() { super(); } public static void test() { - Child x = Child.builder().field3(0.0).field1(5).item("").build(); + Child x = Child.builder().field3(0.0).field1(5).item("").build().toBuilder().build(); } } From a2b884c71194c780d37ccbd84561abd6a0f63741 Mon Sep 17 00:00:00 2001 From: Jan Rieke Date: Tue, 11 Sep 2018 20:37:16 +0200 Subject: [PATCH 06/19] SuperBuilder: fixed ObtainVia --- .../eclipse/handlers/HandleSuperBuilder.java | 4 +- .../SuperBuilderBasicToBuilder.java | 38 ++++++++++++++++++- .../after-ecj/SuperBuilderBasicToBuilder.java | 33 +++++++++++++++- .../before/SuperBuilderBasicToBuilder.java | 14 +++++++ 4 files changed, 84 insertions(+), 5 deletions(-) diff --git a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java index 1bbc514ee8..bb78e82a70 100644 --- a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java @@ -646,9 +646,9 @@ private MessageSend createSetterCallWithInstanceValue(BuilderFieldData bfd, fina boolean obtainIsStatic = bfd.obtainVia.isStatic(); for (int i = 0; i < tgt.length; i++) { MessageSend obtainExpr = new MessageSend(); - obtainExpr.receiver = obtainIsStatic ? new SingleNameReference(type.getName().toCharArray(), 0) : new ThisReference(0, 0); + obtainExpr.receiver = obtainIsStatic ? new SingleNameReference(type.getName().toCharArray(), 0) : new SingleNameReference(instanceVariableName.toCharArray(), 0); obtainExpr.selector = obtainName.toCharArray(); - if (obtainIsStatic) obtainExpr.arguments = new Expression[] {new ThisReference(0, 0)}; + if (obtainIsStatic) obtainExpr.arguments = new Expression[] {new SingleNameReference(instanceVariableName.toCharArray(), 0)}; tgt[i] = obtainExpr; } } diff --git a/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java b/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java index 404984b961..7d34d28ac6 100644 --- a/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java +++ b/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java @@ -2,16 +2,34 @@ public class SuperBuilderBasicToBuilder { public static class Parent { int field1; + int obtainViaField; + int obtainViaMethod; + String obtainViaStaticMethod; List items; + int method() { + return 2; + } + private static String staticMethod(Parent instance) { + return "staticMethod"; + } @java.lang.SuppressWarnings("all") public static abstract class ParentBuilder> { @java.lang.SuppressWarnings("all") private int field1; @java.lang.SuppressWarnings("all") + private int obtainViaField; + @java.lang.SuppressWarnings("all") + private int obtainViaMethod; + @java.lang.SuppressWarnings("all") + private String obtainViaStaticMethod; + @java.lang.SuppressWarnings("all") private java.util.ArrayList items; @java.lang.SuppressWarnings("all") protected B $fillValuesFrom(final C instance) { this.field1(instance.field1); + this.obtainViaField(instance.field1); + this.obtainViaMethod(instance.method()); + this.obtainViaStaticMethod(Parent.staticMethod(instance)); this.items(instance.items == null ? java.util.Collections.emptyList() : instance.items); return self(); } @@ -25,6 +43,21 @@ public B field1(final int field1) { return self(); } @java.lang.SuppressWarnings("all") + public B obtainViaField(final int obtainViaField) { + this.obtainViaField = obtainViaField; + return self(); + } + @java.lang.SuppressWarnings("all") + public B obtainViaMethod(final int obtainViaMethod) { + this.obtainViaMethod = obtainViaMethod; + return self(); + } + @java.lang.SuppressWarnings("all") + public B obtainViaStaticMethod(final String obtainViaStaticMethod) { + this.obtainViaStaticMethod = obtainViaStaticMethod; + return self(); + } + @java.lang.SuppressWarnings("all") public B item(final String item) { if (this.items == null) this.items = new java.util.ArrayList(); this.items.add(item); @@ -44,7 +77,7 @@ public B clearItems() { @java.lang.Override @java.lang.SuppressWarnings("all") public java.lang.String toString() { - return "SuperBuilderBasicToBuilder.Parent.ParentBuilder(field1=" + this.field1 + ", items=" + this.items + ")"; + return "SuperBuilderBasicToBuilder.Parent.ParentBuilder(field1=" + this.field1 + ", obtainViaField=" + this.obtainViaField + ", obtainViaMethod=" + this.obtainViaMethod + ", obtainViaStaticMethod=" + this.obtainViaStaticMethod + ", items=" + this.items + ")"; } } @java.lang.SuppressWarnings("all") @@ -66,6 +99,9 @@ public Parent build() { @java.lang.SuppressWarnings("all") protected Parent(final ParentBuilder b) { this.field1 = b.field1; + this.obtainViaField = b.obtainViaField; + this.obtainViaMethod = b.obtainViaMethod; + this.obtainViaStaticMethod = b.obtainViaStaticMethod; java.util.List items; switch (b.items == null ? 0 : b.items.size()) { case 0: diff --git a/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java b/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java index 2b294d6501..974d933d9f 100644 --- a/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java +++ b/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java @@ -1,15 +1,20 @@ import java.util.List; - public class SuperBuilderBasicToBuilder { public static @lombok.experimental.SuperBuilder(toBuilder = true) class Parent { public static abstract @java.lang.SuppressWarnings("all") class ParentBuilder> { private @java.lang.SuppressWarnings("all") int field1; + private @java.lang.SuppressWarnings("all") int obtainViaField; + private @java.lang.SuppressWarnings("all") int obtainViaMethod; + private @java.lang.SuppressWarnings("all") String obtainViaStaticMethod; private @java.lang.SuppressWarnings("all") java.util.ArrayList items; public ParentBuilder() { super(); } protected @java.lang.SuppressWarnings("all") B $fillValuesFrom(final C instance) { field1(instance.field1); + obtainViaField(instance.field1); + obtainViaMethod(instance.method()); + obtainViaStaticMethod(Parent.staticMethod(instance)); items(((instance.items == null) ? java.util.Collections.emptyList() : instance.items)); return self(); } @@ -19,6 +24,18 @@ public ParentBuilder() { this.field1 = field1; return self(); } + public @java.lang.SuppressWarnings("all") B obtainViaField(final int obtainViaField) { + this.obtainViaField = obtainViaField; + return self(); + } + public @java.lang.SuppressWarnings("all") B obtainViaMethod(final int obtainViaMethod) { + this.obtainViaMethod = obtainViaMethod; + return self(); + } + public @java.lang.SuppressWarnings("all") B obtainViaStaticMethod(final String obtainViaStaticMethod) { + this.obtainViaStaticMethod = obtainViaStaticMethod; + return self(); + } public @java.lang.SuppressWarnings("all") B item(String item) { if ((this.items == null)) this.items = new java.util.ArrayList(); @@ -37,7 +54,7 @@ public ParentBuilder() { return self(); } public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { - return (((("SuperBuilderBasicToBuilder.Parent.ParentBuilder(field1=" + this.field1) + ", items=") + this.items) + ")"); + return (((((((((("SuperBuilderBasicToBuilder.Parent.ParentBuilder(field1=" + this.field1) + ", obtainViaField=") + this.obtainViaField) + ", obtainViaMethod=") + this.obtainViaMethod) + ", obtainViaStaticMethod=") + this.obtainViaStaticMethod) + ", items=") + this.items) + ")"); } } private static final @java.lang.SuppressWarnings("all") class ParentBuilderImpl extends ParentBuilder { @@ -52,10 +69,22 @@ private ParentBuilderImpl() { } } int field1; + @lombok.Builder.ObtainVia(field = "field1") int obtainViaField; + @lombok.Builder.ObtainVia(method = "method") int obtainViaMethod; + @lombok.Builder.ObtainVia(method = "staticMethod",isStatic = true) String obtainViaStaticMethod; @lombok.Singular List items; + int method() { + return 2; + } + private static String staticMethod(Parent instance) { + return "staticMethod"; + } protected @java.lang.SuppressWarnings("all") Parent(final ParentBuilder b) { super(); this.field1 = b.field1; + this.obtainViaField = b.obtainViaField; + this.obtainViaMethod = b.obtainViaMethod; + this.obtainViaStaticMethod = b.obtainViaStaticMethod; java.util.List items; switch (((b.items == null) ? 0 : b.items.size())) { case 0 : diff --git a/test/transform/resource/before/SuperBuilderBasicToBuilder.java b/test/transform/resource/before/SuperBuilderBasicToBuilder.java index bc3578f5ef..4dfac613e2 100644 --- a/test/transform/resource/before/SuperBuilderBasicToBuilder.java +++ b/test/transform/resource/before/SuperBuilderBasicToBuilder.java @@ -4,7 +4,21 @@ public class SuperBuilderBasicToBuilder { @lombok.experimental.SuperBuilder(toBuilder=true) public static class Parent { int field1; + @lombok.Builder.ObtainVia(field="field1") + int obtainViaField; + @lombok.Builder.ObtainVia(method="method") + int obtainViaMethod; + @lombok.Builder.ObtainVia(method = "staticMethod", isStatic = true) + String obtainViaStaticMethod; @lombok.Singular List items; + + int method() { + return 2; + } + + private static String staticMethod(Parent instance) { + return "staticMethod"; + } } @lombok.experimental.SuperBuilder(toBuilder=true) From ddd42acb599d45c432a200313a0f403a8a2a3928 Mon Sep 17 00:00:00 2001 From: Jan Rieke Date: Tue, 11 Sep 2018 21:39:10 +0200 Subject: [PATCH 07/19] adjusted toBuilder tests to cover private variables (impl not yet done) --- .../SuperBuilderBasicToBuilder.java | 26 ++++++++++++------- .../after-ecj/SuperBuilderBasicToBuilder.java | 24 ++++++++++------- .../before/SuperBuilderBasicToBuilder.java | 6 ++--- 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java b/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java index 7d34d28ac6..e44b3aebd1 100644 --- a/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java +++ b/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java @@ -1,12 +1,12 @@ import java.util.List; public class SuperBuilderBasicToBuilder { public static class Parent { - int field1; + private int field1; int obtainViaField; int obtainViaMethod; String obtainViaStaticMethod; List items; - int method() { + private int method() { return 2; } private static String staticMethod(Parent instance) { @@ -26,11 +26,7 @@ public static abstract class ParentBuilder items; @java.lang.SuppressWarnings("all") protected B $fillValuesFrom(final C instance) { - this.field1(instance.field1); - this.obtainViaField(instance.field1); - this.obtainViaMethod(instance.method()); - this.obtainViaStaticMethod(Parent.staticMethod(instance)); - this.items(instance.items == null ? java.util.Collections.emptyList() : instance.items); + ParentBuilderImpl.$fillValuesFromInto(instance, this); return self(); } @java.lang.SuppressWarnings("all") @@ -82,6 +78,14 @@ public java.lang.String toString() { } @java.lang.SuppressWarnings("all") private static final class ParentBuilderImpl extends ParentBuilder { + @java.lang.SuppressWarnings("all") + private static void $fillValuesFromInto(Parent instance, ParentBuilder b) { + b.field1(instance.field1); + b.obtainViaField(instance.field1); + b.obtainViaMethod(instance.method()); + b.obtainViaStaticMethod(Parent.staticMethod(instance)); + b.items(instance.items == null ? java.util.Collections.emptyList() : instance.items); + } @java.lang.SuppressWarnings("all") private ParentBuilderImpl() { } @@ -125,7 +129,7 @@ protected Parent(final ParentBuilder b) { } } public static class Child extends Parent { - double field3; + private double field3; @java.lang.SuppressWarnings("all") public static abstract class ChildBuilder> extends Parent.ParentBuilder { @java.lang.SuppressWarnings("all") @@ -134,7 +138,7 @@ public static abstract class ChildBuilder { + @java.lang.SuppressWarnings("all") + private static void $fillValuesFromInto(Parent instance, ParentBuilder b) { + b.field3(instance.field3); + } @java.lang.SuppressWarnings("all") private ChildBuilderImpl() { } diff --git a/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java b/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java index 974d933d9f..6ff2849db6 100644 --- a/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java +++ b/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java @@ -11,11 +11,7 @@ public ParentBuilder() { super(); } protected @java.lang.SuppressWarnings("all") B $fillValuesFrom(final C instance) { - field1(instance.field1); - obtainViaField(instance.field1); - obtainViaMethod(instance.method()); - obtainViaStaticMethod(Parent.staticMethod(instance)); - items(((instance.items == null) ? java.util.Collections.emptyList() : instance.items)); + ParentBuilderImpl.$fillValuesFromInto(instance, this); return self(); } protected abstract @java.lang.SuppressWarnings("all") B self(); @@ -61,6 +57,13 @@ public ParentBuilder() { private ParentBuilderImpl() { super(); } + private static @java.lang.SuppressWarnings("all") void $fillValuesFromInto(Parent instance, ParentBuilder b) { + b.field1(instance.field1); + b.obtainViaField(instance.field1); + b.obtainViaMethod(instance.method()); + b.obtainViaStaticMethod(Parent.staticMethod(instance)); + b.items(((instance.items == null) ? java.util.Collections.emptyList() : instance.items)); + } protected @java.lang.Override @java.lang.SuppressWarnings("all") ParentBuilderImpl self() { return this; } @@ -68,12 +71,12 @@ private ParentBuilderImpl() { return new Parent(this); } } - int field1; + private int field1; @lombok.Builder.ObtainVia(field = "field1") int obtainViaField; @lombok.Builder.ObtainVia(method = "method") int obtainViaMethod; @lombok.Builder.ObtainVia(method = "staticMethod",isStatic = true) String obtainViaStaticMethod; @lombok.Singular List items; - int method() { + private int method() { return 2; } private static String staticMethod(Parent instance) { @@ -113,7 +116,7 @@ public ChildBuilder() { } protected @java.lang.Override @java.lang.SuppressWarnings("all") B $fillValuesFrom(final C instance) { super.$fillValuesFrom(instance); - field3(instance.field3); + ChildBuilderImpl.$fillValuesFromInto(instance, this); return self(); } protected abstract @java.lang.Override @java.lang.SuppressWarnings("all") B self(); @@ -130,6 +133,9 @@ public ChildBuilder() { private ChildBuilderImpl() { super(); } + private static @java.lang.SuppressWarnings("all") void $fillValuesFromInto(Child instance, ChildBuilder b) { + b.field3(instance.field3); + } protected @java.lang.Override @java.lang.SuppressWarnings("all") ChildBuilderImpl self() { return this; } @@ -137,7 +143,7 @@ private ChildBuilderImpl() { return new Child(this); } } - double field3; + private double field3; protected @java.lang.SuppressWarnings("all") Child(final ChildBuilder b) { super(b); this.field3 = b.field3; diff --git a/test/transform/resource/before/SuperBuilderBasicToBuilder.java b/test/transform/resource/before/SuperBuilderBasicToBuilder.java index 4dfac613e2..93161443bc 100644 --- a/test/transform/resource/before/SuperBuilderBasicToBuilder.java +++ b/test/transform/resource/before/SuperBuilderBasicToBuilder.java @@ -3,7 +3,7 @@ public class SuperBuilderBasicToBuilder { @lombok.experimental.SuperBuilder(toBuilder=true) public static class Parent { - int field1; + private int field1; @lombok.Builder.ObtainVia(field="field1") int obtainViaField; @lombok.Builder.ObtainVia(method="method") @@ -12,7 +12,7 @@ public static class Parent { String obtainViaStaticMethod; @lombok.Singular List items; - int method() { + private int method() { return 2; } @@ -23,7 +23,7 @@ private static String staticMethod(Parent instance) { @lombok.experimental.SuperBuilder(toBuilder=true) public static class Child extends Parent { - double field3; + private double field3; } public static void test() { From 14fa70ae183a1d15b8cee30d10775e0da281114b Mon Sep 17 00:00:00 2001 From: Jan Rieke Date: Fri, 14 Sep 2018 09:28:02 +0200 Subject: [PATCH 08/19] SuperBuilder toBuilder: renamed generated static helper method --- .../after-delombok/SuperBuilderBasicToBuilder.java | 8 ++++---- .../resource/after-ecj/SuperBuilderBasicToBuilder.java | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java b/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java index e44b3aebd1..c5099480c3 100644 --- a/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java +++ b/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java @@ -26,7 +26,7 @@ public static abstract class ParentBuilder items; @java.lang.SuppressWarnings("all") protected B $fillValuesFrom(final C instance) { - ParentBuilderImpl.$fillValuesFromInto(instance, this); + ParentBuilderImpl.$fillValuesFromInstanceIntoBuilder(instance, this); return self(); } @java.lang.SuppressWarnings("all") @@ -79,7 +79,7 @@ public java.lang.String toString() { @java.lang.SuppressWarnings("all") private static final class ParentBuilderImpl extends ParentBuilder { @java.lang.SuppressWarnings("all") - private static void $fillValuesFromInto(Parent instance, ParentBuilder b) { + private static void $fillValuesFromInstanceIntoBuilder(Parent instance, ParentBuilder b) { b.field1(instance.field1); b.obtainViaField(instance.field1); b.obtainViaMethod(instance.method()); @@ -138,7 +138,7 @@ public static abstract class ChildBuilder { @java.lang.SuppressWarnings("all") - private static void $fillValuesFromInto(Parent instance, ParentBuilder b) { + private static void $fillValuesFromInstanceIntoBuilder(Parent instance, ParentBuilder b) { b.field3(instance.field3); } @java.lang.SuppressWarnings("all") diff --git a/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java b/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java index 6ff2849db6..fac9ab5579 100644 --- a/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java +++ b/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java @@ -11,7 +11,7 @@ public ParentBuilder() { super(); } protected @java.lang.SuppressWarnings("all") B $fillValuesFrom(final C instance) { - ParentBuilderImpl.$fillValuesFromInto(instance, this); + ParentBuilderImpl.$fillValuesFromInstanceIntoBuilder(instance, this); return self(); } protected abstract @java.lang.SuppressWarnings("all") B self(); @@ -57,7 +57,7 @@ public ParentBuilder() { private ParentBuilderImpl() { super(); } - private static @java.lang.SuppressWarnings("all") void $fillValuesFromInto(Parent instance, ParentBuilder b) { + private static @java.lang.SuppressWarnings("all") void $fillValuesFromInstanceIntoBuilder(Parent instance, ParentBuilder b) { b.field1(instance.field1); b.obtainViaField(instance.field1); b.obtainViaMethod(instance.method()); @@ -116,7 +116,7 @@ public ChildBuilder() { } protected @java.lang.Override @java.lang.SuppressWarnings("all") B $fillValuesFrom(final C instance) { super.$fillValuesFrom(instance); - ChildBuilderImpl.$fillValuesFromInto(instance, this); + ChildBuilderImpl.$fillValuesFromInstanceIntoBuilder(instance, this); return self(); } protected abstract @java.lang.Override @java.lang.SuppressWarnings("all") B self(); @@ -133,7 +133,7 @@ public ChildBuilder() { private ChildBuilderImpl() { super(); } - private static @java.lang.SuppressWarnings("all") void $fillValuesFromInto(Child instance, ChildBuilder b) { + private static @java.lang.SuppressWarnings("all") void $fillValuesFromInstanceIntoBuilder(Child instance, ChildBuilder b) { b.field3(instance.field3); } protected @java.lang.Override @java.lang.SuppressWarnings("all") ChildBuilderImpl self() { From f5fff49f73ceabb776f470a9ed346ace0ce12be6 Mon Sep 17 00:00:00 2001 From: Jan Rieke Date: Fri, 14 Sep 2018 22:53:02 +0200 Subject: [PATCH 09/19] SuperBuilder toBuilder: generate static toBuilder helper method (ecj) --- .../eclipse/handlers/HandleSuperBuilder.java | 168 ++++++++++++------ .../SuperBuilderBasicToBuilder.java | 4 +- .../after-ecj/SuperBuilderBasicToBuilder.java | 16 +- 3 files changed, 119 insertions(+), 69 deletions(-) diff --git a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java index bb78e82a70..a68ede3cf6 100644 --- a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java @@ -107,7 +107,11 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler { private static final String TO_BUILDER_METHOD_NAME_STRING = "toBuilder"; private static final char[] TO_BUILDER_METHOD_NAME = TO_BUILDER_METHOD_NAME_STRING.toCharArray(); private static final char[] FILL_VALUES_METHOD_NAME = "$fillValuesFrom".toCharArray(); + private static final char[] FILL_VALUES_STATIC_METHOD_NAME = "$fillValuesFromInstanceIntoBuilder".toCharArray(); private static final char[] EMPTY_LIST = "emptyList".toCharArray(); + private static final char[] INSTANCE_VARIABLE_NAME = "instance".toCharArray(); + private static final String BUILDER_VARIABLE_NAME_STRING = "b"; + private static final char[] BUILDER_VARIABLE_NAME = BUILDER_VARIABLE_NAME_STRING.toCharArray(); private static final AbstractMethodDeclaration[] EMPTY_METHODS = {}; @@ -294,7 +298,7 @@ public void handle(AnnotationValues annotation, Annotation ast, Ec if (toBuilder) { // Generate $fillValuesFrom() method in the abstract builder. - injectMethod(builderType, generateFillValuesMethod(tdParent, superclassBuilderClass != null, builderGenericName, classGenericName, typeParams, builderFields, ast)); + injectMethod(builderType, generateFillValuesMethod(tdParent, superclassBuilderClass != null, builderGenericName, classGenericName, builderImplClassName, typeParams, builderFields, ast)); } // Generate abstract self() and build() methods in the abstract builder. @@ -323,8 +327,9 @@ public void handle(AnnotationValues annotation, Annotation ast, Ec if (addCleaning) injectMethod(builderType, generateCleanMethod(builderFields, builderType, ast)); - if ((td.modifiers & ClassFileConstants.AccAbstract) != 0) { + if ((td.modifiers & ClassFileConstants.AccAbstract) != 0 && !toBuilder) { // Only non-abstract classes get the Builder implementation. + // However, if we want to generate a toBuilder, we need a helper function in the builder implementation class. return; } @@ -336,7 +341,24 @@ public void handle(AnnotationValues annotation, Annotation ast, Ec annotationNode.addError("@SuperBuilder does not support customized builders. Use @Builder instead."); return; } - + + if (toBuilder) { + // Generate $fillValuesFromInstanceIntoBuilder() method in the builder implementation class. + injectMethod(builderImplType, generateStaticFillValuesMethod(tdParent, superclassBuilderClass != null, builderClassName, classGenericName, typeParams, builderFields, ast)); + + switch (methodExists(TO_BUILDER_METHOD_NAME_STRING, tdParent, 0)) { + case EXISTS_BY_USER: + annotationNode.addWarning("Not generating toBuilder() as it already exists."); + break; + case NOT_EXISTS: + injectMethod(tdParent, generateToBuilderMethod(builderClassName, builderImplClassName, tdParent, typeParams, ast)); + } + if ((td.modifiers & ClassFileConstants.AccAbstract) != 0) { + // The rest of the builder implementation class is not necessary for abstract classes. + return; + } + } + // Create the self() and build() methods in the BuilderImpl. injectMethod(builderImplType, generateSelfMethod(builderImplType, typeParams, p)); injectMethod(builderImplType, generateBuildMethod(tdParent, buildMethodName, returnType, ast)); @@ -346,15 +368,6 @@ public void handle(AnnotationValues annotation, Annotation ast, Ec MethodDeclaration md = generateBuilderMethod(builderMethodName, builderClassName, builderImplClassName, tdParent, typeParams, ast); if (md != null) injectMethod(tdParent, md); } - - if (toBuilder) switch (methodExists(TO_BUILDER_METHOD_NAME_STRING, tdParent, 0)) { - case EXISTS_BY_USER: - annotationNode.addWarning("Not generating toBuilder() as it already exists."); - break; - case NOT_EXISTS: - MethodDeclaration md = generateToBuilderMethod(builderClassName, builderImplClassName, tdParent, typeParams, ast); - if (md != null) injectMethod(tdParent, md); - } } private EclipseNode generateBuilderAbstractClass(EclipseNode tdParent, String builderClass, @@ -449,7 +462,7 @@ private void generateBuilderBasedConstructor(EclipseNode typeNode, TypeParameter constructor.selector = typeDeclaration.name; if (callBuilderBasedSuperConstructor) { constructor.constructorCall = new ExplicitConstructorCall(ExplicitConstructorCall.Super); - constructor.constructorCall.arguments = new Expression[] {new SingleNameReference("b".toCharArray(), p)}; + constructor.constructorCall.arguments = new Expression[] {new SingleNameReference(BUILDER_VARIABLE_NAME, p)}; } else { constructor.constructorCall = new ExplicitConstructorCall(ExplicitConstructorCall.ImplicitSuper); } @@ -463,7 +476,7 @@ private void generateBuilderBasedConstructor(EclipseNode typeNode, TypeParameter TypeReference[] wildcards = new TypeReference[] {new Wildcard(Wildcard.UNBOUND), new Wildcard(Wildcard.UNBOUND)}; TypeReference builderType = new ParameterizedSingleTypeReference(builderClassName.toCharArray(), mergeToTypeReferences(typeParams, wildcards), 0, p); - constructor.arguments = new Argument[] {new Argument("b".toCharArray(), p, builderType, Modifier.FINAL)}; + constructor.arguments = new Argument[] {new Argument(BUILDER_VARIABLE_NAME, p, builderType, Modifier.FINAL)}; List statements = new ArrayList(); @@ -476,10 +489,10 @@ private void generateBuilderBasedConstructor(EclipseNode typeNode, TypeParameter Expression assignmentExpr; if (fieldNode.singularData != null && fieldNode.singularData.getSingularizer() != null) { - fieldNode.singularData.getSingularizer().appendBuildCode(fieldNode.singularData, typeNode, statements, fieldNode.name, "b"); + fieldNode.singularData.getSingularizer().appendBuildCode(fieldNode.singularData, typeNode, statements, fieldNode.name, BUILDER_VARIABLE_NAME_STRING); assignmentExpr = new SingleNameReference(fieldNode.name, p); } else { - char[][] variableInBuilder = new char[][] {"b".toCharArray(), fieldName}; + char[][] variableInBuilder = new char[][] {BUILDER_VARIABLE_NAME, fieldName}; long[] positions = new long[] {p, p}; assignmentExpr = new QualifiedNameReference(variableInBuilder, positions, s, e); } @@ -487,7 +500,7 @@ private void generateBuilderBasedConstructor(EclipseNode typeNode, TypeParameter // In case of @Builder.Default, set the value to the default if it was NOT set in the builder. if (fieldNode.nameOfSetFlag != null) { - char[][] setVariableInBuilder = new char[][] {"b".toCharArray(), fieldNode.nameOfSetFlag}; + char[][] setVariableInBuilder = new char[][] {BUILDER_VARIABLE_NAME, fieldNode.nameOfSetFlag}; long[] positions = new long[] {p, p}; QualifiedNameReference setVariableInBuilderRef = new QualifiedNameReference(setVariableInBuilder, positions, s, e); @@ -585,12 +598,12 @@ private MethodDeclaration generateToBuilderMethod(String builderClassName, Strin *
 	 * protected B $fillValuesFrom(final C instance) {
 	 *     super.$fillValuesFrom(instance);
-	 *     this.field(instance.field);
+	 *     FoobarBuilderImpl.$fillValuesFromInstanceIntoBuilder(instance, this);
 	 *     return self();
 	 * }
 	 * 
*/ - private MethodDeclaration generateFillValuesMethod(EclipseNode tdParent, boolean inherited, String builderGenericName, String classGenericName, TypeParameter[] typeParams, java.util.List builderFields, ASTNode source) { + private MethodDeclaration generateFillValuesMethod(EclipseNode tdParent, boolean inherited, String builderGenericName, String classGenericName, String builderImplClassName, TypeParameter[] typeParams, java.util.List builderFields, ASTNode source) { MethodDeclaration out = new MethodDeclaration(((CompilationUnitDeclaration) tdParent.top().get()).compilationResult); out.selector = FILL_VALUES_METHOD_NAME; out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; @@ -598,9 +611,8 @@ private MethodDeclaration generateFillValuesMethod(EclipseNode tdParent, boolean if (inherited) out.annotations = new Annotation[] {makeMarkerAnnotation(TypeConstants.JAVA_LANG_OVERRIDE, tdParent.get())}; out.returnType = new SingleTypeReference(builderGenericName.toCharArray(), 0); - final String instanceVariableName = "instance"; TypeReference builderType = createTypeReferenceWithTypeParameters(classGenericName, typeParams); - out.arguments = new Argument[] {new Argument(instanceVariableName.toCharArray(), 0, builderType, Modifier.FINAL)}; + out.arguments = new Argument[] {new Argument(INSTANCE_VARIABLE_NAME, 0, builderType, Modifier.FINAL)}; List body = new ArrayList(); @@ -609,16 +621,18 @@ private MethodDeclaration generateFillValuesMethod(EclipseNode tdParent, boolean MessageSend callToSuper = new MessageSend(); callToSuper.receiver = new SuperReference(0, 0); callToSuper.selector = FILL_VALUES_METHOD_NAME; - callToSuper.arguments = new Expression[] {new SingleNameReference(instanceVariableName.toCharArray(), 0)}; + callToSuper.arguments = new Expression[] {new SingleNameReference(INSTANCE_VARIABLE_NAME, 0)}; body.add(callToSuper); } - // Call the builder's setter methods to fill the values from the instance. - for (BuilderFieldData bfd : builderFields) { - MessageSend exec = createSetterCallWithInstanceValue(bfd, instanceVariableName, tdParent, source); - body.add(exec); - } + // Call the builder implemention's helper method that actually fills the values from the instance. + MessageSend callStaticFillValuesMethod = new MessageSend(); + callStaticFillValuesMethod.receiver = new SingleNameReference(builderImplClassName.toCharArray(), 0); + callStaticFillValuesMethod.selector = FILL_VALUES_STATIC_METHOD_NAME; + callStaticFillValuesMethod.arguments = new Expression[] {new SingleNameReference(INSTANCE_VARIABLE_NAME, 0), new ThisReference(0, 0)}; + body.add(callStaticFillValuesMethod); + // Return self(). MessageSend returnCall = new MessageSend(); returnCall.receiver = ThisReference.implicitThis(); returnCall.selector = SELF_METHOD_NAME; @@ -629,40 +643,76 @@ private MethodDeclaration generateFillValuesMethod(EclipseNode tdParent, boolean return out; } - private MessageSend createSetterCallWithInstanceValue(BuilderFieldData bfd, final String instanceVariableName, EclipseNode type, ASTNode source) { - char[] setterName = bfd.name; - MessageSend ms = new MessageSend(); - Expression[] tgt = new Expression[bfd.singularData == null ? 1 : 2]; - - if (bfd.obtainVia == null || !bfd.obtainVia.field().isEmpty()) { - char[] fieldName = bfd.obtainVia == null ? bfd.rawName : bfd.obtainVia.field().toCharArray(); - for (int i = 0; i < tgt.length; i++) { - FieldReference fr = new FieldReference(fieldName, 0); - fr.receiver = new SingleNameReference(instanceVariableName.toCharArray(), 0); - tgt[i] = fr; - } - } else { - String obtainName = bfd.obtainVia.method(); - boolean obtainIsStatic = bfd.obtainVia.isStatic(); - for (int i = 0; i < tgt.length; i++) { - MessageSend obtainExpr = new MessageSend(); - obtainExpr.receiver = obtainIsStatic ? new SingleNameReference(type.getName().toCharArray(), 0) : new SingleNameReference(instanceVariableName.toCharArray(), 0); - obtainExpr.selector = obtainName.toCharArray(); - if (obtainIsStatic) obtainExpr.arguments = new Expression[] {new SingleNameReference(instanceVariableName.toCharArray(), 0)}; - tgt[i] = obtainExpr; - } + /** + * Generates a $fillValuesFromInstanceIntoBuilder() method in + * the builder implementation class that copies all fields from the instance + * to the builder. It looks like this: + * + *
+	 * protected B $fillValuesFromInstanceIntoBuilder(Foobar instance, FoobarBuilder<?, ?> b) {
+	 * 	b.field(instance.field);
+	 * }
+	 * 
+ */ + private MethodDeclaration generateStaticFillValuesMethod(EclipseNode tdParent, boolean inherited, String builderClassName, String classGenericName, TypeParameter[] typeParams, java.util.List builderFields, ASTNode source) { + MethodDeclaration out = new MethodDeclaration(((CompilationUnitDeclaration) tdParent.top().get()).compilationResult); + out.selector = FILL_VALUES_STATIC_METHOD_NAME; + out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + out.modifiers = ClassFileConstants.AccPrivate | ClassFileConstants.AccStatic; + out.returnType = TypeReference.baseTypeReference(TypeIds.T_void, 0); + + TypeReference[] wildcards = new TypeReference[] {new Wildcard(Wildcard.UNBOUND), new Wildcard(Wildcard.UNBOUND)}; + TypeReference builderType = new ParameterizedSingleTypeReference(builderClassName.toCharArray(), mergeToTypeReferences(typeParams, wildcards), 0, 0); + Argument builderArgument = new Argument(BUILDER_VARIABLE_NAME, 0, builderType, Modifier.FINAL); + out.arguments = new Argument[] {new Argument(INSTANCE_VARIABLE_NAME, 0, new SingleTypeReference(tdParent.getName().toCharArray(), 0), Modifier.FINAL), builderArgument}; + + List body = new ArrayList(); + + // Call the builder's setter methods to fill the values from the instance. + for (BuilderFieldData bfd : builderFields) { + MessageSend exec = createSetterCallWithInstanceValue(bfd, tdParent, source); + body.add(exec); + } + + out.statements = body.isEmpty() ? null : body.toArray(new Statement[body.size()]); + + return out; + } + + private MessageSend createSetterCallWithInstanceValue(BuilderFieldData bfd, EclipseNode type, ASTNode source) { + char[] setterName = bfd.name; + MessageSend ms = new MessageSend(); + Expression[] tgt = new Expression[bfd.singularData == null ? 1 : 2]; + + if (bfd.obtainVia == null || !bfd.obtainVia.field().isEmpty()) { + char[] fieldName = bfd.obtainVia == null ? bfd.rawName : bfd.obtainVia.field().toCharArray(); + for (int i = 0; i < tgt.length; i++) { + FieldReference fr = new FieldReference(fieldName, 0); + fr.receiver = new SingleNameReference(INSTANCE_VARIABLE_NAME, 0); + tgt[i] = fr; } - if (bfd.singularData == null) { - ms.arguments = tgt; - } else { - Expression ifNull = new EqualExpression(tgt[0], new NullLiteral(0, 0), OperatorIds.EQUAL_EQUAL); - MessageSend emptyList = new MessageSend(); - emptyList.receiver = generateQualifiedNameRef(source, TypeConstants.JAVA, TypeConstants.UTIL, "Collections".toCharArray()); - emptyList.selector = EMPTY_LIST; - ms.arguments = new Expression[] {new ConditionalExpression(ifNull, emptyList, tgt[1])}; + } else { + String obtainName = bfd.obtainVia.method(); + boolean obtainIsStatic = bfd.obtainVia.isStatic(); + for (int i = 0; i < tgt.length; i++) { + MessageSend obtainExpr = new MessageSend(); + obtainExpr.receiver = obtainIsStatic ? new SingleNameReference(type.getName().toCharArray(), 0) : new SingleNameReference(INSTANCE_VARIABLE_NAME, 0); + obtainExpr.selector = obtainName.toCharArray(); + if (obtainIsStatic) obtainExpr.arguments = new Expression[] {new SingleNameReference(INSTANCE_VARIABLE_NAME, 0)}; + tgt[i] = obtainExpr; } - ms.receiver = ThisReference.implicitThis(); - ms.selector = setterName; + } + if (bfd.singularData == null) { + ms.arguments = tgt; + } else { + Expression ifNull = new EqualExpression(tgt[0], new NullLiteral(0, 0), OperatorIds.EQUAL_EQUAL); + MessageSend emptyList = new MessageSend(); + emptyList.receiver = generateQualifiedNameRef(source, TypeConstants.JAVA, TypeConstants.UTIL, "Collections".toCharArray()); + emptyList.selector = EMPTY_LIST; + ms.arguments = new Expression[] {new ConditionalExpression(ifNull, emptyList, tgt[1])}; + } + ms.receiver = new SingleNameReference(BUILDER_VARIABLE_NAME, 0); + ms.selector = setterName; return ms; } diff --git a/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java b/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java index c5099480c3..dc0b6e0c8f 100644 --- a/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java +++ b/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java @@ -79,7 +79,7 @@ public java.lang.String toString() { @java.lang.SuppressWarnings("all") private static final class ParentBuilderImpl extends ParentBuilder { @java.lang.SuppressWarnings("all") - private static void $fillValuesFromInstanceIntoBuilder(Parent instance, ParentBuilder b) { + private static void $fillValuesFromInstanceIntoBuilder(final Parent instance, final ParentBuilder b) { b.field1(instance.field1); b.obtainViaField(instance.field1); b.obtainViaMethod(instance.method()); @@ -161,7 +161,7 @@ public java.lang.String toString() { @java.lang.SuppressWarnings("all") private static final class ChildBuilderImpl extends ChildBuilder { @java.lang.SuppressWarnings("all") - private static void $fillValuesFromInstanceIntoBuilder(Parent instance, ParentBuilder b) { + private static void $fillValuesFromInstanceIntoBuilder(final Parent instance, final ParentBuilder b) { b.field3(instance.field3); } @java.lang.SuppressWarnings("all") diff --git a/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java b/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java index fac9ab5579..be819d055c 100644 --- a/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java +++ b/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java @@ -57,7 +57,7 @@ public ParentBuilder() { private ParentBuilderImpl() { super(); } - private static @java.lang.SuppressWarnings("all") void $fillValuesFromInstanceIntoBuilder(Parent instance, ParentBuilder b) { + private static @java.lang.SuppressWarnings("all") void $fillValuesFromInstanceIntoBuilder(final Parent instance, final ParentBuilder b) { b.field1(instance.field1); b.obtainViaField(instance.field1); b.obtainViaMethod(instance.method()); @@ -101,12 +101,12 @@ private static String staticMethod(Parent instance) { } this.items = items; } - public static @java.lang.SuppressWarnings("all") ParentBuilder builder() { - return new ParentBuilderImpl(); - } public @java.lang.SuppressWarnings("all") ParentBuilder toBuilder() { return new ParentBuilderImpl().$fillValuesFrom(this); } + public static @java.lang.SuppressWarnings("all") ParentBuilder builder() { + return new ParentBuilderImpl(); + } } public static @lombok.experimental.SuperBuilder(toBuilder = true) class Child extends Parent { public static abstract @java.lang.SuppressWarnings("all") class ChildBuilder> extends Parent.ParentBuilder { @@ -133,7 +133,7 @@ public ChildBuilder() { private ChildBuilderImpl() { super(); } - private static @java.lang.SuppressWarnings("all") void $fillValuesFromInstanceIntoBuilder(Child instance, ChildBuilder b) { + private static @java.lang.SuppressWarnings("all") void $fillValuesFromInstanceIntoBuilder(final Child instance, final ChildBuilder b) { b.field3(instance.field3); } protected @java.lang.Override @java.lang.SuppressWarnings("all") ChildBuilderImpl self() { @@ -148,12 +148,12 @@ private ChildBuilderImpl() { super(b); this.field3 = b.field3; } - public static @java.lang.SuppressWarnings("all") ChildBuilder builder() { - return new ChildBuilderImpl(); - } public @java.lang.SuppressWarnings("all") ChildBuilder toBuilder() { return new ChildBuilderImpl().$fillValuesFrom(this); } + public static @java.lang.SuppressWarnings("all") ChildBuilder builder() { + return new ChildBuilderImpl(); + } } public SuperBuilderBasicToBuilder() { super(); From 7a287670f4bb6b22db920b980ce5dde4fcef5af5 Mon Sep 17 00:00:00 2001 From: Jan Rieke Date: Sun, 16 Sep 2018 15:51:19 +0200 Subject: [PATCH 10/19] SuperBuilder toBuilder: generate static toBuilder helper method (javac) --- .../eclipse/handlers/HandleSuperBuilder.java | 4 +- .../javac/handlers/HandleSuperBuilder.java | 114 +++++++++++++----- .../SuperBuilderBasicToBuilder.java | 4 +- 3 files changed, 90 insertions(+), 32 deletions(-) diff --git a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java index a68ede3cf6..fe53c7edbe 100644 --- a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java @@ -344,7 +344,7 @@ public void handle(AnnotationValues annotation, Annotation ast, Ec if (toBuilder) { // Generate $fillValuesFromInstanceIntoBuilder() method in the builder implementation class. - injectMethod(builderImplType, generateStaticFillValuesMethod(tdParent, superclassBuilderClass != null, builderClassName, classGenericName, typeParams, builderFields, ast)); + injectMethod(builderImplType, generateStaticFillValuesMethod(tdParent, superclassBuilderClass != null, builderClassName, typeParams, builderFields, ast)); switch (methodExists(TO_BUILDER_METHOD_NAME_STRING, tdParent, 0)) { case EXISTS_BY_USER: @@ -654,7 +654,7 @@ private MethodDeclaration generateFillValuesMethod(EclipseNode tdParent, boolean * } *
*/ - private MethodDeclaration generateStaticFillValuesMethod(EclipseNode tdParent, boolean inherited, String builderClassName, String classGenericName, TypeParameter[] typeParams, java.util.List builderFields, ASTNode source) { + private MethodDeclaration generateStaticFillValuesMethod(EclipseNode tdParent, boolean inherited, String builderClassName, TypeParameter[] typeParams, java.util.List builderFields, ASTNode source) { MethodDeclaration out = new MethodDeclaration(((CompilationUnitDeclaration) tdParent.top().get()).compilationResult); out.selector = FILL_VALUES_STATIC_METHOD_NAME; out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; diff --git a/src/core/lombok/javac/handlers/HandleSuperBuilder.java b/src/core/lombok/javac/handlers/HandleSuperBuilder.java index bdcd043a36..84adc72eaf 100644 --- a/src/core/lombok/javac/handlers/HandleSuperBuilder.java +++ b/src/core/lombok/javac/handlers/HandleSuperBuilder.java @@ -63,6 +63,7 @@ import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; import lombok.core.handlers.HandlerUtil; +import lombok.core.handlers.HandlerUtil.FieldAccess; import lombok.core.handlers.InclusionExclusionUtils.Included; import lombok.experimental.NonFinal; import lombok.experimental.SuperBuilder; @@ -73,9 +74,9 @@ import lombok.javac.handlers.HandleBuilder.BuilderFieldData; import lombok.javac.handlers.JavacHandlerUtil.MemberExistsResult; import lombok.javac.handlers.JavacSingularsRecipes.ExpressionMaker; -import lombok.javac.handlers.JavacSingularsRecipes.StatementMaker; import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer; import lombok.javac.handlers.JavacSingularsRecipes.SingularData; +import lombok.javac.handlers.JavacSingularsRecipes.StatementMaker; @ProviderFor(JavacAnnotationHandler.class) @HandlerPriority(-1024) //-2^10; to ensure we've picked up @FieldDefault's changes (-2048) but @Value hasn't removed itself yet (-512), so that we can error on presence of it on the builder classes. @@ -83,6 +84,9 @@ public class HandleSuperBuilder extends JavacAnnotationHandler { private static final String SELF_METHOD = "self"; private static final String TO_BUILDER_METHOD_NAME = "toBuilder"; private static final String FILL_VALUES_METHOD_NAME = "$fillValuesFrom"; + private static final String STATIC_FILL_VALUES_METHOD_NAME = "$fillValuesFromInstanceIntoBuilder"; + private static final String INSTANCE_VARIABLE_NAME = "instance"; + private static final String BUILDER_VARIABLE_NAME = "b"; @Override public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) { @@ -238,7 +242,7 @@ public void handle(AnnotationValues annotation, JCAnnotation ast, if (toBuilder) { // Generate $fillValuesFrom() method in the abstract builder. - injectMethod(builderType, generateFillValuesMethod(tdParent, superclassBuilderClassExpression != null, builderGenericName, classGenericName, builderFields)); + injectMethod(builderType, generateFillValuesMethod(tdParent, superclassBuilderClassExpression != null, builderGenericName, classGenericName, builderImplClassName, builderFields)); } // Generate abstract self() and build() methods in the abstract builder. @@ -267,8 +271,10 @@ public void handle(AnnotationValues annotation, JCAnnotation ast, recursiveSetGeneratedBy(builderType.get(), ast, annotationNode.getContext()); - if ((td.mods.flags & Flags.ABSTRACT) == 0) { + boolean isAbstract = (td.mods.flags & Flags.ABSTRACT) != 0; + if (!isAbstract || toBuilder) { // Only non-abstract classes get the Builder implementation. + // However, if we want to generate a toBuilder, we need a helper function in the builder implementation class. // Create the builder implementation class. JavacNode builderImplType = findInnerClass(tdParent, builderImplClassName); @@ -278,14 +284,21 @@ public void handle(AnnotationValues annotation, JCAnnotation ast, annotationNode.addError("@SuperBuilder does not support customized builders. Use @Builder instead."); return; } + + if (toBuilder) { + // Generate $fillValuesFromInstanceIntoBuilder() method in the builder implementation class. + injectMethod(builderImplType, generateStaticFillValuesMethod(tdParent, superclassBuilderClassExpression != null, builderClassName, classGenericName, typeParams, builderFields, ast)); + } - // Create a simple constructor for the BuilderImpl class. - JCMethodDecl cd = HandleConstructor.createConstructor(AccessLevel.PRIVATE, List.nil(), builderImplType, List.nil(), false, annotationNode); - if (cd != null) injectMethod(builderImplType, cd); - - // Create the self() and build() methods in the BuilderImpl. - injectMethod(builderImplType, generateSelfMethod(builderImplType, typeParams)); - injectMethod(builderImplType, generateBuildMethod(buildMethodName, returnType, builderImplType, thrownExceptions)); + if (!isAbstract) { + // Create a simple constructor for the BuilderImpl class. + JCMethodDecl cd = HandleConstructor.createConstructor(AccessLevel.PRIVATE, List.nil(), builderImplType, List.nil(), false, annotationNode); + if (cd != null) injectMethod(builderImplType, cd); + + // Create the self() and build() methods in the BuilderImpl. + injectMethod(builderImplType, generateSelfMethod(builderImplType, typeParams)); + injectMethod(builderImplType, generateBuildMethod(buildMethodName, returnType, builderImplType, thrownExceptions)); + } recursiveSetGeneratedBy(builderImplType.get(), ast, annotationNode.getContext()); } @@ -294,8 +307,8 @@ public void handle(AnnotationValues annotation, JCAnnotation ast, generateBuilderBasedConstructor(tdParent, typeParams, builderFields, annotationNode, builderClassName, superclassBuilderClassExpression != null); - if ((td.mods.flags & Flags.ABSTRACT) == 0) { - // Only non-abstract classes get the Builder implementation and the builder() method. + if (!isAbstract) { + // Only non-abstract classes get the builder() method. // Add the builder() method to the annotated class. // Allow users to specify their own builder() methods, e.g., to provide default values. @@ -419,7 +432,7 @@ private void generateBuilderBasedConstructor(JavacNode typeNode, List statements = new ListBuffer(); - Name builderVariableName = typeNode.toName("b"); + Name builderVariableName = typeNode.toName(BUILDER_VARIABLE_NAME); for (BuilderFieldData bfd : builderFields) { JCExpression rhs; if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { @@ -546,12 +559,12 @@ private JCMethodDecl generateToBuilderMethod(String builderClassName, String bui *
 	 * protected B $fillValuesFrom(final C instance) {
 	 *     super.$fillValuesFrom(instance);
-	 *     this.field(instance.field);
+	 *     FoobarBuilderImpl.$fillValuesFromInstanceIntoBuilder(instance, this);
 	 *     return self();
 	 * }
 	 * 
*/ - private JCMethodDecl generateFillValuesMethod(JavacNode type, boolean inherited, String builderGenericName, String classGenericName, java.util.List builderFields) { + private JCMethodDecl generateFillValuesMethod(JavacNode type, boolean inherited, String builderGenericName, String classGenericName, String builderImplClassName, java.util.List builderFields) { JavacTreeMaker maker = type.getTreeMaker(); List annotations = List.nil(); if (inherited) { @@ -562,9 +575,8 @@ private JCMethodDecl generateFillValuesMethod(JavacNode type, boolean inherited, Name name = type.toName(FILL_VALUES_METHOD_NAME); JCExpression returnType = maker.Ident(type.toName(builderGenericName)); - final String instanceVariableName = "instance"; JCExpression classGenericNameExpr = maker.Ident(type.toName(classGenericName)); - JCVariableDecl param = maker.VarDef(maker.Modifiers(Flags.LocalVarFlags), type.toName(instanceVariableName), classGenericNameExpr, null); + JCVariableDecl param = maker.VarDef(maker.Modifiers(Flags.LocalVarFlags), type.toName(INSTANCE_VARIABLE_NAME), classGenericNameExpr, null); ListBuffer body = new ListBuffer(); @@ -572,15 +584,15 @@ private JCMethodDecl generateFillValuesMethod(JavacNode type, boolean inherited, // Call super. JCMethodInvocation callToSuper = maker.Apply(List.nil(), maker.Select(maker.Ident(type.toName("super")), name), - List.of(maker.Ident(type.toName(instanceVariableName)))); + List.of(maker.Ident(type.toName(INSTANCE_VARIABLE_NAME)))); body.append(maker.Exec(callToSuper)); } - // Call the builder's setter methods to fill the values from the instance. - for (BuilderFieldData bfd : builderFields) { - JCExpressionStatement exec = createSetterCallWithInstanceValue(bfd, instanceVariableName, type, maker); - body.append(exec); - } + // Call the builder implemention's helper method that actually fills the values from the instance. + JCMethodInvocation callStaticFillValuesMethod = maker.Apply(List.nil(), + maker.Select(maker.Ident(type.toName(builderImplClassName)), type.toName(STATIC_FILL_VALUES_METHOD_NAME)), + List.of(maker.Ident(type.toName(INSTANCE_VARIABLE_NAME)), maker.Ident(type.toName("this")))); + body.append(maker.Exec(callStaticFillValuesMethod)); JCReturn returnStatement = maker.Return(maker.Apply(List.nil(), maker.Ident(type.toName(SELF_METHOD)), List.nil())); body.append(returnStatement); @@ -589,21 +601,67 @@ private JCMethodDecl generateFillValuesMethod(JavacNode type, boolean inherited, return maker.MethodDef(modifiers, name, returnType, List.nil(), List.of(param), List.nil(), bodyBlock, null); } - private JCExpressionStatement createSetterCallWithInstanceValue(BuilderFieldData bfd, final String instanceVariableName, JavacNode type, JavacTreeMaker maker) { + /** + * Generates a $fillValuesFromInstanceIntoBuilder() method in + * the builder implementation class that copies all fields from the instance + * to the builder. It looks like this: + * + *
+	 * protected B $fillValuesFromInstanceIntoBuilder(Foobar instance, FoobarBuilder<?, ?> b) {
+	 * 	b.field(instance.field);
+	 * }
+	 * 
+ */ + private JCMethodDecl generateStaticFillValuesMethod(JavacNode type, boolean inherited, String builderClassname, String classGenericName, List typeParams, java.util.List builderFields, JCAnnotation ast) { + JavacTreeMaker maker = type.getTreeMaker(); + List annotations = List.nil(); + JCModifiers modifiers = maker.Modifiers(Flags.PRIVATE | Flags.STATIC, annotations); + Name name = type.toName(STATIC_FILL_VALUES_METHOD_NAME); + JCExpression returnType = maker.TypeIdent(CTC_VOID); + + // 1st parameter: "Foobar instance" + JCExpression classGenericNameExpr = maker.Ident(type.toName(type.getName())); + JCVariableDecl paramInstance = maker.VarDef(maker.Modifiers(Flags.LocalVarFlags), type.toName(INSTANCE_VARIABLE_NAME), classGenericNameExpr, null); + + // 2nd parameter: "FoobarBuilder b" (plus generics on the annotated type) + // First add all generics that are present on the parent type. + ListBuffer typeParamsForBuilderParameter = getTypeParamExpressions(typeParams, maker); + // Now add the . + JCWildcard wildcard = maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null); + typeParamsForBuilderParameter.add(wildcard); + wildcard = maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null); + typeParamsForBuilderParameter.add(wildcard); + JCTypeApply builderType = maker.TypeApply(maker.Ident(type.toName(builderClassname)), typeParamsForBuilderParameter.toList()); + JCVariableDecl paramBuilder = maker.VarDef(maker.Modifiers(Flags.LocalVarFlags), type.toName(BUILDER_VARIABLE_NAME), builderType, null); + + ListBuffer body = new ListBuffer(); + + // Call the builder's setter methods to fill the values from the instance. + for (BuilderFieldData bfd : builderFields) { + JCExpressionStatement exec = createSetterCallWithInstanceValue(bfd, type, maker); + body.append(exec); + } + + JCBlock bodyBlock = maker.Block(0, body.toList()); + + return maker.MethodDef(modifiers, name, returnType, List.nil(), List.of(paramInstance, paramBuilder), List.nil(), bodyBlock, null); + } + + private JCExpressionStatement createSetterCallWithInstanceValue(BuilderFieldData bfd, JavacNode type, JavacTreeMaker maker) { JCExpression[] tgt = new JCExpression[bfd.singularData == null ? 1 : 2]; if (bfd.obtainVia == null || !bfd.obtainVia.field().isEmpty()) { for (int i = 0; i < tgt.length; i++) { - tgt[i] = maker.Select(maker.Ident(type.toName(instanceVariableName)), bfd.obtainVia == null ? bfd.rawName : type.toName(bfd.obtainVia.field())); + tgt[i] = maker.Select(maker.Ident(type.toName(INSTANCE_VARIABLE_NAME)), bfd.obtainVia == null ? bfd.rawName : type.toName(bfd.obtainVia.field())); } } else { if (bfd.obtainVia.isStatic()) { for (int i = 0; i < tgt.length; i++) { JCExpression c = maker.Select(maker.Ident(type.toName(type.getName())), type.toName(bfd.obtainVia.method())); - tgt[i] = maker.Apply(List.nil(), c, List.of(maker.Ident(type.toName(instanceVariableName)))); + tgt[i] = maker.Apply(List.nil(), c, List.of(maker.Ident(type.toName(INSTANCE_VARIABLE_NAME)))); } } else { for (int i = 0; i < tgt.length; i++) { - JCExpression c = maker.Select(maker.Ident(type.toName(instanceVariableName)), type.toName(bfd.obtainVia.method())); + JCExpression c = maker.Select(maker.Ident(type.toName(INSTANCE_VARIABLE_NAME)), type.toName(bfd.obtainVia.method())); tgt[i] = maker.Apply(List.nil(), c, List.nil()); } } @@ -617,7 +675,7 @@ private JCExpressionStatement createSetterCallWithInstanceValue(BuilderFieldData JCExpression emptyList = maker.Apply(List.nil(), chainDots(type, "java", "util", "Collections", "emptyList"), List.nil()); arg = maker.Conditional(eqNull, emptyList, tgt[1]); } - JCMethodInvocation apply = maker.Apply(List.nil(), maker.Select(maker.Ident(type.toName("this")), bfd.name), List.of(arg)); + JCMethodInvocation apply = maker.Apply(List.nil(), maker.Select(maker.Ident(type.toName(BUILDER_VARIABLE_NAME)), bfd.name), List.of(arg)); JCExpressionStatement exec = maker.Exec(apply); return exec; } diff --git a/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java b/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java index dc0b6e0c8f..7a80f019fd 100644 --- a/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java +++ b/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java @@ -79,7 +79,7 @@ public java.lang.String toString() { @java.lang.SuppressWarnings("all") private static final class ParentBuilderImpl extends ParentBuilder { @java.lang.SuppressWarnings("all") - private static void $fillValuesFromInstanceIntoBuilder(final Parent instance, final ParentBuilder b) { + private static void $fillValuesFromInstanceIntoBuilder(final Parent instance, final ParentBuilder b) { b.field1(instance.field1); b.obtainViaField(instance.field1); b.obtainViaMethod(instance.method()); @@ -161,7 +161,7 @@ public java.lang.String toString() { @java.lang.SuppressWarnings("all") private static final class ChildBuilderImpl extends ChildBuilder { @java.lang.SuppressWarnings("all") - private static void $fillValuesFromInstanceIntoBuilder(final Parent instance, final ParentBuilder b) { + private static void $fillValuesFromInstanceIntoBuilder(final Child instance, final ChildBuilder b) { b.field3(instance.field3); } @java.lang.SuppressWarnings("all") From 41d8929630cedb1f310a7baebcfead616215093a Mon Sep 17 00:00:00 2001 From: Jan Rieke Date: Sun, 16 Sep 2018 16:02:28 +0200 Subject: [PATCH 11/19] SuperBuilder: removed unnecessary method params --- .../eclipse/handlers/HandleSuperBuilder.java | 10 +++++---- .../javac/handlers/HandleSuperBuilder.java | 22 ++++++++++--------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java index fe53c7edbe..fc532afb65 100644 --- a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java @@ -298,7 +298,7 @@ public void handle(AnnotationValues annotation, Annotation ast, Ec if (toBuilder) { // Generate $fillValuesFrom() method in the abstract builder. - injectMethod(builderType, generateFillValuesMethod(tdParent, superclassBuilderClass != null, builderGenericName, classGenericName, builderImplClassName, typeParams, builderFields, ast)); + injectMethod(builderType, generateFillValuesMethod(tdParent, superclassBuilderClass != null, builderGenericName, classGenericName, builderImplClassName, typeParams)); } // Generate abstract self() and build() methods in the abstract builder. @@ -344,7 +344,7 @@ public void handle(AnnotationValues annotation, Annotation ast, Ec if (toBuilder) { // Generate $fillValuesFromInstanceIntoBuilder() method in the builder implementation class. - injectMethod(builderImplType, generateStaticFillValuesMethod(tdParent, superclassBuilderClass != null, builderClassName, typeParams, builderFields, ast)); + injectMethod(builderImplType, generateStaticFillValuesMethod(tdParent, builderClassName, typeParams, builderFields, ast)); switch (methodExists(TO_BUILDER_METHOD_NAME_STRING, tdParent, 0)) { case EXISTS_BY_USER: @@ -352,6 +352,8 @@ public void handle(AnnotationValues annotation, Annotation ast, Ec break; case NOT_EXISTS: injectMethod(tdParent, generateToBuilderMethod(builderClassName, builderImplClassName, tdParent, typeParams, ast)); + default: + // Should not happen. } if ((td.modifiers & ClassFileConstants.AccAbstract) != 0) { // The rest of the builder implementation class is not necessary for abstract classes. @@ -603,7 +605,7 @@ private MethodDeclaration generateToBuilderMethod(String builderClassName, Strin * } * */ - private MethodDeclaration generateFillValuesMethod(EclipseNode tdParent, boolean inherited, String builderGenericName, String classGenericName, String builderImplClassName, TypeParameter[] typeParams, java.util.List builderFields, ASTNode source) { + private MethodDeclaration generateFillValuesMethod(EclipseNode tdParent, boolean inherited, String builderGenericName, String classGenericName, String builderImplClassName, TypeParameter[] typeParams) { MethodDeclaration out = new MethodDeclaration(((CompilationUnitDeclaration) tdParent.top().get()).compilationResult); out.selector = FILL_VALUES_METHOD_NAME; out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; @@ -654,7 +656,7 @@ private MethodDeclaration generateFillValuesMethod(EclipseNode tdParent, boolean * } * */ - private MethodDeclaration generateStaticFillValuesMethod(EclipseNode tdParent, boolean inherited, String builderClassName, TypeParameter[] typeParams, java.util.List builderFields, ASTNode source) { + private MethodDeclaration generateStaticFillValuesMethod(EclipseNode tdParent, String builderClassName, TypeParameter[] typeParams, java.util.List builderFields, ASTNode source) { MethodDeclaration out = new MethodDeclaration(((CompilationUnitDeclaration) tdParent.top().get()).compilationResult); out.selector = FILL_VALUES_STATIC_METHOD_NAME; out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; diff --git a/src/core/lombok/javac/handlers/HandleSuperBuilder.java b/src/core/lombok/javac/handlers/HandleSuperBuilder.java index 84adc72eaf..f858cd2923 100644 --- a/src/core/lombok/javac/handlers/HandleSuperBuilder.java +++ b/src/core/lombok/javac/handlers/HandleSuperBuilder.java @@ -226,7 +226,7 @@ public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode builderType = findInnerClass(tdParent, builderClassName); if (builderType == null) { builderType = generateBuilderAbstractClass(annotationNode, tdParent, builderClassName, superclassBuilderClassExpression, - typeParams, superclassTypeParams, ast, classGenericName, builderGenericName); + typeParams, superclassTypeParams, classGenericName, builderGenericName); } else { annotationNode.addError("@SuperBuilder does not support customized builders. Use @Builder instead."); return; @@ -242,7 +242,7 @@ public void handle(AnnotationValues annotation, JCAnnotation ast, if (toBuilder) { // Generate $fillValuesFrom() method in the abstract builder. - injectMethod(builderType, generateFillValuesMethod(tdParent, superclassBuilderClassExpression != null, builderGenericName, classGenericName, builderImplClassName, builderFields)); + injectMethod(builderType, generateFillValuesMethod(tdParent, superclassBuilderClassExpression != null, builderGenericName, classGenericName, builderImplClassName)); } // Generate abstract self() and build() methods in the abstract builder. @@ -279,7 +279,7 @@ public void handle(AnnotationValues annotation, JCAnnotation ast, // Create the builder implementation class. JavacNode builderImplType = findInnerClass(tdParent, builderImplClassName); if (builderImplType == null) { - builderImplType = generateBuilderImplClass(annotationNode, tdParent, builderImplClassName, builderClassName, typeParams, ast); + builderImplType = generateBuilderImplClass(annotationNode, tdParent, builderImplClassName, builderClassName, typeParams); } else { annotationNode.addError("@SuperBuilder does not support customized builders. Use @Builder instead."); return; @@ -287,7 +287,7 @@ public void handle(AnnotationValues annotation, JCAnnotation ast, if (toBuilder) { // Generate $fillValuesFromInstanceIntoBuilder() method in the builder implementation class. - injectMethod(builderImplType, generateStaticFillValuesMethod(tdParent, superclassBuilderClassExpression != null, builderClassName, classGenericName, typeParams, builderFields, ast)); + injectMethod(builderImplType, generateStaticFillValuesMethod(tdParent, builderClassName, typeParams, builderFields)); } if (!isAbstract) { @@ -329,6 +329,8 @@ public void handle(AnnotationValues annotation, JCAnnotation ast, if (md != null) { injectMethod(tdParent, md); } + default: + // Should not happen. } } } @@ -338,7 +340,7 @@ public void handle(AnnotationValues annotation, JCAnnotation ast, */ private JavacNode generateBuilderAbstractClass(JavacNode source, JavacNode tdParent, String builderClass, JCExpression superclassBuilderClassExpression, List typeParams, - List superclassTypeParams, JCAnnotation ast, String classGenericName, String builderGenericName) { + List superclassTypeParams, String classGenericName, String builderGenericName) { JavacTreeMaker maker = tdParent.getTreeMaker(); JCModifiers mods = maker.Modifiers(Flags.STATIC | Flags.ABSTRACT | Flags.PUBLIC); @@ -380,7 +382,7 @@ private JavacNode generateBuilderAbstractClass(JavacNode source, JavacNode tdPar /** * Creates and returns the concrete builder implementation class and injects it into the annotated class. */ - private JavacNode generateBuilderImplClass(JavacNode source, JavacNode tdParent, String builderImplClass, String builderAbstractClass, List typeParams, JCAnnotation ast) { + private JavacNode generateBuilderImplClass(JavacNode source, JavacNode tdParent, String builderImplClass, String builderAbstractClass, List typeParams) { JavacTreeMaker maker = tdParent.getTreeMaker(); JCModifiers mods = maker.Modifiers(Flags.STATIC | Flags.PRIVATE | Flags.FINAL); @@ -564,7 +566,7 @@ private JCMethodDecl generateToBuilderMethod(String builderClassName, String bui * } * */ - private JCMethodDecl generateFillValuesMethod(JavacNode type, boolean inherited, String builderGenericName, String classGenericName, String builderImplClassName, java.util.List builderFields) { + private JCMethodDecl generateFillValuesMethod(JavacNode type, boolean inherited, String builderGenericName, String classGenericName, String builderImplClassName) { JavacTreeMaker maker = type.getTreeMaker(); List annotations = List.nil(); if (inherited) { @@ -612,7 +614,7 @@ private JCMethodDecl generateFillValuesMethod(JavacNode type, boolean inherited, * } * */ - private JCMethodDecl generateStaticFillValuesMethod(JavacNode type, boolean inherited, String builderClassname, String classGenericName, List typeParams, java.util.List builderFields, JCAnnotation ast) { + private JCMethodDecl generateStaticFillValuesMethod(JavacNode type, String builderClassname, List typeParams, java.util.List builderFields) { JavacTreeMaker maker = type.getTreeMaker(); List annotations = List.nil(); JCModifiers modifiers = maker.Modifiers(Flags.PRIVATE | Flags.STATIC, annotations); @@ -802,13 +804,13 @@ private void generateSetterMethodsForBuilder(final JavacNode builderType, Builde }}; if (fieldNode.singularData == null || fieldNode.singularData.getSingularizer() == null) { - generateSimpleSetterMethodForBuilder(builderType, deprecate, fieldNode.createdFields.get(0), fieldNode.nameOfSetFlag, source, true, true, returnTypeMaker.make(), returnStatementMaker.make()); + generateSimpleSetterMethodForBuilder(builderType, deprecate, fieldNode.createdFields.get(0), fieldNode.nameOfSetFlag, source, true, returnTypeMaker.make(), returnStatementMaker.make()); } else { fieldNode.singularData.getSingularizer().generateMethods(fieldNode.singularData, deprecate, builderType, source.get(), true, returnTypeMaker, returnStatementMaker); } } - private void generateSimpleSetterMethodForBuilder(JavacNode builderType, boolean deprecate, JavacNode fieldNode, Name nameOfSetFlag, JavacNode source, boolean fluent, boolean chain, JCExpression returnType, JCStatement returnStatement) { + private void generateSimpleSetterMethodForBuilder(JavacNode builderType, boolean deprecate, JavacNode fieldNode, Name nameOfSetFlag, JavacNode source, boolean fluent, JCExpression returnType, JCStatement returnStatement) { Name fieldName = ((JCVariableDecl) fieldNode.get()).name; for (JavacNode child : builderType.down()) { From 790e060380f01ea113bfeee25b3138ea267ebcb4 Mon Sep 17 00:00:00 2001 From: Jan Rieke Date: Sun, 16 Sep 2018 17:24:39 +0200 Subject: [PATCH 12/19] SuperBuilder toBuilder: moved static helper method to builder impl --- .../eclipse/handlers/HandleSuperBuilder.java | 20 +-- .../javac/handlers/HandleSuperBuilder.java | 46 +++-- .../SuperBuilderAbstractToBuilder.java | 168 ++++++++++++++++++ .../SuperBuilderBasicToBuilder.java | 28 +-- .../SuperBuilderAbstractToBuilder.java | 131 ++++++++++++++ .../after-ecj/SuperBuilderBasicToBuilder.java | 24 +-- .../before/SuperBuilderAbstractToBuilder.java | 20 +++ 7 files changed, 374 insertions(+), 63 deletions(-) create mode 100644 test/transform/resource/after-delombok/SuperBuilderAbstractToBuilder.java create mode 100644 test/transform/resource/after-ecj/SuperBuilderAbstractToBuilder.java create mode 100644 test/transform/resource/before/SuperBuilderAbstractToBuilder.java diff --git a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java index fc532afb65..640e5a714d 100644 --- a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java @@ -298,7 +298,9 @@ public void handle(AnnotationValues annotation, Annotation ast, Ec if (toBuilder) { // Generate $fillValuesFrom() method in the abstract builder. - injectMethod(builderType, generateFillValuesMethod(tdParent, superclassBuilderClass != null, builderGenericName, classGenericName, builderImplClassName, typeParams)); + injectMethod(builderType, generateFillValuesMethod(tdParent, superclassBuilderClass != null, builderGenericName, classGenericName, builderClassName, typeParams)); + // Generate $fillValuesFromInstanceIntoBuilder() method in the builder implementation class. + injectMethod(builderType, generateStaticFillValuesMethod(tdParent, builderClassName, typeParams, builderFields, ast)); } // Generate abstract self() and build() methods in the abstract builder. @@ -327,9 +329,9 @@ public void handle(AnnotationValues annotation, Annotation ast, Ec if (addCleaning) injectMethod(builderType, generateCleanMethod(builderFields, builderType, ast)); - if ((td.modifiers & ClassFileConstants.AccAbstract) != 0 && !toBuilder) { + boolean isAbstract = (td.modifiers & ClassFileConstants.AccAbstract) != 0; + if (isAbstract) { // Only non-abstract classes get the Builder implementation. - // However, if we want to generate a toBuilder, we need a helper function in the builder implementation class. return; } @@ -343,9 +345,7 @@ public void handle(AnnotationValues annotation, Annotation ast, Ec } if (toBuilder) { - // Generate $fillValuesFromInstanceIntoBuilder() method in the builder implementation class. - injectMethod(builderImplType, generateStaticFillValuesMethod(tdParent, builderClassName, typeParams, builderFields, ast)); - + // Add the toBuilder() method to the annotated class. switch (methodExists(TO_BUILDER_METHOD_NAME_STRING, tdParent, 0)) { case EXISTS_BY_USER: annotationNode.addWarning("Not generating toBuilder() as it already exists."); @@ -355,10 +355,6 @@ public void handle(AnnotationValues annotation, Annotation ast, Ec default: // Should not happen. } - if ((td.modifiers & ClassFileConstants.AccAbstract) != 0) { - // The rest of the builder implementation class is not necessary for abstract classes. - return; - } } // Create the self() and build() methods in the BuilderImpl. @@ -605,7 +601,7 @@ private MethodDeclaration generateToBuilderMethod(String builderClassName, Strin * } * */ - private MethodDeclaration generateFillValuesMethod(EclipseNode tdParent, boolean inherited, String builderGenericName, String classGenericName, String builderImplClassName, TypeParameter[] typeParams) { + private MethodDeclaration generateFillValuesMethod(EclipseNode tdParent, boolean inherited, String builderGenericName, String classGenericName, String builderClassName, TypeParameter[] typeParams) { MethodDeclaration out = new MethodDeclaration(((CompilationUnitDeclaration) tdParent.top().get()).compilationResult); out.selector = FILL_VALUES_METHOD_NAME; out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; @@ -629,7 +625,7 @@ private MethodDeclaration generateFillValuesMethod(EclipseNode tdParent, boolean // Call the builder implemention's helper method that actually fills the values from the instance. MessageSend callStaticFillValuesMethod = new MessageSend(); - callStaticFillValuesMethod.receiver = new SingleNameReference(builderImplClassName.toCharArray(), 0); + callStaticFillValuesMethod.receiver = new SingleNameReference(builderClassName.toCharArray(), 0); callStaticFillValuesMethod.selector = FILL_VALUES_STATIC_METHOD_NAME; callStaticFillValuesMethod.arguments = new Expression[] {new SingleNameReference(INSTANCE_VARIABLE_NAME, 0), new ThisReference(0, 0)}; body.add(callStaticFillValuesMethod); diff --git a/src/core/lombok/javac/handlers/HandleSuperBuilder.java b/src/core/lombok/javac/handlers/HandleSuperBuilder.java index f858cd2923..f9686cba50 100644 --- a/src/core/lombok/javac/handlers/HandleSuperBuilder.java +++ b/src/core/lombok/javac/handlers/HandleSuperBuilder.java @@ -242,7 +242,9 @@ public void handle(AnnotationValues annotation, JCAnnotation ast, if (toBuilder) { // Generate $fillValuesFrom() method in the abstract builder. - injectMethod(builderType, generateFillValuesMethod(tdParent, superclassBuilderClassExpression != null, builderGenericName, classGenericName, builderImplClassName)); + injectMethod(builderType, generateFillValuesMethod(tdParent, superclassBuilderClassExpression != null, builderGenericName, classGenericName, builderClassName)); + // Generate $fillValuesFromInstanceIntoBuilder() method in the builder implementation class. + injectMethod(builderType, generateStaticFillValuesMethod(tdParent, builderClassName, typeParams, builderFields)); } // Generate abstract self() and build() methods in the abstract builder. @@ -272,9 +274,8 @@ public void handle(AnnotationValues annotation, JCAnnotation ast, recursiveSetGeneratedBy(builderType.get(), ast, annotationNode.getContext()); boolean isAbstract = (td.mods.flags & Flags.ABSTRACT) != 0; - if (!isAbstract || toBuilder) { + if (!isAbstract) { // Only non-abstract classes get the Builder implementation. - // However, if we want to generate a toBuilder, we need a helper function in the builder implementation class. // Create the builder implementation class. JavacNode builderImplType = findInnerClass(tdParent, builderImplClassName); @@ -285,20 +286,13 @@ public void handle(AnnotationValues annotation, JCAnnotation ast, return; } - if (toBuilder) { - // Generate $fillValuesFromInstanceIntoBuilder() method in the builder implementation class. - injectMethod(builderImplType, generateStaticFillValuesMethod(tdParent, builderClassName, typeParams, builderFields)); - } + // Create a simple constructor for the BuilderImpl class. + JCMethodDecl cd = HandleConstructor.createConstructor(AccessLevel.PRIVATE, List.nil(), builderImplType, List.nil(), false, annotationNode); + if (cd != null) injectMethod(builderImplType, cd); - if (!isAbstract) { - // Create a simple constructor for the BuilderImpl class. - JCMethodDecl cd = HandleConstructor.createConstructor(AccessLevel.PRIVATE, List.nil(), builderImplType, List.nil(), false, annotationNode); - if (cd != null) injectMethod(builderImplType, cd); - - // Create the self() and build() methods in the BuilderImpl. - injectMethod(builderImplType, generateSelfMethod(builderImplType, typeParams)); - injectMethod(builderImplType, generateBuildMethod(buildMethodName, returnType, builderImplType, thrownExceptions)); - } + // Create the self() and build() methods in the BuilderImpl. + injectMethod(builderImplType, generateSelfMethod(builderImplType, typeParams)); + injectMethod(builderImplType, generateBuildMethod(buildMethodName, returnType, builderImplType, thrownExceptions)); recursiveSetGeneratedBy(builderImplType.get(), ast, annotationNode.getContext()); } @@ -307,18 +301,20 @@ public void handle(AnnotationValues annotation, JCAnnotation ast, generateBuilderBasedConstructor(tdParent, typeParams, builderFields, annotationNode, builderClassName, superclassBuilderClassExpression != null); - if (!isAbstract) { - // Only non-abstract classes get the builder() method. + if (isAbstract) { + // Only non-abstract classes get the builder() and toBuilder() methods. + return; + } - // Add the builder() method to the annotated class. - // Allow users to specify their own builder() methods, e.g., to provide default values. - if (methodExists(builderMethodName, tdParent, -1) == MemberExistsResult.NOT_EXISTS) { - JCMethodDecl builderMethod = generateBuilderMethod(builderMethodName, builderClassName, builderImplClassName, annotationNode, tdParent, typeParams); - recursiveSetGeneratedBy(builderMethod, ast, annotationNode.getContext()); - if (builderMethod != null) injectMethod(tdParent, builderMethod); - } + // Add the builder() method to the annotated class. + // Allow users to specify their own builder() methods, e.g., to provide default values. + if (methodExists(builderMethodName, tdParent, -1) == MemberExistsResult.NOT_EXISTS) { + JCMethodDecl builderMethod = generateBuilderMethod(builderMethodName, builderClassName, builderImplClassName, annotationNode, tdParent, typeParams); + recursiveSetGeneratedBy(builderMethod, ast, annotationNode.getContext()); + if (builderMethod != null) injectMethod(tdParent, builderMethod); } + // Add the toBuilder() method to the annotated class. if (toBuilder) { switch (methodExists(TO_BUILDER_METHOD_NAME, tdParent, 0)) { case EXISTS_BY_USER: diff --git a/test/transform/resource/after-delombok/SuperBuilderAbstractToBuilder.java b/test/transform/resource/after-delombok/SuperBuilderAbstractToBuilder.java new file mode 100644 index 0000000000..096bd533f2 --- /dev/null +++ b/test/transform/resource/after-delombok/SuperBuilderAbstractToBuilder.java @@ -0,0 +1,168 @@ +public class SuperBuilderAbstractToBuilder { + public static class Parent { + int parentField; + @java.lang.SuppressWarnings("all") + public static abstract class ParentBuilder> { + @java.lang.SuppressWarnings("all") + private int parentField; + @java.lang.SuppressWarnings("all") + protected B $fillValuesFrom(final C instance) { + ParentBuilder.$fillValuesFromInstanceIntoBuilder(instance, this); + return self(); + } + @java.lang.SuppressWarnings("all") + private static void $fillValuesFromInstanceIntoBuilder(final Parent instance, final ParentBuilder b) { + b.parentField(instance.parentField); + } + @java.lang.SuppressWarnings("all") + protected abstract B self(); + @java.lang.SuppressWarnings("all") + public abstract C build(); + @java.lang.SuppressWarnings("all") + public B parentField(final int parentField) { + this.parentField = parentField; + return self(); + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "SuperBuilderAbstractToBuilder.Parent.ParentBuilder(parentField=" + this.parentField + ")"; + } + } + @java.lang.SuppressWarnings("all") + private static final class ParentBuilderImpl extends ParentBuilder { + @java.lang.SuppressWarnings("all") + private ParentBuilderImpl() { + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + protected ParentBuilderImpl self() { + return this; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public Parent build() { + return new Parent(this); + } + } + @java.lang.SuppressWarnings("all") + protected Parent(final ParentBuilder b) { + this.parentField = b.parentField; + } + @java.lang.SuppressWarnings("all") + public static ParentBuilder builder() { + return new ParentBuilderImpl(); + } + @java.lang.SuppressWarnings("all") + public ParentBuilder toBuilder() { + return new ParentBuilderImpl().$fillValuesFrom(this); + } + } + public static abstract class Child extends Parent { + double childField; + @java.lang.SuppressWarnings("all") + public static abstract class ChildBuilder> extends Parent.ParentBuilder { + @java.lang.SuppressWarnings("all") + private double childField; + @java.lang.Override + @java.lang.SuppressWarnings("all") + protected B $fillValuesFrom(final C instance) { + super.$fillValuesFrom(instance); + ChildBuilder.$fillValuesFromInstanceIntoBuilder(instance, this); + return self(); + } + @java.lang.SuppressWarnings("all") + private static void $fillValuesFromInstanceIntoBuilder(final Child instance, final ChildBuilder b) { + b.childField(instance.childField); + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + protected abstract B self(); + @java.lang.Override + @java.lang.SuppressWarnings("all") + public abstract C build(); + @java.lang.SuppressWarnings("all") + public B childField(final double childField) { + this.childField = childField; + return self(); + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "SuperBuilderAbstractToBuilder.Child.ChildBuilder(super=" + super.toString() + ", childField=" + this.childField + ")"; + } + } + @java.lang.SuppressWarnings("all") + protected Child(final ChildBuilder b) { + super(b); + this.childField = b.childField; + } + } + public static class GrandChild extends Child { + String grandChildField; + @java.lang.SuppressWarnings("all") + public static abstract class GrandChildBuilder> extends Child.ChildBuilder { + @java.lang.SuppressWarnings("all") + private String grandChildField; + @java.lang.Override + @java.lang.SuppressWarnings("all") + protected B $fillValuesFrom(final C instance) { + super.$fillValuesFrom(instance); + GrandChildBuilder.$fillValuesFromInstanceIntoBuilder(instance, this); + return self(); + } + @java.lang.SuppressWarnings("all") + private static void $fillValuesFromInstanceIntoBuilder(final GrandChild instance, final GrandChildBuilder b) { + b.grandChildField(instance.grandChildField); + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + protected abstract B self(); + @java.lang.Override + @java.lang.SuppressWarnings("all") + public abstract C build(); + @java.lang.SuppressWarnings("all") + public B grandChildField(final String grandChildField) { + this.grandChildField = grandChildField; + return self(); + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "SuperBuilderAbstractToBuilder.GrandChild.GrandChildBuilder(super=" + super.toString() + ", grandChildField=" + this.grandChildField + ")"; + } + } + @java.lang.SuppressWarnings("all") + private static final class GrandChildBuilderImpl extends GrandChildBuilder { + @java.lang.SuppressWarnings("all") + private GrandChildBuilderImpl() { + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + protected GrandChildBuilderImpl self() { + return this; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public GrandChild build() { + return new GrandChild(this); + } + } + @java.lang.SuppressWarnings("all") + protected GrandChild(final GrandChildBuilder b) { + super(b); + this.grandChildField = b.grandChildField; + } + @java.lang.SuppressWarnings("all") + public static GrandChildBuilder builder() { + return new GrandChildBuilderImpl(); + } + @java.lang.SuppressWarnings("all") + public GrandChildBuilder toBuilder() { + return new GrandChildBuilderImpl().$fillValuesFrom(this); + } + } + public static void test() { + GrandChild x = GrandChild.builder().grandChildField("").parentField(5).childField(2.5).build().toBuilder().build(); + } +} diff --git a/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java b/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java index 7a80f019fd..dc0b4e708d 100644 --- a/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java +++ b/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java @@ -26,10 +26,18 @@ public static abstract class ParentBuilder items; @java.lang.SuppressWarnings("all") protected B $fillValuesFrom(final C instance) { - ParentBuilderImpl.$fillValuesFromInstanceIntoBuilder(instance, this); + ParentBuilder.$fillValuesFromInstanceIntoBuilder(instance, this); return self(); } @java.lang.SuppressWarnings("all") + private static void $fillValuesFromInstanceIntoBuilder(final Parent instance, final ParentBuilder b) { + b.field1(instance.field1); + b.obtainViaField(instance.field1); + b.obtainViaMethod(instance.method()); + b.obtainViaStaticMethod(Parent.staticMethod(instance)); + b.items(instance.items == null ? java.util.Collections.emptyList() : instance.items); + } + @java.lang.SuppressWarnings("all") protected abstract B self(); @java.lang.SuppressWarnings("all") public abstract C build(); @@ -78,14 +86,6 @@ public java.lang.String toString() { } @java.lang.SuppressWarnings("all") private static final class ParentBuilderImpl extends ParentBuilder { - @java.lang.SuppressWarnings("all") - private static void $fillValuesFromInstanceIntoBuilder(final Parent instance, final ParentBuilder b) { - b.field1(instance.field1); - b.obtainViaField(instance.field1); - b.obtainViaMethod(instance.method()); - b.obtainViaStaticMethod(Parent.staticMethod(instance)); - b.items(instance.items == null ? java.util.Collections.emptyList() : instance.items); - } @java.lang.SuppressWarnings("all") private ParentBuilderImpl() { } @@ -138,9 +138,13 @@ public static abstract class ChildBuilder b) { + b.field3(instance.field3); + } @java.lang.Override @java.lang.SuppressWarnings("all") protected abstract B self(); @@ -160,10 +164,6 @@ public java.lang.String toString() { } @java.lang.SuppressWarnings("all") private static final class ChildBuilderImpl extends ChildBuilder { - @java.lang.SuppressWarnings("all") - private static void $fillValuesFromInstanceIntoBuilder(final Child instance, final ChildBuilder b) { - b.field3(instance.field3); - } @java.lang.SuppressWarnings("all") private ChildBuilderImpl() { } diff --git a/test/transform/resource/after-ecj/SuperBuilderAbstractToBuilder.java b/test/transform/resource/after-ecj/SuperBuilderAbstractToBuilder.java new file mode 100644 index 0000000000..668f6acfc9 --- /dev/null +++ b/test/transform/resource/after-ecj/SuperBuilderAbstractToBuilder.java @@ -0,0 +1,131 @@ +public class SuperBuilderAbstractToBuilder { + public static @lombok.experimental.SuperBuilder(toBuilder = true) class Parent { + public static abstract @java.lang.SuppressWarnings("all") class ParentBuilder> { + private @java.lang.SuppressWarnings("all") int parentField; + public ParentBuilder() { + super(); + } + protected @java.lang.SuppressWarnings("all") B $fillValuesFrom(final C instance) { + ParentBuilder.$fillValuesFromInstanceIntoBuilder(instance, this); + return self(); + } + private static @java.lang.SuppressWarnings("all") void $fillValuesFromInstanceIntoBuilder(final Parent instance, final ParentBuilder b) { + b.parentField(instance.parentField); + } + protected abstract @java.lang.SuppressWarnings("all") B self(); + public abstract @java.lang.SuppressWarnings("all") C build(); + public @java.lang.SuppressWarnings("all") B parentField(final int parentField) { + this.parentField = parentField; + return self(); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (("SuperBuilderAbstractToBuilder.Parent.ParentBuilder(parentField=" + this.parentField) + ")"); + } + } + private static final @java.lang.SuppressWarnings("all") class ParentBuilderImpl extends ParentBuilder { + private ParentBuilderImpl() { + super(); + } + protected @java.lang.Override @java.lang.SuppressWarnings("all") ParentBuilderImpl self() { + return this; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") Parent build() { + return new Parent(this); + } + } + int parentField; + protected @java.lang.SuppressWarnings("all") Parent(final ParentBuilder b) { + super(); + this.parentField = b.parentField; + } + public @java.lang.SuppressWarnings("all") ParentBuilder toBuilder() { + return new ParentBuilderImpl().$fillValuesFrom(this); + } + public static @java.lang.SuppressWarnings("all") ParentBuilder builder() { + return new ParentBuilderImpl(); + } + } + public static abstract @lombok.experimental.SuperBuilder(toBuilder = true) class Child extends Parent { + public static abstract @java.lang.SuppressWarnings("all") class ChildBuilder> extends Parent.ParentBuilder { + private @java.lang.SuppressWarnings("all") double childField; + public ChildBuilder() { + super(); + } + protected @java.lang.Override @java.lang.SuppressWarnings("all") B $fillValuesFrom(final C instance) { + super.$fillValuesFrom(instance); + ChildBuilder.$fillValuesFromInstanceIntoBuilder(instance, this); + return self(); + } + private static @java.lang.SuppressWarnings("all") void $fillValuesFromInstanceIntoBuilder(final Child instance, final ChildBuilder b) { + b.childField(instance.childField); + } + protected abstract @java.lang.Override @java.lang.SuppressWarnings("all") B self(); + public abstract @java.lang.Override @java.lang.SuppressWarnings("all") C build(); + public @java.lang.SuppressWarnings("all") B childField(final double childField) { + this.childField = childField; + return self(); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (((("SuperBuilderAbstractToBuilder.Child.ChildBuilder(super=" + super.toString()) + ", childField=") + this.childField) + ")"); + } + } + double childField; + protected @java.lang.SuppressWarnings("all") Child(final ChildBuilder b) { + super(b); + this.childField = b.childField; + } + } + public static @lombok.experimental.SuperBuilder(toBuilder = true) class GrandChild extends Child { + public static abstract @java.lang.SuppressWarnings("all") class GrandChildBuilder> extends Child.ChildBuilder { + private @java.lang.SuppressWarnings("all") String grandChildField; + public GrandChildBuilder() { + super(); + } + protected @java.lang.Override @java.lang.SuppressWarnings("all") B $fillValuesFrom(final C instance) { + super.$fillValuesFrom(instance); + GrandChildBuilder.$fillValuesFromInstanceIntoBuilder(instance, this); + return self(); + } + private static @java.lang.SuppressWarnings("all") void $fillValuesFromInstanceIntoBuilder(final GrandChild instance, final GrandChildBuilder b) { + b.grandChildField(instance.grandChildField); + } + protected abstract @java.lang.Override @java.lang.SuppressWarnings("all") B self(); + public abstract @java.lang.Override @java.lang.SuppressWarnings("all") C build(); + public @java.lang.SuppressWarnings("all") B grandChildField(final String grandChildField) { + this.grandChildField = grandChildField; + return self(); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (((("SuperBuilderAbstractToBuilder.GrandChild.GrandChildBuilder(super=" + super.toString()) + ", grandChildField=") + this.grandChildField) + ")"); + } + } + private static final @java.lang.SuppressWarnings("all") class GrandChildBuilderImpl extends GrandChildBuilder { + private GrandChildBuilderImpl() { + super(); + } + protected @java.lang.Override @java.lang.SuppressWarnings("all") GrandChildBuilderImpl self() { + return this; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") GrandChild build() { + return new GrandChild(this); + } + } + String grandChildField; + protected @java.lang.SuppressWarnings("all") GrandChild(final GrandChildBuilder b) { + super(b); + this.grandChildField = b.grandChildField; + } + public @java.lang.SuppressWarnings("all") GrandChildBuilder toBuilder() { + return new GrandChildBuilderImpl().$fillValuesFrom(this); + } + public static @java.lang.SuppressWarnings("all") GrandChildBuilder builder() { + return new GrandChildBuilderImpl(); + } + } + public SuperBuilderAbstractToBuilder() { + super(); + } + public static void test() { + GrandChild x = GrandChild.builder().grandChildField("").parentField(5).childField(2.5).build().toBuilder().build(); + } +} diff --git a/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java b/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java index be819d055c..a491430a2e 100644 --- a/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java +++ b/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java @@ -11,9 +11,16 @@ public ParentBuilder() { super(); } protected @java.lang.SuppressWarnings("all") B $fillValuesFrom(final C instance) { - ParentBuilderImpl.$fillValuesFromInstanceIntoBuilder(instance, this); + ParentBuilder.$fillValuesFromInstanceIntoBuilder(instance, this); return self(); } + private static @java.lang.SuppressWarnings("all") void $fillValuesFromInstanceIntoBuilder(final Parent instance, final ParentBuilder b) { + b.field1(instance.field1); + b.obtainViaField(instance.field1); + b.obtainViaMethod(instance.method()); + b.obtainViaStaticMethod(Parent.staticMethod(instance)); + b.items(((instance.items == null) ? java.util.Collections.emptyList() : instance.items)); + } protected abstract @java.lang.SuppressWarnings("all") B self(); public abstract @java.lang.SuppressWarnings("all") C build(); public @java.lang.SuppressWarnings("all") B field1(final int field1) { @@ -57,13 +64,6 @@ public ParentBuilder() { private ParentBuilderImpl() { super(); } - private static @java.lang.SuppressWarnings("all") void $fillValuesFromInstanceIntoBuilder(final Parent instance, final ParentBuilder b) { - b.field1(instance.field1); - b.obtainViaField(instance.field1); - b.obtainViaMethod(instance.method()); - b.obtainViaStaticMethod(Parent.staticMethod(instance)); - b.items(((instance.items == null) ? java.util.Collections.emptyList() : instance.items)); - } protected @java.lang.Override @java.lang.SuppressWarnings("all") ParentBuilderImpl self() { return this; } @@ -116,9 +116,12 @@ public ChildBuilder() { } protected @java.lang.Override @java.lang.SuppressWarnings("all") B $fillValuesFrom(final C instance) { super.$fillValuesFrom(instance); - ChildBuilderImpl.$fillValuesFromInstanceIntoBuilder(instance, this); + ChildBuilder.$fillValuesFromInstanceIntoBuilder(instance, this); return self(); } + private static @java.lang.SuppressWarnings("all") void $fillValuesFromInstanceIntoBuilder(final Child instance, final ChildBuilder b) { + b.field3(instance.field3); + } protected abstract @java.lang.Override @java.lang.SuppressWarnings("all") B self(); public abstract @java.lang.Override @java.lang.SuppressWarnings("all") C build(); public @java.lang.SuppressWarnings("all") B field3(final double field3) { @@ -133,9 +136,6 @@ public ChildBuilder() { private ChildBuilderImpl() { super(); } - private static @java.lang.SuppressWarnings("all") void $fillValuesFromInstanceIntoBuilder(final Child instance, final ChildBuilder b) { - b.field3(instance.field3); - } protected @java.lang.Override @java.lang.SuppressWarnings("all") ChildBuilderImpl self() { return this; } diff --git a/test/transform/resource/before/SuperBuilderAbstractToBuilder.java b/test/transform/resource/before/SuperBuilderAbstractToBuilder.java new file mode 100644 index 0000000000..3359829c28 --- /dev/null +++ b/test/transform/resource/before/SuperBuilderAbstractToBuilder.java @@ -0,0 +1,20 @@ +public class SuperBuilderAbstractToBuilder { + @lombok.experimental.SuperBuilder(toBuilder = true) + public static class Parent { + int parentField; + } + + @lombok.experimental.SuperBuilder(toBuilder = true) + public abstract static class Child extends Parent { + double childField; + } + + @lombok.experimental.SuperBuilder(toBuilder = true) + public static class GrandChild extends Child { + String grandChildField; + } + + public static void test() { + GrandChild x = GrandChild.builder().grandChildField("").parentField(5).childField(2.5).build().toBuilder().build(); + } +} From 63343fced581dc9f5721d5cca81265db138254a5 Mon Sep 17 00:00:00 2001 From: Jan Rieke Date: Sun, 16 Sep 2018 21:20:58 +0200 Subject: [PATCH 13/19] SuperBuilder toBuilder: test with generics --- .../SuperBuilderWithGenericsAndToBuilder.java | 162 ++++++++++++++++++ .../SuperBuilderWithGenericsAndToBuilder.java | 140 +++++++++++++++ .../SuperBuilderWithGenericsAndToBuilder.java | 18 ++ 3 files changed, 320 insertions(+) create mode 100644 test/transform/resource/after-delombok/SuperBuilderWithGenericsAndToBuilder.java create mode 100644 test/transform/resource/after-ecj/SuperBuilderWithGenericsAndToBuilder.java create mode 100644 test/transform/resource/before/SuperBuilderWithGenericsAndToBuilder.java diff --git a/test/transform/resource/after-delombok/SuperBuilderWithGenericsAndToBuilder.java b/test/transform/resource/after-delombok/SuperBuilderWithGenericsAndToBuilder.java new file mode 100644 index 0000000000..93d0f9b57f --- /dev/null +++ b/test/transform/resource/after-delombok/SuperBuilderWithGenericsAndToBuilder.java @@ -0,0 +1,162 @@ +import java.util.List; +public class SuperBuilderWithGenericsAndToBuilder { + public static class Parent { + A field1; + List items; + @java.lang.SuppressWarnings("all") + public static abstract class ParentBuilder, B extends ParentBuilder> { + @java.lang.SuppressWarnings("all") + private A field1; + @java.lang.SuppressWarnings("all") + private java.util.ArrayList items; + @java.lang.SuppressWarnings("all") + protected B $fillValuesFrom(final C instance) { + ParentBuilder.$fillValuesFromInstanceIntoBuilder(instance, this); + return self(); + } + @java.lang.SuppressWarnings("all") + private static void $fillValuesFromInstanceIntoBuilder(final Parent instance, final ParentBuilder b) { + b.field1(instance.field1); + b.items(instance.items == null ? java.util.Collections.emptyList() : instance.items); + } + @java.lang.SuppressWarnings("all") + protected abstract B self(); + @java.lang.SuppressWarnings("all") + public abstract C build(); + @java.lang.SuppressWarnings("all") + public B field1(final A field1) { + this.field1 = field1; + return self(); + } + @java.lang.SuppressWarnings("all") + public B item(final String item) { + if (this.items == null) this.items = new java.util.ArrayList(); + this.items.add(item); + return self(); + } + @java.lang.SuppressWarnings("all") + public B items(final java.util.Collection items) { + if (this.items == null) this.items = new java.util.ArrayList(); + this.items.addAll(items); + return self(); + } + @java.lang.SuppressWarnings("all") + public B clearItems() { + if (this.items != null) this.items.clear(); + return self(); + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "SuperBuilderWithGenericsAndToBuilder.Parent.ParentBuilder(field1=" + this.field1 + ", items=" + this.items + ")"; + } + } + @java.lang.SuppressWarnings("all") + private static final class ParentBuilderImpl extends ParentBuilder, ParentBuilderImpl> { + @java.lang.SuppressWarnings("all") + private ParentBuilderImpl() { + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + protected ParentBuilderImpl self() { + return this; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public Parent build() { + return new Parent(this); + } + } + @java.lang.SuppressWarnings("all") + protected Parent(final ParentBuilder b) { + this.field1 = b.field1; + java.util.List items; + switch (b.items == null ? 0 : b.items.size()) { + case 0: + items = java.util.Collections.emptyList(); + break; + case 1: + items = java.util.Collections.singletonList(b.items.get(0)); + break; + default: + items = java.util.Collections.unmodifiableList(new java.util.ArrayList(b.items)); + } + this.items = items; + } + @java.lang.SuppressWarnings("all") + public static ParentBuilder builder() { + return new ParentBuilderImpl(); + } + @java.lang.SuppressWarnings("all") + public ParentBuilder toBuilder() { + return new ParentBuilderImpl().$fillValuesFrom(this); + } + } + public static class Child extends Parent { + double field3; + @java.lang.SuppressWarnings("all") + public static abstract class ChildBuilder, B extends ChildBuilder> extends Parent.ParentBuilder { + @java.lang.SuppressWarnings("all") + private double field3; + @java.lang.Override + @java.lang.SuppressWarnings("all") + protected B $fillValuesFrom(final C instance) { + super.$fillValuesFrom(instance); + ChildBuilder.$fillValuesFromInstanceIntoBuilder(instance, this); + return self(); + } + @java.lang.SuppressWarnings("all") + private static void $fillValuesFromInstanceIntoBuilder(final Child instance, final ChildBuilder b) { + b.field3(instance.field3); + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + protected abstract B self(); + @java.lang.Override + @java.lang.SuppressWarnings("all") + public abstract C build(); + @java.lang.SuppressWarnings("all") + public B field3(final double field3) { + this.field3 = field3; + return self(); + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public java.lang.String toString() { + return "SuperBuilderWithGenericsAndToBuilder.Child.ChildBuilder(super=" + super.toString() + ", field3=" + this.field3 + ")"; + } + } + @java.lang.SuppressWarnings("all") + private static final class ChildBuilderImpl extends ChildBuilder, ChildBuilderImpl> { + @java.lang.SuppressWarnings("all") + private ChildBuilderImpl() { + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + protected ChildBuilderImpl self() { + return this; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + public Child build() { + return new Child(this); + } + } + @java.lang.SuppressWarnings("all") + protected Child(final ChildBuilder b) { + super(b); + this.field3 = b.field3; + } + @java.lang.SuppressWarnings("all") + public static ChildBuilder builder() { + return new ChildBuilderImpl(); + } + @java.lang.SuppressWarnings("all") + public ChildBuilder toBuilder() { + return new ChildBuilderImpl().$fillValuesFrom(this); + } + } + public static void test() { + Child x = Child.builder().field3(0.0).field1(5).item("").build().toBuilder().build(); + } +} diff --git a/test/transform/resource/after-ecj/SuperBuilderWithGenericsAndToBuilder.java b/test/transform/resource/after-ecj/SuperBuilderWithGenericsAndToBuilder.java new file mode 100644 index 0000000000..fcdadb3bf2 --- /dev/null +++ b/test/transform/resource/after-ecj/SuperBuilderWithGenericsAndToBuilder.java @@ -0,0 +1,140 @@ +import java.util.List; + +import SuperBuilderBasicToBuilder.Child; +import SuperBuilderBasicToBuilder.Parent; +import SuperBuilderBasicToBuilder.Child.ChildBuilder; +import SuperBuilderBasicToBuilder.Child.ChildBuilderImpl; +import SuperBuilderBasicToBuilder.Parent.ParentBuilder; +public class SuperBuilderWithGenericsAndToBuilder { + public static @lombok.experimental.SuperBuilder class Parent { + public static abstract @java.lang.SuppressWarnings("all") class ParentBuilder, B extends ParentBuilder> { + private @java.lang.SuppressWarnings("all") A field1; + private @java.lang.SuppressWarnings("all") java.util.ArrayList items; + public ParentBuilder() { + super(); + } + protected @java.lang.SuppressWarnings("all") B $fillValuesFrom(final C instance) { + ParentBuilder.$fillValuesFromInstanceIntoBuilder(instance, this); + return self(); + } + private static @java.lang.SuppressWarnings("all") void $fillValuesFromInstanceIntoBuilder(final Parent instance, final ParentBuilder b) { + b.field1(instance.field1); + b.items(((instance.items == null) ? java.util.Collections.emptyList() : instance.items)); + } + protected abstract @java.lang.SuppressWarnings("all") B self(); + public abstract @java.lang.SuppressWarnings("all") C build(); + public @java.lang.SuppressWarnings("all") B field1(final A field1) { + this.field1 = field1; + return self(); + } + public @java.lang.SuppressWarnings("all") B item(String item) { + if ((this.items == null)) + this.items = new java.util.ArrayList(); + this.items.add(item); + return self(); + } + public @java.lang.SuppressWarnings("all") B items(java.util.Collection items) { + if ((this.items == null)) + this.items = new java.util.ArrayList(); + this.items.addAll(items); + return self(); + } + public @java.lang.SuppressWarnings("all") B clearItems() { + if ((this.items != null)) + this.items.clear(); + return self(); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (((("SuperBuilderWithGenericsAndToBuilder.Parent.ParentBuilder(field1=" + this.field1) + ", items=") + this.items) + ")"); + } + } + private static final @java.lang.SuppressWarnings("all") class ParentBuilderImpl extends ParentBuilder, ParentBuilderImpl> { + private ParentBuilderImpl() { + super(); + } + protected @java.lang.Override @java.lang.SuppressWarnings("all") ParentBuilderImpl self() { + return this; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") Parent build() { + return new Parent(this); + } + } + A field1; + @lombok.Singular List items; + protected @java.lang.SuppressWarnings("all") Parent(final ParentBuilder b) { + super(); + this.field1 = b.field1; + java.util.List items; + switch (((b.items == null) ? 0 : b.items.size())) { + case 0 : + items = java.util.Collections.emptyList(); + break; + case 1 : + items = java.util.Collections.singletonList(b.items.get(0)); + break; + default : + items = java.util.Collections.unmodifiableList(new java.util.ArrayList(b.items)); + } + this.items = items; + } + public @java.lang.SuppressWarnings("all") ParentBuilder toBuilder() { + return new ParentBuilderImpl().$fillValuesFrom(this); + } + public static @java.lang.SuppressWarnings("all") ParentBuilder builder() { + return new ParentBuilderImpl(); + } + } + public static @lombok.experimental.SuperBuilder class Child extends Parent { + public static abstract @java.lang.SuppressWarnings("all") class ChildBuilder, B extends ChildBuilder> extends Parent.ParentBuilder { + private @java.lang.SuppressWarnings("all") double field3; + public ChildBuilder() { + super(); + } + protected @java.lang.Override @java.lang.SuppressWarnings("all") B $fillValuesFrom(final C instance) { + super.$fillValuesFrom(instance); + ChildBuilder.$fillValuesFromInstanceIntoBuilder(instance, this); + return self(); + } + private static @java.lang.SuppressWarnings("all") void $fillValuesFromInstanceIntoBuilder(final Child instance, final ChildBuilder b) { + b.field3(instance.field3); + } + protected abstract @java.lang.Override @java.lang.SuppressWarnings("all") B self(); + public abstract @java.lang.Override @java.lang.SuppressWarnings("all") C build(); + public @java.lang.SuppressWarnings("all") B field3(final double field3) { + this.field3 = field3; + return self(); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() { + return (((("SuperBuilderWithGenericsAndToBuilder.Child.ChildBuilder(super=" + super.toString()) + ", field3=") + this.field3) + ")"); + } + } + private static final @java.lang.SuppressWarnings("all") class ChildBuilderImpl extends ChildBuilder, ChildBuilderImpl> { + private ChildBuilderImpl() { + super(); + } + protected @java.lang.Override @java.lang.SuppressWarnings("all") ChildBuilderImpl self() { + return this; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") Child build() { + return new Child(this); + } + } + double field3; + protected @java.lang.SuppressWarnings("all") Child(final ChildBuilder b) { + super(b); + this.field3 = b.field3; + } + public @java.lang.SuppressWarnings("all") ChildBuilder toBuilder() { + return new ChildBuilderImpl().$fillValuesFrom(this); + } + public static @java.lang.SuppressWarnings("all") ChildBuilder builder() { + return new ChildBuilderImpl(); + } + } + public SuperBuilderWithGenerics() { + super(); + } + public static void test() { + Child x = Child.builder().field3(0.0).field1(5).item("").build().toBuilder().build(); + } +} diff --git a/test/transform/resource/before/SuperBuilderWithGenericsAndToBuilder.java b/test/transform/resource/before/SuperBuilderWithGenericsAndToBuilder.java new file mode 100644 index 0000000000..dae680348f --- /dev/null +++ b/test/transform/resource/before/SuperBuilderWithGenericsAndToBuilder.java @@ -0,0 +1,18 @@ +import java.util.List; + +public class SuperBuilderWithGenericsAndToBuilder { + @lombok.experimental.SuperBuilder(toBuilder = true) + public static class Parent { + A field1; + @lombok.Singular List items; + } + + @lombok.experimental.SuperBuilder(toBuilder = true) + public static class Child extends Parent { + double field3; + } + + public static void test() { + Child x = Child.builder().field3(0.0).field1(5).item("").build().toBuilder().build(); + } +} From 68318742ef2e4f6b680ea90a279c2c96da7c2d74 Mon Sep 17 00:00:00 2001 From: Jan Rieke Date: Fri, 21 Sep 2018 11:25:55 +0200 Subject: [PATCH 14/19] SuperBuilder toBuilder with generics (javac) --- .../javac/handlers/HandleSuperBuilder.java | 17 +++++++---------- .../SuperBuilderWithGenericsAndToBuilder.java | 4 ++-- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/core/lombok/javac/handlers/HandleSuperBuilder.java b/src/core/lombok/javac/handlers/HandleSuperBuilder.java index f9686cba50..a440c9bea3 100644 --- a/src/core/lombok/javac/handlers/HandleSuperBuilder.java +++ b/src/core/lombok/javac/handlers/HandleSuperBuilder.java @@ -109,7 +109,6 @@ public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode tdParent = annotationNode.up(); java.util.List builderFields = new ArrayList(); - JCExpression returnType; List typeParams = List.nil(); List thrownExceptions = List.nil(); List superclassTypeParams = List.nil(); @@ -187,7 +186,6 @@ public void handle(AnnotationValues annotation, JCAnnotation ast, // If there is no superclass, superclassBuilderClassExpression is still == null at this point. // You can use it to check whether to inherit or not. - returnType = namePlusTypeParamsToTypeReference(tdParent.getTreeMaker(), td.name, td.typarams); typeParams = td.typarams; // are the generics for our builder. @@ -292,7 +290,7 @@ public void handle(AnnotationValues annotation, JCAnnotation ast, // Create the self() and build() methods in the BuilderImpl. injectMethod(builderImplType, generateSelfMethod(builderImplType, typeParams)); - injectMethod(builderImplType, generateBuildMethod(buildMethodName, returnType, builderImplType, thrownExceptions)); + injectMethod(builderImplType, generateBuildMethod(buildMethodName, tdParent, builderImplType, thrownExceptions)); recursiveSetGeneratedBy(builderImplType.get(), ast, annotationNode.getContext()); } @@ -548,7 +546,7 @@ private JCMethodDecl generateToBuilderMethod(String builderClassName, String bui typeParameterNames.add(wildcard); JCTypeApply returnType = maker.TypeApply(maker.Ident(type.toName(builderClassName)), typeParameterNames.toList()); - return maker.MethodDef(maker.Modifiers(modifiers), type.toName(TO_BUILDER_METHOD_NAME), returnType, copyTypeParams(source, typeParams), List.nil(), List.nil(), body, null); + return maker.MethodDef(maker.Modifiers(modifiers), type.toName(TO_BUILDER_METHOD_NAME), returnType, List.nil(), List.nil(), List.nil(), body, null); } /** @@ -618,8 +616,7 @@ private JCMethodDecl generateStaticFillValuesMethod(JavacNode type, String build JCExpression returnType = maker.TypeIdent(CTC_VOID); // 1st parameter: "Foobar instance" - JCExpression classGenericNameExpr = maker.Ident(type.toName(type.getName())); - JCVariableDecl paramInstance = maker.VarDef(maker.Modifiers(Flags.LocalVarFlags), type.toName(INSTANCE_VARIABLE_NAME), classGenericNameExpr, null); + JCVariableDecl paramInstance = maker.VarDef(maker.Modifiers(Flags.LocalVarFlags), type.toName(INSTANCE_VARIABLE_NAME), cloneSelfType(type), null); // 2nd parameter: "FoobarBuilder b" (plus generics on the annotated type) // First add all generics that are present on the parent type. @@ -642,7 +639,7 @@ private JCMethodDecl generateStaticFillValuesMethod(JavacNode type, String build JCBlock bodyBlock = maker.Block(0, body.toList()); - return maker.MethodDef(modifiers, name, returnType, List.nil(), List.of(paramInstance, paramBuilder), List.nil(), bodyBlock, null); + return maker.MethodDef(modifiers, name, returnType, copyTypeParams(type, typeParams), List.of(paramInstance, paramBuilder), List.nil(), bodyBlock, null); } private JCExpressionStatement createSetterCallWithInstanceValue(BuilderFieldData bfd, JavacNode type, JavacTreeMaker maker) { @@ -720,7 +717,7 @@ private JCMethodDecl generateAbstractBuildMethod(JavacNode type, String methodNa return maker.MethodDef(modifiers, name, returnType, List.nil(), List.nil(), List.nil(), null, null); } - private JCMethodDecl generateBuildMethod(String buildName, JCExpression returnType, JavacNode type, List thrownExceptions) { + private JCMethodDecl generateBuildMethod(String buildName, JavacNode returnType, JavacNode type, List thrownExceptions) { JavacTreeMaker maker = type.getTreeMaker(); JCExpression call; @@ -728,7 +725,7 @@ private JCMethodDecl generateBuildMethod(String buildName, JCExpression returnTy // Use a constructor that only has this builder as parameter. List builderArg = List.of(maker.Ident(type.toName("this"))); - call = maker.NewClass(null, List.nil(), returnType, builderArg, null); + call = maker.NewClass(null, List.nil(), cloneSelfType(returnType), builderArg, null); statements.append(maker.Return(call)); JCBlock body = maker.Block(0, statements.toList()); @@ -736,7 +733,7 @@ private JCMethodDecl generateBuildMethod(String buildName, JCExpression returnTy JCAnnotation overrideAnnotation = maker.Annotation(genJavaLangTypeRef(type, "Override"), List.nil()); JCModifiers modifiers = maker.Modifiers(Flags.PUBLIC, List.of(overrideAnnotation)); - return maker.MethodDef(modifiers, type.toName(buildName), returnType, List.nil(), List.nil(), thrownExceptions, body, null); + return maker.MethodDef(modifiers, type.toName(buildName), cloneSelfType(returnType), List.nil(), List.nil(), thrownExceptions, body, null); } private JCMethodDecl generateCleanMethod(java.util.List builderFields, JavacNode type, JCTree source) { diff --git a/test/transform/resource/after-delombok/SuperBuilderWithGenericsAndToBuilder.java b/test/transform/resource/after-delombok/SuperBuilderWithGenericsAndToBuilder.java index 93d0f9b57f..deb5a2236b 100644 --- a/test/transform/resource/after-delombok/SuperBuilderWithGenericsAndToBuilder.java +++ b/test/transform/resource/after-delombok/SuperBuilderWithGenericsAndToBuilder.java @@ -15,7 +15,7 @@ public static abstract class ParentBuilder, B extends Par return self(); } @java.lang.SuppressWarnings("all") - private static void $fillValuesFromInstanceIntoBuilder(final Parent instance, final ParentBuilder b) { + private static void $fillValuesFromInstanceIntoBuilder(final Parent instance, final ParentBuilder b) { b.field1(instance.field1); b.items(instance.items == null ? java.util.Collections.emptyList() : instance.items); } @@ -106,7 +106,7 @@ public static abstract class ChildBuilder, B extends Child return self(); } @java.lang.SuppressWarnings("all") - private static void $fillValuesFromInstanceIntoBuilder(final Child instance, final ChildBuilder b) { + private static void $fillValuesFromInstanceIntoBuilder(final Child instance, final ChildBuilder b) { b.field3(instance.field3); } @java.lang.Override From 279ceaa72795ea7b427ab37535892e3c5cbde958 Mon Sep 17 00:00:00 2001 From: Jan Rieke Date: Mon, 24 Sep 2018 09:26:37 +0200 Subject: [PATCH 15/19] SuperBuilder toBuilder with generics (ecj) --- .../eclipse/handlers/HandleSuperBuilder.java | 11 +++++---- .../SuperBuilderWithGenericsAndToBuilder.java | 24 +++++++------------ 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java index 640e5a714d..a2ea11c6b7 100644 --- a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java @@ -572,9 +572,6 @@ private MethodDeclaration generateToBuilderMethod(String builderClassName, Strin out.modifiers = ClassFileConstants.AccPublic; out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; - // Add type params if there are any. - if (typeParams != null && typeParams.length > 0) out.typeParameters = copyTypeParams(typeParams, source); - TypeReference[] wildcards = new TypeReference[] {new Wildcard(Wildcard.UNBOUND), new Wildcard(Wildcard.UNBOUND) }; out.returnType = new ParameterizedSingleTypeReference(builderClassName.toCharArray(), mergeToTypeReferences(typeParams, wildcards), 0, p); @@ -609,7 +606,7 @@ private MethodDeclaration generateFillValuesMethod(EclipseNode tdParent, boolean if (inherited) out.annotations = new Annotation[] {makeMarkerAnnotation(TypeConstants.JAVA_LANG_OVERRIDE, tdParent.get())}; out.returnType = new SingleTypeReference(builderGenericName.toCharArray(), 0); - TypeReference builderType = createTypeReferenceWithTypeParameters(classGenericName, typeParams); + TypeReference builderType = new SingleTypeReference(classGenericName.toCharArray(), 0); out.arguments = new Argument[] {new Argument(INSTANCE_VARIABLE_NAME, 0, builderType, Modifier.FINAL)}; List body = new ArrayList(); @@ -662,7 +659,11 @@ private MethodDeclaration generateStaticFillValuesMethod(EclipseNode tdParent, S TypeReference[] wildcards = new TypeReference[] {new Wildcard(Wildcard.UNBOUND), new Wildcard(Wildcard.UNBOUND)}; TypeReference builderType = new ParameterizedSingleTypeReference(builderClassName.toCharArray(), mergeToTypeReferences(typeParams, wildcards), 0, 0); Argument builderArgument = new Argument(BUILDER_VARIABLE_NAME, 0, builderType, Modifier.FINAL); - out.arguments = new Argument[] {new Argument(INSTANCE_VARIABLE_NAME, 0, new SingleTypeReference(tdParent.getName().toCharArray(), 0), Modifier.FINAL), builderArgument}; + TypeReference parentArgument = new ParameterizedSingleTypeReference(tdParent.getName().toCharArray(), mergeToTypeReferences(typeParams, new TypeReference[0]), 0, 0); + out.arguments = new Argument[] {new Argument(INSTANCE_VARIABLE_NAME, 0, parentArgument, Modifier.FINAL), builderArgument}; + + // Add type params if there are any. + if (typeParams.length > 0) out.typeParameters = copyTypeParams(typeParams, source); List body = new ArrayList(); diff --git a/test/transform/resource/after-ecj/SuperBuilderWithGenericsAndToBuilder.java b/test/transform/resource/after-ecj/SuperBuilderWithGenericsAndToBuilder.java index fcdadb3bf2..40e3bdcac1 100644 --- a/test/transform/resource/after-ecj/SuperBuilderWithGenericsAndToBuilder.java +++ b/test/transform/resource/after-ecj/SuperBuilderWithGenericsAndToBuilder.java @@ -1,12 +1,6 @@ import java.util.List; - -import SuperBuilderBasicToBuilder.Child; -import SuperBuilderBasicToBuilder.Parent; -import SuperBuilderBasicToBuilder.Child.ChildBuilder; -import SuperBuilderBasicToBuilder.Child.ChildBuilderImpl; -import SuperBuilderBasicToBuilder.Parent.ParentBuilder; public class SuperBuilderWithGenericsAndToBuilder { - public static @lombok.experimental.SuperBuilder class Parent { + public static @lombok.experimental.SuperBuilder(toBuilder = true) class Parent { public static abstract @java.lang.SuppressWarnings("all") class ParentBuilder, B extends ParentBuilder> { private @java.lang.SuppressWarnings("all") A field1; private @java.lang.SuppressWarnings("all") java.util.ArrayList items; @@ -14,10 +8,10 @@ public ParentBuilder() { super(); } protected @java.lang.SuppressWarnings("all") B $fillValuesFrom(final C instance) { - ParentBuilder.$fillValuesFromInstanceIntoBuilder(instance, this); - return self(); - } - private static @java.lang.SuppressWarnings("all") void $fillValuesFromInstanceIntoBuilder(final Parent instance, final ParentBuilder b) { + ParentBuilder.$fillValuesFromInstanceIntoBuilder(instance, this); + return self(); + } + private static @java.lang.SuppressWarnings("all") void $fillValuesFromInstanceIntoBuilder(final Parent instance, final ParentBuilder b) { b.field1(instance.field1); b.items(((instance.items == null) ? java.util.Collections.emptyList() : instance.items)); } @@ -77,14 +71,14 @@ private ParentBuilderImpl() { } this.items = items; } - public @java.lang.SuppressWarnings("all") ParentBuilder toBuilder() { + public @java.lang.SuppressWarnings("all") ParentBuilder toBuilder() { return new ParentBuilderImpl().$fillValuesFrom(this); } public static @java.lang.SuppressWarnings("all") ParentBuilder builder() { return new ParentBuilderImpl(); } } - public static @lombok.experimental.SuperBuilder class Child extends Parent { + public static @lombok.experimental.SuperBuilder(toBuilder = true) class Child extends Parent { public static abstract @java.lang.SuppressWarnings("all") class ChildBuilder, B extends ChildBuilder> extends Parent.ParentBuilder { private @java.lang.SuppressWarnings("all") double field3; public ChildBuilder() { @@ -95,7 +89,7 @@ public ChildBuilder() { ChildBuilder.$fillValuesFromInstanceIntoBuilder(instance, this); return self(); } - private static @java.lang.SuppressWarnings("all") void $fillValuesFromInstanceIntoBuilder(final Child instance, final ChildBuilder b) { + private static @java.lang.SuppressWarnings("all") void $fillValuesFromInstanceIntoBuilder(final Child instance, final ChildBuilder b) { b.field3(instance.field3); } protected abstract @java.lang.Override @java.lang.SuppressWarnings("all") B self(); @@ -131,7 +125,7 @@ private ChildBuilderImpl() { return new ChildBuilderImpl(); } } - public SuperBuilderWithGenerics() { + public SuperBuilderWithGenericsAndToBuilder() { super(); } public static void test() { From fd4c9d4bff6e75b30a3ee247edafaabc6888a691 Mon Sep 17 00:00:00 2001 From: Jan Rieke Date: Mon, 24 Sep 2018 09:32:07 +0200 Subject: [PATCH 16/19] SuperBuilder toBuilder fix with no generics (ecj) --- src/core/lombok/eclipse/handlers/HandleSuperBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java index a2ea11c6b7..8f353cce51 100644 --- a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java @@ -659,7 +659,7 @@ private MethodDeclaration generateStaticFillValuesMethod(EclipseNode tdParent, S TypeReference[] wildcards = new TypeReference[] {new Wildcard(Wildcard.UNBOUND), new Wildcard(Wildcard.UNBOUND)}; TypeReference builderType = new ParameterizedSingleTypeReference(builderClassName.toCharArray(), mergeToTypeReferences(typeParams, wildcards), 0, 0); Argument builderArgument = new Argument(BUILDER_VARIABLE_NAME, 0, builderType, Modifier.FINAL); - TypeReference parentArgument = new ParameterizedSingleTypeReference(tdParent.getName().toCharArray(), mergeToTypeReferences(typeParams, new TypeReference[0]), 0, 0); + TypeReference parentArgument = createTypeReferenceWithTypeParameters(tdParent.getName(), typeParams); out.arguments = new Argument[] {new Argument(INSTANCE_VARIABLE_NAME, 0, parentArgument, Modifier.FINAL), builderArgument}; // Add type params if there are any. From afec417115abadd58c9d94a845b6c300e7e1b96f Mon Sep 17 00:00:00 2001 From: Jan Rieke Date: Mon, 24 Sep 2018 16:49:03 +0200 Subject: [PATCH 17/19] make BuilderFieldData package-private, so that SuperBuilder can use it --- src/core/lombok/eclipse/handlers/HandleBuilder.java | 2 +- src/core/lombok/javac/handlers/HandleBuilder.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java index f6e0a17559..09765efc72 100644 --- a/src/core/lombok/eclipse/handlers/HandleBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java @@ -105,7 +105,7 @@ private static final boolean toBoolean(Object expr, boolean defaultValue) { return ((Boolean) expr).booleanValue(); } - public static class BuilderFieldData { + static class BuilderFieldData { Annotation[] annotations; TypeReference type; char[] rawName; diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java index bf26064460..bc1a80723e 100644 --- a/src/core/lombok/javac/handlers/HandleBuilder.java +++ b/src/core/lombok/javac/handlers/HandleBuilder.java @@ -84,7 +84,7 @@ private static final boolean toBoolean(Object expr, boolean defaultValue) { return ((Boolean) expr).booleanValue(); } - public static class BuilderFieldData { + static class BuilderFieldData { List annotations; JCExpression type; Name rawName; From 80af03a3e48456e14b1b3da250047c531f460bb4 Mon Sep 17 00:00:00 2001 From: Jan Rieke Date: Mon, 24 Sep 2018 17:09:45 +0200 Subject: [PATCH 18/19] SuperBuilder toBuilder documentation --- website/templates/features/experimental/SuperBuilder.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/website/templates/features/experimental/SuperBuilder.html b/website/templates/features/experimental/SuperBuilder.html index 8189a254de..c0d24606c8 100644 --- a/website/templates/features/experimental/SuperBuilder.html +++ b/website/templates/features/experimental/SuperBuilder.html @@ -22,6 +22,8 @@ @SuperBuilder generates a private constructor on the class that takes a builder instance as a parameter. This constructor sets the fields of the new instance to the values from the builder.

@SuperBuilder is not compatible with @Builder. +

+ You can use @SuperBuilder(toBuilder = true) to also generate an instance method in your class called toBuilder(); it creates a new builder that starts out with all the values of this instance. You can put the @Builder.ObtainVia annotation on the fields to indicate alternative means by which the value for that field/parameter is obtained from this instance. For example, you can specify a method to be invoked: @Builder.ObtainVia(method = "calculateFoo").

To ensure type-safety, @SuperBuilder generates two inner builder classes for each annotated class, one abstract and one concrete class named FoobarBuilder and FoobarBuilderImpl (where Foobar is the name of the annotated class).

@@ -31,10 +33,12 @@ The build() method's name (default: "build")

  • The builder() method's name (default: "builder") +
  • + If you want toBuilder() (default: no)
  • Example usage where all options are changed from their defaults:
    - @SuperBuilder(buildMethodName = "execute", builderMethodName = "helloWorld")
    + @SuperBuilder(buildMethodName = "execute", builderMethodName = "helloWorld", toBuilder = true)

    From bd494bb5ff01eea2413cfe5a4820bfff34c07261 Mon Sep 17 00:00:00 2001 From: Jan Rieke Date: Mon, 24 Sep 2018 18:07:46 +0200 Subject: [PATCH 19/19] SuperBuilder: fixed ecj tests (params are now final) --- .../resource/after-ecj/SuperBuilderBasicToBuilder.java | 4 ++-- .../after-ecj/SuperBuilderWithGenericsAndToBuilder.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java b/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java index a491430a2e..f4d99112ab 100644 --- a/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java +++ b/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java @@ -39,13 +39,13 @@ public ParentBuilder() { this.obtainViaStaticMethod = obtainViaStaticMethod; return self(); } - public @java.lang.SuppressWarnings("all") B item(String item) { + public @java.lang.SuppressWarnings("all") B item(final String item) { if ((this.items == null)) this.items = new java.util.ArrayList(); this.items.add(item); return self(); } - public @java.lang.SuppressWarnings("all") B items(java.util.Collection items) { + public @java.lang.SuppressWarnings("all") B items(final java.util.Collection items) { if ((this.items == null)) this.items = new java.util.ArrayList(); this.items.addAll(items); diff --git a/test/transform/resource/after-ecj/SuperBuilderWithGenericsAndToBuilder.java b/test/transform/resource/after-ecj/SuperBuilderWithGenericsAndToBuilder.java index 40e3bdcac1..107ee36276 100644 --- a/test/transform/resource/after-ecj/SuperBuilderWithGenericsAndToBuilder.java +++ b/test/transform/resource/after-ecj/SuperBuilderWithGenericsAndToBuilder.java @@ -21,13 +21,13 @@ public ParentBuilder() { this.field1 = field1; return self(); } - public @java.lang.SuppressWarnings("all") B item(String item) { + public @java.lang.SuppressWarnings("all") B item(final String item) { if ((this.items == null)) this.items = new java.util.ArrayList(); this.items.add(item); return self(); } - public @java.lang.SuppressWarnings("all") B items(java.util.Collection items) { + public @java.lang.SuppressWarnings("all") B items(final java.util.Collection items) { if ((this.items == null)) this.items = new java.util.ArrayList(); this.items.addAll(items);