Skip to content

Commit

Permalink
Merge pull request #2357 from janrieke/superBuilderSetterPrefix
Browse files Browse the repository at this point in the history
setter prefixes for SuperBuilder
  • Loading branch information
rzwitserloot committed Feb 8, 2020
2 parents cf4e596 + 6e2d23f commit d1c81ab
Show file tree
Hide file tree
Showing 12 changed files with 582 additions and 43 deletions.
34 changes: 17 additions & 17 deletions src/core/lombok/eclipse/handlers/HandleSuperBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ public void handle(AnnotationValues<SuperBuilder> annotation, Annotation ast, Ec
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) {
Expand Down Expand Up @@ -335,7 +335,7 @@ public void handle(AnnotationValues<SuperBuilder> annotation, Annotation ast, Ec
// 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.
Expand All @@ -344,7 +344,7 @@ public void handle(AnnotationValues<SuperBuilder> annotation, Annotation ast, Ec

// 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.
Expand Down Expand Up @@ -701,8 +701,9 @@ private MethodDeclaration generateFillValuesMethod(EclipseNode tdParent, boolean
* b.field(instance.field);
* }
* </pre>
* @param setterPrefix the prefix for setter methods
*/
private MethodDeclaration generateStaticFillValuesMethod(EclipseNode tdParent, String builderClassName, TypeParameter[] typeParams, java.util.List<BuilderFieldData> builderFields, ASTNode source) {
private MethodDeclaration generateStaticFillValuesMethod(EclipseNode tdParent, String builderClassName, TypeParameter[] typeParams, java.util.List<BuilderFieldData> 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;
Expand Down Expand Up @@ -731,7 +732,7 @@ private MethodDeclaration generateStaticFillValuesMethod(EclipseNode tdParent, S

// 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);
}

Expand All @@ -740,8 +741,8 @@ private MethodDeclaration generateStaticFillValuesMethod(EclipseNode tdParent, S
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];

Expand Down Expand Up @@ -919,7 +920,7 @@ private void generateBuilderFields(EclipseNode builderType, List<BuilderFieldDat
}
}

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() {
Expand All @@ -938,29 +939,27 @@ private void generateSetterMethodsForBuilder(CheckerFrameworkVersion cfv, Eclips
};

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<Annotation> methodAnnsList = Arrays.asList(EclipseHandlerUtil.findCopyableToSetterAnnotations(originalFieldNode));
if (cfv.generateReturnsReceiver()) {
methodAnnsList = new ArrayList<Annotation>(methodAnnsList);
Expand Down Expand Up @@ -997,8 +996,9 @@ private void addObtainVia(BuilderFieldData bfd, EclipseNode node) {
* 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;

Expand Down Expand Up @@ -1047,7 +1047,7 @@ private SingularData getSingularData(EclipseNode node, ASTNode source) {
return null;
}

return new SingularData(child, singularName, pluralName, typeArgs == null ? Collections.<TypeReference>emptyList() : Arrays.asList(typeArgs), targetFqn, singularizer, source, singularInstance.ignoreNullCollections());
return new SingularData(child, singularName, pluralName, typeArgs == null ? Collections.<TypeReference>emptyList() : Arrays.asList(typeArgs), targetFqn, singularizer, source, singularInstance.ignoreNullCollections(), setterPrefix.toCharArray());
}

return null;
Expand Down
19 changes: 19 additions & 0 deletions src/core/lombok/experimental/SuperBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,23 @@
* @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 "";
}
37 changes: 20 additions & 17 deletions src/core/lombok/javac/handlers/HandleSuperBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public void handle(AnnotationValues<SuperBuilder> annotation, JCAnnotation ast,
if (!checkName("buildMethodName", buildMethodName, annotationNode)) return;

boolean toBuilder = superbuilderAnnotation.toBuilder();

JavacNode tdParent = annotationNode.up();

java.util.List<BuilderFieldData> builderFields = new ArrayList<BuilderFieldData>();
Expand Down Expand Up @@ -148,7 +148,7 @@ public void handle(AnnotationValues<SuperBuilder> annotation, JCAnnotation ast,
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) {
Expand Down Expand Up @@ -281,7 +281,7 @@ public void handle(AnnotationValues<SuperBuilder> annotation, JCAnnotation ast,
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);
}
Expand All @@ -296,7 +296,7 @@ public void handle(AnnotationValues<SuperBuilder> annotation, JCAnnotation ast,

// 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.
Expand Down Expand Up @@ -671,8 +671,9 @@ private JCMethodDecl generateFillValuesMethod(JavacNode type, boolean inherited,
* b.field(instance.field);
* }
* </pre>
* @param setterPrefix the prefix for setter methods
*/
private JCMethodDecl generateStaticFillValuesMethod(JavacNode type, String builderClassname, List<JCTypeParameter> typeParams, java.util.List<BuilderFieldData> builderFields) {
private JCMethodDecl generateStaticFillValuesMethod(JavacNode type, String builderClassname, List<JCTypeParameter> typeParams, java.util.List<BuilderFieldData> builderFields, String setterPrefix) {
JavacTreeMaker maker = type.getTreeMaker();
List<JCAnnotation> annotations = List.nil();
JCModifiers modifiers = maker.Modifiers(Flags.PRIVATE | Flags.STATIC, annotations);
Expand All @@ -697,7 +698,7 @@ private JCMethodDecl generateStaticFillValuesMethod(JavacNode type, String build

// 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);
}

Expand All @@ -706,7 +707,7 @@ private JCMethodDecl generateStaticFillValuesMethod(JavacNode type, String build
return maker.MethodDef(modifiers, name, returnType, copyTypeParams(type, typeParams), List.of(paramInstance, paramBuilder), List.<JCExpression>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++) {
Expand Down Expand Up @@ -736,7 +737,9 @@ private JCExpressionStatement createSetterCallWithInstanceValue(BuilderFieldData
JCExpression emptyCollection = maker.Apply(List.<JCExpression>nil(), chainDots(type, emptyMaker.split("\\.")), List.<JCExpression>nil());
arg = maker.Conditional(eqNull, emptyCollection, tgt[1]);
}
JCMethodInvocation apply = maker.Apply(List.<JCExpression>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.<JCExpression>nil(), maker.Select(maker.Ident(type.toName(BUILDER_VARIABLE_NAME)), type.toName(setterName)), List.of(arg));
JCExpressionStatement exec = maker.Exec(apply);
return exec;
}
Expand Down Expand Up @@ -872,7 +875,7 @@ private void generateBuilderFields(JavacNode builderType, java.util.List<Builder
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() {
Expand All @@ -884,24 +887,23 @@ private void generateSetterMethodsForBuilder(CheckerFrameworkVersion cfv, final
}};

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<JCAnnotation> 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<JCAnnotation> 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<JCAnnotation> methodAnns = JavacHandlerUtil.findCopyableToSetterAnnotations(originalFieldNode);
Expand Down Expand Up @@ -940,8 +942,9 @@ private void addObtainVia(BuilderFieldData bfd, JavacNode node) {
* 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;
Expand Down Expand Up @@ -982,7 +985,7 @@ private SingularData getSingularData(JavacNode node) {
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@ public B resetToDefault() {
field1 = 0;
return self();
}
public B field1(int field1) {
this.field1 = field1 + 1;
return self();
}
@java.lang.SuppressWarnings("all")
protected abstract B self();
@java.lang.SuppressWarnings("all")
public abstract C build();
@java.lang.SuppressWarnings("all")
public B field1(final int field1) {
this.field1 = field1;
return self();
}
@java.lang.Override
@java.lang.SuppressWarnings("all")
public java.lang.String toString() {
Expand Down
Loading

0 comments on commit d1c81ab

Please sign in to comment.