aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/core/lombok/javac/handlers/HandleBuilder.java66
-rw-r--r--src/core/lombok/javac/handlers/HandleConstructor.java79
2 files changed, 126 insertions, 19 deletions
diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java
index 8588fc16..32bf7949 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,19 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> {
String buildMethodName = builderInstance.buildMethodName();
String builderClassName = builderInstance.builderClassName();
String toBuilderMethodName = "toBuilder";
+
+ boolean inherit = builderInstance.inherit();
+ 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,15 +167,27 @@ 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, null, annotationNode);
+ if (builderClassName.isEmpty()) {
+ builderClassName = td.name.toString() + "Builder";
+ }
+
+ JCTree extendsClause = Javac.getExtendsClause(td);
+ if (superclassBuilderClassName.isEmpty() && extendsClause != null) {
+ superclassBuilderClassName = extendsClause + "Builder";
+ }
+
+ boolean callSuperConstructor = inherit && extendsClause != null;
+ new HandleConstructor().generateConstructor(tdParent, AccessLevel.PROTECTED, List.<JCAnnotation>nil(), allFields.toList(), false, null, SkipIfConstructorExists.I_AM_BUILDER, null, annotationNode, builderClassName, callSuperConstructor);
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>")) {
- JCMethodDecl jmd = (JCMethodDecl) fillParametersFrom.get();
+ if (inherit) {
+ annotationNode.addError("@Builder(inherit=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.");
return;
@@ -177,6 +201,10 @@ 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;
+ }
tdParent = parent.up();
JCClassDecl td = (JCClassDecl) tdParent.get();
JCMethodDecl jmd = (JCMethodDecl) fillParametersFrom.get();
@@ -298,7 +326,7 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> {
JavacNode 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 {
JCClassDecl builderTypeDeclaration = (JCClassDecl) builderType.get();
if (isStatic && !builderTypeDeclaration.getModifiers().getFlags().contains(Modifier.STATIC)) {
@@ -350,7 +378,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, null, annotationNode);
+ JCMethodDecl cd = HandleConstructor.createConstructor(AccessLevel.PACKAGE, List.<JCAnnotation>nil(), builderType, List.<JavacNode>nil(), false, null, annotationNode, null, false);
if (cd != null) injectMethod(builderType, cd);
}
@@ -359,7 +387,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;
+ JCMethodDecl md = generateBuildMethod(isStatic, buildMethodName, nameOfBuilderMethod, returnType, builderFields, builderType, thrownExceptions, ast, addCleaning, useBuilderBasedConstructor);
if (md != null) injectMethod(builderType, md);
}
@@ -496,7 +525,13 @@ 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;
@@ -525,7 +560,14 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> {
}
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 {
@@ -629,12 +671,16 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> {
return null;
}
- public JavacNode makeBuilderClass(boolean isStatic, JavacNode tdParent, String builderClassName, List<JCTypeParameter> typeParams, JCAnnotation ast) {
+ public JavacNode makeBuilderClass(boolean isStatic, 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(maker, 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(maker, 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 ef4ba088..3481b2c3 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, null, annotationNode);
+ new HandleConstructor().generateConstructor(typeNode, level, onConstructor, fields, force, staticName, SkipIfConstructorExists.NO, null, annotationNode, null, false);
}
}
@@ -103,7 +107,7 @@ public class HandleConstructor {
suppressConstructorProperties = suppress;
}
- new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findRequiredFields(typeNode), false, staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, annotationNode);
+ new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findRequiredFields(typeNode), false, staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, annotationNode, null, false);
}
}
@@ -152,7 +156,7 @@ public class HandleConstructor {
boolean suppress = ann.suppressConstructorProperties();
suppressConstructorProperties = suppress;
}
- new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findAllFields(typeNode), false, staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, annotationNode);
+ new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findAllFields(typeNode), false, staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, annotationNode, null, false);
}
}
@@ -188,7 +192,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, null, source);
+ generateConstructor(typeNode, level, List.<JCAnnotation>nil(), findRequiredFields(typeNode), false, staticName, skipIfConstructorExists, null, source, null, false);
}
public enum SkipIfConstructorExists {
@@ -196,10 +200,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, null, source);
+ generateConstructor(typeNode, level, List.<JCAnnotation>nil(), findAllFields(typeNode), false, staticName, skipIfConstructorExists, null, source, null, false);
}
- public void generateConstructor(JavacNode typeNode, AccessLevel level, List<JCAnnotation> onConstructor, List<JavacNode> fields, boolean allToDefault, String staticName, SkipIfConstructorExists skipIfConstructorExists, Boolean suppressConstructorProperties, 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, Boolean suppressConstructorProperties, JavacNode source, String builderClassnameAsParameter, boolean callBuilderBasedSuperConstructor) {
boolean staticConstrRequired = staticName != null && !staticName.equals("");
if (skipIfConstructorExists != SkipIfConstructorExists.NO && constructorExists(typeNode) != MemberExistsResult.NOT_EXISTS) return;
@@ -228,7 +243,7 @@ public class HandleConstructor {
}
}
- JCMethodDecl constr = createConstructor(staticConstrRequired ? AccessLevel.PRIVATE : level, onConstructor, typeNode, fields, allToDefault, suppressConstructorProperties, source);
+ JCMethodDecl constr = createConstructor(staticConstrRequired ? AccessLevel.PRIVATE : level, onConstructor, typeNode, fields, allToDefault, suppressConstructorProperties, source, builderClassnameAsParameter, callBuilderBasedSuperConstructor);
ListBuffer<Type> argTypes = new ListBuffer<Type>();
for (JavacNode fieldNode : fields) {
Type mirror = getMirrorForFieldType(fieldNode);
@@ -262,7 +277,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, Boolean suppressConstructorProperties, 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, Boolean suppressConstructorProperties, 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;
@@ -280,6 +313,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);
@@ -296,7 +330,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));
}
@@ -306,6 +350,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());