diff options
16 files changed, 492 insertions, 152 deletions
diff --git a/src/core/lombok/Builder.java b/src/core/lombok/Builder.java index a16717cc..7ae43bfa 100644 --- a/src/core/lombok/Builder.java +++ b/src/core/lombok/Builder.java @@ -132,6 +132,48 @@ public @interface Builder { String builderClassName() default ""; /** + * Should the generated builder extend the parent class's builder? + * + * If {@code true}, the generated builder class will extend the builder of the + * superclass. This means the builder also contains the methods for fields from + * the superclass. + * <p> + * Note that both this builder and the superclass' builder must be a builder + * for a type (so, not for a static method or a constructor). You must mark + * the parent extendable: {@code @Builder(extensible = true)}. + * <p> + * This builder will also be {@code extensible = true} if you set {@code inherit = true}. + * + * @see #extensible() + */ + boolean inherit() default false; + + /** + * Should the generated builder be extensible by subclasses? + * + * If {@code true} the generated builder class will be extensible by + * {@code @Builder} classes of this class's subclasses, by marking them + * with {@code @Builder(inherit = true)}. + * <p> + * The {@code @Builder} extensible system only works for {@code @Builder} + * annotations on types (so, not on a static method or a constructor). + * + * @see #inherit() + */ + boolean extensible() default false; + + /** + * Name of the builder class in the superclass. + * + * Only relevant if {@code inherit = true} + * + * Default: {@code (SuperclassTypeName)Builder}. + * + * @see #inherit() + */ + String superclassBuilderClassName() default ""; + + /** * If true, generate an instance method to obtain a builder that is initialized with the values of this instance. * Legal only if {@code @Builder} is used on a constructor, on the type itself, or on a static method that returns * an instance of the declaring type. diff --git a/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java b/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java index bc779dab..2cec2388 100644 --- a/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java +++ b/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java @@ -45,6 +45,8 @@ import org.eclipse.jdt.internal.compiler.ast.OperatorIds; import org.eclipse.jdt.internal.compiler.ast.ParameterizedQualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference; import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.Reference; +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.ThisReference; @@ -216,7 +218,7 @@ public class EclipseSingularsRecipes { public abstract List<EclipseNode> generateFields(SingularData data, EclipseNode builderType); public abstract void generateMethods(SingularData data, boolean deprecate, EclipseNode builderType, boolean fluent, boolean chain); - public abstract void appendBuildCode(SingularData data, EclipseNode builderType, List<Statement> statements, char[] targetVariableName); + public abstract void appendBuildCode(SingularData data, EclipseNode builderType, List<Statement> statements, char[] targetVariableName, String builderVariable); public boolean requiresCleaning() { try { @@ -307,16 +309,16 @@ public class EclipseSingularsRecipes { private static final char[] SIZE_TEXT = new char[] {'s', 'i', 'z', 'e'}; /** Generates 'this.<em>name</em>.size()' as an expression; if nullGuard is true, it's this.name == null ? 0 : this.name.size(). */ - protected Expression getSize(EclipseNode builderType, char[] name, boolean nullGuard) { + protected Expression getSize(EclipseNode builderType, char[] name, boolean nullGuard, String builderVariable) { MessageSend invoke = new MessageSend(); - ThisReference thisRef = new ThisReference(0, 0); + Reference thisRef = getBuilderReference(builderVariable); FieldReference thisDotName = new FieldReference(name, 0L); thisDotName.receiver = thisRef; invoke.receiver = thisDotName; invoke.selector = SIZE_TEXT; if (!nullGuard) return invoke; - ThisReference cdnThisRef = new ThisReference(0, 0); + Reference cdnThisRef = getBuilderReference(builderVariable); FieldReference cdnThisDotName = new FieldReference(name, 0L); cdnThisDotName.receiver = cdnThisRef; NullLiteral nullLiteral = new NullLiteral(0, 0); @@ -345,5 +347,14 @@ public class EclipseSingularsRecipes { return new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, NULL_POSS); } + + /** @return a {@code SingleNameReference} to the builder in the variable <code>builderVariable</code>. If {@ code builderVariable == "this"}, a {@code ThisReference} is returned. */ + protected static Reference getBuilderReference(String builderVariable) { + if ("this".equals(builderVariable)) { + return new ThisReference(0, 0); + } else { + return new SingleNameReference(builderVariable.toCharArray(), 0); + } + } } } diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java index d4cdc654..b5c6e793 100644 --- a/src/core/lombok/eclipse/handlers/HandleBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java @@ -25,6 +25,7 @@ import static lombok.eclipse.Eclipse.*; import static lombok.core.handlers.HandlerUtil.*; import static lombok.eclipse.handlers.EclipseHandlerUtil.*; +import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -40,6 +41,7 @@ import org.eclipse.jdt.internal.compiler.ast.Assignment; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.ast.ConditionalExpression; import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ExplicitConstructorCall; import org.eclipse.jdt.internal.compiler.ast.Expression; import org.eclipse.jdt.internal.compiler.ast.FalseLiteral; import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; @@ -50,6 +52,7 @@ import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.OperatorIds; import org.eclipse.jdt.internal.compiler.ast.ParameterizedQualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference; +import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference; import org.eclipse.jdt.internal.compiler.ast.QualifiedThisReference; import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; @@ -81,6 +84,7 @@ import lombok.core.HandlerPriority; import lombok.eclipse.Eclipse; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.EclipseHandlerUtil.MemberExistsResult; import lombok.eclipse.handlers.EclipseSingularsRecipes.EclipseSingularizer; import lombok.eclipse.handlers.EclipseSingularsRecipes.SingularData; import lombok.eclipse.handlers.HandleConstructor.SkipIfConstructorExists; @@ -102,6 +106,7 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { } private static class BuilderFieldData { + EclipseNode fieldNode; TypeReference type; char[] rawName; char[] name; @@ -155,6 +160,10 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { String builderMethodName = builderInstance.builderMethodName(); String buildMethodName = builderInstance.buildMethodName(); String builderClassName = builderInstance.builderClassName(); + + boolean inherit = builderInstance.inherit(); + boolean extensible = inherit || builderInstance.extensible(); // inherit implies extendable + String toBuilderMethodName = "toBuilder"; boolean toBuilder = builderInstance.toBuilder(); List<char[]> typeArgsForToBuilder = null; @@ -182,9 +191,11 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { boolean addCleaning = false; boolean isStatic = true; + TypeDeclaration td = null; + if (parent.get() instanceof TypeDeclaration) { tdParent = parent; - TypeDeclaration td = (TypeDeclaration) tdParent.get(); + td = (TypeDeclaration) tdParent.get(); List<EclipseNode> allFields = new ArrayList<EclipseNode>(); boolean valuePresent = (hasAnnotation(lombok.Value.class, parent) || hasAnnotation("lombok.experimental.Value", parent)); @@ -194,6 +205,7 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { boolean isFinal = ((fd.modifiers & ClassFileConstants.AccFinal) != 0) || (valuePresent && !hasAnnotation(NonFinal.class, fieldNode)); BuilderFieldData bfd = new BuilderFieldData(); + bfd.fieldNode = fieldNode; bfd.rawName = fieldNode.getName().toCharArray(); bfd.name = removePrefixFromField(fieldNode); bfd.type = fd.type; @@ -227,15 +239,29 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { allFields.add(fieldNode); } - handleConstructor.generateConstructor(tdParent, AccessLevel.PACKAGE, allFields, false, null, SkipIfConstructorExists.I_AM_BUILDER, - Collections.<Annotation>emptyList(), annotationNode); + if (builderClassName.isEmpty()) builderClassName = new String(td.name) + "Builder"; + + if (extensible) { + boolean callBuilderBasedSuperConstructor = td.superclass != null; + generateBuilderBasedConstructor(tdParent, builderFields, annotationNode, builderClassName, callBuilderBasedSuperConstructor); + } else { + handleConstructor.generateConstructor(tdParent, AccessLevel.PACKAGE, allFields, false, null, SkipIfConstructorExists.I_AM_BUILDER, + Collections.<Annotation>emptyList(), annotationNode); + } 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; + } + if (extensible) { + annotationNode.addError("@Builder(extensible=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."); @@ -243,13 +269,21 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { } tdParent = parent.up(); - TypeDeclaration td = (TypeDeclaration) tdParent.get(); + td = (TypeDeclaration) tdParent.get(); returnType = namePlusTypeParamsToTypeReference(td.name, td.typeParameters, p); typeParams = td.typeParameters; thrownExceptions = cd.thrownExceptions; 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; + } + if (extensible) { + annotationNode.addError("@Builder(extendable=true) is only supported for type builders."); + return; + } MethodDeclaration md = (MethodDeclaration) parent.get(); tdParent = parent.up(); isStatic = md.isStatic(); @@ -380,7 +414,20 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { EclipseNode builderType = findInnerClass(tdParent, builderClassName); if (builderType == null) { - builderType = makeBuilderClass(isStatic, tdParent, builderClassName, typeParams, ast); + String superclassBuilderClassName = null; + if (inherit) { + if (td.superclass == null) { + annotationNode.addError("@Builder(inherit = true) requires that your class has an 'extends' clause."); + return; + } + + superclassBuilderClassName = builderInstance.superclassBuilderClassName(); + if (superclassBuilderClassName == null || superclassBuilderClassName.isEmpty()) { + superclassBuilderClassName = new String(td.superclass.getLastToken()) + "Builder"; + } + } + + builderType = makeBuilderClass(isStatic, tdParent, builderClassName, typeParams, ast, superclassBuilderClassName); } else { TypeDeclaration builderTypeDeclaration = (TypeDeclaration) builderType.get(); if (isStatic && (builderTypeDeclaration.modifiers & ClassFileConstants.AccStatic) == 0) { @@ -444,7 +491,7 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { } if (methodExists(buildMethodName, builderType, -1) == MemberExistsResult.NOT_EXISTS) { - MethodDeclaration md = generateBuildMethod(tdParent, isStatic, buildMethodName, nameOfStaticBuilderMethod, returnType, builderFields, builderType, thrownExceptions, addCleaning, ast); + MethodDeclaration md = generateBuildMethod(tdParent, isStatic, buildMethodName, nameOfStaticBuilderMethod, returnType, builderFields, builderType, thrownExceptions, addCleaning, ast, extensible); if (md != null) injectMethod(builderType, md); } @@ -492,8 +539,7 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { int pS = source.sourceStart, pE = source.sourceEnd; long p = (long) pS << 32 | pE; - MethodDeclaration out = new MethodDeclaration( - ((CompilationUnitDeclaration) type.top().get()).compilationResult); + MethodDeclaration out = new MethodDeclaration(((CompilationUnitDeclaration) type.top().get()).compilationResult); out.selector = methodName.toCharArray(); out.modifiers = ClassFileConstants.AccPublic; out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; @@ -531,6 +577,95 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { } + /** + * Generates a constructor that has a builder as the only parameter. + * The values from the builder are used to initialize the fields of new instances. + * + * @param typeNode + * the type (with the {@code @Builder} annotation) for which a + * constructor should be generated. + * @param builderFields a list of fields in the builder which should be assigned to new instances. + * @param sourceNode the annotation (used for setting source code locations for the generated code). + * @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}. + */ + private void generateBuilderBasedConstructor(EclipseNode typeNode, List<BuilderFieldData> builderFields, EclipseNode sourceNode, + String builderClassnameAsParameter, boolean callBuilderBasedSuperConstructor) { + + ASTNode source = sourceNode.get(); + + TypeDeclaration typeDeclaration = ((TypeDeclaration) typeNode.get()); + long p = (long) source.sourceStart << 32 | source.sourceEnd; + + boolean isEnum = (((TypeDeclaration) typeNode.get()).modifiers & ClassFileConstants.AccEnum) != 0; + AccessLevel level = isEnum ? AccessLevel.PRIVATE : AccessLevel.PROTECTED; + + ConstructorDeclaration constructor = new ConstructorDeclaration(((CompilationUnitDeclaration) typeNode.top().get()).compilationResult); + + constructor.modifiers = toEclipseModifier(level); + constructor.selector = typeDeclaration.name; + 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; + constructor.typeParameters = null; + constructor.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + constructor.bodyStart = constructor.declarationSourceStart = constructor.sourceStart = source.sourceStart; + constructor.bodyEnd = constructor.declarationSourceEnd = constructor.sourceEnd = source.sourceEnd; + constructor.arguments = null; + + List<Statement> statements = new ArrayList<Statement>(); + List<Statement> nullChecks = new ArrayList<Statement>(); + + for (BuilderFieldData fieldNode : builderFields) { + char[] fieldName = removePrefixFromField(fieldNode.fieldNode); + FieldReference thisX = new FieldReference(fieldNode.rawName, p); + int s = (int) (p >> 32); + int e = (int) p; + thisX.receiver = new ThisReference(s, e); + + Expression assignmentExpr; + if (fieldNode.singularData != null && fieldNode.singularData.getSingularizer() != null) { + fieldNode.singularData.getSingularizer().appendBuildCode(fieldNode.singularData, typeNode, statements, fieldNode.name, "b"); + assignmentExpr = new SingleNameReference(fieldNode.name, p); + } else { + char[][] variableInBuilder = new char[][] {"b".toCharArray(), fieldName}; + long[] positions = new long[] {p, p}; + assignmentExpr = new QualifiedNameReference(variableInBuilder, positions, s, e); + } + + Assignment assignment = new Assignment(thisX, assignmentExpr, (int) p); + statements.add(assignment); + Annotation[] nonNulls = findAnnotations((FieldDeclaration)fieldNode.fieldNode.get(), NON_NULL_PATTERN); + if (nonNulls.length != 0) { + Statement nullCheck = generateNullCheck((FieldDeclaration)fieldNode.fieldNode.get(), sourceNode); + if (nullCheck != null) { + nullChecks.add(nullCheck); + } + } + } + + nullChecks.addAll(statements); + constructor.statements = nullChecks.isEmpty() ? null : nullChecks.toArray(new Statement[nullChecks.size()]); + constructor.arguments = new Argument[] {new Argument("b".toCharArray(), p, new SingleTypeReference(builderClassnameAsParameter.toCharArray(), p), Modifier.FINAL)}; + + constructor.traverse(new SetGeneratedByVisitor(source), typeDeclaration.scope); + + injectMethod(typeNode, constructor); + } + private MethodDeclaration generateCleanMethod(List<BuilderFieldData> builderFields, EclipseNode builderType, ASTNode source) { List<Statement> statements = new ArrayList<Statement>(); @@ -553,49 +688,58 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { return decl; } - public MethodDeclaration generateBuildMethod(EclipseNode tdParent, boolean isStatic, String name, char[] staticName, TypeReference returnType, List<BuilderFieldData> builderFields, EclipseNode type, TypeReference[] thrownExceptions, boolean addCleaning, ASTNode source) { + /** + * @param useBuilderBasedConstructor + * if true, the {@code build()} method will use a constructor + * that takes the builder instance as parameter (instead of a + * constructor with all relevant fields as parameters) + */ + public MethodDeclaration generateBuildMethod(EclipseNode tdParent, boolean isStatic, String name, char[] staticName, TypeReference returnType, List<BuilderFieldData> builderFields, EclipseNode type, TypeReference[] thrownExceptions, boolean addCleaning, ASTNode source, boolean useBuilderBasedConstructor) { MethodDeclaration out = new MethodDeclaration(((CompilationUnitDeclaration) type.top().get()).compilationResult); out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; List<Statement> statements = new ArrayList<Statement>(); + List<Expression> args = new ArrayList<Expression>(); - 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); + // 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)); } - } - - List<Expression> args = new ArrayList<Expression>(); - for (BuilderFieldData bfd : builderFields) { - if (bfd.nameOfSetFlag != null) { - MessageSend inv = new MessageSend(); - inv.sourceStart = source.sourceStart; - inv.sourceEnd = source.sourceEnd; - inv.receiver = new SingleNameReference(((TypeDeclaration) tdParent.get()).name, 0L); - inv.selector = bfd.nameOfDefaultProvider; - inv.typeArguments = typeParameterNames(((TypeDeclaration) type.get()).typeParameters); - - args.add(new ConditionalExpression( - new SingleNameReference(bfd.nameOfSetFlag, 0L), - new SingleNameReference(bfd.name, 0L), - inv)); - } else { - args.add(new SingleNameReference(bfd.name, 0L)); + + for (BuilderFieldData bfd : builderFields) { + if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { + bfd.singularData.getSingularizer().appendBuildCode(bfd.singularData, type, statements, bfd.name, "this"); + } + } + + for (BuilderFieldData bfd : builderFields) { + if (bfd.nameOfSetFlag != null) { + MessageSend inv = new MessageSend(); + inv.sourceStart = source.sourceStart; + inv.sourceEnd = source.sourceEnd; + inv.receiver = new SingleNameReference(((TypeDeclaration) tdParent.get()).name, 0L); + inv.selector = bfd.nameOfDefaultProvider; + inv.typeArguments = typeParameterNames(((TypeDeclaration) type.get()).typeParameters); + + args.add(new ConditionalExpression( + new SingleNameReference(bfd.nameOfSetFlag, 0L), + new SingleNameReference(bfd.name, 0L), + inv)); + } else { + 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; @@ -607,7 +751,13 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { if (staticName == null) { AllocationExpression allocationStatement = new AllocationExpression(); allocationStatement.type = copyType(out.returnType); - allocationStatement.arguments = args.isEmpty() ? null : args.toArray(new Expression[args.size()]); + if (useBuilderBasedConstructor) { + // Use a constructor that only has this builder as parameter. + allocationStatement.arguments = new Expression[] {new ThisReference(0, 0)}; + } else { + // Use a constructor with all the fields. + allocationStatement.arguments = args.isEmpty() ? null : args.toArray(new Expression[args.size()]); + } statements.add(new ReturnStatement(allocationStatement, 0, 0)); } else { MessageSend invoke = new MessageSend(); @@ -756,7 +906,7 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { return null; } - public EclipseNode makeBuilderClass(boolean isStatic, EclipseNode tdParent, String builderClassName, TypeParameter[] typeParams, ASTNode source) { + public EclipseNode makeBuilderClass(boolean isStatic, EclipseNode tdParent, String builderClassName, TypeParameter[] typeParams, ASTNode source, String parentBuilderClassName) { TypeDeclaration parent = (TypeDeclaration) tdParent.get(); TypeDeclaration builder = new TypeDeclaration(parent.compilationResult); builder.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; @@ -764,6 +914,7 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { if (isStatic) builder.modifiers |= ClassFileConstants.AccStatic; builder.typeParameters = copyTypeParams(typeParams, source); builder.name = builderClassName.toCharArray(); + if (parentBuilderClassName != null) builder.superclass = new SingleTypeReference(parentBuilderClassName.toCharArray(), 0); builder.traverse(new SetGeneratedByVisitor(source), (ClassScope) null); return injectType(tdParent, builder); } diff --git a/src/core/lombok/eclipse/handlers/HandleConstructor.java b/src/core/lombok/eclipse/handlers/HandleConstructor.java index 62e2c18c..a2940b88 100644 --- a/src/core/lombok/eclipse/handlers/HandleConstructor.java +++ b/src/core/lombok/eclipse/handlers/HandleConstructor.java @@ -267,7 +267,7 @@ public class HandleConstructor { } } - private static final char[][] JAVA_BEANS_CONSTRUCTORPROPERTIES = new char[][] { "java".toCharArray(), "beans".toCharArray(), "ConstructorProperties".toCharArray() }; + public static final char[][] JAVA_BEANS_CONSTRUCTORPROPERTIES = new char[][] { "java".toCharArray(), "beans".toCharArray(), "ConstructorProperties".toCharArray() }; public static Annotation[] createConstructorProperties(ASTNode source, Collection<EclipseNode> fields) { if (fields.isEmpty()) return null; diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java index 8e925b3f..eb2c9b35 100644 --- a/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java @@ -200,7 +200,7 @@ abstract class EclipseGuavaSingularizer extends EclipseSingularizer { injectMethod(builderType, md); } - @Override public void appendBuildCode(SingularData data, EclipseNode builderType, List<Statement> statements, char[] targetVariableName) { + @Override public void appendBuildCode(SingularData data, EclipseNode builderType, List<Statement> statements, char[] targetVariableName, String builderVariable) { TypeReference varType = new QualifiedTypeReference(fromQualifiedName(data.getTargetFqn()), NULL_POSS); String simpleTypeName = getSimpleTargetTypeName(data); int agrumentsCount = getTypeArgumentsCount(); @@ -219,14 +219,14 @@ abstract class EclipseGuavaSingularizer extends EclipseSingularizer { invokeBuild = new MessageSend(); invokeBuild.selector = new char[] {'b', 'u', 'i', 'l', 'd'}; FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L); - thisDotField.receiver = new ThisReference(0, 0); + thisDotField.receiver = getBuilderReference(builderVariable); invokeBuild.receiver = thisDotField; } Expression isNull; { //this.pluralName == null FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L); - thisDotField.receiver = new ThisReference(0, 0); + thisDotField.receiver = getBuilderReference(builderVariable); isNull = new EqualExpression(thisDotField, new NullLiteral(0, 0), OperatorIds.EQUAL_EQUAL); } diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSingularizer.java index 576115b0..f512bacf 100644 --- a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSingularizer.java +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSingularizer.java @@ -45,7 +45,6 @@ import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; import org.eclipse.jdt.internal.compiler.ast.Statement; import org.eclipse.jdt.internal.compiler.ast.SwitchStatement; -import org.eclipse.jdt.internal.compiler.ast.ThisReference; import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.mangosdk.spi.ProviderFor; @@ -56,9 +55,9 @@ public class EclipseJavaUtilListSingularizer extends EclipseJavaUtilListSetSingu return LombokImmutableList.of("java.util.List", "java.util.Collection", "java.lang.Iterable"); } - @Override public void appendBuildCode(SingularData data, EclipseNode builderType, List<Statement> statements, char[] targetVariableName) { + @Override public void appendBuildCode(SingularData data, EclipseNode builderType, List<Statement> statements, char[] targetVariableName, String builderVariable) { if (useGuavaInstead(builderType)) { - guavaListSetSingularizer.appendBuildCode(data, builderType, statements, targetVariableName); + guavaListSetSingularizer.appendBuildCode(data, builderType, statements, targetVariableName, builderVariable); return; } @@ -76,7 +75,7 @@ public class EclipseJavaUtilListSingularizer extends EclipseJavaUtilListSetSingu /* case 1: (singleton) break; */ { switchContents.add(new CaseStatement(makeIntLiteral(new char[] {'1'}, null), 0, 0)); FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L); - thisDotField.receiver = new ThisReference(0, 0); + thisDotField.receiver = getBuilderReference(builderVariable); MessageSend thisDotFieldGet0 = new MessageSend(); thisDotFieldGet0.receiver = thisDotField; thisDotFieldGet0.selector = new char[] {'g', 'e', 't'}; @@ -97,7 +96,7 @@ public class EclipseJavaUtilListSingularizer extends EclipseJavaUtilListSetSingu Expression argToUnmodifiable; /* new j.u.ArrayList<Generics>(this.pluralName); */ { FieldReference thisDotPluralName = new FieldReference(data.getPluralName(), 0L); - thisDotPluralName.receiver = new ThisReference(0, 0); + thisDotPluralName.receiver = getBuilderReference(builderVariable); TypeReference targetTypeExpr = new QualifiedTypeReference(JAVA_UTIL_ARRAYLIST, NULL_POSS); targetTypeExpr = addTypeArgs(1, false, builderType, targetTypeExpr, data.getTypeArgs()); AllocationExpression constructorCall = new AllocationExpression(); @@ -117,7 +116,7 @@ public class EclipseJavaUtilListSingularizer extends EclipseJavaUtilListSetSingu SwitchStatement switchStat = new SwitchStatement(); switchStat.statements = switchContents.toArray(new Statement[switchContents.size()]); - switchStat.expression = getSize(builderType, data.getPluralName(), true); + switchStat.expression = getSize(builderType, data.getPluralName(), true, builderVariable); TypeReference localShadowerType = new QualifiedTypeReference(Eclipse.fromQualifiedName(data.getTargetFqn()), NULL_POSS); localShadowerType = addTypeArgs(1, false, builderType, localShadowerType, data.getTypeArgs()); diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java index 9aac32a3..ee0e6409 100644 --- a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java @@ -298,16 +298,16 @@ public class EclipseJavaUtilMapSingularizer extends EclipseJavaUtilSingularizer injectMethod(builderType, md); } - @Override public void appendBuildCode(SingularData data, EclipseNode builderType, List<Statement> statements, char[] targetVariableName) { + @Override public void appendBuildCode(SingularData data, EclipseNode builderType, List<Statement> statements, char[] targetVariableName, String builderVariable) { if (useGuavaInstead(builderType)) { - guavaMapSingularizer.appendBuildCode(data, builderType, statements, targetVariableName); + guavaMapSingularizer.appendBuildCode(data, builderType, statements, targetVariableName, builderVariable); return; } if (data.getTargetFqn().equals("java.util.Map")) { - statements.addAll(createJavaUtilSetMapInitialCapacitySwitchStatements(data, builderType, true, "emptyMap", "singletonMap", "LinkedHashMap")); + statements.addAll(createJavaUtilSetMapInitialCapacitySwitchStatements(data, builderType, true, "emptyMap", "singletonMap", "LinkedHashMap", builderVariable)); } else { - statements.addAll(createJavaUtilSimpleCreationAndFillStatements(data, builderType, true, true, false, true, "TreeMap")); + statements.addAll(createJavaUtilSimpleCreationAndFillStatements(data, builderType, true, true, false, true, "TreeMap", builderVariable)); } } } diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSetSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSetSingularizer.java index 2d16eae0..200e615e 100644 --- a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSetSingularizer.java +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSetSingularizer.java @@ -37,16 +37,16 @@ public class EclipseJavaUtilSetSingularizer extends EclipseJavaUtilListSetSingul return LombokImmutableList.of("java.util.Set", "java.util.SortedSet", "java.util.NavigableSet"); } - @Override public void appendBuildCode(SingularData data, EclipseNode builderType, List<Statement> statements, char[] targetVariableName) { + @Override public void appendBuildCode(SingularData data, EclipseNode builderType, List<Statement> statements, char[] targetVariableName, String builderVariable) { if (useGuavaInstead(builderType)) { - guavaListSetSingularizer.appendBuildCode(data, builderType, statements, targetVariableName); + guavaListSetSingularizer.appendBuildCode(data, builderType, statements, targetVariableName, builderVariable); return; } if (data.getTargetFqn().equals("java.util.Set")) { - statements.addAll(createJavaUtilSetMapInitialCapacitySwitchStatements(data, builderType, false, "emptySet", "singleton", "LinkedHashSet")); + statements.addAll(createJavaUtilSetMapInitialCapacitySwitchStatements(data, builderType, false, "emptySet", "singleton", "LinkedHashSet", builderVariable)); } else { - statements.addAll(createJavaUtilSimpleCreationAndFillStatements(data, builderType, false, true, false, true, "TreeSet")); + statements.addAll(createJavaUtilSimpleCreationAndFillStatements(data, builderType, false, true, false, true, "TreeSet", builderVariable)); } } } diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSingularizer.java index 6661f4af..8bcfa65d 100644 --- a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSingularizer.java +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSingularizer.java @@ -90,7 +90,7 @@ abstract class EclipseJavaUtilSingularizer extends EclipseSingularizer { return Boolean.TRUE.equals(node.getAst().readConfiguration(ConfigurationKeys.SINGULAR_USE_GUAVA)); } - protected List<Statement> createJavaUtilSetMapInitialCapacitySwitchStatements(SingularData data, EclipseNode builderType, boolean mapMode, String emptyCollectionMethod, String singletonCollectionMethod, String targetType) { + protected List<Statement> createJavaUtilSetMapInitialCapacitySwitchStatements(SingularData data, EclipseNode builderType, boolean mapMode, String emptyCollectionMethod, String singletonCollectionMethod, String targetType, String builderVariable) { List<Statement> switchContents = new ArrayList<Statement>(); char[] keyName = mapMode ? (new String(data.getPluralName()) + "$key").toCharArray() : data.getPluralName(); @@ -112,7 +112,7 @@ abstract class EclipseJavaUtilSingularizer extends EclipseSingularizer { /* !mapMode: pluralName = java.util.Collections.singletonCollectionMethod(this.pluralName.get(0)); mapMode: pluralName = java.util.Collections.singletonCollectionMethod(this.pluralName$key.get(0), this.pluralName$value.get(0)); */ { FieldReference thisDotKey = new FieldReference(keyName, 0L); - thisDotKey.receiver = new ThisReference(0, 0); + thisDotKey.receiver = getBuilderReference(builderVariable); MessageSend thisDotKeyGet0 = new MessageSend(); thisDotKeyGet0.receiver = thisDotKey; thisDotKeyGet0.selector = new char[] {'g', 'e', 't'}; @@ -122,7 +122,7 @@ abstract class EclipseJavaUtilSingularizer extends EclipseSingularizer { if (mapMode) { char[] valueName = (new String(data.getPluralName()) + "$value").toCharArray(); FieldReference thisDotValue = new FieldReference(valueName, 0L); - thisDotValue.receiver = new ThisReference(0, 0); + thisDotValue.receiver = getBuilderReference(builderVariable); MessageSend thisDotValueGet0 = new MessageSend(); thisDotValueGet0.receiver = thisDotValue; thisDotValueGet0.selector = new char[] {'g', 'e', 't'}; @@ -143,12 +143,12 @@ abstract class EclipseJavaUtilSingularizer extends EclipseSingularizer { { // default: switchContents.add(new CaseStatement(null, 0, 0)); - switchContents.addAll(createJavaUtilSimpleCreationAndFillStatements(data, builderType, mapMode, false, true, emptyCollectionMethod == null, targetType)); + switchContents.addAll(createJavaUtilSimpleCreationAndFillStatements(data, builderType, mapMode, false, true, emptyCollectionMethod == null, targetType, builderVariable)); } SwitchStatement switchStat = new SwitchStatement(); switchStat.statements = switchContents.toArray(new Statement[switchContents.size()]); - switchStat.expression = getSize(builderType, keyName, true); + switchStat.expression = getSize(builderType, keyName, true, builderVariable); TypeReference localShadowerType = new QualifiedTypeReference(fromQualifiedName(data.getTargetFqn()), NULL_POSS); localShadowerType = addTypeArgs(mapMode ? 2 : 1, false, builderType, localShadowerType, data.getTypeArgs()); @@ -157,7 +157,7 @@ abstract class EclipseJavaUtilSingularizer extends EclipseSingularizer { return Arrays.asList(varDefStat, switchStat); } - protected List<Statement> createJavaUtilSimpleCreationAndFillStatements(SingularData data, EclipseNode builderType, boolean mapMode, boolean defineVar, boolean addInitialCapacityArg, boolean nullGuard, String targetType) { + protected List<Statement> createJavaUtilSimpleCreationAndFillStatements(SingularData data, EclipseNode builderType, boolean mapMode, boolean defineVar, boolean addInitialCapacityArg, boolean nullGuard, String targetType, String builderVariable) { char[] varName = mapMode ? (new String(data.getPluralName()) + "$key").toCharArray() : data.getPluralName(); Statement createStat; { @@ -166,11 +166,11 @@ abstract class EclipseJavaUtilSingularizer extends EclipseSingularizer { if (addInitialCapacityArg) { // this.varName.size() < MAX_POWER_OF_2 ? 1 + this.varName.size() + (this.varName.size() - 3) / 3 : Integer.MAX_VALUE; // lessThanCutOff = this.varName.size() < MAX_POWER_OF_2 - Expression lessThanCutoff = new BinaryExpression(getSize(builderType, varName, nullGuard), makeIntLiteral("0x40000000".toCharArray(), null), OperatorIds.LESS); + Expression lessThanCutoff = new BinaryExpression(getSize(builderType, varName, nullGuard, builderVariable), makeIntLiteral("0x40000000".toCharArray(), null), OperatorIds.LESS); FieldReference integerMaxValue = new FieldReference("MAX_VALUE".toCharArray(), 0L); integerMaxValue.receiver = new QualifiedNameReference(TypeConstants.JAVA_LANG_INTEGER, NULL_POSS, 0, 0); - Expression sizeFormulaLeft = new BinaryExpression(makeIntLiteral(new char[] {'1'}, null), getSize(builderType, varName, nullGuard), OperatorIds.PLUS); - Expression sizeFormulaRightLeft = new BinaryExpression(getSize(builderType, varName, nullGuard), makeIntLiteral(new char[] {'3'}, null), OperatorIds.MINUS); + Expression sizeFormulaLeft = new BinaryExpression(makeIntLiteral(new char[] {'1'}, null), getSize(builderType, varName, nullGuard, builderVariable), OperatorIds.PLUS); + Expression sizeFormulaRightLeft = new BinaryExpression(getSize(builderType, varName, nullGuard, builderVariable), makeIntLiteral(new char[] {'3'}, null), OperatorIds.MINUS); Expression sizeFormulaRight = new BinaryExpression(sizeFormulaRightLeft, makeIntLiteral(new char[] {'3'}, null), OperatorIds.DIVIDE); Expression sizeFormula = new BinaryExpression(sizeFormulaLeft, sizeFormulaRight, OperatorIds.PLUS); Expression cond = new ConditionalExpression(lessThanCutoff, sizeFormula, integerMaxValue); @@ -203,9 +203,9 @@ abstract class EclipseJavaUtilSingularizer extends EclipseSingularizer { pluralnameDotPut.selector = new char[] {'p', 'u', 't'}; pluralnameDotPut.receiver = new SingleNameReference(data.getPluralName(), 0L); FieldReference thisDotKey = new FieldReference(varName, 0L); - thisDotKey.receiver = new ThisReference(0, 0); + thisDotKey.receiver = getBuilderReference(builderVariable); FieldReference thisDotValue = new FieldReference((new String(data.getPluralName()) + "$value").toCharArray(), 0L); - thisDotValue.receiver = new ThisReference(0, 0); + thisDotValue.receiver = getBuilderReference(builderVariable); MessageSend keyArg = new MessageSend(); keyArg.receiver = thisDotKey; keyArg.arguments = new Expression[] {new SingleNameReference(iVar, 0L)}; @@ -219,7 +219,7 @@ abstract class EclipseJavaUtilSingularizer extends EclipseSingularizer { LocalDeclaration forInit = new LocalDeclaration(iVar, 0, 0); forInit.type = TypeReference.baseTypeReference(TypeIds.T_int, 0); forInit.initialization = makeIntLiteral(new char[] {'0'}, null); - Expression checkExpr = new BinaryExpression(new SingleNameReference(iVar, 0L), getSize(builderType, varName, nullGuard), OperatorIds.LESS); + Expression checkExpr = new BinaryExpression(new SingleNameReference(iVar, 0L), getSize(builderType, varName, nullGuard, builderVariable), OperatorIds.LESS); Expression incrementExpr = new PostfixExpression(new SingleNameReference(iVar, 0L), IntLiteral.One, OperatorIds.PLUS, 0); fillStat = new ForStatement(new Statement[] {forInit}, checkExpr, new Statement[] {incrementExpr}, pluralnameDotPut, true, 0, 0); } else { @@ -228,14 +228,14 @@ abstract class EclipseJavaUtilSingularizer extends EclipseSingularizer { pluralnameDotAddAll.selector = new char[] {'a', 'd', 'd', 'A', 'l', 'l'}; pluralnameDotAddAll.receiver = new SingleNameReference(data.getPluralName(), 0L); FieldReference thisDotPluralname = new FieldReference(varName, 0L); - thisDotPluralname.receiver = new ThisReference(0, 0); + thisDotPluralname.receiver = getBuilderReference(builderVariable); pluralnameDotAddAll.arguments = new Expression[] {thisDotPluralname}; fillStat = pluralnameDotAddAll; } if (nullGuard) { FieldReference thisDotField = new FieldReference(varName, 0L); - thisDotField.receiver = new ThisReference(0, 0); + thisDotField.receiver = getBuilderReference(builderVariable); Expression cond = new EqualExpression(thisDotField, new NullLiteral(0, 0), OperatorIds.NOT_EQUAL); fillStat = new IfStatement(cond, fillStat, 0, 0); } diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java index 86ac00e6..4c575d91 100644 --- a/src/core/lombok/javac/handlers/HandleBuilder.java +++ b/src/core/lombok/javac/handlers/HandleBuilder.java @@ -39,6 +39,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; @@ -83,6 +84,7 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { } private static class BuilderFieldData { + JavacNode fieldNode; JCExpression type; Name rawName; Name name; @@ -107,6 +109,10 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { String buildMethodName = builderInstance.buildMethodName(); String builderClassName = builderInstance.builderClassName(); String toBuilderMethodName = "toBuilder"; + + boolean inherit = builderInstance.inherit(); + boolean extensible = inherit || builderInstance.extensible(); // inherit implies extendable + boolean toBuilder = builderInstance.toBuilder(); java.util.List<Name> typeArgsForToBuilder = null; @@ -135,9 +141,11 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { boolean addCleaning = false; boolean isStatic = true; + JCClassDecl td = null; + if (parent.get() instanceof JCClassDecl) { tdParent = parent; - JCClassDecl td = (JCClassDecl) tdParent.get(); + td = (JCClassDecl) tdParent.get(); ListBuffer<JavacNode> allFields = new ListBuffer<JavacNode>(); boolean valuePresent = (hasAnnotation(lombok.Value.class, parent) || hasAnnotation("lombok.experimental.Value", parent)); for (JavacNode fieldNode : HandleConstructor.findAllFields(tdParent, true)) { @@ -145,6 +153,7 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { JavacNode isDefault = findAnnotation(Builder.Default.class, fieldNode, true); boolean isFinal = (fd.mods.flags & Flags.FINAL) != 0 || (valuePresent && !hasAnnotation(NonFinal.class, fieldNode)); BuilderFieldData bfd = new BuilderFieldData(); + bfd.fieldNode = fieldNode; bfd.rawName = fd.name; bfd.name = removePrefixFromField(fieldNode); bfd.type = fd.vartype; @@ -178,14 +187,30 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { allFields.append(fieldNode); } - handleConstructor.generateConstructor(tdParent, AccessLevel.PACKAGE, List.<JCAnnotation>nil(), allFields.toList(), false, null, SkipIfConstructorExists.I_AM_BUILDER, annotationNode); + if (builderClassName.isEmpty()) builderClassName = td.name.toString() + "Builder"; + + JCTree extendsClause = Javac.getExtendsClause(td); + + if (extensible) { + boolean callBuilderBasedSuperConstructor = extendsClause != null; + generateBuilderBasedConstructor(tdParent, builderFields, annotationNode, builderClassName, callBuilderBasedSuperConstructor); + } else { + handleConstructor.generateConstructor(tdParent, AccessLevel.PROTECTED, List.<JCAnnotation>nil(), allFields.toList(), false, null, SkipIfConstructorExists.I_AM_BUILDER, annotationNode); + } 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>")) { + if (inherit) { + annotationNode.addError("@Builder(inherit=true) is only supported for type builders."); + return; + } + if (extensible) { + 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."); @@ -193,15 +218,23 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { } tdParent = parent.up(); - JCClassDecl td = (JCClassDecl) tdParent.get(); + td = (JCClassDecl) tdParent.get(); returnType = namePlusTypeParamsToTypeReference(tdParent.getTreeMaker(), td.name, td.typarams); typeParams = td.typarams; thrownExceptions = jmd.thrown; 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; + } + if (extensible) { + annotationNode.addError("@Builder(extendable=true) is only supported for type builders."); + return; + } tdParent = parent.up(); - JCClassDecl td = (JCClassDecl) tdParent.get(); + td = (JCClassDecl) tdParent.get(); JCMethodDecl jmd = (JCMethodDecl) fillParametersFrom.get(); isStatic = (jmd.mods.flags & Flags.STATIC) != 0; JCExpression fullReturnType = jmd.restype; @@ -333,7 +366,18 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { JavacNode builderType = findInnerClass(tdParent, builderClassName); if (builderType == null) { - builderType = makeBuilderClass(isStatic, annotationNode, tdParent, builderClassName, typeParams, ast); + String superclassBuilderClassName = null; + if (inherit) { + JCTree extendsClause = Javac.getExtendsClause(td); + if (extendsClause == null) { + annotationNode.addError("@Builder(inherit = true) requires that your class has an 'extends' clause."); + return; + } + + superclassBuilderClassName = builderInstance.superclassBuilderClassName(); + if (superclassBuilderClassName == null || superclassBuilderClassName.isEmpty()) superclassBuilderClassName = extendsClause + "Builder"; + } + builderType = makeBuilderClass(isStatic, annotationNode, tdParent, builderClassName, typeParams, ast, superclassBuilderClassName); } else { JCClassDecl builderTypeDeclaration = (JCClassDecl) builderType.get(); if (isStatic && !builderTypeDeclaration.getModifiers().getFlags().contains(Modifier.STATIC)) { @@ -393,7 +437,7 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { } if (methodExists(buildMethodName, builderType, -1) == MemberExistsResult.NOT_EXISTS) { - JCMethodDecl md = generateBuildMethod(tdParent, isStatic, buildMethodName, nameOfBuilderMethod, returnType, builderFields, builderType, thrownExceptions, ast, addCleaning); + JCMethodDecl md = generateBuildMethod(tdParent, isStatic, buildMethodName, nameOfBuilderMethod, returnType, builderFields, builderType, thrownExceptions, ast, addCleaning, extensible); if (md != null) injectMethod(builderType, md); } @@ -502,6 +546,82 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { JCBlock body = maker.Block(0, List.<JCStatement>of(statement)); return maker.MethodDef(maker.Modifiers(Flags.PUBLIC), type.toName(toBuilderMethodName), namePlusTypeParamsToTypeReference(maker, type.toName(builderClassName), typeParams), List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null); } + + /** + * Generates a constructor that has a builder as the only parameter. + * The values from the builder are used to initialize the fields of new instances. + * + * @param typeNode + * the type (with the {@code @Builder} annotation) for which a + * constructor should be generated. + * @param builderFields a list of fields in the builder which should be assigned to new instances. + * @param source the annotation (used for setting source code locations for the generated code). + * @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}. + */ + private void generateBuilderBasedConstructor(JavacNode typeNode, java.util.List<BuilderFieldData> builderFields, JavacNode source, String builderClassnameAsParameter, boolean callBuilderBasedSuperConstructor) { + JavacTreeMaker maker = typeNode.getTreeMaker(); + + boolean isEnum = (((JCClassDecl) typeNode.get()).mods.flags & Flags.ENUM) != 0; + AccessLevel level = isEnum ? AccessLevel.PRIVATE : AccessLevel.PROTECTED; + + ListBuffer<JCStatement> nullChecks = new ListBuffer<JCStatement>(); + ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); + + Name builderVariableName = typeNode.toName("b"); + for (BuilderFieldData bfd : builderFields) { + List<JCAnnotation> nonNulls = findAnnotations(bfd.fieldNode, NON_NULL_PATTERN); + if (!nonNulls.isEmpty()) { + JCStatement nullCheck = generateNullCheck(maker, bfd.fieldNode, source); + if (nullCheck != null) { + nullChecks.append(nullCheck); + } + } + + JCExpression rhs; + if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { + bfd.singularData.getSingularizer().appendBuildCode(bfd.singularData, bfd.fieldNode, bfd.type, statements, bfd.name, "b"); + rhs = maker.Ident(bfd.singularData.getPluralName()); + } else { + rhs = maker.Select(maker.Ident(builderVariableName), bfd.rawName); + } + JCFieldAccess thisX = maker.Select(maker.Ident(bfd.fieldNode.toName("this")), bfd.rawName); + + JCExpression assign = maker.Assign(thisX, rhs); + + statements.append(maker.Exec(assign)); + } + + JCModifiers mods = maker.Modifiers(toJavacModifier(level), List.<JCAnnotation>nil()); + + // Create a constructor that has just the builder as parameter. + ListBuffer<JCVariableDecl> params = new ListBuffer<JCVariableDecl>(); + 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))); + statements.prepend(maker.Exec(callToSuperConstructor)); + } + + JCMethodDecl constr = recursiveSetGeneratedBy(maker.MethodDef(mods, typeNode.toName("<init>"), + null, List.<JCTypeParameter>nil(), params.toList(), List.<JCExpression>nil(), + maker.Block(0L, nullChecks.appendList(statements).toList()), null), source.get(), typeNode.getContext()); + + injectMethod(typeNode, constr, null, Javac.createVoidType(typeNode.getSymbolTable(), CTC_VOID)); + } private JCMethodDecl generateCleanMethod(java.util.List<BuilderFieldData> builderFields, JavacNode type, JCTree source) { JavacTreeMaker maker = type.getTreeMaker(); @@ -530,40 +650,56 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { */ } - private JCMethodDecl generateBuildMethod(JavacNode tdParent, 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(JavacNode tdParent, 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; ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); + ListBuffer<JCExpression> args = new ListBuffer<JCExpression>(); - 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.<JCExpression>nil(), maker.Ident(type.toName("$lombokClean")), List.<JCExpression>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); + // 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.<JCExpression>nil(), maker.Ident(type.toName("$lombokClean")), List.<JCExpression>nil())); + JCIf ifUnclean = maker.If(notClean, invokeClean, null); + statements.append(ifUnclean); } - } - - ListBuffer<JCExpression> args = new ListBuffer<JCExpression>(); - for (BuilderFieldData bfd : builderFields) { - if (bfd.nameOfSetFlag != null) { - statements.append(maker.VarDef(maker.Modifiers(0L), bfd.name, cloneType(maker, bfd.type, source, tdParent.getContext()), maker.Select(maker.Ident(type.toName("this")), bfd.name))); - statements.append(maker.If(maker.Unary(CTC_NOT, maker.Ident(bfd.nameOfSetFlag)), maker.Exec(maker.Assign(maker.Ident(bfd.name),maker.Apply(typeParameterNames(maker, ((JCClassDecl) tdParent.get()).typarams), maker.Select(maker.Ident(((JCClassDecl) tdParent.get()).name), bfd.nameOfDefaultProvider), List.<JCExpression>nil()))), null)); + + for (BuilderFieldData bfd : builderFields) { + if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { + bfd.singularData.getSingularizer().appendBuildCode(bfd.singularData, type, source, statements, bfd.name, "this"); + } + } + + for (BuilderFieldData bfd : builderFields) { + if (bfd.nameOfSetFlag != null) { + statements.append(maker.VarDef(maker.Modifiers(0L), bfd.name, cloneType(maker, bfd.type, source, tdParent.getContext()), maker.Select(maker.Ident(type.toName("this")), bfd.name))); + statements.append(maker.If(maker.Unary(CTC_NOT, maker.Ident(bfd.nameOfSetFlag)), maker.Exec(maker.Assign(maker.Ident(bfd.name),maker.Apply(typeParameterNames(maker, ((JCClassDecl) tdParent.get()).typarams), maker.Select(maker.Ident(((JCClassDecl) tdParent.get()).name), bfd.nameOfDefaultProvider), List.<JCExpression>nil()))), null)); + } + 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, 1)))); } - 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, 1)))); } 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 { ListBuffer<JCExpression> typeParams = new ListBuffer<JCExpression>(); @@ -686,12 +822,13 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { return null; } - public JavacNode makeBuilderClass(boolean isStatic, JavacNode source, JavacNode tdParent, String builderClassName, List<JCTypeParameter> typeParams, JCAnnotation ast) { + public JavacNode makeBuilderClass(boolean isStatic, JavacNode source, 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(source, typeParams), null, List.<JCExpression>nil(), List.<JCTree>nil()); + JCExpression extending = parentBuilderClassName == null ? null : maker.Ident(tdParent.toName(parentBuilderClassName)); + JCClassDecl builder = maker.ClassDef(mods, tdParent.toName(builderClassName), copyTypeParams(source, typeParams), extending, List.<JCExpression>nil(), List.<JCTree>nil()); return injectType(tdParent, builder); } diff --git a/src/core/lombok/javac/handlers/JavacSingularsRecipes.java b/src/core/lombok/javac/handlers/JavacSingularsRecipes.java index 6a76e1dd..4279ec63 100644 --- a/src/core/lombok/javac/handlers/JavacSingularsRecipes.java +++ b/src/core/lombok/javac/handlers/JavacSingularsRecipes.java @@ -195,7 +195,7 @@ public class JavacSingularsRecipes { public abstract java.util.List<JavacNode> generateFields(SingularData data, JavacNode builderType, JCTree source); public abstract void generateMethods(SingularData data, boolean deprecate, JavacNode builderType, JCTree source, boolean fluent, boolean chain); - public abstract void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName); + public abstract void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName, String builderVariable); public boolean requiresCleaning() { try { @@ -274,9 +274,9 @@ public class JavacSingularsRecipes { return arguments.toList(); } - /** Generates 'this.<em>name</em>.size()' as an expression; if nullGuard is true, it's this.name == null ? 0 : this.name.size(). */ - protected JCExpression getSize(JavacTreeMaker maker, JavacNode builderType, Name name, boolean nullGuard, boolean parens) { - Name thisName = builderType.toName("this"); + /** Generates '<em>builderVariable</em>.<em>name</em>.size()' as an expression; if nullGuard is true, it's this.name == null ? 0 : this.name.size(). */ + protected JCExpression getSize(JavacTreeMaker maker, JavacNode builderType, Name name, boolean nullGuard, boolean parens, String builderVariable) { + Name thisName = builderType.toName(builderVariable); JCExpression fn = maker.Select(maker.Select(maker.Ident(thisName), name), builderType.toName("size")); JCExpression sizeInvoke = maker.Apply(List.<JCExpression>nil(), fn, List.<JCExpression>nil()); if (nullGuard) { diff --git a/src/core/lombok/javac/handlers/singulars/JavacGuavaSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacGuavaSingularizer.java index 0ab7da54..1798d52c 100644 --- a/src/core/lombok/javac/handlers/singulars/JavacGuavaSingularizer.java +++ b/src/core/lombok/javac/handlers/singulars/JavacGuavaSingularizer.java @@ -168,7 +168,7 @@ abstract class JavacGuavaSingularizer extends JavacSingularizer { injectMethod(builderType, method); } - @Override public void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName) { + @Override public void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName, String builderVariable) { JavacTreeMaker maker = builderType.getTreeMaker(); List<JCExpression> jceBlank = List.nil(); @@ -185,12 +185,12 @@ abstract class JavacGuavaSingularizer extends JavacSingularizer { JCExpression invokeBuild; { //this.pluralName.build(); - invokeBuild = maker.Apply(jceBlank, chainDots(builderType, "this", data.getPluralName().toString(), "build"), jceBlank); + invokeBuild = maker.Apply(jceBlank, chainDots(builderType, builderVariable, data.getPluralName().toString(), "build"), jceBlank); } JCExpression isNull; { //this.pluralName == null - isNull = maker.Binary(CTC_EQUAL, maker.Select(maker.Ident(builderType.toName("this")), data.getPluralName()), maker.Literal(CTC_BOT, null)); + isNull = maker.Binary(CTC_EQUAL, maker.Select(maker.Ident(builderType.toName(builderVariable)), data.getPluralName()), maker.Literal(CTC_BOT, null)); } JCExpression init = maker.Conditional(isNull, empty, invokeBuild); // this.pluralName == null ? ImmutableX.of() : this.pluralName.build() diff --git a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSingularizer.java index 3002a98f..b1375151 100644 --- a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSingularizer.java +++ b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSingularizer.java @@ -46,9 +46,9 @@ public class JavacJavaUtilListSingularizer extends JavacJavaUtilListSetSingulari return LombokImmutableList.of("java.util.List", "java.util.Collection", "java.lang.Iterable"); } - @Override public void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName) { + @Override public void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName, String builderVariable) { if (useGuavaInstead(builderType)) { - guavaListSetSingularizer.appendBuildCode(data, builderType, source, statements, targetVariableName); + guavaListSetSingularizer.appendBuildCode(data, builderType, source, statements, targetVariableName, builderVariable); return; } @@ -71,7 +71,7 @@ public class JavacJavaUtilListSingularizer extends JavacJavaUtilListSetSingulari JCStatement assignStat; { // pluralName = java.util.Collections.singletonList(this.pluralName.get(0)); JCExpression zeroLiteral = maker.Literal(CTC_INT, 0); - JCExpression arg = maker.Apply(jceBlank, chainDots(builderType, "this", data.getPluralName().toString(), "get"), List.of(zeroLiteral)); + JCExpression arg = maker.Apply(jceBlank, chainDots(builderType, builderVariable, data.getPluralName().toString(), "get"), List.of(zeroLiteral)); List<JCExpression> args = List.of(arg); JCExpression invoke = maker.Apply(jceBlank, chainDots(builderType, "java", "util", "Collections", "singletonList"), args); assignStat = maker.Exec(maker.Assign(maker.Ident(data.getPluralName()), invoke)); @@ -82,12 +82,12 @@ public class JavacJavaUtilListSingularizer extends JavacJavaUtilListSetSingulari } /* default: Create with right size, then addAll */ { - List<JCStatement> defStats = createListCopy(maker, data, builderType, source); + List<JCStatement> defStats = createListCopy(maker, data, builderType, source, builderVariable); JCCase defaultCase = maker.Case(null, defStats); cases.append(defaultCase); } - JCStatement switchStat = maker.Switch(getSize(maker, builderType, data.getPluralName(), true, false), cases.toList()); + JCStatement switchStat = maker.Switch(getSize(maker, builderType, data.getPluralName(), true, false, builderVariable), cases.toList()); JCExpression localShadowerType = chainDotsString(builderType, data.getTargetFqn()); localShadowerType = addTypeArgs(1, false, builderType, localShadowerType, data.getTypeArgs(), source); JCStatement varDefStat = maker.VarDef(maker.Modifiers(0), data.getPluralName(), localShadowerType, null); @@ -95,9 +95,9 @@ public class JavacJavaUtilListSingularizer extends JavacJavaUtilListSetSingulari statements.append(switchStat); } - private List<JCStatement> createListCopy(JavacTreeMaker maker, SingularData data, JavacNode builderType, JCTree source) { + private List<JCStatement> createListCopy(JavacTreeMaker maker, SingularData data, JavacNode builderType, JCTree source, String builderVariable) { List<JCExpression> jceBlank = List.nil(); - Name thisName = builderType.toName("this"); + Name thisName = builderType.toName(builderVariable); JCExpression argToUnmodifiable; { // new java.util.ArrayList<Generics>(this.pluralName); diff --git a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilMapSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilMapSingularizer.java index fd699275..fbcf776e 100644 --- a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilMapSingularizer.java +++ b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilMapSingularizer.java @@ -212,18 +212,18 @@ public class JavacJavaUtilMapSingularizer extends JavacJavaUtilSingularizer { injectMethod(builderType, method); } - @Override public void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName) { + @Override public void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName, String builderVariable) { if (useGuavaInstead(builderType)) { - guavaMapSingularizer.appendBuildCode(data, builderType, source, statements, targetVariableName); + guavaMapSingularizer.appendBuildCode(data, builderType, source, statements, targetVariableName, builderVariable); return; } JavacTreeMaker maker = builderType.getTreeMaker(); if (data.getTargetFqn().equals("java.util.Map")) { - statements.appendList(createJavaUtilSetMapInitialCapacitySwitchStatements(maker, data, builderType, true, "emptyMap", "singletonMap", "LinkedHashMap", source)); + statements.appendList(createJavaUtilSetMapInitialCapacitySwitchStatements(maker, data, builderType, true, "emptyMap", "singletonMap", "LinkedHashMap", source, builderVariable)); } else { - statements.appendList(createJavaUtilSimpleCreationAndFillStatements(maker, data, builderType, true, true, false, true, "TreeMap", source)); + statements.appendList(createJavaUtilSimpleCreationAndFillStatements(maker, data, builderType, true, true, false, true, "TreeMap", source, builderVariable)); } } } diff --git a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSetSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSetSingularizer.java index 317233cb..71a36c0e 100644 --- a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSetSingularizer.java +++ b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSetSingularizer.java @@ -40,18 +40,18 @@ public class JavacJavaUtilSetSingularizer extends JavacJavaUtilListSetSingulariz return LombokImmutableList.of("java.util.Set", "java.util.SortedSet", "java.util.NavigableSet"); } - @Override public void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName) { + @Override public void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName, String builderVariable) { if (useGuavaInstead(builderType)) { - guavaListSetSingularizer.appendBuildCode(data, builderType, source, statements, targetVariableName); + guavaListSetSingularizer.appendBuildCode(data, builderType, source, statements, targetVariableName, builderVariable); return; } JavacTreeMaker maker = builderType.getTreeMaker(); if (data.getTargetFqn().equals("java.util.Set")) { - statements.appendList(createJavaUtilSetMapInitialCapacitySwitchStatements(maker, data, builderType, false, "emptySet", "singleton", "LinkedHashSet", source)); + statements.appendList(createJavaUtilSetMapInitialCapacitySwitchStatements(maker, data, builderType, false, "emptySet", "singleton", "LinkedHashSet", source, builderVariable)); } else { - statements.appendList(createJavaUtilSimpleCreationAndFillStatements(maker, data, builderType, false, true, false, true, "TreeSet", source)); + statements.appendList(createJavaUtilSimpleCreationAndFillStatements(maker, data, builderType, false, true, false, true, "TreeSet", source, builderVariable)); } } } diff --git a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSingularizer.java index 0589ac34..df521fd8 100644 --- a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSingularizer.java +++ b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSingularizer.java @@ -46,7 +46,7 @@ abstract class JavacJavaUtilSingularizer extends JavacSingularizer { return Boolean.TRUE.equals(node.getAst().readConfiguration(ConfigurationKeys.SINGULAR_USE_GUAVA)); } - protected List<JCStatement> createJavaUtilSetMapInitialCapacitySwitchStatements(JavacTreeMaker maker, SingularData data, JavacNode builderType, boolean mapMode, String emptyCollectionMethod, String singletonCollectionMethod, String targetType, JCTree source) { + protected List<JCStatement> createJavaUtilSetMapInitialCapacitySwitchStatements(JavacTreeMaker maker, SingularData data, JavacNode builderType, boolean mapMode, String emptyCollectionMethod, String singletonCollectionMethod, String targetType, JCTree source, String builderVariable) { List<JCExpression> jceBlank = List.nil(); ListBuffer<JCCase> cases = new ListBuffer<JCCase>(); @@ -66,11 +66,11 @@ abstract class JavacJavaUtilSingularizer extends JavacSingularizer { // !mapMode: pluralName = java.util.Collections.singletonCollectionMethod(this.pluralName.get(0)); // mapMode: pluralName = java.util.Collections.singletonCollectionMethod(this.pluralName$key.get(0), this.pluralName$value.get(0)); JCExpression zeroLiteral = maker.Literal(CTC_INT, 0); - JCExpression arg = maker.Apply(jceBlank, chainDots(builderType, "this", data.getPluralName() + (mapMode ? "$key" : ""), "get"), List.of(zeroLiteral)); + JCExpression arg = maker.Apply(jceBlank, chainDots(builderType, builderVariable, data.getPluralName() + (mapMode ? "$key" : ""), "get"), List.of(zeroLiteral)); List<JCExpression> args; if (mapMode) { JCExpression zeroLiteralClone = maker.Literal(CTC_INT, 0); - JCExpression arg2 = maker.Apply(jceBlank, chainDots(builderType, "this", data.getPluralName() + (mapMode ? "$value" : ""), "get"), List.of(zeroLiteralClone)); + JCExpression arg2 = maker.Apply(jceBlank, chainDots(builderType, builderVariable, data.getPluralName() + (mapMode ? "$value" : ""), "get"), List.of(zeroLiteralClone)); args = List.of(arg, arg2); } else { args = List.of(arg); @@ -84,12 +84,12 @@ abstract class JavacJavaUtilSingularizer extends JavacSingularizer { } { // default: - List<JCStatement> statements = createJavaUtilSimpleCreationAndFillStatements(maker, data, builderType, mapMode, false, true, emptyCollectionMethod == null, targetType, source); + List<JCStatement> statements = createJavaUtilSimpleCreationAndFillStatements(maker, data, builderType, mapMode, false, true, emptyCollectionMethod == null, targetType, source, builderVariable); JCCase defaultCase = maker.Case(null, statements); cases.append(defaultCase); } - JCStatement switchStat = maker.Switch(getSize(maker, builderType, mapMode ? builderType.toName(data.getPluralName() + "$key") : data.getPluralName(), true, false), cases.toList()); + JCStatement switchStat = maker.Switch(getSize(maker, builderType, mapMode ? builderType.toName(data.getPluralName() + "$key") : data.getPluralName(), true, false, builderVariable), cases.toList()); JCExpression localShadowerType = chainDotsString(builderType, data.getTargetFqn()); localShadowerType = addTypeArgs(mapMode ? 2 : 1, false, builderType, localShadowerType, data.getTypeArgs(), source); JCStatement varDefStat = maker.VarDef(maker.Modifiers(0), data.getPluralName(), localShadowerType, null); @@ -125,9 +125,9 @@ abstract class JavacJavaUtilSingularizer extends JavacSingularizer { return maker.If(cond, thenPart, null); } - protected List<JCStatement> createJavaUtilSimpleCreationAndFillStatements(JavacTreeMaker maker, SingularData data, JavacNode builderType, boolean mapMode, boolean defineVar, boolean addInitialCapacityArg, boolean nullGuard, String targetType, JCTree source) { + protected List<JCStatement> createJavaUtilSimpleCreationAndFillStatements(JavacTreeMaker maker, SingularData data, JavacNode builderType, boolean mapMode, boolean defineVar, boolean addInitialCapacityArg, boolean nullGuard, String targetType, JCTree source, String builderVariable) { List<JCExpression> jceBlank = List.nil(); - Name thisName = builderType.toName("this"); + Name thisName = builderType.toName(builderVariable); JCStatement createStat; { // pluralName = new java.util.TargetType(initialCap); @@ -136,10 +136,10 @@ abstract class JavacJavaUtilSingularizer extends JavacSingularizer { Name varName = mapMode ? builderType.toName(data.getPluralName() + "$key") : data.getPluralName(); // this.varName.size() < MAX_POWER_OF_2 ? 1 + this.varName.size() + (this.varName.size() - 3) / 3 : Integer.MAX_VALUE; // lessThanCutOff = this.varName.size() < MAX_POWER_OF_2 - JCExpression lessThanCutoff = maker.Binary(CTC_LESS_THAN, getSize(maker, builderType, varName, nullGuard, true), maker.Literal(CTC_INT, 0x40000000)); + JCExpression lessThanCutoff = maker.Binary(CTC_LESS_THAN, getSize(maker, builderType, varName, nullGuard, true, builderVariable), maker.Literal(CTC_INT, 0x40000000)); JCExpression integerMaxValue = genJavaLangTypeRef(builderType, "Integer", "MAX_VALUE"); - JCExpression sizeFormulaLeft = maker.Binary(CTC_PLUS, maker.Literal(CTC_INT, 1), getSize(maker, builderType, varName, nullGuard, true)); - JCExpression sizeFormulaRightLeft = maker.Parens(maker.Binary(CTC_MINUS, getSize(maker, builderType, varName, nullGuard, true), maker.Literal(CTC_INT, 3))); + JCExpression sizeFormulaLeft = maker.Binary(CTC_PLUS, maker.Literal(CTC_INT, 1), getSize(maker, builderType, varName, nullGuard, true, builderVariable)); + JCExpression sizeFormulaRightLeft = maker.Parens(maker.Binary(CTC_MINUS, getSize(maker, builderType, varName, nullGuard, true, builderVariable), maker.Literal(CTC_INT, 3))); JCExpression sizeFormulaRight = maker.Binary(CTC_DIV, sizeFormulaRightLeft, maker.Literal(CTC_INT, 3)); JCExpression sizeFormula = maker.Binary(CTC_PLUS, sizeFormulaLeft, sizeFormulaRight); constructorArgs = List.<JCExpression>of(maker.Conditional(lessThanCutoff, sizeFormula, integerMaxValue)); @@ -163,14 +163,14 @@ abstract class JavacJavaUtilSingularizer extends JavacSingularizer { Name ivar = builderType.toName("$i"); Name keyVarName = builderType.toName(data.getPluralName() + "$key"); JCExpression pluralnameDotPut = maker.Select(maker.Ident(data.getPluralName()), builderType.toName("put")); - JCExpression arg1 = maker.Apply(jceBlank, chainDots(builderType, "this", data.getPluralName() + "$key", "get"), List.<JCExpression>of(maker.Ident(ivar))); - JCExpression arg2 = maker.Apply(jceBlank, chainDots(builderType, "this", data.getPluralName() + "$value", "get"), List.<JCExpression>of(maker.Ident(ivar))); + JCExpression arg1 = maker.Apply(jceBlank, chainDots(builderType, builderVariable, data.getPluralName() + "$key", "get"), List.<JCExpression>of(maker.Ident(ivar))); + JCExpression arg2 = maker.Apply(jceBlank, chainDots(builderType, builderVariable, data.getPluralName() + "$value", "get"), List.<JCExpression>of(maker.Ident(ivar))); // [jdk9] We add an unneccessary (V) cast here. Not doing so gives an error in javac (build 9-ea+156-jigsaw-nightly-h6072-20170212): // error: method put in interface Map<K#2,V#2> cannot be applied to given types; arg2 = maker.TypeCast(createTypeArgs(2, false, builderType, data.getTypeArgs(), source).get(1), arg2); JCStatement putStatement = maker.Exec(maker.Apply(jceBlank, pluralnameDotPut, List.of(arg1, arg2))); JCStatement forInit = maker.VarDef(maker.Modifiers(0), ivar, maker.TypeIdent(CTC_INT), maker.Literal(CTC_INT, 0)); - JCExpression checkExpr = maker.Binary(CTC_LESS_THAN, maker.Ident(ivar), getSize(maker, builderType, keyVarName, nullGuard, true)); + JCExpression checkExpr = maker.Binary(CTC_LESS_THAN, maker.Ident(ivar), getSize(maker, builderType, keyVarName, nullGuard, true, builderVariable)); JCExpression incrementExpr = maker.Unary(CTC_POSTINC, maker.Ident(ivar)); fillStat = maker.ForLoop(List.of(forInit), checkExpr, List.of(maker.Exec(incrementExpr)), putStatement); } else { |