From f46aa53e8c592f562ef5dc21e65e9c74c98a8ecc Mon Sep 17 00:00:00 2001 From: Jan Rieke Date: Fri, 10 Feb 2017 17:02:29 +0100 Subject: builder-based constructors for type @Builders [Eclipse] --- src/core/lombok/Builder.java | 17 +++++ .../lombok/eclipse/handlers/HandleBuilder.java | 56 ++++++++++++++--- .../lombok/eclipse/handlers/HandleConstructor.java | 72 ++++++++++++++++++---- 3 files changed, 125 insertions(+), 20 deletions(-) (limited to 'src') 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 @@ -122,6 +122,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 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 { 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 typeArgsForToBuilder = null; @@ -146,6 +150,9 @@ public class HandleBuilder extends EclipseAnnotationHandler { 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 { allFields.add(fieldNode); } - new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, allFields, false, null, SkipIfConstructorExists.I_AM_BUILDER, null, - Collections.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.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 { 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 { 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 { if (constructorExists(builderType) == MemberExistsResult.NOT_EXISTS) { ConstructorDeclaration cd = HandleConstructor.createConstructor( AccessLevel.PACKAGE, builderType, Collections.emptyList(), false, null, - annotationNode, Collections.emptyList()); + annotationNode, Collections.emptyList(), null, false); if (cd != null) injectMethod(builderType, cd); } @@ -405,7 +427,8 @@ public class HandleBuilder extends EclipseAnnotationHandler { } 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 { return decl; } - public MethodDeclaration generateBuildMethod(boolean isStatic, String name, char[] staticName, TypeReference returnType, List 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 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 statements = new ArrayList(); @@ -554,7 +583,13 @@ public class HandleBuilder extends EclipseAnnotationHandler { 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 { 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 { 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 fields = force ? findFinalFields(typeNode) : Collections.emptyList(); List 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 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 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 fields, boolean allToDefault, String staticName, SkipIfConstructorExists skipIfConstructorExists, - Boolean suppressConstructorProperties, List onConstructor, EclipseNode sourceNode) { + Boolean suppressConstructorProperties, List 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.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 fields, boolean allToDefault, - Boolean suppressConstructorProperties, EclipseNode sourceNode, List onConstructor) { + Boolean suppressConstructorProperties, EclipseNode sourceNode, List 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; -- cgit From 4ec2e5135c373242dd00151eedf40997b0d75fe7 Mon Sep 17 00:00:00 2001 From: Jan Rieke Date: Wed, 15 Feb 2017 15:25:41 +0100 Subject: builder-based constructors for type @Builders [javac] --- src/core/lombok/javac/handlers/HandleBuilder.java | 66 +++++++++++++++--- .../lombok/javac/handlers/HandleConstructor.java | 79 +++++++++++++++++++--- 2 files changed, 126 insertions(+), 19 deletions(-) (limited to 'src') 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 { 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 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 { allFields.append(fieldNode); } - new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, List.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.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("")) { - 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 { 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 { 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 { } if (constructorExists(builderType) == MemberExistsResult.NOT_EXISTS) { - JCMethodDecl cd = HandleConstructor.createConstructor(AccessLevel.PACKAGE, List.nil(), builderType, List.nil(), false, null, annotationNode); + JCMethodDecl cd = HandleConstructor.createConstructor(AccessLevel.PACKAGE, List.nil(), builderType, List.nil(), false, null, annotationNode, null, false); if (cd != null) injectMethod(builderType, cd); } @@ -359,7 +387,8 @@ public class HandleBuilder extends JavacAnnotationHandler { } 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 { */ } - private JCMethodDecl generateBuildMethod(boolean isStatic, String buildName, Name builderName, JCExpression returnType, java.util.List builderFields, JavacNode type, List 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 builderFields, JavacNode type, List thrownExceptions, JCTree source, boolean addCleaning, boolean useBuilderBasedConstructor) { JavacTreeMaker maker = type.getTreeMaker(); JCExpression call; @@ -525,7 +560,14 @@ public class HandleBuilder extends JavacAnnotationHandler { } if (builderName == null) { - call = maker.NewClass(null, List.nil(), returnType, args.toList(), null); + if (useBuilderBasedConstructor) { + // Use a constructor that only has this builder as parameter. + List builderArg = List.of(maker.Ident(type.toName("this"))); + call = maker.NewClass(null, List.nil(), returnType, builderArg, null); + } else { + // Use a constructor with all the fields. + call = maker.NewClass(null, List.nil(), returnType, args.toList(), null); + } statements.append(maker.Return(call)); } else { @@ -629,12 +671,16 @@ public class HandleBuilder extends JavacAnnotationHandler { return null; } - public JavacNode makeBuilderClass(boolean isStatic, JavacNode tdParent, String builderClassName, List typeParams, JCAnnotation ast) { + public JavacNode makeBuilderClass(boolean isStatic, JavacNode tdParent, String builderClassName, List 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.nil(), List.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.nil(), List.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 fields = force ? findFinalFields(typeNode) : List.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.nil(), findRequiredFields(typeNode), false, staticName, skipIfConstructorExists, null, source); + generateConstructor(typeNode, level, List.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.nil(), findAllFields(typeNode), false, staticName, skipIfConstructorExists, null, source); + generateConstructor(typeNode, level, List.nil(), findAllFields(typeNode), false, staticName, skipIfConstructorExists, null, source, null, false); } - public void generateConstructor(JavacNode typeNode, AccessLevel level, List onConstructor, List 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 onConstructor, List 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 argTypes = new ListBuffer(); 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 onConstructor, JavacNode typeNode, List 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 onConstructor, JavacNode typeNode, List 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 assigns = new ListBuffer(); ListBuffer params = new ListBuffer(); + 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.nil(), + maker.Ident(typeNode.toName("super")), + List.of(maker.Ident(builderVariableName))); + assigns.prepend(maker.Exec(callToSuperConstructor)); + } + return recursiveSetGeneratedBy(maker.MethodDef(mods, typeNode.toName(""), null, List.nil(), params.toList(), List.nil(), maker.Block(0L, nullChecks.appendList(assigns).toList()), null), source.get(), typeNode.getContext()); -- cgit From bd51fa8484fde587eef10fc58e4527f26ec16430 Mon Sep 17 00:00:00 2001 From: Jan Rieke Date: Mon, 13 Mar 2017 14:32:57 +0100 Subject: activate extendable builders explicitly (ensure backwards compatibility) --- src/core/lombok/Builder.java | 23 ++++++++++++++++++---- .../lombok/eclipse/handlers/HandleBuilder.java | 15 +++++++++++--- src/core/lombok/javac/handlers/HandleBuilder.java | 18 +++++++++++++---- 3 files changed, 45 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/core/lombok/Builder.java b/src/core/lombok/Builder.java index 855e96e8..9cf82191 100644 --- a/src/core/lombok/Builder.java +++ b/src/core/lombok/Builder.java @@ -123,14 +123,29 @@ public @interface Builder { String builderClassName() default ""; /** - * If true, the generated builder class will extend the builder of the + * 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. 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. + * 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. The parent {@code @Builder} must be + * {@link #extendable()}.
+ * 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}).
+ * Note that this builder must be a type {@code @Builder}; this feature does + * neither work for constructor nor method {@code @Builder}s.
+ * 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()}). diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java index 4f152fe3..4bfc89f4 100644 --- a/src/core/lombok/eclipse/handlers/HandleBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java @@ -141,6 +141,7 @@ public class HandleBuilder extends EclipseAnnotationHandler { String builderClassName = builderInstance.builderClassName(); boolean inherit = builderInstance.inherit(); + boolean extendable = inherit || builderInstance.extendable(); // inherit implies extendable String superclassBuilderClassName = builderInstance.superclassBuilderClassName(); String toBuilderMethodName = "toBuilder"; @@ -203,9 +204,9 @@ public class HandleBuilder extends EclipseAnnotationHandler { superclassBuilderClassName = new String(td.superclass.getLastToken()) + "Builder"; } - boolean callSuperConstructor = inherit && td.superclass != null; + boolean callBuilderBasedSuperConstructor = inherit && td.superclass != null; new HandleConstructor().generateConstructor(tdParent, AccessLevel.PROTECTED, allFields, false, null, SkipIfConstructorExists.I_AM_BUILDER, true, - Collections.emptyList(), annotationNode, builderClassName, callSuperConstructor); + Collections.emptyList(), annotationNode, extendable ? builderClassName : null, callBuilderBasedSuperConstructor); returnType = namePlusTypeParamsToTypeReference(td.name, td.typeParameters, p); typeParams = td.typeParameters; @@ -216,6 +217,10 @@ public class HandleBuilder extends EclipseAnnotationHandler { 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."); @@ -234,6 +239,10 @@ public class HandleBuilder extends EclipseAnnotationHandler { 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(); @@ -427,7 +436,7 @@ public class HandleBuilder extends EclipseAnnotationHandler { } if (methodExists(buildMethodName, builderType, -1) == MemberExistsResult.NOT_EXISTS) { - boolean useBuilderBasedConstructor = parent.get() instanceof TypeDeclaration; + 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); } diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java index 32bf7949..7701d85c 100644 --- a/src/core/lombok/javac/handlers/HandleBuilder.java +++ b/src/core/lombok/javac/handlers/HandleBuilder.java @@ -110,6 +110,7 @@ public class HandleBuilder extends JavacAnnotationHandler { String toBuilderMethodName = "toBuilder"; boolean inherit = builderInstance.inherit(); + boolean extendable = inherit || builderInstance.extendable(); // inherit implies extendable String superclassBuilderClassName = builderInstance.superclassBuilderClassName(); boolean toBuilder = builderInstance.toBuilder(); @@ -176,8 +177,8 @@ public class HandleBuilder extends JavacAnnotationHandler { superclassBuilderClassName = extendsClause + "Builder"; } - boolean callSuperConstructor = inherit && extendsClause != null; - new HandleConstructor().generateConstructor(tdParent, AccessLevel.PROTECTED, List.nil(), allFields.toList(), false, null, SkipIfConstructorExists.I_AM_BUILDER, null, annotationNode, builderClassName, callSuperConstructor); + boolean callBuilderBasedSuperConstructor = inherit && extendsClause != null; + new HandleConstructor().generateConstructor(tdParent, AccessLevel.PROTECTED, List.nil(), allFields.toList(), false, null, SkipIfConstructorExists.I_AM_BUILDER, null, annotationNode, extendable ? builderClassName : null, callBuilderBasedSuperConstructor); returnType = namePlusTypeParamsToTypeReference(tdParent.getTreeMaker(), td.name, td.typarams); typeParams = td.typarams; @@ -187,7 +188,12 @@ public class HandleBuilder extends JavacAnnotationHandler { if (inherit) { annotationNode.addError("@Builder(inherit=true) is only supported for type builders."); return; - } JCMethodDecl jmd = (JCMethodDecl) fillParametersFrom.get(); + } + 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."); return; @@ -205,6 +211,10 @@ public class HandleBuilder extends JavacAnnotationHandler { 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(); @@ -387,7 +397,7 @@ public class HandleBuilder extends JavacAnnotationHandler { } if (methodExists(buildMethodName, builderType, -1) == MemberExistsResult.NOT_EXISTS) { - boolean useBuilderBasedConstructor = parent.get() instanceof JCClassDecl; + 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); } -- cgit From f51bbce3e396a0151bc0242d00e250f2bc720316 Mon Sep 17 00:00:00 2001 From: Jan Rieke Date: Mon, 13 Mar 2017 15:59:46 +0100 Subject: do not assign values in build() method for extendable builders (WIP) --- .../lombok/eclipse/handlers/HandleBuilder.java | 43 ++++++++++++---------- src/core/lombok/javac/handlers/HandleBuilder.java | 41 +++++++++++---------- 2 files changed, 45 insertions(+), 39 deletions(-) (limited to 'src') diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java index 4bfc89f4..74ef57a7 100644 --- a/src/core/lombok/eclipse/handlers/HandleBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java @@ -556,31 +556,34 @@ public class HandleBuilder extends EclipseAnnotationHandler { MethodDeclaration out = new MethodDeclaration(((CompilationUnitDeclaration) type.top().get()).compilationResult); out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; List statements = new ArrayList(); + List args = new ArrayList(); - 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 args = new ArrayList(); - 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; diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java index 7701d85c..41ff64e0 100644 --- a/src/core/lombok/javac/handlers/HandleBuilder.java +++ b/src/core/lombok/javac/handlers/HandleBuilder.java @@ -546,27 +546,30 @@ public class HandleBuilder extends JavacAnnotationHandler { JCExpression call; ListBuffer statements = new ListBuffer(); - - 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.nil(), maker.Ident(type.toName("$lombokClean")), List.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 args = new ListBuffer(); - 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.nil(), maker.Ident(type.toName("$lombokClean")), List.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) { -- cgit From 2e0d4183c236a7b42f2f6ff0e4a68812bba3e559 Mon Sep 17 00:00:00 2001 From: Jan Rieke Date: Tue, 14 Mar 2017 11:56:41 +0100 Subject: moved generation of builder-based constructor to HandleBuilder --- src/core/lombok/javac/handlers/HandleBuilder.java | 93 +++++++++++++++++++++- .../lombok/javac/handlers/HandleConstructor.java | 27 ++----- 2 files changed, 99 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java index 43520656..be8c9765 100644 --- a/src/core/lombok/javac/handlers/HandleBuilder.java +++ b/src/core/lombok/javac/handlers/HandleBuilder.java @@ -43,6 +43,7 @@ import com.sun.tools.javac.tree.JCTree.JCIdent; import com.sun.tools.javac.tree.JCTree.JCIf; import com.sun.tools.javac.tree.JCTree.JCLiteral; 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.JCStatement; @@ -63,6 +64,7 @@ import lombok.core.AST.Kind; import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; import lombok.core.handlers.HandlerUtil; +import lombok.delombok.LombokOptionsFactory; import lombok.experimental.NonFinal; import lombok.javac.Javac; import lombok.javac.JavacAnnotationHandler; @@ -87,6 +89,7 @@ public class HandleBuilder extends JavacAnnotationHandler { } private static class BuilderFieldData { + JavacNode fieldNode; JCExpression type; Name rawName; Name name; @@ -159,6 +162,7 @@ public class HandleBuilder extends JavacAnnotationHandler { // Value will only skip making a field final if it has an explicit @NonFinal annotation, so we check for that. if (fd.init != null && valuePresent && !hasAnnotation(NonFinal.class, fieldNode)) continue; BuilderFieldData bfd = new BuilderFieldData(); + bfd.fieldNode = fieldNode; bfd.rawName = fd.name; bfd.name = removePrefixFromField(fieldNode); bfd.type = fd.vartype; @@ -178,7 +182,11 @@ public class HandleBuilder extends JavacAnnotationHandler { } boolean callBuilderBasedSuperConstructor = inherit && extendsClause != null; - new HandleConstructor().generateConstructor(tdParent, AccessLevel.PROTECTED, List.nil(), allFields.toList(), false, null, SkipIfConstructorExists.I_AM_BUILDER, annotationNode, extendable ? builderClassName : null, callBuilderBasedSuperConstructor); + if (extendable) { + generateBuilderBasedConstructor(tdParent, builderFields, annotationNode, builderClassName, callBuilderBasedSuperConstructor); + } else { + new HandleConstructor().generateConstructor(tdParent, AccessLevel.PROTECTED, List.nil(), allFields.toList(), false, null, SkipIfConstructorExists.I_AM_BUILDER, annotationNode); + } returnType = namePlusTypeParamsToTypeReference(tdParent.getTreeMaker(), td.name, td.typarams); typeParams = td.typarams; @@ -505,6 +513,89 @@ public class HandleBuilder extends JavacAnnotationHandler { JCBlock body = maker.Block(0, List.of(statement)); return maker.MethodDef(maker.Modifiers(Flags.PUBLIC), type.toName(toBuilderMethodName), namePlusTypeParamsToTypeReference(maker, type.toName(builderClassName), typeParams), List.nil(), List.nil(), List.nil(), body, null); } + + /** + * Generates a constructor that has a builder as the only parameter. + * + * @param builderClassnameAsParameter + * the only parameter of the constructor will be a builder with + * this classname; the constructor will 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}. + */ + private void generateBuilderBasedConstructor(JavacNode typeNode, java.util.List builderFields, JavacNode source, String builderClassnameAsParameter, boolean callBuilderBasedSuperConstructor) { + if (builderClassnameAsParameter == null || builderClassnameAsParameter.isEmpty()) { + source.addError("A builder-based constructor requires a non-empty 'builderClassnameAsParameter' value."); + } + + JavacTreeMaker maker = typeNode.getTreeMaker(); + + AccessLevel level = AccessLevel.PROTECTED; + boolean isEnum = (((JCClassDecl) typeNode.get()).mods.flags & Flags.ENUM) != 0; + if (isEnum) { + level = AccessLevel.PRIVATE; + } + + ListBuffer nullChecks = new ListBuffer(); + ListBuffer assigns = new ListBuffer(); + + Name builderVariableName = typeNode.toName("b"); + for (BuilderFieldData field : builderFields) { + List nonNulls = findAnnotations(field.fieldNode, NON_NULL_PATTERN); + if (!nonNulls.isEmpty()) { + JCStatement nullCheck = generateNullCheck(maker, field.fieldNode, source); + if (nullCheck != null) { + nullChecks.append(nullCheck); + } + } + + if (field.singularData != null && field.singularData.getSingularizer() != null) { + field.singularData.getSingularizer().appendBuildCode(field.singularData, field.fieldNode, field.type, assigns, field.name); + } else { + JCFieldAccess thisX = maker.Select(maker.Ident(field.fieldNode.toName("this")), field.rawName); + + JCExpression rhs; + rhs = maker.Select(maker.Ident(builderVariableName), field.rawName); + JCExpression assign = maker.Assign(thisX, rhs); + + assigns.append(maker.Exec(assign)); + } + } + + JCModifiers mods = maker.Modifiers(toJavacModifier(level), List.nil()); + + // Add ConstructorProperties + JCExpression constructorPropertiesType = chainDots(typeNode, "java", "beans", "ConstructorProperties"); + ListBuffer fieldNames = new ListBuffer(); + fieldNames.append(maker.Literal(builderVariableName.toString())); + JCExpression fieldNamesArray = maker.NewArray(null, List.nil(), fieldNames.toList()); + JCAnnotation annotation = maker.Annotation(constructorPropertiesType, List.of(fieldNamesArray)); + mods.annotations = mods.annotations.append(annotation); + + // Create a constructor that has just the builder as parameter. + ListBuffer params = new ListBuffer(); + 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.nil(), + maker.Ident(typeNode.toName("super")), + List.of(maker.Ident(builderVariableName))); + assigns.prepend(maker.Exec(callToSuperConstructor)); + } + + JCMethodDecl constr = recursiveSetGeneratedBy(maker.MethodDef(mods, typeNode.toName(""), + null, List.nil(), params.toList(), List.nil(), + maker.Block(0L, nullChecks.appendList(assigns).toList()), null), source.get(), typeNode.getContext()); + + injectMethod(typeNode, constr, null, Javac.createVoidType(typeNode.getSymbolTable(), CTC_VOID)); + } private JCMethodDecl generateCleanMethod(java.util.List builderFields, JavacNode type, JCTree source) { JavacTreeMaker maker = type.getTreeMaker(); diff --git a/src/core/lombok/javac/handlers/HandleConstructor.java b/src/core/lombok/javac/handlers/HandleConstructor.java index 06ac74bd..eed3ebd3 100644 --- a/src/core/lombok/javac/handlers/HandleConstructor.java +++ b/src/core/lombok/javac/handlers/HandleConstructor.java @@ -24,8 +24,6 @@ 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; @@ -82,7 +80,7 @@ public class HandleConstructor { String staticName = ann.staticName(); boolean force = ann.force(); List fields = force ? findFinalFields(typeNode) : List.nil(); - new HandleConstructor().generateConstructor(typeNode, level, onConstructor, fields, force, staticName, SkipIfConstructorExists.NO, annotationNode, null, false); + new HandleConstructor().generateConstructor(typeNode, level, onConstructor, fields, force, staticName, SkipIfConstructorExists.NO, annotationNode); } } @@ -104,7 +102,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, null, false); + new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findRequiredFields(typeNode), false, staticName, SkipIfConstructorExists.NO, annotationNode); } } @@ -150,7 +148,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, null, false); + new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findAllFields(typeNode), false, staticName, SkipIfConstructorExists.NO, annotationNode); } } @@ -186,7 +184,7 @@ public class HandleConstructor { } public void generateRequiredArgsConstructor(JavacNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, JavacNode source) { - generateConstructor(typeNode, level, List.nil(), findRequiredFields(typeNode), false, staticName, skipIfConstructorExists, source, null, false); + generateConstructor(typeNode, level, List.nil(), findRequiredFields(typeNode), false, staticName, skipIfConstructorExists, source); } public enum SkipIfConstructorExists { @@ -194,21 +192,10 @@ public class HandleConstructor { } public void generateAllArgsConstructor(JavacNode typeNode,