diff options
author | Reinier Zwitserloot <reinier@zwitserloot.com> | 2017-03-20 22:54:36 +0100 |
---|---|---|
committer | Reinier Zwitserloot <reinier@zwitserloot.com> | 2017-03-20 22:54:36 +0100 |
commit | bf54986e8a08d246877fae902c58dc41ca2f559b (patch) | |
tree | f1f05e8d65e35aa0627c69c9fc8ad0d1a69e38b0 | |
parent | c63c1528843a3ac591c9fbd2db3732af8824d097 (diff) | |
download | lombok-bf54986e8a08d246877fae902c58dc41ca2f559b.tar.gz lombok-bf54986e8a08d246877fae902c58dc41ca2f559b.tar.bz2 lombok-bf54986e8a08d246877fae902c58dc41ca2f559b.zip |
Fixing issue #1201: Builder now supports defaults!
21 files changed, 563 insertions, 72 deletions
diff --git a/doc/changelog.markdown b/doc/changelog.markdown index 6fc3af38..69050895 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -3,6 +3,7 @@ Lombok Changelog ### v1.16.15 "Edgy Guinea Pig" * v1.16.14 is the latest stable release of Project Lombok. +* FEATURE: `@Builder.Default` lets you configure default values for your fields when using `@Builder`. See the [Builder feature page](https://projectlombok.org/features/Builder.html) for more information. [Issue #1201](https://github.com/rzwitserloot/lombok/issues/1201) * PLATFORM: Improved jdk9 support, work in progress. * BUGFIX: The `onX` feature (which lets you add annotations to generated methods) did not work if the annotation you added contained named parameters, and you are compiling with JDK8's javac. We can't fix this (it's a bug in javac), but we have provided an alternate, prettier way to do `onX` on javac8+. [Issue #778](https://github.com/rzwitserloot/lombok/issues/778) [onX documentation](https://projectlombok.org/features/experimental/onX.html) diff --git a/src/core/lombok/Builder.java b/src/core/lombok/Builder.java index a0b8242f..3076581e 100644 --- a/src/core/lombok/Builder.java +++ b/src/core/lombok/Builder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2014 The Project Lombok Authors. + * Copyright (C) 2013-2017 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -108,11 +108,11 @@ import java.lang.annotation.Target; @Retention(SOURCE) public @interface Builder { /** - * Marks a given field as being a 'constant' (initialized by the initializing expression during construction and not eligible to be modified as part of {@code @Builder}. + * The field annotated with {@code @Default} must have an initializing expression; that expression is taken as the default to be used if not explicitly set during building. */ @Target(FIELD) @Retention(SOURCE) - public @interface Constant {} + public @interface Default {} /** Name of the method that creates a new builder instance. Default: {@code builder}. */ String builderMethodName() default "builder"; diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index 4726b17e..e366cff9 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -443,6 +443,24 @@ public class EclipseHandlerUtil { } } + public static EclipseNode findAnnotation(Class<? extends java.lang.annotation.Annotation> type, EclipseNode node) { + if (node == null) return null; + if (type == null) return null; + switch (node.getKind()) { + case ARGUMENT: + case FIELD: + case LOCAL: + case TYPE: + case METHOD: + for (EclipseNode child : node.down()) { + if (annotationTypeMatches(type, child)) return child; + } + // intentional fallthrough + default: + return null; + } + } + /** * Checks if the provided annotation type is likely to be the intended type for the given annotation node. * diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java index 8c200a94..ae080c96 100644 --- a/src/core/lombok/eclipse/handlers/HandleBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2015 The Project Lombok Authors. + * Copyright (C) 2013-2017 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -38,6 +38,7 @@ import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.Argument; 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.Expression; import org.eclipse.jdt.internal.compiler.ast.FalseLiteral; @@ -102,6 +103,8 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { TypeReference type; char[] rawName; char[] name; + char[] nameOfDefaultProvider; + char[] nameOfSetFlag; SingularData singularData; ObtainVia obtainVia; EclipseNode obtainViaNode; @@ -127,6 +130,16 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { return true; } + private static final char[] DEFAULT_PREFIX = {'$', 'd', 'e', 'f', 'a', 'u', 'l', 't', '$'}; + private static final char[] SET_PREFIX = {'$', 's', 'e', 't'}; + + private static final char[] prefixWith(char[] prefix, char[] name) { + char[] out = new char[prefix.length + name.length]; + System.arraycopy(prefix, 0, out, 0, prefix.length); + System.arraycopy(name, 0, out, prefix.length, name.length); + return out; + } + @Override public void handle(AnnotationValues<Builder> annotation, Annotation ast, EclipseNode annotationNode) { long p = (long) ast.sourceStart << 32 | ast.sourceEnd; @@ -173,17 +186,38 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { List<EclipseNode> allFields = new ArrayList<EclipseNode>(); @SuppressWarnings("deprecation") boolean valuePresent = (hasAnnotation(lombok.Value.class, parent) || hasAnnotation(lombok.experimental.Value.class, parent)); - for (EclipseNode fieldNode : HandleConstructor.findAllFields(tdParent)) { + for (EclipseNode fieldNode : HandleConstructor.findAllFields(tdParent, true)) { FieldDeclaration fd = (FieldDeclaration) fieldNode.get(); - // final fields with an initializer cannot be written to, so they can't be 'builderized'. Unfortunately presence of @Value makes - // non-final fields final, but @Value's handler hasn't done this yet, so we have to do this math ourselves. - // Value will only skip making a field final if it has an explicit @NonFinal annotation, so we check for that. - if (fd.initialization != null && valuePresent && !hasAnnotation(NonFinal.class, fieldNode)) continue; + EclipseNode isDefault = findAnnotation(Builder.Default.class, fieldNode); + boolean isFinal = ((fd.modifiers & ClassFileConstants.AccFinal) != 0) || (valuePresent && !hasAnnotation(NonFinal.class, fieldNode)); + + if (fd.initialization == null && isDefault != null) { + isDefault.addWarning("@Builder.Default requires an initializing expression (' = something;')."); + isDefault = null; + } + + if (fd.initialization != null && isDefault == null) { + if (isFinal) continue; + fieldNode.addWarning("@Builder will ignore the initializing expression entirely. If you want the initializing expression to serve as default, add @Builder.Default. if it is not supposed to be settable during building, add @Builder.Constant."); + } + BuilderFieldData bfd = new BuilderFieldData(); bfd.rawName = fieldNode.getName().toCharArray(); bfd.name = removePrefixFromField(fieldNode); bfd.type = fd.type; bfd.singularData = getSingularData(fieldNode, ast); + if (isDefault != null) { + if (bfd.singularData != null) { + isDefault.addError("@Builder.Default and @Singular cannot be mixed."); + isDefault = null; + } else { + bfd.nameOfDefaultProvider = prefixWith(DEFAULT_PREFIX, bfd.name); + bfd.nameOfSetFlag = prefixWith(bfd.name, SET_PREFIX); + + MethodDeclaration md = generateDefaultProvider(bfd.nameOfDefaultProvider, fieldNode, ast); + if (md != null) injectMethod(tdParent, md); + } + } addObtainVia(bfd, fieldNode); builderFields.add(bfd); allFields.add(fieldNode); @@ -405,7 +439,7 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { } if (methodExists(buildMethodName, builderType, -1) == MemberExistsResult.NOT_EXISTS) { - MethodDeclaration md = generateBuildMethod(isStatic, buildMethodName, nameOfStaticBuilderMethod, returnType, builderFields, builderType, thrownExceptions, addCleaning, ast); + MethodDeclaration md = generateBuildMethod(tdParent, isStatic, buildMethodName, nameOfStaticBuilderMethod, returnType, builderFields, builderType, thrownExceptions, addCleaning, ast); if (md != null) injectMethod(builderType, md); } @@ -514,7 +548,7 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { return decl; } - public MethodDeclaration generateBuildMethod(boolean isStatic, String name, char[] staticName, TypeReference returnType, List<BuilderFieldData> builderFields, EclipseNode type, TypeReference[] thrownExceptions, boolean addCleaning, ASTNode source) { + public MethodDeclaration generateBuildMethod(EclipseNode tdParent, boolean isStatic, String name, char[] staticName, TypeReference returnType, List<BuilderFieldData> builderFields, EclipseNode type, TypeReference[] thrownExceptions, boolean addCleaning, ASTNode source) { MethodDeclaration out = new MethodDeclaration(((CompilationUnitDeclaration) type.top().get()).compilationResult); out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; List<Statement> statements = new ArrayList<Statement>(); @@ -536,7 +570,20 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { List<Expression> args = new ArrayList<Expression>(); for (BuilderFieldData bfd : builderFields) { - args.add(new SingleNameReference(bfd.name, 0L)); + 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; + + 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) { @@ -583,6 +630,22 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { return out; } + public MethodDeclaration generateDefaultProvider(char[] methodName, EclipseNode fieldNode, ASTNode source) { + int pS = source.sourceStart, pE = source.sourceEnd; + + MethodDeclaration out = new MethodDeclaration(((CompilationUnitDeclaration) fieldNode.top().get()).compilationResult); + out.selector = methodName; + out.modifiers = ClassFileConstants.AccPrivate | ClassFileConstants.AccStatic; + out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + FieldDeclaration fd = (FieldDeclaration) fieldNode.get(); + out.returnType = copyType(fd.type, source); + out.statements = new Statement[] {new ReturnStatement(fd.initialization, pS, pE)}; + fd.initialization = null; + + out.traverse(new SetGeneratedByVisitor(source), ((TypeDeclaration) fieldNode.up().get()).scope); + return out; + } + public MethodDeclaration generateBuilderMethod(boolean isStatic, String builderMethodName, String builderClassName, EclipseNode type, TypeParameter[] typeParams, ASTNode source) { int pS = source.sourceStart, pE = source.sourceEnd; long p = (long) pS << 32 | pE; @@ -608,25 +671,34 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { if (child.getKind() == Kind.FIELD) existing.add(child); } - top: for (BuilderFieldData bfd : builderFields) { if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { bfd.createdFields.addAll(bfd.singularData.getSingularizer().generateFields(bfd.singularData, builderType)); } else { + EclipseNode field = null, setFlag = null; for (EclipseNode exists : existing) { char[] n = ((FieldDeclaration) exists.get()).name; - if (Arrays.equals(n, bfd.name)) { - bfd.createdFields.add(exists); - continue top; - } + if (Arrays.equals(n, bfd.name)) field = exists; + if (bfd.nameOfSetFlag != null && Arrays.equals(n, bfd.nameOfSetFlag)) setFlag = exists; } - FieldDeclaration fd = new FieldDeclaration(bfd.name, 0, 0); - fd.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; - fd.modifiers = ClassFileConstants.AccPrivate; - fd.type = copyType(bfd.type); - fd.traverse(new SetGeneratedByVisitor(source), (MethodScope) null); - bfd.createdFields.add(injectFieldAndMarkGenerated(builderType, fd)); + if (field == null) { + FieldDeclaration fd = new FieldDeclaration(bfd.name, 0, 0); + fd.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; + fd.modifiers = ClassFileConstants.AccPrivate; + fd.type = copyType(bfd.type); + fd.traverse(new SetGeneratedByVisitor(source), (MethodScope) null); + field = injectFieldAndMarkGenerated(builderType, fd); + } + if (setFlag == null && bfd.nameOfSetFlag != null) { + FieldDeclaration fd = new FieldDeclaration(bfd.nameOfSetFlag, 0, 0); + fd.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; + fd.modifiers = ClassFileConstants.AccPrivate; + fd.type = TypeReference.baseTypeReference(TypeIds.T_boolean, 0); + fd.traverse(new SetGeneratedByVisitor(source), (MethodScope) null); + injectFieldAndMarkGenerated(builderType, fd); + } + bfd.createdFields.add(field); } } } @@ -635,13 +707,13 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { public void makeSetterMethodsForBuilder(EclipseNode builderType, BuilderFieldData bfd, EclipseNode sourceNode, boolean fluent, boolean chain) { if (bfd.singularData == null || bfd.singularData.getSingularizer() == null) { - makeSimpleSetterMethodForBuilder(builderType, bfd.createdFields.get(0), sourceNode, fluent, chain); + makeSimpleSetterMethodForBuilder(builderType, bfd.createdFields.get(0), bfd.nameOfSetFlag, sourceNode, fluent, chain); } else { bfd.singularData.getSingularizer().generateMethods(bfd.singularData, builderType, fluent, chain); } } - private void makeSimpleSetterMethodForBuilder(EclipseNode builderType, EclipseNode fieldNode, EclipseNode sourceNode, boolean fluent, boolean chain) { + private void makeSimpleSetterMethodForBuilder(EclipseNode builderType, EclipseNode fieldNode, char[] nameOfSetFlag, EclipseNode sourceNode, boolean fluent, boolean chain) { TypeDeclaration td = (TypeDeclaration) builderType.get(); AbstractMethodDeclaration[] existing = td.methods; if (existing == null) existing = EMPTY; @@ -657,7 +729,7 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { String setterName = fluent ? fieldNode.getName() : HandlerUtil.buildAccessorName("set", fieldNode.getName()); - MethodDeclaration setter = HandleSetter.createSetter(td, fieldNode, setterName, chain, ClassFileConstants.AccPublic, + MethodDeclaration setter = HandleSetter.createSetter(td, fieldNode, setterName, nameOfSetFlag, chain, ClassFileConstants.AccPublic, sourceNode, Collections.<Annotation>emptyList(), Collections.<Annotation>emptyList()); injectMethod(builderType, setter); } diff --git a/src/core/lombok/eclipse/handlers/HandleConstructor.java b/src/core/lombok/eclipse/handlers/HandleConstructor.java index 49b09231..5978d542 100644 --- a/src/core/lombok/eclipse/handlers/HandleConstructor.java +++ b/src/core/lombok/eclipse/handlers/HandleConstructor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2015 The Project Lombok Authors. + * Copyright (C) 2010-2017 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -144,14 +144,17 @@ public class HandleConstructor { } static List<EclipseNode> findAllFields(EclipseNode typeNode) { + return findAllFields(typeNode, false); + } + + static List<EclipseNode> findAllFields(EclipseNode typeNode, boolean evenFinalInitialized) { List<EclipseNode> fields = new ArrayList<EclipseNode>(); for (EclipseNode child : typeNode.down()) { if (child.getKind() != Kind.FIELD) continue; FieldDeclaration fieldDecl = (FieldDeclaration) child.get(); if (!filterField(fieldDecl)) continue; - // Skip initialized final fields. - if (((fieldDecl.modifiers & ClassFileConstants.AccFinal) != 0) && fieldDecl.initialization != null) continue; + if (!evenFinalInitialized && ((fieldDecl.modifiers & ClassFileConstants.AccFinal) != 0) && fieldDecl.initialization != null) continue; fields.add(child); } diff --git a/src/core/lombok/eclipse/handlers/HandleSetter.java b/src/core/lombok/eclipse/handlers/HandleSetter.java index 7e7ea121..84124d68 100644 --- a/src/core/lombok/eclipse/handlers/HandleSetter.java +++ b/src/core/lombok/eclipse/handlers/HandleSetter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2014 The Project Lombok Authors. + * Copyright (C) 2009-2017 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -52,6 +52,7 @@ import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; import org.eclipse.jdt.internal.compiler.ast.Statement; import org.eclipse.jdt.internal.compiler.ast.ThisReference; +import org.eclipse.jdt.internal.compiler.ast.TrueLiteral; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; @@ -192,11 +193,11 @@ public class HandleSetter extends EclipseAnnotationHandler<Setter> { } } - MethodDeclaration method = createSetter((TypeDeclaration) fieldNode.up().get(), fieldNode, setterName, shouldReturnThis, modifier, sourceNode, onMethod, onParam); + MethodDeclaration method = createSetter((TypeDeclaration) fieldNode.up().get(), fieldNode, setterName, null, shouldReturnThis, modifier, sourceNode, onMethod, onParam); injectMethod(fieldNode.up(), method); } - static MethodDeclaration createSetter(TypeDeclaration parent, EclipseNode fieldNode, String name, boolean shouldReturnThis, int modifier, EclipseNode sourceNode, List<Annotation> onMethod, List<Annotation> onParam) { + static MethodDeclaration createSetter(TypeDeclaration parent, EclipseNode fieldNode, String name, char[] booleanFieldToSet, boolean shouldReturnThis, int modifier, EclipseNode sourceNode, List<Annotation> onMethod, List<Annotation> onParam) { FieldDeclaration field = (FieldDeclaration) fieldNode.get(); ASTNode source = sourceNode.get(); int pS = source.sourceStart, pE = source.sourceEnd; @@ -243,6 +244,10 @@ public class HandleSetter extends EclipseAnnotationHandler<Setter> { statements.add(assignment); } + if (booleanFieldToSet != null) { + statements.add(new Assignment(new SingleNameReference(booleanFieldToSet, p), new TrueLiteral(pS, pE), pE)); + } + if (shouldReturnThis) { ThisReference thisRef = new ThisReference(pS, pE); ReturnStatement returnThis = new ReturnStatement(thisRef, pS, pE); diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java index 5dde551d..83388848 100644 --- a/src/core/lombok/javac/handlers/HandleBuilder.java +++ b/src/core/lombok/javac/handlers/HandleBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2015 The Project Lombok Authors. + * Copyright (C) 2013-2017 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -85,6 +85,8 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { JCExpression type; Name rawName; Name name; + Name nameOfDefaultProvider; + Name nameOfSetFlag; SingularData singularData; ObtainVia obtainVia; JavacNode obtainViaNode; @@ -141,17 +143,17 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { boolean valuePresent = (hasAnnotation(lombok.Value.class, parent) || hasAnnotation(lombok.experimental.Value.class, parent)); for (JavacNode fieldNode : HandleConstructor.findAllFields(tdParent, true)) { JCVariableDecl fd = (JCVariableDecl) fieldNode.get(); + JavacNode isDefault = findAnnotation(Builder.Default.class, fieldNode, true); boolean isFinal = (fd.mods.flags & Flags.FINAL) != 0 || (valuePresent && !hasAnnotation(NonFinal.class, fieldNode)); - JavacNode isConstant = findAnnotation(Builder.Constant.class, fieldNode, true); - if (fd.init != null && isFinal) { - if (isConstant != null) continue; + if (fd.init == null && isDefault != null) { + isDefault.addWarning("@Builder.Default requires an initializing expression (' = something;')."); + isDefault = null; } - if (isConstant != null) { - if (!isFinal && fd.init == null) isConstant.addWarning("@Builder.Constant doesn't do anything unless the field has an initializing expression (' = something;') and is final."); - else if (!isFinal) isConstant.addWarning("@Builder.Constant doesn't do anything unless the field is final."); - else if (fd.init == null) isConstant.addWarning("@Builder.Constant doesn't do anything unless the field has an initializing expression (' = something;')."); + if (fd.init != null && isDefault == null) { + if (isFinal) continue; + fieldNode.addWarning("@Builder will ignore the initializing expression entirely. If you want the initializing expression to serve as default, add @Builder.Default. if it is not supposed to be settable during building, add @Builder.Constant."); } BuilderFieldData bfd = new BuilderFieldData(); @@ -159,6 +161,18 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { bfd.name = removePrefixFromField(fieldNode); bfd.type = fd.vartype; bfd.singularData = getSingularData(fieldNode); + if (isDefault != null) { + if (bfd.singularData != null) { + isDefault.addError("@Builder.Default and @Singular cannot be mixed."); + isDefault = null; + } else { + bfd.nameOfDefaultProvider = parent.toName("$default$" + bfd.name); + bfd.nameOfSetFlag = parent.toName(bfd.name + "$set"); + JCMethodDecl md = generateDefaultProvider(bfd.nameOfDefaultProvider, fieldNode); + recursiveSetGeneratedBy(md, ast, annotationNode.getContext()); + if (md != null) injectMethod(tdParent, md); + } + } addObtainVia(bfd, fieldNode); builderFields.add(bfd); allFields.append(fieldNode); @@ -366,7 +380,7 @@ 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); + JCMethodDecl md = generateBuildMethod(tdParent, isStatic, buildMethodName, nameOfBuilderMethod, returnType, builderFields, builderType, thrownExceptions, ast, addCleaning); if (md != null) injectMethod(builderType, md); } @@ -486,7 +500,7 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { } } - statements.append(maker.Exec(maker.Assign(maker.Select(maker.Ident(type.toName("this")), type.toName("$lombokUnclean")), maker.Literal(CTC_BOOLEAN, false)))); + statements.append(maker.Exec(maker.Assign(maker.Select(maker.Ident(type.toName("this")), type.toName("$lombokUnclean")), maker.Literal(CTC_BOOLEAN, 0)))); JCBlock body = maker.Block(0, statements.toList()); return maker.MethodDef(maker.Modifiers(Flags.PUBLIC), type.toName("$lombokClean"), maker.Type(Javac.createVoidType(type.getSymbolTable(), CTC_VOID)), List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null); /* @@ -503,7 +517,7 @@ 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) { + 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) { JavacTreeMaker maker = type.getTreeMaker(); JCExpression call; @@ -524,25 +538,28 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { ListBuffer<JCExpression> args = new ListBuffer<JCExpression>(); for (BuilderFieldData bfd : builderFields) { - args.append(maker.Ident(bfd.name)); + if (bfd.nameOfSetFlag != null) { + args.append(maker.Conditional(maker.Ident(bfd.nameOfSetFlag), maker.Ident(bfd.name), + maker.Apply(List.<JCExpression>nil(), maker.Select(maker.Ident(((JCClassDecl) tdParent.get()).name), bfd.nameOfDefaultProvider), List.<JCExpression>nil()))); + } else { + args.append(maker.Ident(bfd.name)); + } } if (addCleaning) { - statements.append(maker.Exec(maker.Assign(maker.Select(maker.Ident(type.toName("this")), type.toName("$lombokUnclean")), maker.Literal(CTC_BOOLEAN, true)))); + 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); statements.append(maker.Return(call)); } else { - ListBuffer<JCExpression> typeParams = new ListBuffer<JCExpression>(); for (JCTypeParameter tp : ((JCClassDecl) type.get()).typarams) { typeParams.append(maker.Ident(tp.name)); } JCExpression callee = maker.Ident(((JCClassDecl) type.up().get()).name); - if (!isStatic) - callee = maker.Select(callee, type.up().toName("this")); + if (!isStatic) callee = maker.Select(callee, type.up().toName("this")); JCExpression fn = maker.Select(callee, builderName); call = maker.Apply(typeParams.toList(), fn, args.toList()); if (returnType instanceof JCPrimitiveTypeTree && CTC_VOID.equals(typeTag(returnType))) { @@ -557,6 +574,18 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { return maker.MethodDef(maker.Modifiers(Flags.PUBLIC), type.toName(buildName), returnType, List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), thrownExceptions, body, null); } + public JCMethodDecl generateDefaultProvider(Name methodName, JavacNode fieldNode) { + JavacTreeMaker maker = fieldNode.getTreeMaker(); + JCVariableDecl field = (JCVariableDecl) fieldNode.get(); + + JCStatement statement = maker.Return(field.init); + field.init = null; + + JCBlock body = maker.Block(0, List.<JCStatement>of(statement)); + int modifiers = Flags.PRIVATE | Flags.STATIC; + return maker.MethodDef(maker.Modifiers(modifiers), methodName, cloneType(maker, field.vartype, field, fieldNode.getContext()), List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null); + } + public JCMethodDecl generateBuilderMethod(boolean isStatic, String builderMethodName, String builderClassName, JavacNode source, JavacNode type, List<JCTypeParameter> typeParams) { JavacTreeMaker maker = type.getTreeMaker(); @@ -581,36 +610,42 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { if (child.getKind() == Kind.FIELD) existing.add(child); } - top: for (int i = len - 1; i >= 0; i--) { BuilderFieldData bfd = builderFields.get(i); if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { bfd.createdFields.addAll(bfd.singularData.getSingularizer().generateFields(bfd.singularData, builderType, source)); } else { + JavacNode field = null, setFlag = null; for (JavacNode exists : existing) { Name n = ((JCVariableDecl) exists.get()).name; - if (n.equals(bfd.name)) { - bfd.createdFields.add(exists); - continue top; - } + if (n.equals(bfd.name)) field = exists; + if (n.equals(bfd.nameOfSetFlag)) setFlag = exists; } JavacTreeMaker maker = builderType.getTreeMaker(); - JCModifiers mods = maker.Modifiers(Flags.PRIVATE); - JCVariableDecl newField = maker.VarDef(mods, bfd.name, cloneType(maker, bfd.type, source, builderType.getContext()), null); - bfd.createdFields.add(injectFieldAndMarkGenerated(builderType, newField)); + if (field == null) { + JCModifiers mods = maker.Modifiers(Flags.PRIVATE); + JCVariableDecl newField = maker.VarDef(mods, bfd.name, cloneType(maker, bfd.type, source, builderType.getContext()), null); + field = injectFieldAndMarkGenerated(builderType, newField); + } + if (setFlag == null && bfd.nameOfSetFlag != null) { + JCModifiers mods = maker.Modifiers(Flags.PRIVATE); + JCVariableDecl newField = maker.VarDef(mods, bfd.nameOfSetFlag, maker.TypeIdent(CTC_BOOLEAN), null); + injectFieldAndMarkGenerated(builderType, newField); + } + bfd.createdFields.add(field); } } } public void makeSetterMethodsForBuilder(JavacNode builderType, BuilderFieldData fieldNode, JavacNode source, boolean fluent, boolean chain) { if (fieldNode.singularData == null || fieldNode.singularData.getSingularizer() == null) { - makeSimpleSetterMethodForBuilder(builderType, fieldNode.createdFields.get(0), source, fluent, chain); + makeSimpleSetterMethodForBuilder(builderType, fieldNode.createdFields.get(0), fieldNode.nameOfSetFlag, source, fluent, chain); } else { fieldNode.singularData.getSingularizer().generateMethods(fieldNode.singularData, builderType, source.get(), fluent, chain); } } - private void makeSimpleSetterMethodForBuilder(JavacNode builderType, JavacNode fieldNode, JavacNode source, boolean fluent, boolean chain) { + private void makeSimpleSetterMethodForBuilder(JavacNode builderType, JavacNode fieldNode, Name nameOfSetFlag, JavacNode source, boolean fluent, boolean chain) { Name fieldName = ((JCVariableDecl) fieldNode.get()).name; for (JavacNode child : builderType.down()) { @@ -623,7 +658,7 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { String setterName = fluent ? fieldNode.getName() : HandlerUtil.buildAccessorName("set", fieldNode.getName()); JavacTreeMaker maker = fieldNode.getTreeMaker(); - JCMethodDecl newMethod = HandleSetter.createSetter(Flags.PUBLIC, fieldNode, maker, setterName, chain, source, List.<JCAnnotation>nil(), List.<JCAnnotation>nil()); + JCMethodDecl newMethod = HandleSetter.createSetter(Flags.PUBLIC, fieldNode, maker, setterName, nameOfSetFlag, chain, source, List.<JCAnnotation>nil(), List.<JCAnnotation>nil()); injectMethod(builderType, newMethod); } diff --git a/src/core/lombok/javac/handlers/HandleSetter.java b/src/core/lombok/javac/handlers/HandleSetter.java index e766127a..6f09ca40 100644 --- a/src/core/lombok/javac/handlers/HandleSetter.java +++ b/src/core/lombok/javac/handlers/HandleSetter.java @@ -206,10 +206,10 @@ public class HandleSetter extends JavacAnnotationHandler<Setter> { public static JCMethodDecl createSetter(long access, JavacNode field, JavacTreeMaker treeMaker, JavacNode source, List<JCAnnotation> onMethod, List<JCAnnotation> onParam) { String setterName = toSetterName(field); boolean returnThis = shouldReturnThis(field); - return createSetter(access, field, treeMaker, setterName, returnThis, source, onMethod, onParam); + return createSetter(access, field, treeMaker, setterName, null, returnThis, source, onMethod, onParam); } - public static JCMethodDecl createSetter(long access, JavacNode field, JavacTreeMaker treeMaker, String setterName, boolean shouldReturnThis, JavacNode source, List<JCAnnotation> onMethod, List<JCAnnotation> onParam) { + public static JCMethodDecl createSetter(long access, JavacNode field, JavacTreeMaker treeMaker, String setterName, Name booleanFieldToSet, boolean shouldReturnThis, JavacNode source, List<JCAnnotation> onMethod, List<JCAnnotation> onParam) { if (setterName == null) return null; JCVariableDecl fieldDecl = (JCVariableDecl) field.get(); @@ -235,6 +235,11 @@ public class HandleSetter extends JavacAnnotationHandler<Setter> { statements.append(treeMaker.Exec(assign)); } + if (booleanFieldToSet != null) { + JCAssign setBool = treeMaker.Assign(treeMaker.Ident(booleanFieldToSet), treeMaker.Literal(CTC_BOOLEAN, 1)); + statements.append(treeMaker.Exec(setBool)); + } + JCExpression methodType = null; if (shouldReturnThis) { methodType = cloneSelfType(field); diff --git a/test/transform/resource/after-delombok/BuilderDefaults.java b/test/transform/resource/after-delombok/BuilderDefaults.java new file mode 100644 index 00000000..c5c8006a --- /dev/null +++ b/test/transform/resource/after-delombok/BuilderDefaults.java @@ -0,0 +1,129 @@ +public final class BuilderDefaults { + private final int x; + private final String name; + private final long z; + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + private static int $default$x() { + return 10; + } + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + private static long $default$z() { + return System.currentTimeMillis(); + } + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + BuilderDefaults(final int x, final String name, final long z) { + this.x = x; + this.name = name; + this.z = z; + } + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + public static class BuilderDefaultsBuilder { + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + private boolean x$set; + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + private int x; + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + private String name; + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + private boolean z$set; + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + private long z; + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + BuilderDefaultsBuilder() { + } + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + public BuilderDefaultsBuilder x(final int x) { + this.x = x; + x$set = true; + return this; + } + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + public BuilderDefaultsBuilder name(final String name) { + this.name = name; + return this; + } + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + public BuilderDefaultsBuilder z(final long z) { + this.z = z; + z$set = true; + return this; + } + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + public BuilderDefaults build() { + return new BuilderDefaults(x$set ? x : BuilderDefaults.$default$x(), name, z$set ? z : BuilderDefaults.$default$z()); + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + public java.lang.String toString() { + return "BuilderDefaults.BuilderDefaultsBuilder(x=" + this.x + ", name=" + this.name + ", z=" + this.z + ")"; + } + } + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + public static BuilderDefaultsBuilder builder() { + return new BuilderDefaultsBuilder(); + } + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + public int getX() { + return this.x; + } + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + public String getName() { + return this.name; + } + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + public long getZ() { + return this.z; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + public boolean equals(final java.lang.Object o) { + if (o == this) return true; + if (!(o instanceof BuilderDefaults)) return false; + final BuilderDefaults other = (BuilderDefaults) o; + if (this.getX() != other.getX()) return false; + final java.lang.Object this$name = this.getName(); + final java.lang.Object other$name = other.getName(); + if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false; + if (this.getZ() != other.getZ()) return false; + return true; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + public int hashCode() { + final int PRIME = 59; + int result = 1; + result = result * PRIME + this.getX(); + final java.lang.Object $name = this.getName(); + result = result * PRIME + ($name == null ? 43 : $name.hashCode()); + final long $z = this.getZ(); + result = result * PRIME + (int) ($z >>> 32 ^ $z); + return result; + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + public java.lang.String toString() { + return "BuilderDefaults(x=" + this.getX() + ", name=" + this.getName() + ", z=" + this.getZ() + ")"; + } +} diff --git a/test/transform/resource/after-delombok/BuilderDefaultsWarnings.java b/test/transform/resource/after-delombok/BuilderDefaultsWarnings.java new file mode 100644 index 00000000..e8247f52 --- /dev/null +++ b/test/transform/resource/after-delombok/BuilderDefaultsWarnings.java @@ -0,0 +1,53 @@ +public class BuilderDefaultsWarnings { + long x = System.currentTimeMillis(); + final int y = 5; + int z; + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + BuilderDefaultsWarnings(final long x, final int z) { + this.x = x; + this.z = z; + } + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + public static class BuilderDefaultsWarningsBuilder { + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + private long x; + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + private int z; + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + BuilderDefaultsWarningsBuilder() { + } + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + public BuilderDefaultsWarningsBuilder x(final long x) { + this.x = x; + return this; + } + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + public BuilderDefaultsWarningsBuilder z(final int z) { + this.z = z; + return this; + } + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + public BuilderDefaultsWarnings build() { + return new BuilderDefaultsWarnings(x, z); + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + public java.lang.String toString() { + return "BuilderDefaultsWarnings.BuilderDefaultsWarningsBuilder(x=" + this.x + ", z=" + this.z + ")"; + } + } + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + public static BuilderDefaultsWarningsBuilder builder() { + return new BuilderDefaultsWarningsBuilder(); + } +} diff --git a/test/transform/resource/after-ecj/BuilderDefaults.java b/test/transform/resource/after-ecj/BuilderDefaults.java new file mode 100644 index 00000000..96903dca --- /dev/null +++ b/test/transform/resource/after-ecj/BuilderDefaults.java @@ -0,0 +1,90 @@ +import lombok.Builder; +import lombok.Value; +public final @Value @Builder class BuilderDefaults { + public static @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") class BuilderDefaultsBuilder { + private @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") int x; + private @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") boolean x$set; + private @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") String name; + private @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") long z; + private @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") boolean z$set; + @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") BuilderDefaultsBuilder() { + super(); + } + public @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") BuilderDefaultsBuilder x(final int x) { + this.x = x; + x$set = true; + return this; + } + public @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") BuilderDefaultsBuilder name(final String name) { + this.name = name; + return this; + } + public @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") BuilderDefaultsBuilder z(final long z) { + this.z = z; + z$set = true; + return this; + } + public @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") BuilderDefaults build() { + return new BuilderDefaults((x$set ? x : BuilderDefaults.$default$x()), name, (z$set ? z : BuilderDefaults.$default$z())); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") java.lang.String toString() { + return (((((("BuilderDefaults.BuilderDefaultsBuilder(x=" + this.x) + ", name=") + this.name) + ", z=") + this.z) + ")"); + } + } + private final @Builder.Default int x; + private final String name; + private final @Builder.Default long z; + private static @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") int $default$x() { + return 10; + } + private static @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") long $default$z() { + return System.currentTimeMillis(); + } + @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") BuilderDefaults(final int x, final String name, final long z) { + super(); + this.x = x; + this.name = name; + this.z = z; + } + public static @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") BuilderDefaultsBuilder builder() { + return new BuilderDefaultsBuilder(); + } + public @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") int getX() { + return this.x; + } + public @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") String getName() { + return this.name; + } + public @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") long getZ() { + return this.z; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") boolean equals(final java.lang.Object o) { + if ((o == this)) + return true; + if ((! (o instanceof BuilderDefaults))) + return false; + final BuilderDefaults other = (BuilderDefaults) o; + if ((this.getX() != other.getX())) + return false; + final java.lang.Object this$name = this.getName(); + final java.lang.Object other$name = other.getName(); + if (((this$name == null) ? (other$name != null) : (! this$name.equals(other$name)))) + return false; + if ((this.getZ() != other.getZ())) + return false; + return true; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") int hashCode() { + final int PRIME = 59; + int result = 1; + result = ((result * PRIME) + this.getX()); + final java.lang.Object $name = this.getName(); + result = ((result * PRIME) + (($name == null) ? 43 : $name.hashCode())); + final long $z = this.getZ(); + result = ((result * PRIME) + (int) ($z ^ ($z >>> 32))); + return result; + } + public @java.lang.Override @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") java.lang.String toString() { + return (((((("BuilderDefaults(x=" + this.getX()) + ", name=") + this.getName()) + ", z=") + this.getZ()) + ")"); + } +} diff --git a/test/transform/resource/after-ecj/BuilderDefaultsWarnings.java b/test/transform/resource/after-ecj/BuilderDefaultsWarnings.java new file mode 100644 index 00000000..3b93f046 --- /dev/null +++ b/test/transform/resource/after-ecj/BuilderDefaultsWarnings.java @@ -0,0 +1,35 @@ +import lombok.Builder; +public @Builder class BuilderDefaultsWarnings { + public static @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") class BuilderDefaultsWarningsBuilder { + private @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") long x; + private @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") int z; + @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") BuilderDefaultsWarningsBuilder() { + super(); + } + public @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") BuilderDefaultsWarningsBuilder x(final long x) { + this.x = x; + return this; + } + public @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") BuilderDefaultsWarningsBuilder z(final int z) { + this.z = z; + return this; + } + public @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") BuilderDefaultsWarnings build() { + return new BuilderDefaultsWarnings(x, z); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") java.lang.String toString() { + return (((("BuilderDefaultsWarnings.BuilderDefaultsWarningsBuilder(x=" + this.x) + ", z=") + this.z) + ")"); + } + } + long x = System.currentTimeMillis(); + final int y = 5; + @Builder.Default int z; + @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") BuilderDefaultsWarnings(final long x, final int z) { + super(); + this.x = x; + this.z = z; + } + public static @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") BuilderDefaultsWarningsBuilder builder() { + return new BuilderDefaultsWarningsBuilder(); + } +} diff --git a/test/transform/resource/before/BuilderDefaults.java b/test/transform/resource/before/BuilderDefaults.java new file mode 100644 index 00000000..a1ce14a5 --- /dev/null +++ b/test/transform/resource/before/BuilderDefaults.java @@ -0,0 +1,9 @@ +import lombok.Builder; +import lombok.Value; + +@Value @Builder +public class BuilderDefaults { + @Builder.Default int x = 10; + String name; + @Builder.Default long z = System.currentTimeMillis(); +} diff --git a/test/transform/resource/before/BuilderDefaultsWarnings.java b/test/transform/resource/before/BuilderDefaultsWarnings.java new file mode 100644 index 00000000..a93f0f17 --- /dev/null +++ b/test/transform/resource/before/BuilderDefaultsWarnings.java @@ -0,0 +1,8 @@ +import lombok.Builder; + +@Builder +public class BuilderDefaultsWarnings { + long x = System.currentTimeMillis(); + final int y = 5; + @Builder.Default int z; +} diff --git a/test/transform/resource/messages-delombok/BuilderDefaultsWarnings.java.messages b/test/transform/resource/messages-delombok/BuilderDefaultsWarnings.java.messages new file mode 100644 index 00000000..5f607134 --- /dev/null +++ b/test/transform/resource/messages-delombok/BuilderDefaultsWarnings.java.messages @@ -0,0 +1,2 @@ +5 @Builder will ignore the initializing expression entirely. If you want the initializing expression to serve as default, add @Builder.Default. if it is not supposed to be settable during building, add @Builder.Constant. +7 @Builder.Default requires an initializing expression (' = something;'). diff --git a/test/transform/resource/messages-delombok/GetterOnMethodErrors2.java.messages b/test/transform/resource/messages-delombok/GetterOnMethodErrors2.java.messages index 6a181265..3386b565 100644 --- a/test/transform/resource/messages-delombok/GetterOnMethodErrors2.java.messages +++ b/test/transform/resource/messages-delombok/GetterOnMethodErrors2.java.messages @@ -1,4 +1,4 @@ -2 The correct format is @Getter(onMethod_={@SomeAnnotation, @SomeOtherAnnotation}) -3 The correct format is @Getter(onMethod_={@SomeAnnotation, @SomeOtherAnnotation}) -4 The correct format is @Getter(onMethod_={@SomeAnnotation, @SomeOtherAnnotation}) -5 The correct format is @Getter(onMethod_={@SomeAnnotation, @SomeOtherAnnotation}) +3 The correct format is +4 The correct format is +5 The correct format is +6 The correct format is diff --git a/test/transform/resource/messages-ecj/BuilderDefaultsWarnings.java.messages b/test/transform/resource/messages-ecj/BuilderDefaultsWarnings.java.messages new file mode 100644 index 00000000..e369db9c --- /dev/null +++ b/test/transform/resource/messages-ecj/BuilderDefaultsWarnings.java.messages @@ -0,0 +1,3 @@ +5 @Builder will ignore the initializing expression entirely. If you want the initializing expression to serve as default, add @Builder.Default. if it is not supposed to be settable during building, add @Builder.Constant. +7 @Builder.Default requires an initializing expression (' = something;'). + diff --git a/test/transform/resource/messages-ecj/GetterOnMethodErrors2.java.messages b/test/transform/resource/messages-ecj/GetterOnMethodErrors2.java.messages index 6a181265..3386b565 100644 --- a/test/transform/resource/messages-ecj/GetterOnMethodErrors2.java.messages +++ b/test/transform/resource/messages-ecj/GetterOnMethodErrors2.java.messages @@ -1,4 +1,4 @@ -2 The correct format is @Getter(onMethod_={@SomeAnnotation, @SomeOtherAnnotation}) -3 The correct format is @Getter(onMethod_={@SomeAnnotation, @SomeOtherAnnotation}) -4 The correct format is @Getter(onMethod_={@SomeAnnotation, @SomeOtherAnnotation}) -5 The correct format is @Getter(onMethod_={@SomeAnnotation, @SomeOtherAnnotation}) +3 The correct format is +4 The correct format is +5 The correct format is +6 The correct format is diff --git a/usage_examples/BuilderExample_post.jpage b/usage_examples/BuilderExample_post.jpage index 54b064d7..0b64703c 100644 --- a/usage_examples/BuilderExample_post.jpage +++ b/usage_examples/BuilderExample_post.jpage @@ -1,6 +1,7 @@ import java.util.Set; public class BuilderExample { + private long created; private String name; private int age; private Set<String> occupations; @@ -11,11 +12,17 @@ public class BuilderExample { this.occupations = occupations; } + private static long $default$created() { + return System.currentTimeMillis(); + } + public static BuilderExampleBuilder builder() { return new BuilderExampleBuilder(); } public static class BuilderExampleBuilder { + private long created; + private boolean created$set; private String name; private int age; private java.util.ArrayList<String> occupations; @@ -23,6 +30,12 @@ public class BuilderExample { BuilderExampleBuilder() { } + public BuilderExampleBuilder created(long created) { + this.created = created; + this.created$set = true; + return this; + } + public BuilderExampleBuilder name(String name) { this.name = name; return this; @@ -63,12 +76,12 @@ public class BuilderExample { // complicated switch statement to produce a compact properly sized immutable set omitted. // go to https://projectlombok.org/features/Singular-snippet.html to see it. Set<String> occupations = ...; - return new BuilderExample(name, age, occupations); + return new BuilderExample(created$set ? created : BuilderExample.$default$created(), name, age, occupations); } @java.lang.Override public String toString() { - return "BuilderExample.BuilderExampleBuilder(name = " + this.name + ", age = " + this.age + ", occupations = " + this.occupations + ")"; + return "BuilderExample.BuilderExampleBuilder(created = " + this.created + ", name = " + this.name + ", age = " + this.age + ", occupations = " + this.occupations + ")"; } } }
\ No newline at end of file diff --git a/usage_examples/BuilderExample_pre.jpage b/usage_examples/BuilderExample_pre.jpage index 1557fff4..3b1b0df2 100644 --- a/usage_examples/BuilderExample_pre.jpage +++ b/usage_examples/BuilderExample_pre.jpage @@ -4,6 +4,7 @@ import java.util.Set; @Builder public class BuilderExample { + @Builder.Default private long created = System.currentTimeMillis(); private String name; private int age; @Singular private Set<String> occupations; diff --git a/website/features/Builder.html b/website/features/Builder.html index 22708c8d..20518226 100644 --- a/website/features/Builder.html +++ b/website/features/Builder.html @@ -20,6 +20,8 @@ <code>@Builder</code> gained <code>@Singular</code> support and was promoted to the main <code>lombok</code> package since lombok v1.16.0. </p><p> <code>@Builder</code> with <code>@Singular</code> adds a clear method since lombok v1.16.8. + </p><p> + <code>@Builder.Default</code> functionality was added in lombok v1.16.16. </div> <div class="overview"> <h3>Overview</h3> @@ -79,7 +81,14 @@ </p> </div> <div class="overview"> - <h3><a name="singular">@Singular</a></h3> + <h3><a id="builderdefault" name="builderdefault">@Builder.Default</a></h3> + <p> + To specify a default value, which will be used if, during the build process this value is never set, initialize the field and add the <code>@Builder.Default</code> annotation:<br /> + <code>@Builder.Default private final long created = System.currentTimeMillis();</code> + </p> + </div> + <div class="overview"> + <h3><a id="singular" name="singular">@Singular</a></h3> <p> By annotating one of the parameters (if annotating a method or constructor with <code>@Builder</code>) or fields (if annotating a class with <code>@Builder</code>) with the <code>@Singular</code> annotation, lombok will treat that builder node as a collection, and it generates 2 'adder' methods instead of a 'setter' method. One which adds a single element to the collection, and one |