aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/core/lombok/Builder.java17
-rw-r--r--src/core/lombok/eclipse/handlers/HandleBuilder.java56
-rw-r--r--src/core/lombok/eclipse/handlers/HandleConstructor.java72
3 files changed, 125 insertions, 20 deletions
diff --git a/src/core/lombok/Builder.java b/src/core/lombok/Builder.java
index 7a965486..855e96e8 100644
--- a/src/core/lombok/Builder.java
+++ b/src/core/lombok/Builder.java
@@ -123,6 +123,23 @@ public @interface Builder {
String builderClassName() default "";
/**
+ * If true, the generated builder class will extend the builder of the
+ * superclass. In this way, the builder will also contain methods for fields
+ * from the superclass. 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.
+ */
+ boolean inherit() 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 afa03538..4f152fe3 100644
--- a/src/core/lombok/eclipse/handlers/HandleBuilder.java
+++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java
@@ -139,6 +139,10 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
String builderMethodName = builderInstance.builderMethodName();
String buildMethodName = builderInstance.buildMethodName();
String builderClassName = builderInstance.builderClassName();
+
+ boolean inherit = builderInstance.inherit();
+ String superclassBuilderClassName = builderInstance.superclassBuilderClassName();
+
String toBuilderMethodName = "toBuilder";
boolean toBuilder = builderInstance.toBuilder();
List<char[]> typeArgsForToBuilder = null;
@@ -146,6 +150,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 +196,26 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
allFields.add(fieldNode);
}
- new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, allFields, false, null, SkipIfConstructorExists.I_AM_BUILDER, null,
- 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 callSuperConstructor = inherit && td.superclass != null;
+ new HandleConstructor().generateConstructor(tdParent, AccessLevel.PROTECTED, allFields, false, null, SkipIfConstructorExists.I_AM_BUILDER, true,
+ Collections.<Annotation>emptyList(), annotationNode, builderClassName, callSuperConstructor);
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;
+ }
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 +230,10 @@ 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;
+ }
MethodDeclaration md = (MethodDeclaration) parent.get();
tdParent = parent.up();
isStatic = md.isStatic();
@@ -341,7 +363,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 +418,7 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
if (constructorExists(builderType) == MemberExistsResult.NOT_EXISTS) {
ConstructorDeclaration cd = HandleConstructor.createConstructor(
AccessLevel.PACKAGE, builderType, Collections.<EclipseNode>emptyList(), false, null,
- annotationNode, Collections.<Annotation>emptyList());
+ annotationNode, Collections.<Annotation>emptyList(), null, false);
if (cd != null) injectMethod(builderType, cd);
}
@@ -405,7 +427,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;
+ MethodDeclaration md = generateBuildMethod(isStatic, buildMethodName, nameOfStaticBuilderMethod, returnType, builderFields, builderType, thrownExceptions, addCleaning, ast, useBuilderBasedConstructor);
if (md != null) injectMethod(builderType, md);
}
@@ -514,7 +537,13 @@ 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>();
@@ -554,7 +583,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 +707,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 +715,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 a3b0585d..eb11e6e4 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, null, onConstructor, annotationNode);
+ new HandleConstructor().generateConstructor(typeNode, level, fields, force, staticName, SkipIfConstructorExists.NO, null, onConstructor, annotationNode, null, false);
}
}
@@ -121,7 +123,7 @@ public class HandleConstructor {
new HandleConstructor().generateConstructor(
typeNode, level, findRequiredFields(typeNode), false, staticName, SkipIfConstructorExists.NO,
- suppressConstructorProperties, onConstructor, annotationNode);
+ suppressConstructorProperties, onConstructor, annotationNode, null, false);
}
}
@@ -183,7 +185,7 @@ public class HandleConstructor {
new HandleConstructor().generateConstructor(
typeNode, level, findAllFields(typeNode), false, staticName, SkipIfConstructorExists.NO,
- suppressConstructorProperties, onConstructor, annotationNode);
+ suppressConstructorProperties, onConstructor, annotationNode, null, false);
}
}
@@ -205,23 +207,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, null, onConstructor, sourceNode);
+ generateConstructor(typeNode, level, findRequiredFields(typeNode), false, staticName, skipIfConstructorExists, null, 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, null, onConstructor, sourceNode);
+ generateConstructor(typeNode, level, findAllFields(typeNode), false, staticName, skipIfConstructorExists, null, 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,
- Boolean suppressConstructorProperties, List<Annotation> onConstructor, EclipseNode sourceNode) {
+ Boolean suppressConstructorProperties, List<Annotation> onConstructor, EclipseNode sourceNode, String builderClassnameAsParameter, boolean callBuilderBasedSuperConstructor) {
ASTNode source = sourceNode.get();
boolean staticConstrRequired = staticName != null && !staticName.equals("");
@@ -256,7 +269,7 @@ public class HandleConstructor {
ConstructorDeclaration constr = createConstructor(
staticConstrRequired ? AccessLevel.PRIVATE : level, typeNode, fields, allToDefault,
- suppressConstructorProperties, sourceNode, onConstructor);
+ suppressConstructorProperties, sourceNode, onConstructor, builderClassnameAsParameter, callBuilderBasedSuperConstructor);
injectMethod(typeNode, constr);
if (staticConstrRequired) {
MethodDeclaration staticConstr = createStaticConstructor(level, staticName, typeNode, allToDefault ? Collections.<EclipseNode>emptyList() : fields, source);
@@ -296,9 +309,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,
- Boolean suppressConstructorProperties, EclipseNode sourceNode, List<Annotation> onConstructor) {
+ Boolean suppressConstructorProperties, 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());
@@ -320,7 +352,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;
@@ -343,7 +380,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);
@@ -364,7 +410,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;