aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorReinier Zwitserloot <reinier@zwitserloot.com>2013-06-10 23:14:23 +0200
committerReinier Zwitserloot <reinier@zwitserloot.com>2013-06-16 13:20:54 +0200
commit648c3eeee69bede925f794b16b1f3d184359761f (patch)
treed16b94cf04a600dc30f59f8d917cb69ec4eb8877
parent2d76b1d22dea1e78326ebafdb48967512183cede (diff)
downloadlombok-648c3eeee69bede925f794b16b1f3d184359761f.tar.gz
lombok-648c3eeee69bede925f794b16b1f3d184359761f.tar.bz2
lombok-648c3eeee69bede925f794b16b1f3d184359761f.zip
Eclipse Builder implementation finished. Tests need fleshing out though.
-rw-r--r--src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java31
-rw-r--r--src/core/lombok/eclipse/handlers/HandleBuilder.java323
-rw-r--r--src/core/lombok/eclipse/handlers/HandleConstructor.java55
-rw-r--r--src/core/lombok/eclipse/handlers/HandleData.java3
-rw-r--r--src/core/lombok/eclipse/handlers/HandleSetter.java2
-rw-r--r--src/core/lombok/eclipse/handlers/HandleValue.java3
-rw-r--r--src/core/lombok/experimental/Builder.java9
-rw-r--r--test/transform/resource/before/BuilderComplex.java7
-rw-r--r--test/transform/resource/before/BuilderSimple.java9
9 files changed, 387 insertions, 55 deletions
diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java
index f9295150..364ce0a5 100644
--- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java
+++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java
@@ -359,6 +359,20 @@ public class EclipseHandlerUtil {
return out;
}
+ public static TypeReference namePlusTypeParamsToTypeReference(char[] typeName, TypeParameter[] params, long p) {
+ if (params != null && params.length > 0) {
+ TypeReference[] refs = new TypeReference[params.length];
+ int idx = 0;
+ for (TypeParameter param : params) {
+ TypeReference typeRef = new SingleTypeReference(param.name, p);
+ refs[idx++] = typeRef;
+ }
+ return new ParameterizedSingleTypeReference(typeName, refs, 0, p);
+ }
+
+ return new SingleTypeReference(typeName, p);
+ }
+
/**
* Convenience method that creates a new array and copies each TypeReference in the source array via
* {@link #copyType(TypeReference, ASTNode)}.
@@ -1208,15 +1222,15 @@ public class EclipseHandlerUtil {
* Inserts a field into an existing type. The type must represent a {@code TypeDeclaration}.
* The field carries the &#64;{@link SuppressWarnings}("all") annotation.
*/
- public static void injectFieldSuppressWarnings(EclipseNode type, FieldDeclaration field) {
+ public static EclipseNode injectFieldSuppressWarnings(EclipseNode type, FieldDeclaration field) {
field.annotations = createSuppressWarningsAll(field, field.annotations);
- injectField(type, field);
+ return injectField(type, field);
}
/**
* Inserts a field into an existing type. The type must represent a {@code TypeDeclaration}.
*/
- public static void injectField(EclipseNode type, FieldDeclaration field) {
+ public static EclipseNode injectField(EclipseNode type, FieldDeclaration field) {
TypeDeclaration parent = (TypeDeclaration) type.get();
if (parent.fields == null) {
@@ -1243,7 +1257,7 @@ public class EclipseHandlerUtil {
}
}
- type.add(field, Kind.FIELD);
+ return type.add(field, Kind.FIELD);
}
private static boolean isEnumConstant(final FieldDeclaration field) {
@@ -1253,7 +1267,7 @@ public class EclipseHandlerUtil {
/**
* Inserts a method into an existing type. The type must represent a {@code TypeDeclaration}.
*/
- public static void injectMethod(EclipseNode type, AbstractMethodDeclaration method) {
+ public static EclipseNode injectMethod(EclipseNode type, AbstractMethodDeclaration method) {
method.annotations = createSuppressWarningsAll(method, method.annotations);
TypeDeclaration parent = (TypeDeclaration) type.get();
@@ -1286,7 +1300,7 @@ public class EclipseHandlerUtil {
parent.methods = newArray;
}
- type.add(method, Kind.METHOD);
+ return type.add(method, Kind.METHOD);
}
/**
@@ -1295,7 +1309,7 @@ public class EclipseHandlerUtil {
* @param typeNode parent type to inject new type into
* @param type New type (class, interface, etc) to inject.
*/
- public static void injectType(final EclipseNode typeNode, final TypeDeclaration type) {
+ public static EclipseNode injectType(final EclipseNode typeNode, final TypeDeclaration type) {
type.annotations = createSuppressWarningsAll(type, type.annotations);
TypeDeclaration parent = (TypeDeclaration) typeNode.get();
@@ -1307,7 +1321,8 @@ public class EclipseHandlerUtil {
newArray[parent.memberTypes.length] = type;
parent.memberTypes = newArray;
}
- typeNode.add(type, Kind.TYPE);
+
+ return typeNode.add(type, Kind.TYPE);
}
private static final char[] ALL = "all".toCharArray();
diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java
index 13271165..929168da 100644
--- a/src/core/lombok/eclipse/handlers/HandleBuilder.java
+++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java
@@ -21,51 +21,344 @@
*/
package lombok.eclipse.handlers;
+import static lombok.eclipse.Eclipse.*;
import static lombok.eclipse.handlers.EclipseHandlerUtil.*;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import org.eclipse.jdt.internal.compiler.ast.ASTNode;
+import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.AllocationExpression;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
+import org.eclipse.jdt.internal.compiler.ast.Argument;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.Expression;
+import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.MessageSend;
import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.ReturnStatement;
+import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
+import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.Statement;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.TypeParameter;
+import org.eclipse.jdt.internal.compiler.ast.TypeReference;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
+import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
+import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
+import org.mangosdk.spi.ProviderFor;
import lombok.AccessLevel;
+import lombok.core.AST.Kind;
import lombok.core.AnnotationValues;
-import lombok.core.ImmutableList;
import lombok.core.JavaIdentifiers;
+import lombok.eclipse.Eclipse;
import lombok.eclipse.EclipseAnnotationHandler;
import lombok.eclipse.EclipseNode;
+import lombok.eclipse.handlers.HandleConstructor.SkipIfConstructorExists;
import lombok.experimental.Builder;
+@ProviderFor(EclipseAnnotationHandler.class)
public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
@Override public void handle(AnnotationValues<Builder> annotation, Annotation ast, EclipseNode annotationNode) {
- String builderMethodName = annotation.getInstance().builderMethodName();
+ long p = (long) ast.sourceStart << 32 | ast.sourceEnd;
+
+ Builder builderInstance = annotation.getInstance();
+ String builderMethodName = builderInstance.builderMethodName();
+ String buildMethodName = builderInstance.buildMethodName();
+ String builderClassName = builderInstance.builderClassName();
+
if (builderMethodName == null) builderMethodName = "builder";
- if (builderMethodName.length() == 0) {
- annotationNode.addError("builderMethodName cannot be the empty string.");
+ if (buildMethodName == null) builderMethodName = "build";
+ if (builderClassName == null) builderClassName = "";
+
+ checkName("builderMethodName", builderMethodName, annotationNode);
+ checkName("buildMethodName", buildMethodName, annotationNode);
+ if (!builderClassName.isEmpty()) checkName("builderClassName", builderClassName, annotationNode);
+
+ EclipseNode parent = annotationNode.up();
+
+ List<TypeReference> typesOfParameters = new ArrayList<TypeReference>();
+ List<char[]> namesOfParameters = new ArrayList<char[]>();
+ TypeReference returnType;
+ TypeParameter[] typeParams;
+ TypeReference[] thrownExceptions;
+ char[] nameOfStaticBuilderMethod;
+ EclipseNode tdParent;
+
+ AbstractMethodDeclaration fillParametersFrom = null;
+
+ if (parent.get() instanceof TypeDeclaration) {
+ tdParent = parent;
+ TypeDeclaration td = (TypeDeclaration) tdParent.get();
+ new HandleConstructor().generateAllArgsConstructor(parent, AccessLevel.PRIVATE, null, SkipIfConstructorExists.I_AM_BUILDER, Collections.<Annotation>emptyList(), ast);
+
+ for (EclipseNode fieldNode : HandleConstructor.findAllFields(parent)) {
+ FieldDeclaration fd = (FieldDeclaration) fieldNode.get();
+ namesOfParameters.add(fd.name);
+ typesOfParameters.add(fd.type);
+ }
+
+ returnType = namePlusTypeParamsToTypeReference(td.name, td.typeParameters, p);
+ typeParams = td.typeParameters;
+ thrownExceptions = null;
+ nameOfStaticBuilderMethod = null;
+ if (builderClassName.isEmpty()) builderClassName = new String(td.name) + "Builder";
+ } else if (parent.get() instanceof ConstructorDeclaration) {
+ ConstructorDeclaration cd = (ConstructorDeclaration) parent.get();
+ if (cd.typeParameters != null && cd.typeParameters.length > 0) {
+ annotationNode.addError("@Builder is not supported on constructors with constructor type parameters.");
+ return;
+ }
+
+ tdParent = parent.up();
+ TypeDeclaration td = (TypeDeclaration) tdParent.get();
+ fillParametersFrom = cd;
+ returnType = namePlusTypeParamsToTypeReference(td.name, td.typeParameters, p);
+ typeParams = td.typeParameters;
+ thrownExceptions = cd.thrownExceptions;
+ nameOfStaticBuilderMethod = null;
+ if (builderClassName.isEmpty()) builderClassName = new String(cd.selector) + "Builder";
+ } else if (parent.get() instanceof MethodDeclaration) {
+ MethodDeclaration md = (MethodDeclaration) parent.get();
+ tdParent = parent.up();
+ if (!md.isStatic()) {
+ annotationNode.addError("@Builder is only supported on types, constructors, and static methods.");
+ return;
+ }
+ fillParametersFrom = md;
+ returnType = copyType(md.returnType, ast);
+ typeParams = md.typeParameters;
+ thrownExceptions = md.thrownExceptions;
+ nameOfStaticBuilderMethod = md.selector;
+ if (builderClassName.isEmpty()) {
+ char[] token;
+ if (md.returnType instanceof QualifiedTypeReference) {
+ char[][] tokens = ((QualifiedTypeReference) md.returnType).tokens;
+ token = tokens[tokens.length - 1];
+ } else if (md.returnType instanceof SingleTypeReference) {
+ token = ((SingleTypeReference) md.returnType).token;
+ if (!(md.returnType instanceof ParameterizedSingleTypeReference) && typeParams != null) {
+ for (TypeParameter tp : typeParams) {
+ if (Arrays.equals(tp.name, token)) {
+ annotationNode.addError("@Builder requires specifying 'builderClassName' if used on methods with a type parameter as return type.");
+ return;
+ }
+ }
+ }
+ } else {
+ annotationNode.addError("Unexpected kind of return type on annotated method. Specify 'builderClassName' to solve this problem.");
+ return;
+ }
+
+ if (Character.isLowerCase(token[0])) {
+ char[] newToken = new char[token.length];
+ System.arraycopy(token, 1, newToken, 1, token.length - 1);
+ newToken[0] = Character.toTitleCase(token[0]);
+ token = newToken;
+ }
+
+ builderClassName = new String(token) + "Builder";
+ }
+ } else {
+ annotationNode.addError("@Builder is only supported on types, constructors, and static methods.");
return;
}
- if (!JavaIdentifiers.isValidJavaIdentifier(builderMethodName)) {
- annotationNode.addError("builderMethodName must be a valid java method name.");
- return;
+ if (fillParametersFrom != null) {
+ if (fillParametersFrom.arguments != null) for (Argument a : fillParametersFrom.arguments) {
+ namesOfParameters.add(a.name);
+ typesOfParameters.add(a.type);
+ }
}
- EclipseNode parent = annotationNode.up();
+ EclipseNode builderType = findInnerClass(tdParent, builderClassName);
+ if (builderType == null) builderType = makeBuilderClass(tdParent, builderClassName, typeParams, ast);
+ List<EclipseNode> fieldNodes = addFieldsToBuilder(builderType, namesOfParameters, typesOfParameters, ast);
+ List<AbstractMethodDeclaration> newMethods = new ArrayList<AbstractMethodDeclaration>();
+ for (EclipseNode fieldNode : fieldNodes) {
+ MethodDeclaration newMethod = makeSetterMethodForBuilder(builderType, fieldNode, ast);
+ if (newMethod != null) newMethods.add(newMethod);
+ }
- if (parent.get() instanceof ConstructorDeclaration) {
-
+ if (constructorExists(builderType) == MemberExistsResult.NOT_EXISTS) {
+ ConstructorDeclaration cd = HandleConstructor.createConstructor(AccessLevel.PACKAGE, builderType, Collections.<EclipseNode>emptyList(), true, ast, Collections.<Annotation>emptyList());
+ if (cd != null) injectMethod(builderType, cd);
}
- if (parent.get() instanceof MethodDeclaration) {
-
+ for (AbstractMethodDeclaration newMethod : newMethods) injectMethod(builderType, newMethod);
+ if (methodExists(buildMethodName, builderType, -1) == MemberExistsResult.NOT_EXISTS) {
+ MethodDeclaration md = generateBuildMethod(buildMethodName, nameOfStaticBuilderMethod, returnType, namesOfParameters, builderType, ast, thrownExceptions);
+ if (md != null) injectMethod(builderType, md);
}
- if (parent.get() instanceof TypeDeclaration) {
- // TODO: How do we ensure this one will 'win' over the implicit constructors generated by @Data and @Value.
- new HandleConstructor().generateAllArgsConstructor(parent, AccessLevel.PRIVATE, null, true, Collections.<Annotation>emptyList(), ast);
+ if (methodExists(builderMethodName, tdParent, -1) == MemberExistsResult.NOT_EXISTS) {
+ MethodDeclaration md = generateBuilderMethod(builderMethodName, builderClassName, tdParent, typeParams, ast);
+ if (md != null) injectMethod(tdParent, md);
+ }
+
+
+ // create builder method in parent.
+ }
+
+ private MethodDeclaration generateBuilderMethod(String builderMethodName, String builderClassName, EclipseNode type, TypeParameter[] typeParams, ASTNode source) {
+ int pS = source.sourceStart, pE = source.sourceEnd;
+ long p = (long) pS << 32 | pE;
+
+ MethodDeclaration out = new MethodDeclaration(
+ ((CompilationUnitDeclaration) type.top().get()).compilationResult);
+ out.selector = builderMethodName.toCharArray();
+ out.modifiers = ClassFileConstants.AccPublic | ClassFileConstants.AccStatic;
+ out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG;
+ out.returnType = namePlusTypeParamsToTypeReference(builderClassName.toCharArray(), typeParams, p);
+ out.typeParameters = copyTypeParams(typeParams, source);
+ AllocationExpression invoke = new AllocationExpression();
+ invoke.type = namePlusTypeParamsToTypeReference(builderClassName.toCharArray(), typeParams, p);
+ out.statements = new Statement[] {new ReturnStatement(invoke, pS, pE)};
+
+ out.traverse(new SetGeneratedByVisitor(source), ((TypeDeclaration) type.get()).scope);
+ return out;
+ }
+
+ private MethodDeclaration generateBuildMethod(String name, char[] staticName, TypeReference returnType, List<char[]> fieldNames, EclipseNode type, ASTNode source, TypeReference[] thrownExceptions) {
+ int pS = source.sourceStart, pE = source.sourceEnd;
+ long p = (long) pS << 32 | pE;
+
+ MethodDeclaration out = new MethodDeclaration(
+ ((CompilationUnitDeclaration) type.top().get()).compilationResult);
+
+ out.modifiers = ClassFileConstants.AccPublic;
+ TypeDeclaration typeDecl = (TypeDeclaration) type.get();
+ out.selector = name.toCharArray();
+ out.thrownExceptions = copyTypes(thrownExceptions, source);
+ out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG;
+ out.returnType = returnType;
+
+ List<Expression> assigns = new ArrayList<Expression>();
+ for (char[] fieldName : fieldNames) {
+ SingleNameReference nameRef = new SingleNameReference(fieldName, p);
+ assigns.add(nameRef);
+ }
+
+ Statement statement;
+
+ if (staticName == null) {
+ AllocationExpression allocationStatement = new AllocationExpression();
+ allocationStatement.type = copyType(out.returnType, source);
+ allocationStatement.arguments = assigns.isEmpty() ? null : assigns.toArray(new Expression[assigns.size()]);
+ statement = new ReturnStatement(allocationStatement, (int)(p >> 32), (int)p);
+ } else {
+ MessageSend invoke = new MessageSend();
+ invoke.selector = staticName;
+ invoke.receiver = new SingleNameReference(type.up().getName().toCharArray(), p);
+ TypeParameter[] tps = ((TypeDeclaration) type.get()).typeParameters;
+ if (tps != null) {
+ TypeReference[] trs = new TypeReference[tps.length];
+ for (int i = 0; i < trs.length; i++) {
+ trs[i] = new SingleTypeReference(tps[i].name, p);
+ }
+ invoke.typeArguments = trs;
+ }
+ invoke.arguments = assigns.isEmpty() ? null : assigns.toArray(new Expression[assigns.size()]);
+ if (returnType instanceof SingleTypeReference && Arrays.equals(TypeBinding.VOID.simpleName, ((SingleTypeReference) returnType).token)) {
+ statement = invoke;
+ } else {
+ statement = new ReturnStatement(invoke, (int)(p >> 32), (int)p);
+ }
+ }
+
+ out.statements = new Statement[] { statement };
+
+ out.traverse(new SetGeneratedByVisitor(source), typeDecl.scope);
+ return out;
+ }
+
+ private List<EclipseNode> addFieldsToBuilder(EclipseNode builderType, List<char[]> namesOfParameters, List<TypeReference> typesOfParameters, ASTNode source) {
+ int len = namesOfParameters.size();
+ TypeDeclaration td = (TypeDeclaration) builderType.get();
+ FieldDeclaration[] existing = td.fields;
+ if (existing == null) existing = new FieldDeclaration[0];
+
+ List<EclipseNode> out = new ArrayList<EclipseNode>();
+
+ top:
+ for (int i = len - 1; i >= 0; i--) {
+ char[] name = namesOfParameters.get(i);
+ for (FieldDeclaration exists : existing) {
+ if (Arrays.equals(exists.name, name)) {
+ out.add(builderType.getNodeFor(exists));
+ continue top;
+ }
+ }
+ TypeReference fieldReference = copyType(typesOfParameters.get(i), source);
+ FieldDeclaration newField = new FieldDeclaration(name, 0, 0);
+ newField.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG;
+ newField.modifiers = ClassFileConstants.AccPrivate;
+ newField.type = fieldReference;
+ out.add(injectField(builderType, newField));
+ }
+
+ Collections.reverse(out);
+
+ return out;
+ }
+
+ private static final AbstractMethodDeclaration[] EMPTY = {};
+
+ private MethodDeclaration makeSetterMethodForBuilder(EclipseNode builderType, EclipseNode fieldNode, ASTNode source) {
+ TypeDeclaration td = (TypeDeclaration) builderType.get();
+ AbstractMethodDeclaration[] existing = td.methods;
+ if (existing == null) existing = EMPTY;
+ int len = existing.length;
+ FieldDeclaration fd = (FieldDeclaration) fieldNode.get();
+ char[] name = fd.name;
+
+ for (int i = 0; i < len; i++) {
+ if (!(existing[i] instanceof MethodDeclaration)) continue;
+ char[] existingName = existing[i].selector;
+ if (Arrays.equals(name, existingName)) return null;
+ }
+
+ return HandleSetter.createSetter(td, fieldNode, fieldNode.getName(), true, ClassFileConstants.AccPublic,
+ source, Collections.<Annotation>emptyList(), Collections.<Annotation>emptyList());
+ }
+
+ private EclipseNode findInnerClass(EclipseNode parent, String name) {
+ char[] c = name.toCharArray();
+ for (EclipseNode child : parent.down()) {
+ if (child.getKind() != Kind.TYPE) continue;
+ TypeDeclaration td = (TypeDeclaration) child.get();
+ if (Arrays.equals(td.name, c)) return child;
+ }
+ return null;
+ }
+
+ private EclipseNode makeBuilderClass(EclipseNode tdParent, String builderClassName, TypeParameter[] typeParams, ASTNode source) {
+ TypeDeclaration parent = (TypeDeclaration) tdParent.get();
+ TypeDeclaration builder = new TypeDeclaration(parent.compilationResult);
+ builder.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG;
+ builder.modifiers |= ClassFileConstants.AccPublic | ClassFileConstants.AccStatic;
+ builder.typeParameters = copyTypeParams(typeParams, source);
+ builder.name = builderClassName.toCharArray();
+ builder.traverse(new SetGeneratedByVisitor(source), (ClassScope) null);
+ return injectType(tdParent, builder);
+ }
+
+ private static void checkName(String nameSpec, String identifier, EclipseNode annotationNode) {
+ if (identifier.isEmpty()) {
+ annotationNode.addError(nameSpec + " cannot be the empty string.");
+ return;
+ }
+
+ if (!JavaIdentifiers.isValidJavaIdentifier(identifier)) {
+ annotationNode.addError(nameSpec + " must be a valid java identifier.");
+ return;
}
}
}
diff --git a/src/core/lombok/eclipse/handlers/HandleConstructor.java b/src/core/lombok/eclipse/handlers/HandleConstructor.java
index 8ccad77f..1ae680d9 100644
--- a/src/core/lombok/eclipse/handlers/HandleConstructor.java
+++ b/src/core/lombok/eclipse/handlers/HandleConstructor.java
@@ -39,6 +39,7 @@ import lombok.core.AnnotationValues;
import lombok.core.TransformationsUtil;
import lombok.eclipse.EclipseAnnotationHandler;
import lombok.eclipse.EclipseNode;
+import lombok.experimental.Builder;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.AllocationExpression;
@@ -53,18 +54,14 @@ import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.ast.FieldReference;
import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
-import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference;
import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference;
import org.eclipse.jdt.internal.compiler.ast.ReturnStatement;
import org.eclipse.jdt.internal.compiler.ast.SingleMemberAnnotation;
import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
-import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference;
import org.eclipse.jdt.internal.compiler.ast.Statement;
import org.eclipse.jdt.internal.compiler.ast.StringLiteral;
import org.eclipse.jdt.internal.compiler.ast.ThisReference;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
-import org.eclipse.jdt.internal.compiler.ast.TypeParameter;
-import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.mangosdk.spi.ProviderFor;
@@ -82,7 +79,7 @@ public class HandleConstructor {
List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@NoArgsConstructor(onConstructor=", annotationNode);
- new HandleConstructor().generateConstructor(typeNode, level, fields, staticName, false, false, onConstructor, ast);
+ new HandleConstructor().generateConstructor(typeNode, level, fields, staticName, SkipIfConstructorExists.NO, false, onConstructor, ast);
}
}
@@ -100,7 +97,7 @@ public class HandleConstructor {
List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@RequiredArgsConstructor(onConstructor=", annotationNode);
- new HandleConstructor().generateConstructor(typeNode, level, findRequiredFields(typeNode), staticName, false, suppressConstructorProperties, onConstructor, ast);
+ new HandleConstructor().generateConstructor(typeNode, level, findRequiredFields(typeNode), staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, onConstructor, ast);
}
}
@@ -117,7 +114,7 @@ public class HandleConstructor {
return fields;
}
- private static List<EclipseNode> findAllFields(EclipseNode typeNode) {
+ static List<EclipseNode> findAllFields(EclipseNode typeNode) {
List<EclipseNode> fields = new ArrayList<EclipseNode>();
for (EclipseNode child : typeNode.down()) {
if (child.getKind() != Kind.FIELD) continue;
@@ -146,7 +143,7 @@ public class HandleConstructor {
List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@AllArgsConstructor(onConstructor=", annotationNode);
- new HandleConstructor().generateConstructor(typeNode, level, findAllFields(typeNode), staticName, false, suppressConstructorProperties, onConstructor, ast);
+ new HandleConstructor().generateConstructor(typeNode, level, findAllFields(typeNode), staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, onConstructor, ast);
}
}
@@ -164,25 +161,34 @@ public class HandleConstructor {
return true;
}
- public void generateRequiredArgsConstructor(EclipseNode typeNode, AccessLevel level, String staticName, boolean skipIfConstructorExists, List<Annotation> onConstructor, ASTNode source) {
+ public void generateRequiredArgsConstructor(EclipseNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, List<Annotation> onConstructor, ASTNode source) {
generateConstructor(typeNode, level, findRequiredFields(typeNode), staticName, skipIfConstructorExists, false, onConstructor, source);
}
- public void generateAllArgsConstructor(EclipseNode typeNode, AccessLevel level, String staticName, boolean skipIfConstructorExists, List<Annotation> onConstructor, ASTNode source) {
+ public void generateAllArgsConstructor(EclipseNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, List<Annotation> onConstructor, ASTNode source) {
generateConstructor(typeNode, level, findAllFields(typeNode), staticName, skipIfConstructorExists, false, onConstructor, source);
}
- public void generateConstructor(EclipseNode typeNode, AccessLevel level, List<EclipseNode> fields, String staticName, boolean skipIfConstructorExists, boolean suppressConstructorProperties, List<Annotation> onConstructor, ASTNode source) {
+ public enum SkipIfConstructorExists {
+ YES, NO, I_AM_BUILDER;
+ }
+
+ public void generateConstructor(EclipseNode typeNode, AccessLevel level, List<EclipseNode> fields, String staticName, SkipIfConstructorExists skipIfConstructorExists, boolean suppressConstructorProperties, List<Annotation> onConstructor, ASTNode source) {
boolean staticConstrRequired = staticName != null && !staticName.equals("");
- if (skipIfConstructorExists && constructorExists(typeNode) != MemberExistsResult.NOT_EXISTS) return;
- if (skipIfConstructorExists) {
+ if (skipIfConstructorExists != SkipIfConstructorExists.NO && constructorExists(typeNode) != MemberExistsResult.NOT_EXISTS) return;
+ if (skipIfConstructorExists != SkipIfConstructorExists.NO) {
for (EclipseNode child : typeNode.down()) {
if (child.getKind() == Kind.ANNOTATION) {
- if (annotationTypeMatches(NoArgsConstructor.class, child) ||
+ boolean skipGeneration = (annotationTypeMatches(NoArgsConstructor.class, child) ||
annotationTypeMatches(AllArgsConstructor.class, child) ||
- annotationTypeMatches(RequiredArgsConstructor.class, child)) {
-
+ annotationTypeMatches(RequiredArgsConstructor.class, child));
+
+ if (!skipGeneration && skipIfConstructorExists == SkipIfConstructorExists.YES) {
+ skipGeneration = annotationTypeMatches(Builder.class, child);
+ }
+
+ if (skipGeneration) {
if (staticConstrRequired) {
// @Data has asked us to generate a constructor, but we're going to skip this instruction, as an explicit 'make a constructor' annotation
// will take care of it. However, @Data also wants a specific static name; this will be ignored; the appropriate way to do this is to use
@@ -235,7 +241,7 @@ public class HandleConstructor {
return new Annotation[] { ann };
}
- private ConstructorDeclaration createConstructor(
+ static ConstructorDeclaration createConstructor(
AccessLevel level, EclipseNode type, Collection<EclipseNode> fields,
boolean suppressConstructorProperties, ASTNode source, List<Annotation> onConstructor) {
@@ -307,7 +313,7 @@ public class HandleConstructor {
return constructor;
}
- private boolean isLocalType(EclipseNode type) {
+ private static boolean isLocalType(EclipseNode type) {
Kind kind = type.up().getKind();
if (kind == Kind.COMPILATION_UNIT) return false;
if (kind == Kind.TYPE) return isLocalType(type.up());
@@ -321,18 +327,9 @@ public class HandleConstructor {
MethodDeclaration constructor = new MethodDeclaration(
((CompilationUnitDeclaration) type.top().get()).compilationResult);
- constructor.modifiers = toEclipseModifier(level) | Modifier.STATIC;
+ constructor.modifiers = toEclipseModifier(level) | ClassFileConstants.AccStatic;
TypeDeclaration typeDecl = (TypeDeclaration) type.get();
- if (typeDecl.typeParameters != null && typeDecl.typeParameters.length > 0) {
- TypeReference[] refs = new TypeReference[typeDecl.typeParameters.length];
- int idx = 0;
- for (TypeParameter param : typeDecl.typeParameters) {
- TypeReference typeRef = new SingleTypeReference(param.name, (long)param.sourceStart << 32 | param.sourceEnd);
- setGeneratedBy(typeRef, source);
- refs[idx++] = typeRef;
- }
- constructor.returnType = new ParameterizedSingleTypeReference(typeDecl.name, refs, 0, p);
- } else constructor.returnType = new SingleTypeReference(((TypeDeclaration)type.get()).name, p);
+ constructor.returnType = EclipseHandlerUtil.namePlusTypeParamsToTypeReference(typeDecl.name, typeDecl.typeParameters, p);
constructor.annotations = null;
constructor.selector = name.toCharArray();
constructor.thrownExceptions = null;
diff --git a/src/core/lombok/eclipse/handlers/HandleData.java b/src/core/lombok/eclipse/handlers/HandleData.java
index 3a43bd3f..aa309489 100644
--- a/src/core/lombok/eclipse/handlers/HandleData.java
+++ b/src/core/lombok/eclipse/handlers/HandleData.java
@@ -28,6 +28,7 @@ import lombok.Data;
import lombok.core.AnnotationValues;
import lombok.eclipse.EclipseAnnotationHandler;
import lombok.eclipse.EclipseNode;
+import lombok.eclipse.handlers.HandleConstructor.SkipIfConstructorExists;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
@@ -64,6 +65,6 @@ public class HandleData extends EclipseAnnotationHandler<Data> {
new HandleSetter().generateSetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true);
new HandleEqualsAndHashCode().generateEqualsAndHashCodeForType(typeNode, annotationNode);
new HandleToString().generateToStringForType(typeNode, annotationNode);
- new HandleConstructor().generateRequiredArgsConstructor(typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), true, Collections.<Annotation>emptyList(), ast);
+ new HandleConstructor().generateRequiredArgsConstructor(typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), SkipIfConstructorExists.YES, Collections.<Annotation>emptyList(), ast);
}
}
diff --git a/src/core/lombok/eclipse/handlers/HandleSetter.java b/src/core/lombok/eclipse/handlers/HandleSetter.java
index 9b46b704..ae846a4e 100644
--- a/src/core/lombok/eclipse/handlers/HandleSetter.java
+++ b/src/core/lombok/eclipse/handlers/HandleSetter.java
@@ -192,7 +192,7 @@ public class HandleSetter extends EclipseAnnotationHandler<Setter> {
injectMethod(fieldNode.up(), method);
}
- private MethodDeclaration createSetter(TypeDeclaration parent, EclipseNode fieldNode, String name, boolean shouldReturnThis, int modifier, ASTNode source, List<Annotation> onMethod, List<Annotation> onParam) {
+ static MethodDeclaration createSetter(TypeDeclaration parent, EclipseNode fieldNode, String name, boolean shouldReturnThis, int modifier, ASTNode source, List<Annotation> onMethod, List<Annotation> onParam) {
FieldDeclaration field = (FieldDeclaration) fieldNode.get();
int pS = source.sourceStart, pE = source.sourceEnd;
long p = (long)pS << 32 | pE;
diff --git a/src/core/lombok/eclipse/handlers/HandleValue.java b/src/core/lombok/eclipse/handlers/HandleValue.java
index b69b1669..60938649 100644
--- a/src/core/lombok/eclipse/handlers/HandleValue.java
+++ b/src/core/lombok/eclipse/handlers/HandleValue.java
@@ -30,6 +30,7 @@ import lombok.core.AnnotationValues;
import lombok.core.HandlerPriority;
import lombok.eclipse.EclipseAnnotationHandler;
import lombok.eclipse.EclipseNode;
+import lombok.eclipse.handlers.HandleConstructor.SkipIfConstructorExists;
import lombok.experimental.NonFinal;
import lombok.experimental.Value;
@@ -78,6 +79,6 @@ public class HandleValue extends EclipseAnnotationHandler<Value> {
new HandleGetter().generateGetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true);
new HandleEqualsAndHashCode().generateEqualsAndHashCodeForType(typeNode, annotationNode);
new HandleToString().generateToStringForType(typeNode, annotationNode);
- new HandleConstructor().generateAllArgsConstructor(typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), true, Collections.<Annotation>emptyList(), ast);
+ new HandleConstructor().generateAllArgsConstructor(typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), SkipIfConstructorExists.YES, Collections.<Annotation>emptyList(), ast);
}
}
diff --git a/src/core/lombok/experimental/Builder.java b/src/core/lombok/experimental/Builder.java
index b6667462..5f2d1ca6 100644
--- a/src/core/lombok/experimental/Builder.java
+++ b/src/core/lombok/experimental/Builder.java
@@ -109,4 +109,13 @@ import java.lang.annotation.Target;
public @interface Builder {
/** Name of the static method that creates a new builder instance. Default: {@code builder}. */
String builderMethodName() default "builder";
+
+ /** Name of the instance method in the builder class that creates an instance of your {@code @Builder}-annotated class. */
+ String buildMethodName() default "build";
+
+ /** Name of the builder class.
+ * Default for {@code @Builder} on types and constructors: {@code (TypeName)Builder}.
+ * Default for {@code @Builder} on static methods: {@code (ReturnTypeName)Builder}.
+ */
+ String builderClassName() default "";
}
diff --git a/test/transform/resource/before/BuilderComplex.java b/test/transform/resource/before/BuilderComplex.java
new file mode 100644
index 00000000..3d3e7187
--- /dev/null
+++ b/test/transform/resource/before/BuilderComplex.java
@@ -0,0 +1,7 @@
+import java.util.List;
+import lombok.experimental.Builder;
+
+class BuilderComplex {
+ @Builder
+ private static <T extends Number> void testVoidWithGenerics(T number, int arg2, String arg3, BuilderComplex selfRef) {}
+}
diff --git a/test/transform/resource/before/BuilderSimple.java b/test/transform/resource/before/BuilderSimple.java
new file mode 100644
index 00000000..c749bb6c
--- /dev/null
+++ b/test/transform/resource/before/BuilderSimple.java
@@ -0,0 +1,9 @@
+import java.util.List;
+
+@lombok.experimental.Builder
+class BuilderSimple<T> {
+ private final int noshow = 0;
+ private final int yes;
+ private List<T> also;
+ private int $butNotMe;
+}