builder-based constructors for type @Builders [javac]

This commit is contained in:
Jan Rieke committed 2017-02-15 17:10:40 +01:00
1 parent f46aa53e8c
commit 4ec2e5135c
2 files changed
+126 -19

No files matched your search

@@ -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);
}
@@ -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());