diff options
-rw-r--r-- | src/core/lombok/javac/handlers/HandleBuilder.java | 66 | ||||
-rw-r--r-- | src/core/lombok/javac/handlers/HandleConstructor.java | 79 |
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()); |