diff options
author | Reinier Zwitserloot <reinier@zwitserloot.com> | 2013-06-18 04:23:15 +0200 |
---|---|---|
committer | Reinier Zwitserloot <reinier@zwitserloot.com> | 2013-06-18 04:23:15 +0200 |
commit | e1c39bbc601408decb0ae147d181708a5af41307 (patch) | |
tree | f3a620da1778ae4b9654433bec65841a95ff5067 | |
parent | eb3d32c718d9ef46fd30bc677147cda85318fb9c (diff) | |
download | lombok-e1c39bbc601408decb0ae147d181708a5af41307.tar.gz lombok-e1c39bbc601408decb0ae147d181708a5af41307.tar.bz2 lombok-e1c39bbc601408decb0ae147d181708a5af41307.zip |
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.
18 files changed, 350 insertions, 22 deletions
diff --git a/buildScripts/website.ant.xml b/buildScripts/website.ant.xml index 41130bd2..521ca59b 100644 --- a/buildScripts/website.ant.xml +++ b/buildScripts/website.ant.xml @@ -149,6 +149,9 @@ such as converting the changelog into HTML, and creating javadoc. <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> <antcall target="-integrateSnippet"> diff --git a/doc/changelog.markdown b/doc/changelog.markdown index aaf66030..85fcd86a 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -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) diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java index d15f00e6..e2bf5fe2 100644 --- a/src/core/lombok/eclipse/handlers/HandleBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java @@ -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); diff --git a/src/core/lombok/eclipse/handlers/HandleToString.java b/src/core/lombok/eclipse/handlers/HandleToString.java index d864153f..1193af31 100644 --- a/src/core/lombok/eclipse/handlers/HandleToString.java +++ b/src/core/lombok/eclipse/handlers/HandleToString.java @@ -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; diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java index c39255f2..aa485b26 100644 --- a/src/core/lombok/javac/handlers/HandleBuilder.java +++ b/src/core/lombok/javac/handlers/HandleBuilder.java @@ -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); } } diff --git a/src/core/lombok/javac/handlers/HandleSetter.java b/src/core/lombok/javac/handlers/HandleSetter.java index c1e03c35..29728eae 100644 --- a/src/core/lombok/javac/handlers/HandleSetter.java +++ b/src/core/lombok/javac/handlers/HandleSetter.java @@ -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); } diff --git a/src/core/lombok/javac/handlers/HandleToString.java b/src/core/lombok/javac/handlers/HandleToString.java index 5b3c033c..333393da 100644 --- a/src/core/lombok/javac/handlers/HandleToString.java +++ b/src/core/lombok/javac/handlers/HandleToString.java @@ -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) { diff --git a/test/transform/resource/after-delombok/BuilderComplex.java b/test/transform/resource/after-delombok/BuilderComplex.java index d6d12ebf..3c97f92a 100644 --- a/test/transform/resource/after-delombok/BuilderComplex.java +++ b/test/transform/resource/after-delombok/BuilderComplex.java @@ -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() { diff --git a/test/transform/resource/after-delombok/BuilderSimple.java b/test/transform/resource/after-delombok/BuilderSimple.java index 113d538e..24ac369c 100644 --- a/test/transform/resource/after-delombok/BuilderSimple.java +++ b/test/transform/resource/after-delombok/BuilderSimple.java @@ -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() { diff --git a/test/transform/resource/after-delombok/BuilderWithExistingBuilderClass.java b/test/transform/resource/after-delombok/BuilderWithExistingBuilderClass.java index a8800009..1d40dbfa 100644 --- a/test/transform/resource/after-delombok/BuilderWithExistingBuilderClass.java +++ b/test/transform/resource/after-delombok/BuilderWithExistingBuilderClass.java @@ -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() { diff --git a/test/transform/resource/after-ecj/BuilderComplex.java b/test/transform/resource/after-ecj/BuilderComplex.java index ca302a3d..19aeb043 100644 --- a/test/transform/resource/after-ecj/BuilderComplex.java +++ b/test/transform/resource/after-ecj/BuilderComplex.java @@ -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(); diff --git a/test/transform/resource/after-ecj/BuilderSimple.java b/test/transform/resource/after-ecj/BuilderSimple.java index 4ca8e120..228b1928 100644 --- a/test/transform/resource/after-ecj/BuilderSimple.java +++ b/test/transform/resource/after-ecj/BuilderSimple.java @@ -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; diff --git a/test/transform/resource/after-ecj/BuilderWithExistingBuilderClass.java b/test/transform/resource/after-ecj/BuilderWithExistingBuilderClass.java index 02ed259e..38cb0038 100644 --- a/test/transform/resource/after-ecj/BuilderWithExistingBuilderClass.java +++ b/test/transform/resource/after-ecj/BuilderWithExistingBuilderClass.java @@ -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(); diff --git a/usage_examples/experimental/BuilderExample_post.jpage b/usage_examples/experimental/BuilderExample_post.jpage new file mode 100644 index 00000000..624b236b --- /dev/null +++ b/usage_examples/experimental/BuilderExample_post.jpage @@ -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 + ")"; + } + } +}
\ No newline at end of file diff --git a/usage_examples/experimental/BuilderExample_pre.jpage b/usage_examples/experimental/BuilderExample_pre.jpage new file mode 100644 index 00000000..9c754352 --- /dev/null +++ b/usage_examples/experimental/BuilderExample_pre.jpage @@ -0,0 +1,7 @@ +import lombok.experimental.Builder; + +@Builder +public class BuilderExample { + private String name; + private int age; +} diff --git a/website/features/experimental/Accessors.html b/website/features/experimental/Accessors.html index dce77d32..3ca79de5 100644 --- a/website/features/experimental/Accessors.html +++ b/website/features/experimental/Accessors.html @@ -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> diff --git a/website/features/experimental/Builder.html b/website/features/experimental/Builder.html new file mode 100644 index 00000000..5ba74a27 --- /dev/null +++ b/website/features/experimental/Builder.html @@ -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> diff --git a/website/features/experimental/index.html b/website/features/experimental/index.html index 24fbb541..d0a086a0 100644 --- a/website/features/experimental/index.html +++ b/website/features/experimental/index.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> |