From 6e2d23fdd7613de9650ffaf00c6e24a6ead0d74b Mon Sep 17 00:00:00 2001 From: Jan Rieke Date: Fri, 7 Feb 2020 14:28:46 +0100 Subject: setter prefixes for SuperBuilder setter prefixes for SuperBuilder [javac] better javadoc for the SuperBuilder setterPrefix value setter prefixes for SuperBuilder [ecj] add test for SuperBuilder setter method customization fix setter method customization with setterPrefix --- .../eclipse/handlers/HandleSuperBuilder.java | 34 ++++++++++---------- src/core/lombok/experimental/SuperBuilder.java | 19 +++++++++++ .../lombok/javac/handlers/HandleSuperBuilder.java | 37 ++++++++++++---------- 3 files changed, 56 insertions(+), 34 deletions(-) (limited to 'src/core') diff --git a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java index 12fa9b2c..82af39fa 100644 --- a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java @@ -179,7 +179,7 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler { bfd.builderFieldName = bfd.name; bfd.annotations = copyAnnotations(fd, copyableAnnotations); bfd.type = fd.type; - bfd.singularData = getSingularData(fieldNode, ast); + bfd.singularData = getSingularData(fieldNode, ast, superbuilderAnnotation.setterPrefix()); bfd.originalFieldNode = fieldNode; if (bfd.singularData != null && isDefault != null) { @@ -335,7 +335,7 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler { // Generate $fillValuesFrom() method in the abstract builder. injectMethod(builderType, generateFillValuesMethod(tdParent, superclassBuilderClass != null, builderGenericName, classGenericName, builderClassName, typeParams)); // Generate $fillValuesFromInstanceIntoBuilder() method in the builder implementation class. - injectMethod(builderType, generateStaticFillValuesMethod(tdParent, builderClassName, typeParams, builderFields, ast)); + injectMethod(builderType, generateStaticFillValuesMethod(tdParent, builderClassName, typeParams, builderFields, ast, superbuilderAnnotation.setterPrefix())); } // Generate abstract self() and build() methods in the abstract builder. @@ -344,7 +344,7 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler { // Create the setter methods in the abstract builder. for (BuilderFieldData bfd : builderFields) { - generateSetterMethodsForBuilder(cfv, builderType, bfd, annotationNode, builderGenericName); + generateSetterMethodsForBuilder(cfv, builderType, bfd, annotationNode, builderGenericName, superbuilderAnnotation.setterPrefix()); } // Create the toString() method for the abstract builder. @@ -701,8 +701,9 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler { * b.field(instance.field); * } * + * @param setterPrefix the prefix for setter methods */ - private MethodDeclaration generateStaticFillValuesMethod(EclipseNode tdParent, String builderClassName, TypeParameter[] typeParams, java.util.List builderFields, ASTNode source) { + private MethodDeclaration generateStaticFillValuesMethod(EclipseNode tdParent, String builderClassName, TypeParameter[] typeParams, java.util.List builderFields, ASTNode source, String setterPrefix) { MethodDeclaration out = new MethodDeclaration(((CompilationUnitDeclaration) tdParent.top().get()).compilationResult); out.selector = FILL_VALUES_STATIC_METHOD_NAME; out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; @@ -731,7 +732,7 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler { // Call the builder's setter methods to fill the values from the instance. for (BuilderFieldData bfd : builderFields) { - MessageSend exec = createSetterCallWithInstanceValue(bfd, tdParent, source); + MessageSend exec = createSetterCallWithInstanceValue(bfd, tdParent, source, setterPrefix); body.add(exec); } @@ -740,8 +741,8 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler { return out; } - private MessageSend createSetterCallWithInstanceValue(BuilderFieldData bfd, EclipseNode type, ASTNode source) { - char[] setterName = bfd.name; + private MessageSend createSetterCallWithInstanceValue(BuilderFieldData bfd, EclipseNode type, ASTNode source, String setterPrefix) { + char[] setterName = HandlerUtil.buildAccessorName(setterPrefix, String.valueOf(bfd.name)).toCharArray(); MessageSend ms = new MessageSend(); Expression[] tgt = new Expression[bfd.singularData == null ? 1 : 2]; @@ -919,7 +920,7 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler { } } - private void generateSetterMethodsForBuilder(CheckerFrameworkVersion cfv, EclipseNode builderType, BuilderFieldData bfd, EclipseNode sourceNode, final String builderGenericName) { + private void generateSetterMethodsForBuilder(CheckerFrameworkVersion cfv, EclipseNode builderType, BuilderFieldData bfd, EclipseNode sourceNode, final String builderGenericName, String setterPrefix) { boolean deprecate = isFieldDeprecated(bfd.originalFieldNode); TypeReferenceMaker returnTypeMaker = new TypeReferenceMaker() { @@ -938,29 +939,27 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler { }; if (bfd.singularData == null || bfd.singularData.getSingularizer() == null) { - generateSimpleSetterMethodForBuilder(cfv, builderType, deprecate, bfd.createdFields.get(0), bfd.name, bfd.nameOfSetFlag, true, returnTypeMaker.make(), returnStatementMaker.make(), sourceNode, bfd.annotations, bfd.originalFieldNode); + generateSimpleSetterMethodForBuilder(cfv, builderType, deprecate, bfd.createdFields.get(0), bfd.name, bfd.nameOfSetFlag, returnTypeMaker.make(), returnStatementMaker.make(), sourceNode, bfd.annotations, bfd.originalFieldNode, setterPrefix); } else { bfd.singularData.getSingularizer().generateMethods(cfv, bfd.singularData, deprecate, builderType, true, returnTypeMaker, returnStatementMaker, AccessLevel.PUBLIC); } } - private void generateSimpleSetterMethodForBuilder(CheckerFrameworkVersion cfv, EclipseNode builderType, boolean deprecate, EclipseNode fieldNode, char[] paramName, char[] nameOfSetFlag, boolean fluent, TypeReference returnType, Statement returnStatement, EclipseNode sourceNode, Annotation[] annosOnParam, EclipseNode originalFieldNode) { + private void generateSimpleSetterMethodForBuilder(CheckerFrameworkVersion cfv, EclipseNode builderType, boolean deprecate, EclipseNode fieldNode, char[] paramName, char[] nameOfSetFlag, TypeReference returnType, Statement returnStatement, EclipseNode sourceNode, Annotation[] annosOnParam, EclipseNode originalFieldNode, String setterPrefix) { TypeDeclaration td = (TypeDeclaration) builderType.get(); ASTNode source = sourceNode.get(); AbstractMethodDeclaration[] existing = td.methods; if (existing == null) existing = EMPTY_METHODS; int len = existing.length; - FieldDeclaration fd = (FieldDeclaration) fieldNode.get(); - char[] name = fd.name; + + String setterName = HandlerUtil.buildAccessorName(setterPrefix, new String(paramName)); for (int i = 0; i < len; i++) { if (!(existing[i] instanceof MethodDeclaration)) continue; char[] existingName = existing[i].selector; - if (Arrays.equals(name, existingName) && !isTolerate(fieldNode, existing[i])) return; + if (Arrays.equals(setterName.toCharArray(), existingName) && !isTolerate(fieldNode, existing[i])) return; } - String setterName = fluent ? new String(paramName) : HandlerUtil.buildAccessorName("set", new String(paramName)); - List methodAnnsList = Arrays.asList(EclipseHandlerUtil.findCopyableToSetterAnnotations(originalFieldNode)); if (cfv.generateReturnsReceiver()) { methodAnnsList = new ArrayList(methodAnnsList); @@ -997,8 +996,9 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler { * or parameter), or null if there's no {@code @Singular} annotation on it. * * @param node The node (field or method param) to inspect for its name and potential {@code @Singular} annotation. + * @param setterPrefix the prefix for setter methods */ - private SingularData getSingularData(EclipseNode node, ASTNode source) { + private SingularData getSingularData(EclipseNode node, ASTNode source, String setterPrefix) { for (EclipseNode child : node.down()) { if (!annotationTypeMatches(Singular.class, child)) continue; @@ -1047,7 +1047,7 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler { return null; } - return new SingularData(child, singularName, pluralName, typeArgs == null ? Collections.emptyList() : Arrays.asList(typeArgs), targetFqn, singularizer, source, singularInstance.ignoreNullCollections()); + return new SingularData(child, singularName, pluralName, typeArgs == null ? Collections.emptyList() : Arrays.asList(typeArgs), targetFqn, singularizer, source, singularInstance.ignoreNullCollections(), setterPrefix.toCharArray()); } return null; diff --git a/src/core/lombok/experimental/SuperBuilder.java b/src/core/lombok/experimental/SuperBuilder.java index aef76a46..0733a616 100644 --- a/src/core/lombok/experimental/SuperBuilder.java +++ b/src/core/lombok/experimental/SuperBuilder.java @@ -62,4 +62,23 @@ public @interface SuperBuilder { * @return Whether to generate a {@code toBuilder()} method. */ boolean toBuilder() default false; + + /** + * Prefix to prepend to 'set' methods in the generated builder class. By default, generated methods do not include a prefix. + * + * For example, a method normally generated as {@code someField(String someField)} would instead be + * generated as {@code withSomeField(String someField)} if using {@code @SuperBuilder(setterPrefix = "with")}. + * + * Note that using "with" to prefix builder setter methods is strongly discouraged as as "with" normally + * suggests immutable data structures, and builders by definition are mutable objects. + * + * For {@code @Singular} fields, the generated methods are called {@code withName}, {@code withNames}, and {@code clearNames}, instead of + * the default {@code name}, {@code names}, and {@code clearNames}. + * + * This prefix only applies to the 'set' methods for the fields of the annotated class. + * For consistency reasons, you should use the same prefix on all superclasses and subclasses that use {@code @SuperBuilder}. + * + * @return The prefix to prepend to generated method names. + */ + String setterPrefix() default ""; } diff --git a/src/core/lombok/javac/handlers/HandleSuperBuilder.java b/src/core/lombok/javac/handlers/HandleSuperBuilder.java index 650b0964..c9df4811 100644 --- a/src/core/lombok/javac/handlers/HandleSuperBuilder.java +++ b/src/core/lombok/javac/handlers/HandleSuperBuilder.java @@ -117,7 +117,7 @@ public class HandleSuperBuilder extends JavacAnnotationHandler { if (!checkName("buildMethodName", buildMethodName, annotationNode)) return; boolean toBuilder = superbuilderAnnotation.toBuilder(); - + JavacNode tdParent = annotationNode.up(); java.util.List builderFields = new ArrayList(); @@ -148,7 +148,7 @@ public class HandleSuperBuilder extends JavacAnnotationHandler { bfd.builderFieldName = bfd.name; bfd.annotations = findCopyableAnnotations(fieldNode); bfd.type = fd.vartype; - bfd.singularData = getSingularData(fieldNode); + bfd.singularData = getSingularData(fieldNode, superbuilderAnnotation.setterPrefix()); bfd.originalFieldNode = fieldNode; if (bfd.singularData != null && isDefault != null) { @@ -281,7 +281,7 @@ public class HandleSuperBuilder extends JavacAnnotationHandler { recursiveSetGeneratedBy(fvm, ast, annotationNode.getContext()); injectMethod(builderType, fvm); // Generate $fillValuesFromInstanceIntoBuilder() method in the builder implementation class. - JCMethodDecl sfvm = generateStaticFillValuesMethod(tdParent, builderClassName, typeParams, builderFields); + JCMethodDecl sfvm = generateStaticFillValuesMethod(tdParent, builderClassName, typeParams, builderFields, superbuilderAnnotation.setterPrefix()); recursiveSetGeneratedBy(sfvm, ast, annotationNode.getContext()); injectMethod(builderType, sfvm); } @@ -296,7 +296,7 @@ public class HandleSuperBuilder extends JavacAnnotationHandler { // Create the setter methods in the abstract builder. for (BuilderFieldData bfd : builderFields) { - generateSetterMethodsForBuilder(cfv, builderType, bfd, annotationNode, builderGenericName); + generateSetterMethodsForBuilder(cfv, builderType, bfd, annotationNode, builderGenericName, superbuilderAnnotation.setterPrefix()); } // Create the toString() method for the abstract builder. @@ -671,8 +671,9 @@ public class HandleSuperBuilder extends JavacAnnotationHandler { * b.field(instance.field); * } * + * @param setterPrefix the prefix for setter methods */ - private JCMethodDecl generateStaticFillValuesMethod(JavacNode type, String builderClassname, List typeParams, java.util.List builderFields) { + private JCMethodDecl generateStaticFillValuesMethod(JavacNode type, String builderClassname, List typeParams, java.util.List builderFields, String setterPrefix) { JavacTreeMaker maker = type.getTreeMaker(); List annotations = List.nil(); JCModifiers modifiers = maker.Modifiers(Flags.PRIVATE | Flags.STATIC, annotations); @@ -697,7 +698,7 @@ public class HandleSuperBuilder extends JavacAnnotationHandler { // Call the builder's setter methods to fill the values from the instance. for (BuilderFieldData bfd : builderFields) { - JCExpressionStatement exec = createSetterCallWithInstanceValue(bfd, type, maker); + JCExpressionStatement exec = createSetterCallWithInstanceValue(bfd, type, maker, setterPrefix); body.append(exec); } @@ -706,7 +707,7 @@ public class HandleSuperBuilder extends JavacAnnotationHandler { return maker.MethodDef(modifiers, name, returnType, copyTypeParams(type, typeParams), List.of(paramInstance, paramBuilder), List.nil(), bodyBlock, null); } - private JCExpressionStatement createSetterCallWithInstanceValue(BuilderFieldData bfd, JavacNode type, JavacTreeMaker maker) { + private JCExpressionStatement createSetterCallWithInstanceValue(BuilderFieldData bfd, JavacNode type, JavacTreeMaker maker, String setterPrefix) { JCExpression[] tgt = new JCExpression[bfd.singularData == null ? 1 : 2]; if (bfd.obtainVia == null || !bfd.obtainVia.field().isEmpty()) { for (int i = 0; i < tgt.length; i++) { @@ -736,7 +737,9 @@ public class HandleSuperBuilder extends JavacAnnotationHandler { JCExpression emptyCollection = maker.Apply(List.nil(), chainDots(type, emptyMaker.split("\\.")), List.nil()); arg = maker.Conditional(eqNull, emptyCollection, tgt[1]); } - JCMethodInvocation apply = maker.Apply(List.nil(), maker.Select(maker.Ident(type.toName(BUILDER_VARIABLE_NAME)), bfd.name), List.of(arg)); + + String setterName = HandlerUtil.buildAccessorName(setterPrefix, bfd.name.toString()); + JCMethodInvocation apply = maker.Apply(List.nil(), maker.Select(maker.Ident(type.toName(BUILDER_VARIABLE_NAME)), type.toName(setterName)), List.of(arg)); JCExpressionStatement exec = maker.Exec(apply); return exec; } @@ -872,7 +875,7 @@ public class HandleSuperBuilder extends JavacAnnotationHandler { for (JCVariableDecl gen : generated) recursiveSetGeneratedBy(gen, source, builderType.getContext()); } - private void generateSetterMethodsForBuilder(CheckerFrameworkVersion cfv, final JavacNode builderType, BuilderFieldData fieldNode, JavacNode source, final String builderGenericName) { + private void generateSetterMethodsForBuilder(CheckerFrameworkVersion cfv, final JavacNode builderType, BuilderFieldData fieldNode, JavacNode source, final String builderGenericName, String setterPrefix) { boolean deprecate = isFieldDeprecated(fieldNode.originalFieldNode); final JavacTreeMaker maker = builderType.getTreeMaker(); ExpressionMaker returnTypeMaker = new ExpressionMaker() { @Override public JCExpression make() { @@ -884,24 +887,23 @@ public class HandleSuperBuilder extends JavacAnnotationHandler { }}; if (fieldNode.singularData == null || fieldNode.singularData.getSingularizer() == null) { - generateSimpleSetterMethodForBuilder(cfv, builderType, deprecate, fieldNode.createdFields.get(0), fieldNode.name, fieldNode.nameOfSetFlag, source, true, returnTypeMaker.make(), returnStatementMaker.make(), fieldNode.annotations, fieldNode.originalFieldNode); + generateSimpleSetterMethodForBuilder(cfv, builderType, deprecate, fieldNode.createdFields.get(0), fieldNode.name, fieldNode.nameOfSetFlag, source, returnTypeMaker.make(), returnStatementMaker.make(), fieldNode.annotations, fieldNode.originalFieldNode, setterPrefix); } else { fieldNode.singularData.getSingularizer().generateMethods(cfv, fieldNode.singularData, deprecate, builderType, source.get(), true, returnTypeMaker, returnStatementMaker, AccessLevel.PUBLIC); } } - private void generateSimpleSetterMethodForBuilder(CheckerFrameworkVersion cfv, JavacNode builderType, boolean deprecate, JavacNode fieldNode, Name paramName, Name nameOfSetFlag, JavacNode source, boolean fluent, JCExpression returnType, JCStatement returnStatement, List annosOnParam, JavacNode originalFieldNode) { - Name fieldName = ((JCVariableDecl) fieldNode.get()).name; + private void generateSimpleSetterMethodForBuilder(CheckerFrameworkVersion cfv, JavacNode builderType, boolean deprecate, JavacNode fieldNode, Name paramName, Name nameOfSetFlag, JavacNode source, JCExpression returnType, JCStatement returnStatement, List annosOnParam, JavacNode originalFieldNode, String setterPrefix) { + String setterName = HandlerUtil.buildAccessorName(setterPrefix, paramName.toString()); + Name setterName_ = builderType.toName(setterName); for (JavacNode child : builderType.down()) { if (child.getKind() != Kind.METHOD) continue; JCMethodDecl methodDecl = (JCMethodDecl) child.get(); Name existingName = methodDecl.name; - if (existingName.equals(fieldName) && !isTolerate(fieldNode, methodDecl)) return; + if (existingName.equals(setterName_) && !isTolerate(fieldNode, methodDecl)) return; } - String setterName = fluent ? paramName.toString() : HandlerUtil.buildAccessorName("set", paramName.toString()); - JavacTreeMaker maker = fieldNode.getTreeMaker(); List methodAnns = JavacHandlerUtil.findCopyableToSetterAnnotations(originalFieldNode); @@ -940,8 +942,9 @@ public class HandleSuperBuilder extends JavacAnnotationHandler { * or parameter), or null if there's no {@code @Singular} annotation on it. * * @param node The node (field or method param) to inspect for its name and potential {@code @Singular} annotation. + * @param setterPrefix the prefix for setter methods */ - private SingularData getSingularData(JavacNode node) { + private SingularData getSingularData(JavacNode node, String setterPrefix) { for (JavacNode child : node.down()) { if (!annotationTypeMatches(Singular.class, child)) continue; Name pluralName = node.getKind() == Kind.FIELD ? removePrefixFromField(node) : ((JCVariableDecl) node.get()).name; @@ -982,7 +985,7 @@ public class HandleSuperBuilder extends JavacAnnotationHandler { return null; } - return new SingularData(child, singularName, pluralName, typeArgs, targetFqn, singularizer, singularInstance.ignoreNullCollections()); + return new SingularData(child, singularName, pluralName, typeArgs, targetFqn, singularizer, singularInstance.ignoreNullCollections(), setterPrefix); } return null; -- cgit