aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/core/lombok/Builder.java32
-rw-r--r--src/core/lombok/eclipse/handlers/HandleBuilder.java108
-rw-r--r--src/core/lombok/eclipse/handlers/HandleConstructor.java72
-rw-r--r--src/core/lombok/javac/handlers/HandleBuilder.java115
-rw-r--r--src/core/lombok/javac/handlers/HandleConstructor.java79
5 files changed, 329 insertions, 77 deletions
diff --git a/src/core/lombok/Builder.java b/src/core/lombok/Builder.java
index 7a965486..9cf82191 100644
--- a/src/core/lombok/Builder.java
+++ b/src/core/lombok/Builder.java
@@ -123,6 +123,38 @@ public @interface Builder {
String builderClassName() default "";
/**
+ * If true, the generated builder class will extend the {@code @Builder} of the
+ * superclass. In this way, the builder will also contain methods for fields
+ * from the superclass.<br>
+ * Note that both this builder and the superclass' builder must be a type
+ * {@code @Builder}; this feature does neither work for constructor nor
+ * method {@code @Builder}s. The parent {@code @Builder} must be
+ * {@link #extendable()}. <br>
+ * Implies {@link #extendable()}.
+ */
+ boolean inherit() default false;
+
+ /**
+ * If true, the generated builder pattern will be extendable by
+ * {@code @Builder}s of subclasses, using the {@link #inherit()} feature.
+ * This is achieved by generating a special constructor that takes a builder
+ * instance as parameter (instead of an {@link AllArgsConstructor}). <br>
+ * Note that this builder must be a type {@code @Builder}; this feature does
+ * neither work for constructor nor method {@code @Builder}s.<br>
+ * If {@link #inherit()} {@code == true}, {@link #extendable()} will
+ * automatically be {@code true}.
+ */
+ boolean extendable() default false;
+
+ /**
+ * Name of the builder class in the superclass. Only relevant if
+ * {@code inherit = true} (see {@link #inherit()}).
+ *
+ * Default {@code (SuperclassTypeName)Builder}.
+ */
+ String superclassBuilderClassName() default "";
+
+ /**
* If true, generate an instance method to obtain a builder that is initialized with the values of this instance.
* Legal only if {@code @Builder} is used on a constructor, on the type itself, or on a static method that returns
* an instance of the declaring type.
diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java
index 8c200a94..58e08b0d 100644
--- a/src/core/lombok/eclipse/handlers/HandleBuilder.java
+++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java
@@ -139,6 +139,11 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
String builderMethodName = builderInstance.builderMethodName();
String buildMethodName = builderInstance.buildMethodName();
String builderClassName = builderInstance.builderClassName();
+
+ boolean inherit = builderInstance.inherit();
+ boolean extendable = inherit || builderInstance.extendable(); // inherit implies extendable
+ String superclassBuilderClassName = builderInstance.superclassBuilderClassName();
+
String toBuilderMethodName = "toBuilder";
boolean toBuilder = builderInstance.toBuilder();
List<char[]> typeArgsForToBuilder = null;
@@ -146,6 +151,9 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
if (builderMethodName == null) builderMethodName = "builder";
if (buildMethodName == null) builderMethodName = "build";
if (builderClassName == null) builderClassName = "";
+ if (superclassBuilderClassName == null) {
+ superclassBuilderClassName = "";
+ }
if (!checkName("builderMethodName", builderMethodName, annotationNode)) return;
if (!checkName("buildMethodName", buildMethodName, annotationNode)) return;
@@ -189,15 +197,30 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
allFields.add(fieldNode);
}
- new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, allFields, false, null, SkipIfConstructorExists.I_AM_BUILDER,
- Collections.<Annotation>emptyList(), annotationNode);
+ if (builderClassName.isEmpty()) {
+ builderClassName = new String(td.name) + "Builder";
+ }
+ if (superclassBuilderClassName.isEmpty() && td.superclass != null) {
+ superclassBuilderClassName = new String(td.superclass.getLastToken()) + "Builder";
+ }
+
+ boolean callBuilderBasedSuperConstructor = inherit && td.superclass != null;
+ new HandleConstructor().generateConstructor(tdParent, extendable ? AccessLevel.PROTECTED : AccessLevel.PACKAGE, allFields, false, null, SkipIfConstructorExists.I_AM_BUILDER,
+ Collections.<Annotation>emptyList(), annotationNode, extendable ? builderClassName : null, callBuilderBasedSuperConstructor);
returnType = namePlusTypeParamsToTypeReference(td.name, td.typeParameters, p);
typeParams = td.typeParameters;
thrownExceptions = null;
nameOfStaticBuilderMethod = null;
- if (builderClassName.isEmpty()) builderClassName = new String(td.name) + "Builder";
} else if (parent.get() instanceof ConstructorDeclaration) {
+ if (inherit) {
+ annotationNode.addError("@Builder(inherit=true) is only supported for type builders.");
+ return;
+ }
+ if (extendable) {
+ annotationNode.addError("@Builder(extendable=true) is only supported for type builders.");
+ return;
+ }
ConstructorDeclaration cd = (ConstructorDeclaration) parent.get();
if (cd.typeParameters != null && cd.typeParameters.length > 0) {
annotationNode.addError("@Builder is not supported on constructors with constructor type parameters.");
@@ -212,6 +235,14 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
nameOfStaticBuilderMethod = null;
if (builderClassName.isEmpty()) builderClassName = new String(cd.selector) + "Builder";
} else if (parent.get() instanceof MethodDeclaration) {
+ if (inherit) {
+ annotationNode.addError("@Builder(inherit=true) is only supported for type builders.");
+ return;
+ }
+ if (extendable) {
+ annotationNode.addError("@Builder(extendable=true) is only supported for type builders.");
+ return;
+ }
MethodDeclaration md = (MethodDeclaration) parent.get();
tdParent = parent.up();
isStatic = md.isStatic();
@@ -341,7 +372,7 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
EclipseNode builderType = findInnerClass(tdParent, builderClassName);
if (builderType == null) {
- builderType = makeBuilderClass(isStatic, tdParent, builderClassName, typeParams, ast);
+ builderType = makeBuilderClass(isStatic, tdParent, builderClassName, typeParams, ast, inherit ? superclassBuilderClassName : null);
} else {
TypeDeclaration builderTypeDeclaration = (TypeDeclaration) builderType.get();
if (isStatic && (builderTypeDeclaration.modifiers & ClassFileConstants.AccStatic) == 0) {
@@ -396,7 +427,7 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
if (constructorExists(builderType) == MemberExistsResult.NOT_EXISTS) {
ConstructorDeclaration cd = HandleConstructor.createConstructor(
AccessLevel.PACKAGE, builderType, Collections.<EclipseNode>emptyList(), false,
- annotationNode, Collections.<Annotation>emptyList());
+ annotationNode, Collections.<Annotation>emptyList(), null, false);
if (cd != null) injectMethod(builderType, cd);
}
@@ -405,7 +436,8 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
}
if (methodExists(buildMethodName, builderType, -1) == MemberExistsResult.NOT_EXISTS) {
- MethodDeclaration md = generateBuildMethod(isStatic, buildMethodName, nameOfStaticBuilderMethod, returnType, builderFields, builderType, thrownExceptions, addCleaning, ast);
+ boolean useBuilderBasedConstructor = parent.get() instanceof TypeDeclaration && extendable;
+ MethodDeclaration md = generateBuildMethod(isStatic, buildMethodName, nameOfStaticBuilderMethod, returnType, builderFields, builderType, thrownExceptions, addCleaning, ast, useBuilderBasedConstructor);
if (md != null) injectMethod(builderType, md);
}
@@ -514,35 +546,44 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
return decl;
}
- public MethodDeclaration generateBuildMethod(boolean isStatic, String name, char[] staticName, TypeReference returnType, List<BuilderFieldData> builderFields, EclipseNode type, TypeReference[] thrownExceptions, boolean addCleaning, ASTNode source) {
+ /**
+ * @param useBuilderBasedConstructor
+ * if true, the {@code build()} method will use a constructor
+ * that takes the builder instance as parameter (instead of a
+ * constructor with all relevant fields as parameters)
+ */
+ public MethodDeclaration generateBuildMethod(boolean isStatic, String name, char[] staticName, TypeReference returnType, List<BuilderFieldData> builderFields, EclipseNode type, TypeReference[] thrownExceptions, boolean addCleaning, ASTNode source, boolean useBuilderBasedConstructor) {
MethodDeclaration out = new MethodDeclaration(((CompilationUnitDeclaration) type.top().get()).compilationResult);
out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG;
List<Statement> statements = new ArrayList<Statement>();
+ List<Expression> args = new ArrayList<Expression>();
- if (addCleaning) {
- FieldReference thisUnclean = new FieldReference(CLEAN_FIELD_NAME, 0);
- thisUnclean.receiver = new ThisReference(0, 0);
- Expression notClean = new UnaryExpression(thisUnclean, OperatorIds.NOT);
- MessageSend invokeClean = new MessageSend();
- invokeClean.selector = CLEAN_METHOD_NAME;
- statements.add(new IfStatement(notClean, invokeClean, 0, 0));
- }
+ // Extendable builders assign their values in the constructor, not in this build() method.
+ if (!useBuilderBasedConstructor) {
+ if (addCleaning) {
+ FieldReference thisUnclean = new FieldReference(CLEAN_FIELD_NAME, 0);
+ thisUnclean.receiver = new ThisReference(0, 0);
+ Expression notClean = new UnaryExpression(thisUnclean, OperatorIds.NOT);
+ MessageSend invokeClean = new MessageSend();
+ invokeClean.selector = CLEAN_METHOD_NAME;
+ statements.add(new IfStatement(notClean, invokeClean, 0, 0));
+ }
- for (BuilderFieldData bfd : builderFields) {
- if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) {
- bfd.singularData.getSingularizer().appendBuildCode(bfd.singularData, type, statements, bfd.name);
+ for (BuilderFieldData bfd : builderFields) {
+ if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) {
+ bfd.singularData.getSingularizer().appendBuildCode(bfd.singularData, type, statements, bfd.name);
+ }
}
- }
- List<Expression> args = new ArrayList<Expression>();
- for (BuilderFieldData bfd : builderFields) {
- args.add(new SingleNameReference(bfd.name, 0L));
- }
+ for (BuilderFieldData bfd : builderFields) {
+ args.add(new SingleNameReference(bfd.name, 0L));
+ }
- if (addCleaning) {
- FieldReference thisUnclean = new FieldReference(CLEAN_FIELD_NAME, 0);
- thisUnclean.receiver = new ThisReference(0, 0);
- statements.add(new Assignment(thisUnclean, new TrueLiteral(0, 0), 0));
+ if (addCleaning) {
+ FieldReference thisUnclean = new FieldReference(CLEAN_FIELD_NAME, 0);
+ thisUnclean.receiver = new ThisReference(0, 0);
+ statements.add(new Assignment(thisUnclean, new TrueLiteral(0, 0), 0));
+ }
}
out.modifiers = ClassFileConstants.AccPublic;
@@ -554,7 +595,13 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
if (staticName == null) {
AllocationExpression allocationStatement = new AllocationExpression();
allocationStatement.type = copyType(out.returnType);
- allocationStatement.arguments = args.isEmpty() ? null : args.toArray(new Expression[args.size()]);
+ if (useBuilderBasedConstructor) {
+ // Use a constructor that only has this builder as parameter.
+ allocationStatement.arguments = new Expression[] {new ThisReference(0, 0)};
+ } else {
+ // Use a constructor with all the fields.
+ allocationStatement.arguments = args.isEmpty() ? null : args.toArray(new Expression[args.size()]);
+ }
statements.add(new ReturnStatement(allocationStatement, 0, 0));
} else {
MessageSend invoke = new MessageSend();
@@ -672,7 +719,7 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
return null;
}
- public EclipseNode makeBuilderClass(boolean isStatic, EclipseNode tdParent, String builderClassName, TypeParameter[] typeParams, ASTNode source) {
+ public EclipseNode makeBuilderClass(boolean isStatic, EclipseNode tdParent, String builderClassName, TypeParameter[] typeParams, ASTNode source, String parentBuilderClassName) {
TypeDeclaration parent = (TypeDeclaration) tdParent.get();
TypeDeclaration builder = new TypeDeclaration(parent.compilationResult);
builder.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG;
@@ -680,6 +727,9 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
if (isStatic) builder.modifiers |= ClassFileConstants.AccStatic;
builder.typeParameters = copyTypeParams(typeParams, source);
builder.name = builderClassName.toCharArray();
+ if (parentBuilderClassName != null) {
+ builder.superclass = new SingleTypeReference(parentBuilderClassName.toCharArray(), 0);
+ }
builder.traverse(new SetGeneratedByVisitor(source), (ClassScope) null);
return injectType(tdParent, builder);
}
diff --git a/src/core/lombok/eclipse/handlers/HandleConstructor.java b/src/core/lombok/eclipse/handlers/HandleConstructor.java
index 49b09231..a8b823b2 100644
--- a/src/core/lombok/eclipse/handlers/HandleConstructor.java
+++ b/src/core/lombok/eclipse/handlers/HandleConstructor.java
@@ -64,10 +64,12 @@ import org.eclipse.jdt.internal.compiler.ast.IntLiteral;
import org.eclipse.jdt.internal.compiler.ast.LongLiteral;
import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.NullLiteral;
+import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference;
import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference;
import org.eclipse.jdt.internal.compiler.ast.ReturnStatement;
import org.eclipse.jdt.internal.compiler.ast.SingleMemberAnnotation;
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.StringLiteral;
import org.eclipse.jdt.internal.compiler.ast.ThisReference;
@@ -95,7 +97,7 @@ public class HandleConstructor {
List<EclipseNode> fields = force ? findFinalFields(typeNode) : Collections.<EclipseNode>emptyList();
List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@NoArgsConstructor(onConstructor", annotationNode);
- new HandleConstructor().generateConstructor(typeNode, level, fields, force, staticName, SkipIfConstructorExists.NO, onConstructor, annotationNode);
+ new HandleConstructor().generateConstructor(typeNode, level, fields, force, staticName, SkipIfConstructorExists.NO, onConstructor, annotationNode, null, false);
}
}
@@ -118,7 +120,7 @@ public class HandleConstructor {
new HandleConstructor().generateConstructor(
typeNode, level, findRequiredFields(typeNode), false, staticName, SkipIfConstructorExists.NO,
- onConstructor, annotationNode);
+ onConstructor, annotationNode, null, false);
}
}
@@ -177,7 +179,7 @@ public class HandleConstructor {
new HandleConstructor().generateConstructor(
typeNode, level, findAllFields(typeNode), false, staticName, SkipIfConstructorExists.NO,
- onConstructor, annotationNode);
+ onConstructor, annotationNode, null, false);
}
}
@@ -199,23 +201,34 @@ public class HandleConstructor {
EclipseNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists,
List<Annotation> onConstructor, EclipseNode sourceNode) {
- generateConstructor(typeNode, level, findRequiredFields(typeNode), false, staticName, skipIfConstructorExists, onConstructor, sourceNode);
+ generateConstructor(typeNode, level, findRequiredFields(typeNode), false, staticName, skipIfConstructorExists, onConstructor, sourceNode, null, false);
}
public void generateAllArgsConstructor(
EclipseNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists,
List<Annotation> onConstructor, EclipseNode sourceNode) {
- generateConstructor(typeNode, level, findAllFields(typeNode), false, staticName, skipIfConstructorExists, onConstructor, sourceNode);
+ generateConstructor(typeNode, level, findAllFields(typeNode), false, staticName, skipIfConstructorExists, onConstructor, sourceNode, null, false);
}
public enum SkipIfConstructorExists {
YES, NO, I_AM_BUILDER;
}
+ /**
+ * @param builderClassnameAsParameter
+ * if {@code != null}, the only parameter of the constructor will
+ * be a builder with this classname; the constructor will then
+ * use the values within this builder to assign the fields of new
+ * instances.
+ * @param callBuilderBasedSuperConstructor
+ * if {@code true}, the constructor will explicitly call a super
+ * constructor with the builder as argument. Requires
+ * {@code builderClassAsParameter != null}.
+ */
public void generateConstructor(
EclipseNode typeNode, AccessLevel level, List<EclipseNode> fields, boolean allToDefault, String staticName, SkipIfConstructorExists skipIfConstructorExists,
- List<Annotation> onConstructor, EclipseNode sourceNode) {
+ List<Annotation> onConstructor, EclipseNode sourceNode, String builderClassnameAsParameter, boolean callBuilderBasedSuperConstructor) {
ASTNode source = sourceNode.get();
boolean staticConstrRequired = staticName != null && !staticName.equals("");
@@ -250,7 +263,7 @@ public class HandleConstructor {
ConstructorDeclaration constr = createConstructor(
staticConstrRequired ? AccessLevel.PRIVATE : level, typeNode, fields, allToDefault,
- sourceNode, onConstructor);
+ sourceNode, onConstructor, builderClassnameAsParameter, callBuilderBasedSuperConstructor);
injectMethod(typeNode, constr);
if (staticConstrRequired) {
MethodDeclaration staticConstr = createStaticConstructor(level, staticName, typeNode, allToDefault ? Collections.<EclipseNode>emptyList() : fields, source);
@@ -290,9 +303,28 @@ public class HandleConstructor {
return new Annotation[] { ann };
}
+ /**
+ * @param builderClassnameAsParameter
+ * if {@code != null}, the only parameter of the constructor will
+ * be a builder with this classname; the constructor will then
+ * use the values within this builder to assign the fields of new
+ * instances.
+ * @param callBuilderBasedSuperConstructor
+ * if {@code true}, the constructor will explicitly call a super
+ * constructor with the builder as argument. Requires
+ * {@code builderClassnameAsParameter != null}.
+ */
public static ConstructorDeclaration createConstructor(
AccessLevel level, EclipseNode type, Collection<EclipseNode> fields, boolean allToDefault,
- EclipseNode sourceNode, List<Annotation> onConstructor) {
+ EclipseNode sourceNode, List<Annotation> onConstructor,
+ String builderClassnameAsParameter, boolean callBuilderBasedSuperConstructor) {
+
+ if (builderClassnameAsParameter != null && builderClassnameAsParameter.isEmpty()) {
+ builderClassnameAsParameter = null;
+ }
+ if (callBuilderBasedSuperConstructor && builderClassnameAsParameter == null) {
+ type.addError("Calling a builder-based superclass constructor ('callBuilderBasedSuperConstructor') requires a non-empty 'builderClassnameAsParameter' value.");
+ }
ASTNode source = sourceNode.get();
TypeDeclaration typeDeclaration = ((TypeDeclaration) type.get());
@@ -313,7 +345,12 @@ public class HandleConstructor {
constructor.modifiers = toEclipseModifier(level);
constructor.selector = typeDeclaration.name;
- constructor.constructorCall = new ExplicitConstructorCall(ExplicitConstructorCall.ImplicitSuper);
+ if (callBuilderBasedSuperConstructor) {
+ constructor.constructorCall = new ExplicitConstructorCall(ExplicitConstructorCall.Super);
+ constructor.constructorCall.arguments = new Expression[] {new SingleNameReference("b".toCharArray(), p)};
+ } else {
+ constructor.constructorCall = new ExplicitConstructorCall(ExplicitConstructorCall.ImplicitSuper);
+ }
constructor.constructorCall.sourceStart = source.sourceStart;
constructor.constructorCall.sourceEnd = source.sourceEnd;
constructor.thrownExceptions = null;
@@ -336,7 +373,16 @@ public class HandleConstructor {
int e = (int) p;
thisX.receiver = new ThisReference(s, e);
- Expression assignmentExpr = allToDefault ? getDefaultExpr(field.type, s, e) : new SingleNameReference(fieldName, p);
+ Expression assignmentExpr;
+ if (allToDefault) {
+ assignmentExpr = getDefaultExpr(field.type, s, e);
+ } else if (builderClassnameAsParameter != null) {
+ char[][] variableInBuilder = new char[][] {"b".toCharArray(), fieldName};
+ long[] positions = new long[] {p, p};
+ assignmentExpr = new QualifiedNameReference(variableInBuilder, positions, s, e);
+ } else {
+ assignmentExpr = new SingleNameReference(fieldName, p);
+ }
Assignment assignment = new Assignment(thisX, assignmentExpr, (int) p);
assignment.sourceStart = (int) (p >> 32); assignment.sourceEnd = assignment.statementEnd = (int) (p >> 32);
@@ -357,7 +403,11 @@ public class HandleConstructor {
nullChecks.addAll(assigns);
constructor.statements = nullChecks.isEmpty() ? null : nullChecks.toArray(new Statement[nullChecks.size()]);
- constructor.arguments = params.isEmpty() ? null : params.toArray(new Argument[params.size()]);
+ if (builderClassnameAsParameter != null) {
+ constructor.arguments = new Argument[] {new Argument("b".toCharArray(), p, new SingleTypeReference(builderClassnameAsParameter.toCharArray(), p), Modifier.FINAL)};
+ } else {
+ constructor.arguments = params.isEmpty() ? null : params.toArray(new Argument[params.size()]);
+ }
/* Generate annotations that must be put on the generated method, and attach them. */ {
Annotation[] constructorProperties = null;
diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java
index 4c670433..43520656 100644
--- a/src/core/lombok/javac/handlers/HandleBuilder.java
+++ b/src/core/lombok/javac/handlers/HandleBuilder.java
@@ -22,7 +22,10 @@
package lombok.javac.handlers;
import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.function.Consumer;
import javax.lang.model.element.Modifier;
@@ -50,6 +53,7 @@ import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Name;
+import jdk.nashorn.internal.runtime.Context;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Builder.ObtainVia;
@@ -64,6 +68,7 @@ import lombok.javac.Javac;
import lombok.javac.JavacAnnotationHandler;
import lombok.javac.JavacNode;
import lombok.javac.JavacTreeMaker;
+import lombok.javac.JavacTreeMaker.TypeTag;
import lombok.javac.handlers.HandleConstructor.SkipIfConstructorExists;
import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer;
import lombok.javac.handlers.JavacSingularsRecipes.SingularData;
@@ -103,12 +108,20 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> {
String buildMethodName = builderInstance.buildMethodName();
String builderClassName = builderInstance.builderClassName();
String toBuilderMethodName = "toBuilder";
+
+ boolean inherit = builderInstance.inherit();
+ boolean extendable = inherit || builderInstance.extendable(); // inherit implies extendable
+ String superclassBuilderClassName = builderInstance.superclassBuilderClassName();
+
boolean toBuilder = builderInstance.toBuilder();
java.util.List<Name> typeArgsForToBuilder = null;
if (builderMethodName == null) builderMethodName = "builder";
if (buildMethodName == null) buildMethodName = "build";
if (builderClassName == null) builderClassName = "";
+ if (superclassBuilderClassName == null) {
+ superclassBuilderClassName = "";
+ }
if (!checkName("builderMethodName", builderMethodName, annotationNode)) return;
if (!checkName("buildMethodName", buildMethodName, annotationNode)) return;
@@ -155,14 +168,31 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> {
allFields.append(fieldNode);
}
- new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, List.<JCAnnotation>nil(), allFields.toList(), false, null, SkipIfConstructorExists.I_AM_BUILDER, annotationNode);
+ if (builderClassName.isEmpty()) {
+ builderClassName = td.name.toString() + "Builder";
+ }
+
+ JCTree extendsClause = Javac.getExtendsClause(td);
+ if (superclassBuilderClassName.isEmpty() && extendsClause != null) {
+ superclassBuilderClassName = extendsClause + "Builder";
+ }
+
+ boolean callBuilderBasedSuperConstructor = inherit && extendsClause != null;
+ new HandleConstructor().generateConstructor(tdParent, AccessLevel.PROTECTED, List.<JCAnnotation>nil(), allFields.toList(), false, null, SkipIfConstructorExists.I_AM_BUILDER, annotationNode, extendable ? builderClassName : null, callBuilderBasedSuperConstructor);
returnType = namePlusTypeParamsToTypeReference(tdParent.getTreeMaker(), td.name, td.typarams);
typeParams = td.typarams;
thrownExceptions = List.nil();
nameOfBuilderMethod = null;
- if (builderClassName.isEmpty()) builderClassName = td.name.toString() + "Builder";
} else if (fillParametersFrom != null && fillParametersFrom.getName().toString().equals("<init>")) {
+ if (inherit) {
+ annotationNode.addError("@Builder(inherit=true) is only supported for type builders.");
+ return;
+ }
+ if (extendable) {
+ annotationNode.addError("@Builder(extendable=true) is only supported for type builders.");
+ return;
+ }
JCMethodDecl jmd = (JCMethodDecl) fillParametersFrom.get();
if (!jmd.typarams.isEmpty()) {
annotationNode.addError("@Builder is not supported on constructors with constructor type parameters.");
@@ -177,6 +207,14 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> {
nameOfBuilderMethod = null;
if (builderClassName.isEmpty()) builderClassName = td.name.toString() + "Builder";
} else if (fillParametersFrom != null) {
+ if (inherit) {
+ annotationNode.addError("@Builder(inherit=true) is only supported for type builders.");
+ return;
+ }
+ if (extendable) {
+ annotationNode.addError("@Builder(extendable=true) is only supported for type builders.");
+ return;
+ }
tdParent = parent.up();
JCClassDecl td = (JCClassDecl) tdParent.get();
JCMethodDecl jmd = (JCMethodDecl) fillParametersFrom.get();
@@ -297,7 +335,7 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> {
JavacNode builderType = findInnerClass(tdParent, builderClassName);
if (builderType == null) {
- builderType = makeBuilderClass(isStatic, annotationNode, tdParent, builderClassName, typeParams, ast);
+ builderType = makeBuilderClass(isStatic, annotationNode, tdParent, builderClassName, typeParams, ast, inherit ? superclassBuilderClassName : null);
} else {
JCClassDecl builderTypeDeclaration = (JCClassDecl) builderType.get();
if (isStatic && !builderTypeDeclaration.getModifiers().getFlags().contains(Modifier.STATIC)) {
@@ -348,7 +386,7 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> {
}
if (constructorExists(builderType) == MemberExistsResult.NOT_EXISTS) {
- JCMethodDecl cd = HandleConstructor.createConstructor(AccessLevel.PACKAGE, List.<JCAnnotation>nil(), builderType, List.<JavacNode>nil(), false, annotationNode);
+ JCMethodDecl cd = HandleConstructor.createConstructor(AccessLevel.PACKAGE, List.<JCAnnotation>nil(), builderType, List.<JavacNode>nil(), false, annotationNode, null, false);
if (cd != null) injectMethod(builderType, cd);
}
@@ -357,7 +395,8 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> {
}
if (methodExists(buildMethodName, builderType, -1) == MemberExistsResult.NOT_EXISTS) {
- JCMethodDecl md = generateBuildMethod(isStatic, buildMethodName, nameOfBuilderMethod, returnType, builderFields, builderType, thrownExceptions, ast, addCleaning);
+ boolean useBuilderBasedConstructor = parent.get() instanceof JCClassDecl && extendable;
+ JCMethodDecl md = generateBuildMethod(isStatic, buildMethodName, nameOfBuilderMethod, returnType, builderFields, builderType, thrownExceptions, ast, addCleaning, useBuilderBasedConstructor);
if (md != null) injectMethod(builderType, md);
}
@@ -494,36 +533,52 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> {
*/
}
- private JCMethodDecl generateBuildMethod(boolean isStatic, String buildName, Name builderName, JCExpression returnType, java.util.List<BuilderFieldData> builderFields, JavacNode type, List<JCExpression> thrownExceptions, JCTree source, boolean addCleaning) {
+ /**
+ * @param useBuilderBasedConstructor
+ * if true, the {@code build()} method will use a constructor
+ * that takes the builder instance as parameter (instead of a
+ * constructor with all relevant fields as parameters)
+ */
+ private JCMethodDecl generateBuildMethod(boolean isStatic, String buildName, Name builderName, JCExpression returnType, java.util.List<BuilderFieldData> builderFields, JavacNode type, List<JCExpression> thrownExceptions, JCTree source, boolean addCleaning, boolean useBuilderBasedConstructor) {
JavacTreeMaker maker = type.getTreeMaker();
JCExpression call;
ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>();
-
- if (addCleaning) {
- JCExpression notClean = maker.Unary(CTC_NOT, maker.Select(maker.Ident(type.toName("this")), type.toName("$lombokUnclean")));
- JCStatement invokeClean = maker.Exec(maker.Apply(List.<JCExpression>nil(), maker.Ident(type.toName("$lombokClean")), List.<JCExpression>nil()));
- JCIf ifUnclean = maker.If(notClean, invokeClean, null);
- statements.append(ifUnclean);
- }
-
- for (BuilderFieldData bfd : builderFields) {
- if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) {
- bfd.singularData.getSingularizer().appendBuildCode(bfd.singularData, type, source, statements, bfd.name);
- }
- }
-
ListBuffer<JCExpression> args = new ListBuffer<JCExpression>();
- for (BuilderFieldData bfd : builderFields) {
- args.append(maker.Ident(bfd.name));
- }
+
+ // Extendable builders assign their values in the constructor, not in this build() method.
+ if (!useBuilderBasedConstructor) {
+ if (addCleaning) {
+ JCExpression notClean = maker.Unary(CTC_NOT, maker.Select(maker.Ident(type.toName("this")), type.toName("$lombokUnclean")));
+ JCStatement invokeClean = maker.Exec(maker.Apply(List.<JCExpression>nil(), maker.Ident(type.toName("$lombokClean")), List.<JCExpression>nil()));
+ JCIf ifUnclean = maker.If(notClean, invokeClean, null);
+ statements.append(ifUnclean);
+ }
+
+ for (BuilderFieldData bfd : builderFields) {
+ if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) {
+ bfd.singularData.getSingularizer().appendBuildCode(bfd.singularData, type, source, statements, bfd.name);
+ }
+ }
- if (addCleaning) {
- statements.append(maker.Exec(maker.Assign(maker.Select(maker.Ident(type.toName("this")), type.toName("$lombokUnclean")), maker.Literal(CTC_BOOLEAN, true))));
+ for (BuilderFieldData bfd : builderFields) {
+ args.append(maker.Ident(bfd.name));
+ }
+
+ if (addCleaning) {
+ statements.append(maker.Exec(maker.Assign(maker.Select(maker.Ident(type.toName("this")), type.toName("$lombokUnclean")), maker.Literal(CTC_BOOLEAN, true))));
+ }
}
if (builderName == null) {
- call = maker.NewClass(null, List.<JCExpression>nil(), returnType, args.toList(), null);
+ if (useBuilderBasedConstructor) {
+ // Use a constructor that only has this builder as parameter.
+ List<JCExpression> builderArg = List.<JCExpression>of(maker.Ident(type.toName("this")));
+ call = maker.NewClass(null, List.<JCExpression>nil(), returnType, builderArg, null);
+ } else {
+ // Use a constructor with all the fields.
+ call = maker.NewClass(null, List.<JCExpression>nil(), returnType, args.toList(), null);
+ }
statements.append(maker.Return(call));
} else {
@@ -627,12 +682,16 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> {
return null;
}
- public JavacNode makeBuilderClass(boolean isStatic, JavacNode source, JavacNode tdParent, String builderClassName, List<JCTypeParameter> typeParams, JCAnnotation ast) {
+ public JavacNode makeBuilderClass(boolean isStatic, JavacNode source, JavacNode tdParent, String builderClassName, List<JCTypeParameter> typeParams, JCAnnotation ast, String parentBuilderClassName) {
JavacTreeMaker maker = tdParent.getTreeMaker();
int modifiers = Flags.PUBLIC;
if (isStatic) modifiers |= Flags.STATIC;
JCModifiers mods = maker.Modifiers(modifiers);
- JCClassDecl builder = maker.ClassDef(mods, tdParent.toName(builderClassName), copyTypeParams(source, typeParams), null, List.<JCExpression>nil(), List.<JCTree>nil());
+ JCExpression extending = null;
+ if (parentBuilderClassName != null) {
+ extending = maker.Ident(tdParent.toName(parentBuilderClassName));
+ }
+ JCClassDecl builder = maker.ClassDef(mods, tdParent.toName(builderClassName), copyTypeParams(source, typeParams), extending, List.<JCExpression>nil(), List.<JCTree>nil());
return injectType(tdParent, builder);
}
diff --git a/src/core/lombok/javac/handlers/HandleConstructor.java b/src/core/lombok/javac/handlers/HandleConstructor.java
index 4e90b639..06ac74bd 100644
--- a/src/core/lombok/javac/handlers/HandleConstructor.java
+++ b/src/core/lombok/javac/handlers/HandleConstructor.java
@@ -23,6 +23,9 @@ package lombok.javac.handlers;
import static lombok.core.handlers.HandlerUtil.*;
import static lombok.javac.handlers.JavacHandlerUtil.*;
+
+import java.util.Arrays;
+
import static lombok.javac.Javac.*;
import lombok.AccessLevel;
@@ -51,6 +54,7 @@ import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.JCTree.JCExpression;
import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
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.JCPrimitiveTypeTree;
import com.sun.tools.javac.tree.JCTree.JCReturn;
@@ -78,7 +82,7 @@ public class HandleConstructor {
String staticName = ann.staticName();
boolean force = ann.force();
List<JavacNode> fields = force ? findFinalFields(typeNode) : List.<JavacNode>nil();
- new HandleConstructor().generateConstructor(typeNode, level, onConstructor, fields, force, staticName, SkipIfConstructorExists.NO, annotationNode);
+ new HandleConstructor().generateConstructor(typeNode, level, onConstructor, fields, force, staticName, SkipIfConstructorExists.NO, annotationNode, null, false);
}
}
@@ -100,7 +104,7 @@ public class HandleConstructor {
annotationNode.addError("This deprecated feature is no longer supported. Remove it; you can create a lombok.config file with 'lombok.anyConstructor.suppressConstructorProperties = true'.");
}
- new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findRequiredFields(typeNode), false, staticName, SkipIfConstructorExists.NO, annotationNode);
+ new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findRequiredFields(typeNode), false, staticName, SkipIfConstructorExists.NO, annotationNode, null, false);
}
}
@@ -146,7 +150,7 @@ public class HandleConstructor {
if (annotation.isExplicit("suppressConstructorProperties")) {
annotationNode.addError("This deprecated feature is no longer supported. Remove it; you can create a lombok.config file with 'lombok.anyConstructor.suppressConstructorProperties = true'.");
}
- new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findAllFields(typeNode), false, staticName, SkipIfConstructorExists.NO, annotationNode);
+ new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findAllFields(typeNode), false, staticName, SkipIfConstructorExists.NO, annotationNode, null, false);
}
}
@@ -182,7 +186,7 @@ public class HandleConstructor {
}
public void generateRequiredArgsConstructor(JavacNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, JavacNode source) {
- generateConstructor(typeNode, level, List.<JCAnnotation>nil(), findRequiredFields(typeNode), false, staticName, skipIfConstructorExists, source);
+ generateConstructor(typeNode, level, List.<JCAnnotation>nil(), findRequiredFields(typeNode), false, staticName, skipIfConstructorExists, source, null, false);
}
public enum SkipIfConstructorExists {
@@ -190,10 +194,21 @@ public class HandleConstructor {
}
public void generateAllArgsConstructor(JavacNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, JavacNode source) {
- generateConstructor(typeNode, level, List.<JCAnnotation>nil(), findAllFields(typeNode), false, staticName, skipIfConstructorExists, source);
+ generateConstructor(typeNode, level, List.<JCAnnotation>nil(), findAllFields(typeNode), false, staticName, skipIfConstructorExists, source, null, false);
}
- public void generateConstructor(JavacNode typeNode, AccessLevel level, List<JCAnnotation> onConstructor, List<JavacNode> fields, boolean allToDefault, String staticName, SkipIfConstructorExists skipIfConstructorExists, JavacNode source) {
+ /**
+ * @param builderClassnameAsParameter
+ * if {@code != null}, the only parameter of the constructor will
+ * be a builder with this classname; the constructor will then
+ * use the values within this builder to assign the fields of new
+ * instances.
+ * @param callBuilderBasedSuperConstructor
+ * if {@code true}, the constructor will explicitly call a super
+ * constructor with the builder as argument. Requires
+ * {@code builderClassAsParameter != null}.
+ */
+ public void generateConstructor(JavacNode typeNode, AccessLevel level, List<JCAnnotation> onConstructor, List<JavacNode> fields, boolean allToDefault, String staticName, SkipIfConstructorExists skipIfConstructorExists, JavacNode source, String builderClassnameAsParameter, boolean callBuilderBasedSuperConstructor) {
boolean staticConstrRequired = staticName != null && !staticName.equals("");
if (skipIfConstructorExists != SkipIfConstructorExists.NO && constructorExists(typeNode) != MemberExistsResult.NOT_EXISTS) return;
@@ -222,7 +237,7 @@ public class HandleConstructor {
}
}
- JCMethodDecl constr = createConstructor(staticConstrRequired ? AccessLevel.PRIVATE : level, onConstructor, typeNode, fields, allToDefault, source);
+ JCMethodDecl constr = createConstructor(staticConstrRequired ? AccessLevel.PRIVATE : level, onConstructor, typeNode, fields, allToDefault, source, builderClassnameAsParameter, callBuilderBasedSuperConstructor);
ListBuffer<Type> argTypes = new ListBuffer<Type>();
for (JavacNode fieldNode : fields) {
Type mirror = getMirrorForFieldType(fieldNode);
@@ -256,7 +271,25 @@ public class HandleConstructor {
mods.annotations = mods.annotations.append(annotation);
}
- public static JCMethodDecl createConstructor(AccessLevel level, List<JCAnnotation> onConstructor, JavacNode typeNode, List<JavacNode> fields, boolean allToDefault, JavacNode source) {
+ /**
+ * @param builderClassnameAsParameter
+ * if {@code != null}, the only parameter of the constructor will
+ * be a builder with this classname; the constructor will then
+ * use the values within this builder to assign the fields of new
+ * instances.
+ * @param callBuilderBasedSuperConstructor
+ * if {@code true}, the constructor will explicitly call a super
+ * constructor with the builder as argument. Requires
+ * {@code builderClassnameAsParameter != null}.
+ */
+ public static JCMethodDecl createConstructor(AccessLevel level, List<JCAnnotation> onConstructor, JavacNode typeNode, List<JavacNode> fields, boolean allToDefault, JavacNode source, String builderClassnameAsParameter, boolean callBuilderBasedSuperConstructor) {
+ if (builderClassnameAsParameter != null && builderClassnameAsParameter.isEmpty()) {
+ builderClassnameAsParameter = null;
+ }
+ if (callBuilderBasedSuperConstructor && builderClassnameAsParameter == null) {
+ source.addError("Calling a builder-based superclass constructor ('callBuilderBasedSuperConstructor') requires a non-empty 'builderClassnameAsParameter' value.");
+ }
+
JavacTreeMaker maker = typeNode.getTreeMaker();
boolean isEnum = (((JCClassDecl) typeNode.get()).mods.flags & Flags.ENUM) != 0;
@@ -274,6 +307,7 @@ public class HandleConstructor {
ListBuffer<JCStatement> assigns = new ListBuffer<JCStatement>();
ListBuffer<JCVariableDecl> params = new ListBuffer<JCVariableDecl>();
+ Name builderVariableName = typeNode.toName("b");
for (JavacNode fieldNode : fields) {
JCVariableDecl field = (JCVariableDecl) fieldNode.get();
Name fieldName = removePrefixFromField(fieldNode);
@@ -290,7 +324,17 @@ public class HandleConstructor {
}
}
JCFieldAccess thisX = maker.Select(maker.Ident(fieldNode.toName("this")), rawName);
- JCExpression assign = maker.Assign(thisX, allToDefault ? getDefaultExpr(maker, field.vartype) : maker.Ident(fieldName));
+
+ JCExpression rhs;
+ if (allToDefault) {
+ rhs = getDefaultExpr(maker, field.vartype);
+ } else if (builderClassnameAsParameter != null) {
+ rhs = maker.Select(maker.Ident(builderVariableName), rawName);
+ } else {
+ rhs = maker.Ident(fieldName);
+ }
+ JCExpression assign = maker.Assign(thisX, rhs);
+
assigns.append(maker.Exec(assign));
}
@@ -300,6 +344,23 @@ public class HandleConstructor {
}
if (onConstructor != null) mods.annotations = mods.annotations.appendList(copyAnnotations(onConstructor));
+ if (builderClassnameAsParameter != null) {
+ // Create a constructor that has just the builder as parameter.
+ params.clear();
+ long flags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, typeNode.getContext());
+ Name builderClassname = typeNode.toName(builderClassnameAsParameter);
+ JCVariableDecl param = maker.VarDef(maker.Modifiers(flags), builderVariableName, maker.Ident(builderClassname), null);
+ params.append(param);
+ }
+
+ if (callBuilderBasedSuperConstructor) {
+ // The first statement must be the call to the super constructor.
+ JCMethodInvocation callToSuperConstructor = maker.Apply(List.<JCExpression>nil(),
+ maker.Ident(typeNode.toName("super")),
+ List.<JCExpression>of(maker.Ident(builderVariableName)));
+ assigns.prepend(maker.Exec(callToSuperConstructor));
+ }
+
return recursiveSetGeneratedBy(maker.MethodDef(mods, typeNode.toName("<init>"),
null, List.<JCTypeParameter>nil(), params.toList(), List.<JCExpression>nil(),
maker.Block(0L, nullChecks.appendList(assigns).toList()), null), source.get(), typeNode.getContext());