From 648c3eeee69bede925f794b16b1f3d184359761f Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Mon, 10 Jun 2013 23:14:23 +0200 Subject: Eclipse Builder implementation finished. Tests need fleshing out though. --- .../eclipse/handlers/EclipseHandlerUtil.java | 31 +- .../lombok/eclipse/handlers/HandleBuilder.java | 323 ++++++++++++++++++++- .../lombok/eclipse/handlers/HandleConstructor.java | 55 ++-- src/core/lombok/eclipse/handlers/HandleData.java | 3 +- src/core/lombok/eclipse/handlers/HandleSetter.java | 2 +- src/core/lombok/eclipse/handlers/HandleValue.java | 3 +- src/core/lombok/experimental/Builder.java | 9 + test/transform/resource/before/BuilderComplex.java | 7 + test/transform/resource/before/BuilderSimple.java | 9 + 9 files changed, 387 insertions(+), 55 deletions(-) create mode 100644 test/transform/resource/before/BuilderComplex.java create mode 100644 test/transform/resource/before/BuilderSimple.java 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 @{@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 { @Override public void handle(AnnotationValues 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 typesOfParameters = new ArrayList(); + List namesOfParameters = new ArrayList(); + 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.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 fieldNodes = addFieldsToBuilder(builderType, namesOfParameters, typesOfParameters, ast); + List newMethods = new ArrayList(); + 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.emptyList(), true, ast, Collections.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.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 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 assigns = new ArrayList(); + 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 addFieldsToBuilder(EclipseNode builderType, List namesOfParameters, List typesOfParameters, ASTNode source) { + int len = namesOfParameters.size(); + TypeDeclaration td = (TypeDeclaration) builderType.get(); + FieldDeclaration[] existing = td.fields; + if (existing == null) existing = new FieldDeclaration[0]; + + List out = new ArrayList(); + + 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.emptyList(), Collections.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 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 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 findAllFields(EclipseNode typeNode) { + static List findAllFields(EclipseNode typeNode) { List fields = new ArrayList(); for (EclipseNode child : typeNode.down()) { if (child.getKind() != Kind.FIELD) continue; @@ -146,7 +143,7 @@ public class HandleConstructor { List 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 onConstructor, ASTNode source) { + public void generateRequiredArgsConstructor(EclipseNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, List 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 onConstructor, ASTNode source) { + public void generateAllArgsConstructor(EclipseNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, List onConstructor, ASTNode source) { generateConstructor(typeNode, level, findAllFields(typeNode), staticName, skipIfConstructorExists, false, onConstructor, source); } - public void generateConstructor(EclipseNode typeNode, AccessLevel level, List fields, String staticName, boolean skipIfConstructorExists, boolean suppressConstructorProperties, List onConstructor, ASTNode source) { + public enum SkipIfConstructorExists { + YES, NO, I_AM_BUILDER; + } + + public void generateConstructor(EclipseNode typeNode, AccessLevel level, List fields, String staticName, SkipIfConstructorExists skipIfConstructorExists, boolean suppressConstructorProperties, List 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 fields, boolean suppressConstructorProperties, ASTNode source, List 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 { 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.emptyList(), ast); + new HandleConstructor().generateRequiredArgsConstructor(typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), SkipIfConstructorExists.YES, Collections.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 { injectMethod(fieldNode.up(), method); } - private MethodDeclaration createSetter(TypeDeclaration parent, EclipseNode fieldNode, String name, boolean shouldReturnThis, int modifier, ASTNode source, List onMethod, List onParam) { + static MethodDeclaration createSetter(TypeDeclaration parent, EclipseNode fieldNode, String name, boolean shouldReturnThis, int modifier, ASTNode source, List onMethod, List 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 { 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.emptyList(), ast); + new HandleConstructor().generateAllArgsConstructor(typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), SkipIfConstructorExists.YES, Collections.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 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 { + private final int noshow = 0; + private final int yes; + private List also; + private int $butNotMe; +} -- cgit