mirror of
https://github.com/tiennm99/lombok.git
synced 2026-10-13 16:15:16 +00:00
javac builder implementation. Passes all tests.
Added toString() impl for builders in both eclipse and javac. Added all documentation, though it'll need some reviewing.
This commit is contained in:
1 parent
eb3d32c718
commit
e1c39bbc60
18 files changed
+350
-22
No files matched your search
@@ -148,6 +148,9 @@ such as converting the changelog into HTML, and creating javadoc.
|
||||
<antcall target="-integrateSnippet">
|
||||
<param name="transformationName" value="NonNull" />
|
||||
</antcall>
|
||||
<antcall target="-integrateSnippet">
|
||||
<param name="transformationName" value="experimental/Builder" />
|
||||
</antcall>
|
||||
<antcall target="-integrateSnippet">
|
||||
<param name="transformationName" value="experimental/Accessors" />
|
||||
</antcall>
|
||||
|
||||
@@ -2,6 +2,7 @@ Lombok Changelog
|
||||
----------------
|
||||
|
||||
### v0.11.9 (Edgy Guinea Pig)
|
||||
* FEATURE: {Experimental} `@Builder` support. One of our earliest feature request issues has finally been addressed. [@Builder documentation](http://projectlombok.org/features/experimental/Builder.html).
|
||||
* FEATURE: `@NonNull` on a method or constructor parameter now generates a null-check statement at the start of your method. This nullcheck will throw a `NullPointerException` with the name of the parameter as the message. [Issue #514](https://code.google.com/p/projectlombok/issues/detail?id=514)
|
||||
* BUGFIX: Usage of `Lombok.sneakyThrow()` or `@SneakyThrows` would sometimes result in invalid classes (classes which fail with `VerifyError`). [Issue #470](https://code.google.com/p/projectlombok/issues/detail?id=470)
|
||||
* BUGFIX: Using `val` in try-with-resources did not work for javac. [Issue #520](https://code.google.com/p/projectlombok/issues/detail?id=520)
|
||||
|
||||
@@ -78,7 +78,7 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
|
||||
if (buildMethodName == null) builderMethodName = "build";
|
||||
if (builderClassName == null) builderClassName = "";
|
||||
|
||||
if (checkName("builderMethodName", builderMethodName, annotationNode)) return;
|
||||
if (!checkName("builderMethodName", builderMethodName, annotationNode)) return;
|
||||
if (!checkName("buildMethodName", buildMethodName, annotationNode)) return;
|
||||
if (!builderClassName.isEmpty()) {
|
||||
if (!checkName("builderClassName", builderClassName, annotationNode)) return;
|
||||
@@ -200,6 +200,11 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
|
||||
if (md != null) injectMethod(builderType, md);
|
||||
}
|
||||
|
||||
if (methodExists("toString", builderType, 0) == MemberExistsResult.NOT_EXISTS) {
|
||||
MethodDeclaration md = HandleToString.createToString(builderType, fieldNodes, true, false, ast, FieldAccess.ALWAYS_FIELD);
|
||||
if (md != null) injectMethod(builderType, md);
|
||||
}
|
||||
|
||||
if (methodExists(builderMethodName, tdParent, -1) == MemberExistsResult.NOT_EXISTS) {
|
||||
MethodDeclaration md = generateBuilderMethod(builderMethodName, builderClassName, tdParent, typeParams, ast);
|
||||
if (md != null) injectMethod(tdParent, md);
|
||||
|
||||
@@ -170,7 +170,7 @@ public class HandleToString extends EclipseAnnotationHandler<ToString> {
|
||||
}
|
||||
}
|
||||
|
||||
private MethodDeclaration createToString(EclipseNode type, Collection<EclipseNode> fields,
|
||||
static MethodDeclaration createToString(EclipseNode type, Collection<EclipseNode> fields,
|
||||
boolean includeFieldNames, boolean callSuper, ASTNode source, FieldAccess fieldAccess) {
|
||||
String typeName = getTypeName(type);
|
||||
char[] suffix = ")".toCharArray();
|
||||
@@ -282,7 +282,7 @@ public class HandleToString extends EclipseAnnotationHandler<ToString> {
|
||||
return method;
|
||||
}
|
||||
|
||||
private String getTypeName(EclipseNode type) {
|
||||
private static String getTypeName(EclipseNode type) {
|
||||
String typeName = getSingleTypeName(type);
|
||||
EclipseNode upType = type.up();
|
||||
while (upType.getKind() == Kind.TYPE) {
|
||||
@@ -292,7 +292,7 @@ public class HandleToString extends EclipseAnnotationHandler<ToString> {
|
||||
return typeName;
|
||||
}
|
||||
|
||||
private String getSingleTypeName(EclipseNode type) {
|
||||
private static String getSingleTypeName(EclipseNode type) {
|
||||
TypeDeclaration typeDeclaration = (TypeDeclaration)type.get();
|
||||
char[] rawTypeName = typeDeclaration.name;
|
||||
return rawTypeName == null ? "" : new String(rawTypeName);
|
||||
@@ -301,7 +301,7 @@ public class HandleToString extends EclipseAnnotationHandler<ToString> {
|
||||
private static final Set<String> BUILT_IN_TYPES = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
|
||||
"byte", "short", "int", "long", "char", "boolean", "double", "float")));
|
||||
|
||||
private NameReference generateQualifiedNameRef(ASTNode source, char[]... varNames) {
|
||||
private static NameReference generateQualifiedNameRef(ASTNode source, char[]... varNames) {
|
||||
int pS = source.sourceStart, pE = source.sourceEnd;
|
||||
long p = (long)pS << 32 | pE;
|
||||
NameReference ref;
|
||||
|
||||
@@ -22,21 +22,28 @@
|
||||
package lombok.javac.handlers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.mangosdk.spi.ProviderFor;
|
||||
|
||||
import com.sun.tools.javac.code.Flags;
|
||||
import com.sun.tools.javac.tree.JCTree;
|
||||
import com.sun.tools.javac.tree.JCTree.JCAnnotation;
|
||||
import com.sun.tools.javac.tree.JCTree.JCBlock;
|
||||
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
|
||||
import com.sun.tools.javac.tree.JCTree.JCExpression;
|
||||
import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
|
||||
import com.sun.tools.javac.tree.JCTree.JCIdent;
|
||||
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
|
||||
import com.sun.tools.javac.tree.JCTree.JCModifiers;
|
||||
import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree;
|
||||
import com.sun.tools.javac.tree.JCTree.JCStatement;
|
||||
import com.sun.tools.javac.tree.JCTree.JCTypeApply;
|
||||
import com.sun.tools.javac.tree.JCTree.JCTypeParameter;
|
||||
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
|
||||
import com.sun.tools.javac.tree.TreeMaker;
|
||||
import com.sun.tools.javac.util.List;
|
||||
import com.sun.tools.javac.util.ListBuffer;
|
||||
import com.sun.tools.javac.util.Name;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
@@ -51,6 +58,7 @@ import static lombok.javac.Javac.*;
|
||||
import static lombok.core.handlers.HandlerUtil.*;
|
||||
import static lombok.javac.handlers.JavacHandlerUtil.*;
|
||||
|
||||
@ProviderFor(JavacAnnotationHandler.class)
|
||||
public class HandleBuilder extends JavacAnnotationHandler<Builder> {
|
||||
@Override public void handle(AnnotationValues<Builder> annotation, JCAnnotation ast, JavacNode annotationNode) {
|
||||
Builder builderInstance = annotation.getInstance();
|
||||
@@ -68,6 +76,9 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> {
|
||||
if (!checkName("builderClassName", builderClassName, annotationNode)) return;
|
||||
}
|
||||
|
||||
deleteAnnotationIfNeccessary(annotationNode, Builder.class);
|
||||
deleteImportFromCompilationUnit(annotationNode, "lombok.experimental.Builder");
|
||||
|
||||
JavacNode parent = annotationNode.up();
|
||||
|
||||
java.util.List<JCExpression> typesOfParameters = new ArrayList<JCExpression>();
|
||||
@@ -93,7 +104,7 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> {
|
||||
|
||||
returnType = namePlusTypeParamsToTypeReference(tdParent.getTreeMaker(), td.name, td.typarams);
|
||||
typeParams = td.typarams;
|
||||
thrownExceptions = null;
|
||||
thrownExceptions = List.nil();
|
||||
nameOfStaticBuilderMethod = null;
|
||||
if (builderClassName.isEmpty()) builderClassName = td.name.toString() + "Builder";
|
||||
} else if (fillParametersFrom != null && fillParametersFrom.getName().toString().equals("<init>")) {
|
||||
@@ -107,7 +118,7 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> {
|
||||
typeParams = td.typarams;
|
||||
thrownExceptions = fillParametersFrom.thrown;
|
||||
nameOfStaticBuilderMethod = null;
|
||||
if (builderClassName.isEmpty()) builderClassName = td.name.toString();
|
||||
if (builderClassName.isEmpty()) builderClassName = td.name.toString() + "Builder";
|
||||
} else if (fillParametersFrom != null) {
|
||||
tdParent = parent.up();
|
||||
JCClassDecl td = (JCClassDecl) tdParent.get();
|
||||
@@ -124,21 +135,27 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> {
|
||||
returnType = ((JCTypeApply) returnType).clazz;
|
||||
}
|
||||
if (returnType instanceof JCFieldAccess) {
|
||||
builderClassName = ((JCFieldAccess) returnType).name.toString();
|
||||
builderClassName = ((JCFieldAccess) returnType).name.toString() + "Builder";
|
||||
} else if (returnType instanceof JCIdent) {
|
||||
Name n = ((JCIdent) returnType).name;
|
||||
|
||||
for (JCTypeParameter tp : typeParams) {
|
||||
if (tp.name.contentEquals(n)) {
|
||||
if (tp.name.equals(n)) {
|
||||
annotationNode.addError("@Builder requires specifying 'builderClassName' if used on methods with a type parameter as return type.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
builderClassName = n.toString();
|
||||
builderClassName = n.toString() + "Builder";
|
||||
} else if (returnType instanceof JCPrimitiveTypeTree) {
|
||||
builderClassName = returnType.toString() + "Builder";
|
||||
if (Character.isLowerCase(builderClassName.charAt(0))) {
|
||||
builderClassName = Character.toTitleCase(builderClassName.charAt(0)) + builderClassName.substring(1);
|
||||
}
|
||||
|
||||
} else {
|
||||
// This shouldn't happen.
|
||||
System.err.println("Lombok bug ID#20140614-1651: javac HandleBuilder: return type to name conversion failed: " + returnType.getClass());
|
||||
builderClassName = td.name.toString();
|
||||
builderClassName = td.name.toString() + "Builder";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -158,7 +175,7 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> {
|
||||
java.util.List<JavacNode> fieldNodes = addFieldsToBuilder(builderType, namesOfParameters, typesOfParameters, ast);
|
||||
java.util.List<JCMethodDecl> newMethods = new ArrayList<JCMethodDecl>();
|
||||
for (JavacNode fieldNode : fieldNodes) {
|
||||
JCMethodDecl newMethod = makeSetterMethodForBuider(builderType, fieldNode, ast);
|
||||
JCMethodDecl newMethod = makeSetterMethodForBuilder(builderType, fieldNode, ast);
|
||||
if (newMethod != null) newMethods.add(newMethod);
|
||||
}
|
||||
|
||||
@@ -170,16 +187,112 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> {
|
||||
for (JCMethodDecl newMethod : newMethods) injectMethod(builderType, newMethod);
|
||||
|
||||
if (methodExists(buildMethodName, builderType, -1) == MemberExistsResult.NOT_EXISTS) {
|
||||
JCMethodDecl md = generateBuildMethod(buildMethodName, nameOfStaticBuilderMethod, returnType, namesOfParameters, builderType, ast, thrownExceptions);
|
||||
JCMethodDecl md = generateBuildMethod(buildMethodName, nameOfStaticBuilderMethod, returnType, namesOfParameters, builderType, thrownExceptions);
|
||||
if (md != null) injectMethod(builderType, md);
|
||||
}
|
||||
|
||||
if (methodExists("toString", builderType, 0) == MemberExistsResult.NOT_EXISTS) {
|
||||
JCMethodDecl md = HandleToString.createToString(builderType, fieldNodes, true, false, FieldAccess.ALWAYS_FIELD, ast);
|
||||
if (md != null) injectMethod(builderType, md);
|
||||
}
|
||||
|
||||
if (methodExists(builderMethodName, tdParent, -1) == MemberExistsResult.NOT_EXISTS) {
|
||||
JCMethodDecl md = generateBuilderMethod(builderMethodName, builderClassName, tdParent, typeParams, ast);
|
||||
JCMethodDecl md = generateBuilderMethod(builderMethodName, builderClassName, tdParent, typeParams);
|
||||
if (md != null) injectMethod(tdParent, md);
|
||||
}
|
||||
}
|
||||
|
||||
private JCMethodDecl generateBuildMethod(String name, Name staticName, JCExpression returnType, java.util.List<Name> fieldNames, JavacNode type, List<JCExpression> thrownExceptions) {
|
||||
TreeMaker maker = type.getTreeMaker();
|
||||
|
||||
JCExpression call;
|
||||
JCStatement statement;
|
||||
|
||||
ListBuffer<JCExpression> args = ListBuffer.lb();
|
||||
for (Name n : fieldNames) {
|
||||
args.append(maker.Ident(n));
|
||||
}
|
||||
|
||||
if (staticName == null) {
|
||||
call = maker.NewClass(null, List.<JCExpression>nil(), returnType, args.toList(), null);
|
||||
statement = maker.Return(call);
|
||||
} else {
|
||||
ListBuffer<JCExpression> typeParams = ListBuffer.lb();
|
||||
for (JCTypeParameter tp : ((JCClassDecl) type.get()).typarams) {
|
||||
typeParams.append(maker.Ident(tp.name));
|
||||
}
|
||||
|
||||
JCExpression fn = maker.Select(maker.Ident(((JCClassDecl) type.up().get()).name), staticName);
|
||||
call = maker.Apply(typeParams.toList(), fn, args.toList());
|
||||
if (returnType instanceof JCPrimitiveTypeTree && ((JCPrimitiveTypeTree) returnType).typetag == CTC_VOID) {
|
||||
statement = maker.Exec(call);
|
||||
} else {
|
||||
statement = maker.Return(call);
|
||||
}
|
||||
}
|
||||
|
||||
JCBlock body = maker.Block(0, List.<JCStatement>of(statement));
|
||||
|
||||
return maker.MethodDef(maker.Modifiers(Flags.PUBLIC), type.toName(name), returnType, List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), thrownExceptions, body, null);
|
||||
}
|
||||
|
||||
private JCMethodDecl generateBuilderMethod(String builderMethodName, String builderClassName, JavacNode type, List<JCTypeParameter> typeParams) {
|
||||
TreeMaker maker = type.getTreeMaker();
|
||||
|
||||
ListBuffer<JCExpression> typeArgs = ListBuffer.lb();
|
||||
for (JCTypeParameter typeParam : typeParams) {
|
||||
typeArgs.append(maker.Ident(typeParam.name));
|
||||
}
|
||||
|
||||
JCExpression call = maker.NewClass(null, List.<JCExpression>nil(), namePlusTypeParamsToTypeReference(maker, type.toName(builderClassName), typeParams), List.<JCExpression>nil(), null);
|
||||
JCStatement statement = maker.Return(call);
|
||||
|
||||
JCBlock body = maker.Block(0, List.<JCStatement>of(statement));
|
||||
return maker.MethodDef(maker.Modifiers(Flags.STATIC | Flags.PUBLIC), type.toName(builderMethodName), namePlusTypeParamsToTypeReference(maker, type.toName(builderClassName), typeParams), copyTypeParams(maker, typeParams), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null);
|
||||
}
|
||||
|
||||
private java.util.List<JavacNode> addFieldsToBuilder(JavacNode builderType, java.util.List<Name> namesOfParameters, java.util.List<JCExpression> typesOfParameters, JCTree source) {
|
||||
int len = namesOfParameters.size();
|
||||
java.util.List<JavacNode> existing = new ArrayList<JavacNode>();
|
||||
for (JavacNode child : builderType.down()) {
|
||||
if (child.getKind() == Kind.FIELD) existing.add(child);
|
||||
}
|
||||
|
||||
java.util.List<JavacNode>out = new ArrayList<JavacNode>();
|
||||
|
||||
top:
|
||||
for (int i = len - 1; i >= 0; i--) {
|
||||
Name name = namesOfParameters.get(i);
|
||||
for (JavacNode exists : existing) {
|
||||
Name n = ((JCVariableDecl) exists.get()).name;
|
||||
if (n.equals(name)) {
|
||||
out.add(exists);
|
||||
continue top;
|
||||
}
|
||||
}
|
||||
TreeMaker maker = builderType.getTreeMaker();
|
||||
JCModifiers mods = maker.Modifiers(Flags.PRIVATE);
|
||||
JCVariableDecl newField = maker.VarDef(mods, name, cloneType(maker, typesOfParameters.get(i), source), null);
|
||||
out.add(injectField(builderType, newField));
|
||||
}
|
||||
|
||||
Collections.reverse(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
private JCMethodDecl makeSetterMethodForBuilder(JavacNode builderType, JavacNode fieldNode, JCTree source) {
|
||||
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;
|
||||
}
|
||||
|
||||
TreeMaker maker = builderType.getTreeMaker();
|
||||
return HandleSetter.createSetter(Flags.PUBLIC, fieldNode, maker, fieldName.toString(), true, source, List.<JCAnnotation>nil(), List.<JCAnnotation>nil());
|
||||
}
|
||||
|
||||
private JavacNode findInnerClass(JavacNode parent, String name) {
|
||||
for (JavacNode child : parent.down()) {
|
||||
if (child.getKind() != Kind.TYPE) continue;
|
||||
@@ -192,7 +305,7 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> {
|
||||
private JavacNode makeBuilderClass(JavacNode tdParent, String builderClassName, List<JCTypeParameter> typeParams, JCAnnotation ast) {
|
||||
TreeMaker maker = tdParent.getTreeMaker();
|
||||
JCModifiers mods = maker.Modifiers(Flags.PUBLIC | Flags.STATIC);
|
||||
JCClassDecl builder = maker.ClassDef(mods, tdParent.toName(builderClassName), typeParams, null, List.<JCExpression>nil(), List.<JCTree>nil());
|
||||
JCClassDecl builder = ClassDef(maker, mods, tdParent.toName(builderClassName), copyTypeParams(maker, typeParams), null, List.<JCExpression>nil(), List.<JCTree>nil());
|
||||
return injectType(tdParent, builder);
|
||||
}
|
||||
}
|
||||
@@ -194,9 +194,13 @@ public class HandleSetter extends JavacAnnotationHandler<Setter> {
|
||||
injectMethod(fieldNode.up(), createdSetter);
|
||||
}
|
||||
|
||||
private JCMethodDecl createSetter(long access, JavacNode field, TreeMaker treeMaker, JCTree source, List<JCAnnotation> onMethod, List<JCAnnotation> onParam) {
|
||||
static JCMethodDecl createSetter(long access, JavacNode field, TreeMaker treeMaker, JCTree 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);
|
||||
}
|
||||
|
||||
static JCMethodDecl createSetter(long access, JavacNode field, TreeMaker treeMaker, String setterName, boolean shouldReturnThis, JCTree source, List<JCAnnotation> onMethod, List<JCAnnotation> onParam) {
|
||||
if (setterName == null) return null;
|
||||
|
||||
JCVariableDecl fieldDecl = (JCVariableDecl) field.get();
|
||||
@@ -222,17 +226,17 @@ public class HandleSetter extends JavacAnnotationHandler<Setter> {
|
||||
}
|
||||
|
||||
JCExpression methodType = null;
|
||||
if (returnThis) {
|
||||
if (shouldReturnThis) {
|
||||
methodType = cloneSelfType(field);
|
||||
}
|
||||
|
||||
if (methodType == null) {
|
||||
//WARNING: Do not use field.getSymbolTable().voidType - that field has gone through non-backwards compatible API changes within javac1.6.
|
||||
methodType = treeMaker.Type(new JCNoType(CTC_VOID));
|
||||
returnThis = false;
|
||||
shouldReturnThis = false;
|
||||
}
|
||||
|
||||
if (returnThis) {
|
||||
if (shouldReturnThis) {
|
||||
JCReturn returnStatement = treeMaker.Return(treeMaker.Ident(field.toName("this")));
|
||||
statements.append(returnStatement);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ package lombok.javac.handlers;
|
||||
import static lombok.javac.handlers.JavacHandlerUtil.*;
|
||||
import static lombok.javac.Javac.*;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import lombok.ToString;
|
||||
import lombok.core.AnnotationValues;
|
||||
import lombok.core.AST.Kind;
|
||||
@@ -164,7 +166,7 @@ public class HandleToString extends JavacAnnotationHandler<ToString> {
|
||||
}
|
||||
}
|
||||
|
||||
private JCMethodDecl createToString(JavacNode typeNode, List<JavacNode> fields, boolean includeFieldNames, boolean callSuper, FieldAccess fieldAccess, JCTree source) {
|
||||
static JCMethodDecl createToString(JavacNode typeNode, Collection<JavacNode> fields, boolean includeFieldNames, boolean callSuper, FieldAccess fieldAccess, JCTree source) {
|
||||
TreeMaker maker = typeNode.getTreeMaker();
|
||||
|
||||
JCAnnotation overrideAnnotation = maker.Annotation(chainDots(typeNode, "java", "lang", "Override"), List.<JCExpression>nil());
|
||||
@@ -241,7 +243,7 @@ public class HandleToString extends JavacAnnotationHandler<ToString> {
|
||||
List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null), source);
|
||||
}
|
||||
|
||||
private String getTypeName(JavacNode typeNode) {
|
||||
private static String getTypeName(JavacNode typeNode) {
|
||||
String typeName = ((JCClassDecl) typeNode.get()).name.toString();
|
||||
JavacNode upType = typeNode.up();
|
||||
while (upType.getKind() == Kind.TYPE) {
|
||||
|
||||
@@ -35,6 +35,11 @@ class BuilderComplex {
|
||||
public void execute() {
|
||||
BuilderComplex.<T>testVoidWithGenerics(number, arg2, arg3, selfRef);
|
||||
}
|
||||
@java.lang.Override
|
||||
@java.lang.SuppressWarnings("all")
|
||||
public java.lang.String toString() {
|
||||
return "BuilderComplex.VoidBuilder(number=" + this.number + ", arg2=" + this.arg2 + ", arg3=" + this.arg3 + ", selfRef=" + this.selfRef + ")";
|
||||
}
|
||||
}
|
||||
@java.lang.SuppressWarnings("all")
|
||||
public static <T extends Number> VoidBuilder<T> builder() {
|
||||
|
||||
@@ -30,6 +30,11 @@ class BuilderSimple<T> {
|
||||
public BuilderSimple<T> build() {
|
||||
return new BuilderSimple<T>(yes, also);
|
||||
}
|
||||
@java.lang.Override
|
||||
@java.lang.SuppressWarnings("all")
|
||||
public java.lang.String toString() {
|
||||
return "BuilderSimple.BuilderSimpleBuilder(yes=" + this.yes + ", also=" + this.also + ")";
|
||||
}
|
||||
}
|
||||
@java.lang.SuppressWarnings("all")
|
||||
public static <T> BuilderSimpleBuilder<T> builder() {
|
||||
|
||||
@@ -25,6 +25,11 @@ class BuilderWithExistingBuilderClass<T, K extends Number> {
|
||||
public BuilderWithExistingBuilderClass build() {
|
||||
return BuilderWithExistingBuilderClass.<Z>staticMethod(arg1, arg2, arg3);
|
||||
}
|
||||
@java.lang.Override
|
||||
@java.lang.SuppressWarnings("all")
|
||||
public java.lang.String toString() {
|
||||
return "BuilderWithExistingBuilderClass.BuilderWithExistingBuilderClassBuilder(arg1=" + this.arg1 + ", arg2=" + this.arg2 + ", arg3=" + this.arg3 + ")";
|
||||
}
|
||||
}
|
||||
@java.lang.SuppressWarnings("all")
|
||||
public static <Z extends Number> BuilderWithExistingBuilderClassBuilder<Z> builder() {
|
||||
|
||||
@@ -28,6 +28,9 @@ class BuilderComplex {
|
||||
public @java.lang.SuppressWarnings("all") void execute() {
|
||||
BuilderComplex.<T>testVoidWithGenerics(number, arg2, arg3, selfRef);
|
||||
}
|
||||
public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() {
|
||||
return (((((((("BuilderComplex.VoidBuilder(number=" + this.number) + ", arg2=") + this.arg2) + ", arg3=") + this.arg3) + ", selfRef=") + this.selfRef) + ")");
|
||||
}
|
||||
}
|
||||
BuilderComplex() {
|
||||
super();
|
||||
|
||||
@@ -17,6 +17,9 @@ import java.util.List;
|
||||
public @java.lang.SuppressWarnings("all") BuilderSimple<T> build() {
|
||||
return new BuilderSimple<T>(yes, also);
|
||||
}
|
||||
public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() {
|
||||
return (((("BuilderSimple.BuilderSimpleBuilder(yes=" + this.yes) + ", also=") + this.also) + ")");
|
||||
}
|
||||
}
|
||||
private final int noshow = 0;
|
||||
private final int yes;
|
||||
|
||||
@@ -20,6 +20,9 @@ class BuilderWithExistingBuilderClass<T, K extends Number> {
|
||||
public @java.lang.SuppressWarnings("all") BuilderWithExistingBuilderClass<String, Z> build() {
|
||||
return BuilderWithExistingBuilderClass.<Z>staticMethod(arg1, arg2, arg3);
|
||||
}
|
||||
public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() {
|
||||
return (((((("BuilderWithExistingBuilderClass.BuilderWithExistingBuilderClassBuilder(arg1=" + this.arg1) + ", arg2=") + this.arg2) + ", arg3=") + this.arg3) + ")");
|
||||
}
|
||||
}
|
||||
BuilderWithExistingBuilderClass() {
|
||||
super();
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
public class BuilderExample {
|
||||
private String name;
|
||||
private int age;
|
||||
|
||||
BuilderExample(String name, int age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
public static BuilderExampleBuilder builder() {
|
||||
return new BuilderExampleBuilder();
|
||||
}
|
||||
|
||||
public static class BuilderExampleBuilder {
|
||||
private String name;
|
||||
private int age;
|
||||
|
||||
BuilderExampleBuilder() {
|
||||
}
|
||||
|
||||
public BuilderExampleBuilder name(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BuilderExampleBuilder age(int age) {
|
||||
this.age = age;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BuilderExample build() {
|
||||
return new BuilderExample(name, age);
|
||||
}
|
||||
|
||||
@java.lang.Override
|
||||
public String toString() {
|
||||
return "BuilderExample.BuilderExampleBuilder(name = " + this.name + ", age = " + this.age + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import lombok.experimental.Builder;
|
||||
|
||||
@Builder
|
||||
public class BuilderExample {
|
||||
private String name;
|
||||
private int age;
|
||||
}
|
||||
@@ -84,7 +84,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<a href="index.html">Back to experimental features</a> | <span class="disabled">Previous feature</span> | <a href="ExtensionMethod.html">Next feature (@ExtensionMethod)</a><br />
|
||||
<a href="index.html">Back to experimental features</a> | <a href="Builder.html">Previous feature (@Builder)</a> | <a href="ExtensionMethod.html">Next feature (@ExtensionMethod)</a><br />
|
||||
<a href="../../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2013 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span>
|
||||
</div>
|
||||
<div style="clear: both;"></div>
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
<!DOCTYPE html>
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<link rel="stylesheet" type="text/css" href="../../logi/reset.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../features.css" />
|
||||
<link rel="shortcut icon" href="../../favicon.ico" type="image/x-icon" />
|
||||
<meta name="description" content="Spice up your java" />
|
||||
<title>EXPERIMENTAL - @Builder</title>
|
||||
</head><body><div id="pepper">
|
||||
<div class="minimumHeight"></div>
|
||||
<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="since">
|
||||
<h3>Since</h3>
|
||||
<p>
|
||||
@Builder was introduced as experimental feature in lombok v0.11.10.
|
||||
</p>
|
||||
</div>
|
||||
<div class="experimental">
|
||||
<h3>Experimental</h3>
|
||||
<p>
|
||||
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>
|
||||
<div class="overview">
|
||||
<h3>Overview</h3>
|
||||
<p>
|
||||
The <code>@Builder</code> annotation produces complex builder APIs for your classes.
|
||||
</p><p>
|
||||
<code>@Builder</code> lets you automatically produce the code required to have your class be instantiable with code such as:<br />
|
||||
<code>Person.builder().name("Adam Savage").city("San Francisco").worksAt("Mythbusters").build();</code>
|
||||
</p><p>
|
||||
<code>@Builder</code> can be placed on a class, or on a constructor, or on a static method. While the "on a class" and "on a constructor"
|
||||
mode are the most common use-case, <code>@Builder</code> is most easily explained with the "static method" use-case.
|
||||
</p><p>
|
||||
A static method annotated with <code>@Builder</code> (from now on called the <em>target</em>) causes the following 7 things to be generated:<ul>
|
||||
<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.
|
||||
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>
|
||||
<li>In the <em>builder</em>: A sensible <code>toString()</code> implementation.</li>
|
||||
<li>In the class containing the <em>target</em>: A <code>builder()</code> method, which creates a new instance of the <em>builder</em>.</li>
|
||||
</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.
|
||||
</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.
|
||||
</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>
|
||||
on static methods. For example, if <code>@Builder</code> is applied to a class named <code>com.yoyodyne.FancyList<T></code>, then the builder name will be
|
||||
<code>FancyListBuilder<T></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>
|
||||
</p>
|
||||
</div>
|
||||
<div class="snippets">
|
||||
<div class="pre">
|
||||
<h3>With Lombok</h3>
|
||||
<div class="snippet">@HTML_PRE@</div>
|
||||
</div>
|
||||
<div class="sep"></div>
|
||||
<div class="post">
|
||||
<h3>Vanilla Java</h3>
|
||||
<div class="snippet">@HTML_POST@</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="clear: left;"></div>
|
||||
<div class="overview">
|
||||
<h3>Small print</h3><div class="smallprint">
|
||||
<p>
|
||||
Another strategy for fluent APIs is that the programmer using your library statically imports your 'builder' method. In this case, you might want to name your builder
|
||||
method equal to your type's name. So, the builder method for a class called <code>Person</code> would become <code>person()</code>. This is nicer if the builder method
|
||||
is statically imported.
|
||||
</p><p>
|
||||
If the return type of your target static method is a type parameter (such as <code>T</code>), lombok will enforce an explicit builder class name.
|
||||
</p><p>
|
||||
You don't HAVE to use <code>@Builder</code> to build anything; you can for example put it on a static method that has lots of parameter to improve the API of it.
|
||||
In this case, we suggest you use <code>buildMethodName = </code> to rename the build method to <code>execute()</code> instead.
|
||||
</p><p>
|
||||
The builder class will NOT get an auto-generated implementation of <code>hashCode</code> or <code>equals</code> methods! These would suggest that it is sensible to use
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<a href="index.html">Back to experimental features</a> | <span class="disabled">Previous feature</span> | <a href="Accessors.html">Next feature (@Accessors)</a><br />
|
||||
<a href="../../credits.html" class="creditsLink">credits</a> | <span class="copyright">Copyright © 2009-2013 The Project Lombok Authors, licensed under the <a href="http://www.opensource.org/licenses/mit-license.php">MIT license</a>.</span>
|
||||
</div>
|
||||
<div style="clear: both;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
|
||||
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
try {
|
||||
var pageTracker = _gat._getTracker("UA-9884254-1");
|
||||
pageTracker._trackPageview();
|
||||
} catch(err) {}
|
||||
</script>
|
||||
</body></html>
|
||||
@@ -22,6 +22,8 @@
|
||||
Features that receive positive community feedback and which seem to produce clean, flexible code will eventually become accepted
|
||||
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>
|
||||
<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>
|
||||
|
||||
Reference in new issue
Block a user