* Fixed issues with @FieldDefaults and @Value (you can NOT override

@Value's final-by-default and private-by-default with it; now
appropriate warnings are emitted)
* Builder now errors out on presence of most lombok annotations on an
explicit builder class.
* Builder now takes @FieldDefaults/@Value into account.
* Builder on type now generates the constructor as package private
instead of private to avoid synthetic accessor constructors.
* added a bunch of test cases.
* added a test case feature: If the expected file is omitted entirely
but there are expected messages, the differences in the output itself
are ignored.
* streamlined checking for boolean-ness (removed some duplicate code)
* added 'fluent' and 'chain' to @Builder.
This commit is contained in:
Reinier Zwitserloot committed 2013-07-16 00:51:31 +02:00
1 parent ec0cc4348c
commit 7af9add999
23 files changed
+290 -48

No files matched your search

+22 -3
View File
@@ -22,13 +22,25 @@
package lombok.core;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.Value;
import lombok.experimental.Accessors;
import lombok.experimental.FieldDefaults;
import lombok.experimental.Wither;
/**
* Container for static utility methods useful for some of the standard lombok transformations, regardless of
@@ -39,6 +51,13 @@ public class TransformationsUtil {
//Prevent instantiation
}
@SuppressWarnings({"all", "unchecked", "deprecation"})
public static final List<Class<? extends java.lang.annotation.Annotation>> INVALID_ON_BUILDERS = Collections.unmodifiableList(
Arrays.<Class<? extends java.lang.annotation.Annotation>>asList(
Getter.class, Setter.class, Wither.class, ToString.class, EqualsAndHashCode.class,
RequiredArgsConstructor.class, AllArgsConstructor.class, NoArgsConstructor.class,
Data.class, Value.class, lombok.experimental.Value.class, FieldDefaults.class));
/**
* Given the name of a field, return the 'base name' of that field. For example, {@code fFoobar} becomes {@code foobar} if {@code f} is in the prefix list.
* For prefixes that end in a letter character, the next character must be a non-lowercase character (i.e. {@code hashCode} is not {@code ashCode} even if
@@ -159,12 +178,12 @@ public class TransformationsUtil {
if (fieldName.length() == 0) return null;
Accessors ac = accessors.getInstance();
fieldName = removePrefix(fieldName, ac.prefix());
Accessors ac = accessors == null ? null : accessors.getInstance();
fieldName = removePrefix(fieldName, ac == null ? new String[0] : ac.prefix());
if (fieldName == null) return null;
String fName = fieldName.toString();
if (adhereToFluent && ac.fluent()) return fName;
if (adhereToFluent && ac != null && ac.fluent()) return fName;
if (isBoolean && fName.startsWith("is") && fieldName.length() > 2 && !Character.isLowerCase(fieldName.charAt(2))) {
// The field is for example named 'isRunning'.
@@ -22,6 +22,7 @@
package lombok.eclipse.handlers;
import static lombok.eclipse.Eclipse.*;
import static lombok.core.TransformationsUtil.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@@ -296,6 +297,29 @@ public class EclipseHandlerUtil {
}
public static void sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(EclipseNode typeNode, EclipseNode errorNode) {
List<String> disallowed = null;
for (EclipseNode child : typeNode.down()) {
for (Class<? extends java.lang.annotation.Annotation> annType : INVALID_ON_BUILDERS) {
if (annotationTypeMatches(annType, child)) {
if (disallowed == null) disallowed = new ArrayList<String>();
disallowed.add(annType.getSimpleName());
}
}
}
int size = disallowed == null ? 0 : disallowed.size();
if (size == 0) return;
if (size == 1) {
errorNode.addError("@" + disallowed.get(0) + " is not allowed on builder classes.");
return;
}
StringBuilder out = new StringBuilder();
for (String a : disallowed) out.append("@").append(a).append(", ");
out.setLength(out.length() - 2);
errorNode.addError(out.append(" are not allowed on builder classes.").toString());
}
public static Annotation copyAnnotation(Annotation annotation, ASTNode source) {
int pS = source.sourceStart, pE = source.sourceEnd;
@@ -845,15 +869,20 @@ public class EclipseHandlerUtil {
private static final Object MARKER = new Object();
static void registerCreatedLazyGetter(FieldDeclaration field, char[] methodName, TypeReference returnType) {
if (!nameEquals(returnType.getTypeName(), "boolean") || returnType.dimensions() > 0) return;
generatedLazyGettersWithPrimitiveBoolean.put(field, MARKER);
if (isBoolean(returnType)) {
generatedLazyGettersWithPrimitiveBoolean.put(field, MARKER);
}
}
public static boolean isBoolean(TypeReference typeReference) {
return nameEquals(typeReference.getTypeName(), "boolean") && typeReference.dimensions() == 0;
}
private static GetterMethod findGetter(EclipseNode field) {
FieldDeclaration fieldDeclaration = (FieldDeclaration) field.get();
boolean forceBool = generatedLazyGettersWithPrimitiveBoolean.containsKey(fieldDeclaration);
TypeReference fieldType = fieldDeclaration.type;
boolean isBoolean = forceBool || (nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0);
boolean isBoolean = forceBool || isBoolean(fieldType);
EclipseNode typeNode = field.up();
for (String potentialGetterName : toAllGetterNames(field, isBoolean)) {
@@ -58,13 +58,17 @@ import org.mangosdk.spi.ProviderFor;
import lombok.AccessLevel;
import lombok.core.AST.Kind;
import lombok.core.AnnotationValues;
import lombok.core.HandlerPriority;
import lombok.core.TransformationsUtil;
import lombok.eclipse.Eclipse;
import lombok.eclipse.EclipseAnnotationHandler;
import lombok.eclipse.EclipseNode;
import lombok.eclipse.handlers.HandleConstructor.SkipIfConstructorExists;
import lombok.experimental.Builder;
import lombok.experimental.NonFinal;
@ProviderFor(EclipseAnnotationHandler.class)
@HandlerPriority(-1024) //-2^10; to ensure we've picked up @FieldDefault's changes (-2048) but @Value hasn't removed itself yet (-512), so that we can error on presence of it on the builder classes.
public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
@Override public void handle(AnnotationValues<Builder> annotation, Annotation ast, EclipseNode annotationNode) {
long p = (long) ast.sourceStart << 32 | ast.sourceEnd;
@@ -99,14 +103,23 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
if (parent.get() instanceof TypeDeclaration) {
tdParent = parent;
TypeDeclaration td = (TypeDeclaration) tdParent.get();
new HandleConstructor().generateAllArgsConstructor(tdParent, AccessLevel.PRIVATE, null, SkipIfConstructorExists.I_AM_BUILDER, Collections.<Annotation>emptyList(), ast);
List<EclipseNode> fields = new ArrayList<EclipseNode>();
@SuppressWarnings("deprecation")
boolean valuePresent = (hasAnnotation(lombok.Value.class, parent) || hasAnnotation(lombok.experimental.Value.class, parent));
for (EclipseNode fieldNode : HandleConstructor.findAllFields(tdParent)) {
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;
namesOfParameters.add(fd.name);
typesOfParameters.add(fd.type);
fields.add(fieldNode);
}
new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, fields, null, SkipIfConstructorExists.I_AM_BUILDER, true, Collections.<Annotation>emptyList(), ast);
returnType = namePlusTypeParamsToTypeReference(td.name, td.typeParameters, p);
typeParams = td.typeParameters;
thrownExceptions = null;
@@ -181,11 +194,15 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
}
EclipseNode builderType = findInnerClass(tdParent, builderClassName);
if (builderType == null) builderType = makeBuilderClass(tdParent, builderClassName, typeParams, ast);
if (builderType == null) {
builderType = makeBuilderClass(tdParent, builderClassName, typeParams, ast);
} else {
sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(builderType, annotationNode);
}
List<EclipseNode> fieldNodes = addFieldsToBuilder(builderType, namesOfParameters, typesOfParameters, ast);
List<AbstractMethodDeclaration> newMethods = new ArrayList<AbstractMethodDeclaration>();
for (EclipseNode fieldNode : fieldNodes) {
MethodDeclaration newMethod = makeSetterMethodForBuilder(builderType, fieldNode, ast);
MethodDeclaration newMethod = makeSetterMethodForBuilder(builderType, fieldNode, ast, builderInstance.fluent(), builderInstance.chain());
if (newMethod != null) newMethods.add(newMethod);
}
@@ -315,7 +332,7 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
private static final AbstractMethodDeclaration[] EMPTY = {};
private MethodDeclaration makeSetterMethodForBuilder(EclipseNode builderType, EclipseNode fieldNode, ASTNode source) {
private MethodDeclaration makeSetterMethodForBuilder(EclipseNode builderType, EclipseNode fieldNode, ASTNode source, boolean fluent, boolean chain) {
TypeDeclaration td = (TypeDeclaration) builderType.get();
AbstractMethodDeclaration[] existing = td.methods;
if (existing == null) existing = EMPTY;
@@ -329,7 +346,10 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
if (Arrays.equals(name, existingName)) return null;
}
return HandleSetter.createSetter(td, fieldNode, fieldNode.getName(), true, ClassFileConstants.AccPublic,
boolean isBoolean = isBoolean(fd.type);
String setterName = fluent ? fieldNode.getName() : TransformationsUtil.toSetterName(null, fieldNode.getName(), isBoolean);
return HandleSetter.createSetter(td, fieldNode, setterName, chain, ClassFileConstants.AccPublic,
source, Collections.<Annotation>emptyList(), Collections.<Annotation>emptyList());
}
@@ -43,7 +43,7 @@ import org.mangosdk.spi.ProviderFor;
* Handles the {@code lombok.FieldDefaults} annotation for eclipse.
*/
@ProviderFor(EclipseAnnotationHandler.class)
@HandlerPriority(-512) //-2^9; to ensure @Setter and such pick up on messing with the fields' 'final' state, run earlier.
@HandlerPriority(-2048) //-2^11; to ensure @Value picks up on messing with the fields' 'final' state, run earlier.
public class HandleFieldDefaults extends EclipseAnnotationHandler<FieldDefaults> {
public boolean generateFieldDefaultsForType(EclipseNode typeNode, EclipseNode pos, AccessLevel level, boolean makeFinal, boolean checkForTypeLevelFieldDefaults) {
if (checkForTypeLevelFieldDefaults) {
@@ -112,6 +112,14 @@ public class HandleFieldDefaults extends EclipseAnnotationHandler<FieldDefaults>
return;
}
if (level == AccessLevel.PACKAGE) {
annotationNode.addError("Setting 'level' to PACKAGE does nothing. To force fields as package private, use the @PackagePrivate annotation on the field.");
}
if (!makeFinal && annotation.isExplicit("makeFinal")) {
annotationNode.addError("Setting 'makeFinal' to false does nothing. To force fields to be non-final, use the @NonFinal annotation on the field.");
}
if (node == null) return;
generateFieldDefaultsForType(node, annotationNode, level, makeFinal, false);
@@ -187,7 +187,7 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> {
}
TypeReference fieldType = copyType(field.type, source);
boolean isBoolean = nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0;
boolean isBoolean = isBoolean(fieldType);
String getterName = toGetterName(fieldNode, isBoolean);
if (getterName == null) {
@@ -159,7 +159,7 @@ public class HandleSetter extends EclipseAnnotationHandler<Setter> {
FieldDeclaration field = (FieldDeclaration) fieldNode.get();
TypeReference fieldType = copyType(field.type, source);
boolean isBoolean = nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0;
boolean isBoolean = isBoolean(fieldType);
String setterName = toSetterName(fieldNode, isBoolean);
boolean shouldReturnThis = shouldReturnThis(fieldNode);
@@ -160,7 +160,7 @@ public class HandleWither extends EclipseAnnotationHandler<Wither> {
FieldDeclaration field = (FieldDeclaration) fieldNode.get();
TypeReference fieldType = copyType(field.type, source);
boolean isBoolean = nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0;
boolean isBoolean = isBoolean(fieldType);
String witherName = toWitherName(fieldNode, isBoolean);
if (witherName == null) {
+16
View File
@@ -118,4 +118,20 @@ public @interface Builder {
* Default for {@code @Builder} on static methods: {@code (ReturnTypeName)Builder}.
*/
String builderClassName() default "";
/**
* Normally the builder's 'set' methods are fluent, meaning, they have the same name as the field. Set this
* to {@code false} to name the setter method for field {@code someField}: {@code setSomeField}.
* <p>
* <strong>Default: true</strong>
*/
boolean fluent() default true;
/**
* Normally the builder's 'set' methods are chaining, meaning, they return the builder so that you can chain
* calls to set methods. Set this to {@code false} to have these 'set' methods return {@code void} instead.
* <p>
* <strong>Default: true</strong>
*/
boolean chain() default true;
}
@@ -49,16 +49,19 @@ import com.sun.tools.javac.util.Name;
import lombok.AccessLevel;
import lombok.core.AST.Kind;
import lombok.core.AnnotationValues;
import lombok.core.HandlerPriority;
import lombok.core.TransformationsUtil;
import lombok.experimental.Builder;
import lombok.experimental.NonFinal;
import lombok.javac.JavacAnnotationHandler;
import lombok.javac.JavacNode;
import lombok.javac.handlers.HandleConstructor.SkipIfConstructorExists;
import static lombok.javac.Javac.*;
import static lombok.core.handlers.HandlerUtil.*;
import static lombok.javac.handlers.JavacHandlerUtil.*;
@ProviderFor(JavacAnnotationHandler.class)
@HandlerPriority(-1024) //-2^10; to ensure we've picked up @FieldDefault's changes (-2048) but @Value hasn't removed itself yet (-512), so that we can error on presence of it on the builder classes.
public class HandleBuilder extends JavacAnnotationHandler<Builder> {
@Override public void handle(AnnotationValues<Builder> annotation, JCAnnotation ast, JavacNode annotationNode) {
Builder builderInstance = annotation.getInstance();
@@ -94,14 +97,22 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> {
if (parent.get() instanceof JCClassDecl) {
tdParent = parent;
JCClassDecl td = (JCClassDecl) tdParent.get();
new HandleConstructor().generateAllArgsConstructor(tdParent, AccessLevel.PRIVATE, null, SkipIfConstructorExists.I_AM_BUILDER, annotationNode);
ListBuffer<JavacNode> allFields = ListBuffer.lb();
@SuppressWarnings("deprecation")
boolean valuePresent = (hasAnnotation(lombok.Value.class, parent) || hasAnnotation(lombok.experimental.Value.class, parent));
for (JavacNode fieldNode : HandleConstructor.findAllFields(tdParent)) {
JCVariableDecl fd = (JCVariableDecl) 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.init != null && valuePresent && !hasAnnotation(NonFinal.class, fieldNode)) continue;
namesOfParameters.add(fd.name);
typesOfParameters.add(fd.vartype);
allFields.append(fieldNode);
}
new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, List.<JCAnnotation>nil(), allFields.toList(), null, SkipIfConstructorExists.I_AM_BUILDER, true, annotationNode);
returnType = namePlusTypeParamsToTypeReference(tdParent.getTreeMaker(), td.name, td.typarams);
typeParams = td.typarams;
thrownExceptions = List.nil();
@@ -171,11 +182,15 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> {
}
JavacNode builderType = findInnerClass(tdParent, builderClassName);
if (builderType == null) builderType = makeBuilderClass(tdParent, builderClassName, typeParams, ast);
if (builderType == null) {
builderType = makeBuilderClass(tdParent, builderClassName, typeParams, ast);
} else {
sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(builderType, annotationNode);
}
java.util.List<JavacNode> fieldNodes = addFieldsToBuilder(builderType, namesOfParameters, typesOfParameters, ast);
java.util.List<JCMethodDecl> newMethods = new ArrayList<JCMethodDecl>();
for (JavacNode fieldNode : fieldNodes) {
JCMethodDecl newMethod = makeSetterMethodForBuilder(builderType, fieldNode, ast);
JCMethodDecl newMethod = makeSetterMethodForBuilder(builderType, fieldNode, ast, builderInstance.fluent(), builderInstance.chain());
if (newMethod != null) newMethods.add(newMethod);
}
@@ -281,16 +296,20 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> {
}
private JCMethodDecl makeSetterMethodForBuilder(JavacNode builderType, JavacNode fieldNode, JCTree source) {
private JCMethodDecl makeSetterMethodForBuilder(JavacNode builderType, JavacNode fieldNode, JCTree source, boolean fluent, boolean chain) {
Name fieldName = ((JCVariableDecl) fieldNode.get()).name;
for (JavacNode child : builderType.down()) {
if (child.getKind() != Kind.METHOD) continue;
Name existingName = ((JCMethodDecl) child.get()).name;
if (existingName.equals(fieldName)) return null;
}
boolean isBoolean = isBoolean(fieldNode);
String setterName = fluent ? fieldNode.getName() : TransformationsUtil.toSetterName(null, fieldNode.getName(), isBoolean);
TreeMaker maker = builderType.getTreeMaker();
return HandleSetter.createSetter(Flags.PUBLIC, fieldNode, maker, fieldName.toString(), true, source, List.<JCAnnotation>nil(), List.<JCAnnotation>nil());
return HandleSetter.createSetter(Flags.PUBLIC, fieldNode, maker, setterName, chain, source, List.<JCAnnotation>nil(), List.<JCAnnotation>nil());
}
private JavacNode findInnerClass(JavacNode parent, String name) {
@@ -44,7 +44,7 @@ import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
* Handles the {@code lombok.FieldDefaults} annotation for eclipse.
*/
@ProviderFor(JavacAnnotationHandler.class)
@HandlerPriority(-512) //-2^9; to ensure @Setter and such pick up on messing with the fields' 'final' state, run earlier.
@HandlerPriority(-2048) //-2^11; to ensure @Value picks up on messing with the fields' 'final' state, run earlier.
public class HandleFieldDefaults extends JavacAnnotationHandler<FieldDefaults> {
public boolean generateFieldDefaultsForType(JavacNode typeNode, JavacNode errorNode, AccessLevel level, boolean makeFinal, boolean checkForTypeLevelFieldDefaults) {
if (checkForTypeLevelFieldDefaults) {
@@ -108,6 +108,14 @@ public class HandleFieldDefaults extends JavacAnnotationHandler<FieldDefaults> {
return;
}
if (level == AccessLevel.PACKAGE) {
annotationNode.addError("Setting 'level' to PACKAGE does nothing. To force fields as package private, use the @PackagePrivate annotation on the field.");
}
if (!makeFinal && annotation.isExplicit("makeFinal")) {
annotationNode.addError("Setting 'makeFinal' to false does nothing. To force fields to be non-final, use the @NonFinal annotation on the field.");
}
if (node == null) return;
generateFieldDefaultsForType(node, annotationNode, level, makeFinal, false);
@@ -21,6 +21,7 @@
*/
package lombok.javac.handlers;
import static lombok.core.TransformationsUtil.INVALID_ON_BUILDERS;
import static lombok.javac.Javac.*;
import java.lang.annotation.Annotation;
@@ -446,8 +447,12 @@ public class JavacHandlerUtil {
}
}
private static boolean isBoolean(JavacNode field) {
public static boolean isBoolean(JavacNode field) {
JCExpression varType = ((JCVariableDecl) field.get()).vartype;
return isBoolean(varType);
}
public static boolean isBoolean(JCExpression varType) {
return varType != null && varType.toString().equals("boolean");
}
@@ -1065,6 +1070,28 @@ public class JavacHandlerUtil {
return maker.Ident(typeName);
}
public static void sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(JavacNode typeNode, JavacNode errorNode) {
List<String> disallowed = List.nil();
for (JavacNode child : typeNode.down()) {
for (Class<? extends java.lang.annotation.Annotation> annType : INVALID_ON_BUILDERS) {
if (annotationTypeMatches(annType, child)) {
disallowed = disallowed.append(annType.getSimpleName());
}
}
}
int size = disallowed.size();
if (size == 0) return;
if (size == 1) {
errorNode.addError("@" + disallowed.head + " is not allowed on builder classes.");
return;
}
StringBuilder out = new StringBuilder();
for (String a : disallowed) out.append("@").append(a).append(", ");
out.setLength(out.length() - 2);
errorNode.addError(out.append(" are not allowed on builder classes.").toString());
}
static List<JCAnnotation> copyAnnotations(List<? extends JCExpression> in) {
ListBuffer<JCAnnotation> out = ListBuffer.lb();
for (JCExpression expr : in) {
+11 -7
View File
@@ -68,10 +68,12 @@ public abstract class AbstractRunTests {
}
}
StringReader r = new StringReader(expectedFile);
BufferedReader br = new BufferedReader(r);
String firstLine = br.readLine();
if (firstLine != null && (firstLine.startsWith("//ignore") || params.shouldIgnoreBasedOnVersion(firstLine))) return false;
if (expectedFile != null) {
StringReader r = new StringReader(expectedFile);
BufferedReader br = new BufferedReader(r);
String firstLine = br.readLine();
if (firstLine != null && (firstLine.startsWith("//ignore") || params.shouldIgnoreBasedOnVersion(firstLine))) return false;
}
compare(
file.getName(),
@@ -91,7 +93,7 @@ public abstract class AbstractRunTests {
try {
reader = new BufferedReader(new FileReader(file));
} catch (FileNotFoundException e) {
return "";
return null;
}
StringBuilder result = new StringBuilder();
String line;
@@ -104,7 +106,7 @@ public abstract class AbstractRunTests {
}
private String readFile(File dir, File file, boolean messages) throws IOException {
if (dir == null) return "";
if (dir == null) return null;
return readFile(new File(dir, file.getName() + (messages ? ".messages" : "")));
}
@@ -140,7 +142,9 @@ public abstract class AbstractRunTests {
}
private void compare(String name, String expectedFile, String actualFile, List<CompilerMessageMatcher> expectedMessages, LinkedHashSet<CompilerMessage> actualMessages, boolean printErrors) throws Throwable {
try {
if (expectedFile == null && expectedMessages.isEmpty()) expectedFile = "";
if (expectedFile != null) try {
compareContent(name, expectedFile, actualFile);
} catch (Throwable e) {
if (printErrors) {
@@ -0,0 +1,31 @@
class BuilderChainAndFluent {
private final int yes;
@java.lang.SuppressWarnings("all")
BuilderChainAndFluent(final int yes) {
this.yes = yes;
}
@java.lang.SuppressWarnings("all")
public static class BuilderChainAndFluentBuilder {
private int yes;
@java.lang.SuppressWarnings("all")
BuilderChainAndFluentBuilder() {
}
@java.lang.SuppressWarnings("all")
public void setYes(final int yes) {
this.yes = yes;
}
@java.lang.SuppressWarnings("all")
public BuilderChainAndFluent build() {
return new BuilderChainAndFluent(yes);
}
@java.lang.Override
@java.lang.SuppressWarnings("all")
public java.lang.String toString() {
return "BuilderChainAndFluent.BuilderChainAndFluentBuilder(yes=" + this.yes + ")";
}
}
@java.lang.SuppressWarnings("all")
public static BuilderChainAndFluentBuilder builder() {
return new BuilderChainAndFluentBuilder();
}
}
@@ -5,7 +5,7 @@ class BuilderSimple<T> {
private List<T> also;
private int $butNotMe;
@java.lang.SuppressWarnings("all")
private BuilderSimple(final int yes, final List<T> also) {
BuilderSimple(final int yes, final List<T> also) {
this.yes = yes;
this.also = also;
}
@@ -0,0 +1,25 @@
@lombok.experimental.Builder(fluent = false,chain = false) class BuilderChainAndFluent {
public static @java.lang.SuppressWarnings("all") class BuilderChainAndFluentBuilder {
private int yes;
@java.lang.SuppressWarnings("all") BuilderChainAndFluentBuilder() {
super();
}
public @java.lang.SuppressWarnings("all") void setYes(final int yes) {
this.yes = yes;
}
public @java.lang.SuppressWarnings("all") BuilderChainAndFluent build() {
return new BuilderChainAndFluent(yes);
}
public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() {
return (("BuilderChainAndFluent.BuilderChainAndFluentBuilder(yes=" + this.yes) + ")");
}
}
private final int yes;
@java.lang.SuppressWarnings("all") BuilderChainAndFluent(final int yes) {
super();
this.yes = yes;
}
public static @java.lang.SuppressWarnings("all") BuilderChainAndFluentBuilder builder() {
return new BuilderChainAndFluentBuilder();
}
}
@@ -25,7 +25,7 @@ import java.util.List;
private final int yes;
private List<T> also;
private int $butNotMe;
private @java.lang.SuppressWarnings("all") BuilderSimple(final int yes, final List<T> also) {
@java.lang.SuppressWarnings("all") BuilderSimple(final int yes, final List<T> also) {
super();
this.yes = yes;
this.also = also;
@@ -0,0 +1,4 @@
@lombok.experimental.Builder(fluent = false, chain = false)
class BuilderChainAndFluent {
private final int yes;
}
@@ -0,0 +1,18 @@
@lombok.experimental.Builder
class BuilderInvalidUse {
private int something;
@lombok.Getter @lombok.Setter @lombok.experimental.FieldDefaults(makeFinal = true) @lombok.experimental.Wither @lombok.Data @lombok.ToString @lombok.EqualsAndHashCode
@lombok.AllArgsConstructor
public static class BuilderInvalidUseBuilder {
}
}
@lombok.experimental.Builder
class AlsoInvalid {
@lombok.Value
public static class AlsoInvalidBuilder {
}
}
@@ -0,0 +1,2 @@
1:1 @Getter, @Setter, @Wither, @Data, @ToString, @EqualsAndHashCode, @AllArgsConstructor are not allowed on builder classes.
12:1 @Value is not allowed on builder classes.
@@ -0,0 +1,2 @@
1:0 @Getter, @Setter, @FieldDefaults, @Wither, @Data, @ToString, @EqualsAndHashCode, @AllArgsConstructor are not allowed on builder classes.
12:331 @Value is not allowed on builder classes.
+4 -1
View File
@@ -31,7 +31,7 @@
</p><p>
It is possible to override the final-by-default and private-by-default behaviour using either an explicit access level on a field, or by using the <code>@NonFinal</code> or <code>@PackagePrivate</code> annotations.<br />
It is possible to override any default behaviour for any of the 'parts' that make up <code>@Value</code> by explicitly using that annotation.
</p>
</p>
</div>
<div class="snippets">
<div class="pre">
@@ -54,6 +54,9 @@
</p><p>
<code>@Value</code> was an experimental feature from v0.11.4 to v0.11.9 (as <code>@lombok.experimental.Value</code>). It has since been moved into the core package. The old annotation is still
around (and is an alias). It will eventually be removed in a future version, though.
</p><p>
It is not possible to use <code>@FieldDefaults</code> to 'undo' the private-by-default and final-by-default aspect of fields in the annotated class. Use <code>@NonFinal</code> and <code>@PackagePrivate</code> on the fields in the class to override this behaviour.
</p>
</div>
</div>
<div class="footer">
+20 -13
View File
@@ -11,7 +11,7 @@
<div class="meat">
<div class="header"><a href="../../index.html">Project Lombok</a></div>
<h1>@Builder</h1>
<div class="byline">It's like drinking tea with an extended pinky while wearing a monocle: No-hassle fancy-pants APIs for object creation!</div>
<div class="byline">... and Bob's your uncle: No-hassle fancy-pants APIs for object creation!</div>
<div class="since">
<h3>Since</h3>
<p>
@@ -24,7 +24,6 @@
Experimental because:
<ul>
<li>New feature - community feedback requested.</li>
<li>This feature will move to the core package soon.</li>
</ul>
Current status: <em>sure thing</em> - This feature will move to the core package soon.
</div>
@@ -43,7 +42,7 @@
<li>An inner static class named <code><em>Foo</em>Builder</code>, with the same type arguments as the static method (called the <em>builder</em>).</li>
<li>In the <em>builder</em>: One private non-static non-final field for each parameter of the <em>target</em>.</li>
<li>In the <em>builder</em>: A package private no-args empty constructor.</li>
<li>In the <em>builder</em>: A 'setter'-like method for each parmeter of the <em>target</em>: It has the same type as that parameter and the same name.
<li>In the <em>builder</em>: A 'setter'-like method for each parameter of the <em>target</em>: It has the same type as that parameter and the same name.
It returns the builder itself, so that the setter calls can be chained, as in the above example.</li>
<li>In the <em>builder</em>: A <code>build()</code> method which calls the static method, passing in each field. It returns the same type that the
<em>target</em> returns.</li>
@@ -52,18 +51,16 @@
</ul>
Each listed generated element will be silently skipped if that element already exists (disregarding parameter counts and looking only at names). This
includes the <em>builder</em> itself: If that class already exists, lombok will simply start injecting fields and methods inside this already existing
class, unless of course the fields / methods to be injected already exist.
class, unless of course the fields / methods to be injected already exist. You may not put any other method (or constructor) generating lombok annotation
on a builder class though; for example, you can not put <code>@EqualsAndHashCode</code> on the builder class.
</p><p>
Now that the "static method" mode is clear, putting a <code>@Builder</code> annotation on a constructor functions similarly; effectively,
constructors are just static methods that have a special syntax to invoke them: Their 'return type' is the class they construct, and their
type parameters are the same as the type parameters of the class itself.
</p><p>
Finally, applying <code>@Builder</code> to a class is as if you added <code>@AllArgsConstructor(acces = AccessLevel.PACKAGE)</code> to the class and applied the
<code>@Builder</code> annotation to this all-args-constructor. Note that this constructor is only generated if there is no explicit
constructor present in the so annotated class, but this <code>@AllArgsConstructor</code> has priority over any other implicitly
generated lombok constructor (such as <code>@Data</code> and <code>@Value</code>). If an explicit constructor is present, no constructor is generated,
but the builder will be created with the assumption that this constructor exists. If you've written another constructor, you'll get a compilation error.<br />
The solution is to either let lombok write this constructor (delete your own), or, annotate your constructor instead.
Finally, applying <code>@Builder</code> to a class is as if you added <code>@AllArgsConstructor(access = AccessLevel.PACKAGE)</code> to the class and applied the
<code>@Builder</code> annotation to this all-args-constructor. This only works if you haven't written any explicit constructors yourself. If you do have an
explicit constructor, put the <code>@Builder</code> annotation on the constructor instead of on the class.
</p><p>
The name of the builder class is <code><em>Foobar</em>Builder</code>, where <em>Foobar</em> is the simplified, title-cased form of the return type of the
<em>target</em> - that is, the name of your type for <code>@Builder</code> on constructors and types, and the name of the return type for <code>@Builder</code>
@@ -71,9 +68,15 @@
<code>FancyListBuilder&lt;T&gt;</code>. If <code>@Builder</code> is applied to a static method that returns <code>void</code>, the builder will be named
<code>VoidBuilder</code>.
</p><p>
The only configurable aspect of builder are the <em>builder's class name</em> (default: return type + 'Builder'), the <em>build()</em> method's name, and the
<em>builder()</em> method's name:<br />
<code>@Builder(builderClassName = "HelloWorldBuilder", buildMethodName = "execute", builderMethodName = "helloWorld")</code>
The configurable aspects of builder are:<ul>
<li>The <em>builder's class name</em> (default: return type + 'Builder')</li>
<li>The <em>build()</em> method's name (default: <code>"build"</code>)</li>
<li>The <em>builder()</em> method's name (default: <code>"builder"</code>)</li>
<li>The 'fluent' nature of the generated 'setter'-like methods. A fluent 'setter' is named just <code>fieldName()</code>, a non-fluent one is named <code>setFieldName()</code>. (default: <code>true</code>)</li>
<li>The 'chain' nature of the generated 'setter'-like methods. A chainable 'setter' returns the builder object itself, letting you chain 'set' calls. A non-chainable setter returns <code>void</code>. (default: <code>true</code>)</li>
</ul>
Example usage where all options are changed from their defaults:<br />
<code>@Builder(builderClassName = "HelloWorldBuilder", buildMethodName = "execute", builderMethodName = "helloWorld", fluent = false, chain = false)</code><br />
</p>
</div>
<div class="snippets">
@@ -104,6 +107,10 @@
instances of a builder as keys in a set or map. However, that's not a sensible thing to do. Hence, no <code>hashCode</code> or <code>equals</code>.
</p><p>
Generics are sorted out for you.
</p><p>
If an explicit constructor is present, but <code>@Builder</code> is placed on the class, then the builder will be generated as if an explicit constructor is present with the
same arguments list as what <code>@AllArgsConstructor</code> would produce. If this constructor does not exist, a compile time error will result. Usually you should just either let
lombok make this constructor (delete your constructor from the source), or, move the <code>@Builder</code> annotation to the constructor.
</p>
</div>
</div>
+1 -1
View File
@@ -23,7 +23,7 @@
as a core feature and move out of the experimental package.
<dl>
<dt><a href="Builder.html"><code>@Builder</code></a></dt>
<dd>It's like drinking tea with an extended pinky while wearing a monocle: No-hassle fancy-pants APIs for object creation!</dd>
<dd>... and Bob's your uncle: No-hassle fancy-pants APIs for object creation!</dd>
<dt><a href="Accessors.html"><code>@Accessors</code></a></dt>
<dd>A more fluent API for getters and setters.</dd>
<dt><a href="ExtensionMethod.html"><code>@ExtensionMethod</code></a></dt>