diff options
author | Jan Rieke <rieke@subshell.com> | 2018-09-14 22:53:02 +0200 |
---|---|---|
committer | Jan Rieke <rieke@subshell.com> | 2018-09-14 22:53:02 +0200 |
commit | f5fff49f73ceabb776f470a9ed346ace0ce12be6 (patch) | |
tree | 3dc6d2ba1cd712f38e82fae9238029f31c06bf7d | |
parent | 14fa70ae183a1d15b8cee30d10775e0da281114b (diff) | |
download | lombok-f5fff49f73ceabb776f470a9ed346ace0ce12be6.tar.gz lombok-f5fff49f73ceabb776f470a9ed346ace0ce12be6.tar.bz2 lombok-f5fff49f73ceabb776f470a9ed346ace0ce12be6.zip |
SuperBuilder toBuilder: generate static toBuilder helper method (ecj)
3 files changed, 119 insertions, 69 deletions
diff --git a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java index bb78e82a..a68ede3c 100644 --- a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java @@ -107,7 +107,11 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler<SuperBuilder> { private static final String TO_BUILDER_METHOD_NAME_STRING = "toBuilder"; private static final char[] TO_BUILDER_METHOD_NAME = TO_BUILDER_METHOD_NAME_STRING.toCharArray(); private static final char[] FILL_VALUES_METHOD_NAME = "$fillValuesFrom".toCharArray(); + private static final char[] FILL_VALUES_STATIC_METHOD_NAME = "$fillValuesFromInstanceIntoBuilder".toCharArray(); private static final char[] EMPTY_LIST = "emptyList".toCharArray(); + private static final char[] INSTANCE_VARIABLE_NAME = "instance".toCharArray(); + private static final String BUILDER_VARIABLE_NAME_STRING = "b"; + private static final char[] BUILDER_VARIABLE_NAME = BUILDER_VARIABLE_NAME_STRING.toCharArray(); private static final AbstractMethodDeclaration[] EMPTY_METHODS = {}; @@ -294,7 +298,7 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler<SuperBuilder> { if (toBuilder) { // Generate $fillValuesFrom() method in the abstract builder. - injectMethod(builderType, generateFillValuesMethod(tdParent, superclassBuilderClass != null, builderGenericName, classGenericName, typeParams, builderFields, ast)); + injectMethod(builderType, generateFillValuesMethod(tdParent, superclassBuilderClass != null, builderGenericName, classGenericName, builderImplClassName, typeParams, builderFields, ast)); } // Generate abstract self() and build() methods in the abstract builder. @@ -323,8 +327,9 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler<SuperBuilder> { if (addCleaning) injectMethod(builderType, generateCleanMethod(builderFields, builderType, ast)); - if ((td.modifiers & ClassFileConstants.AccAbstract) != 0) { + if ((td.modifiers & ClassFileConstants.AccAbstract) != 0 && !toBuilder) { // Only non-abstract classes get the Builder implementation. + // However, if we want to generate a toBuilder, we need a helper function in the builder implementation class. return; } @@ -336,7 +341,24 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler<SuperBuilder> { annotationNode.addError("@SuperBuilder does not support customized builders. Use @Builder instead."); return; } - + + if (toBuilder) { + // Generate $fillValuesFromInstanceIntoBuilder() method in the builder implementation class. + injectMethod(builderImplType, generateStaticFillValuesMethod(tdParent, superclassBuilderClass != null, builderClassName, classGenericName, typeParams, builderFields, ast)); + + switch (methodExists(TO_BUILDER_METHOD_NAME_STRING, tdParent, 0)) { + case EXISTS_BY_USER: + annotationNode.addWarning("Not generating toBuilder() as it already exists."); + break; + case NOT_EXISTS: + injectMethod(tdParent, generateToBuilderMethod(builderClassName, builderImplClassName, tdParent, typeParams, ast)); + } + if ((td.modifiers & ClassFileConstants.AccAbstract) != 0) { + // The rest of the builder implementation class is not necessary for abstract classes. + return; + } + } + // Create the self() and build() methods in the BuilderImpl. injectMethod(builderImplType, generateSelfMethod(builderImplType, typeParams, p)); injectMethod(builderImplType, generateBuildMethod(tdParent, buildMethodName, returnType, ast)); @@ -346,15 +368,6 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler<SuperBuilder> { MethodDeclaration md = generateBuilderMethod(builderMethodName, builderClassName, builderImplClassName, tdParent, typeParams, ast); if (md != null) injectMethod(tdParent, md); } - - if (toBuilder) switch (methodExists(TO_BUILDER_METHOD_NAME_STRING, tdParent, 0)) { - case EXISTS_BY_USER: - annotationNode.addWarning("Not generating toBuilder() as it already exists."); - break; - case NOT_EXISTS: - MethodDeclaration md = generateToBuilderMethod(builderClassName, builderImplClassName, tdParent, typeParams, ast); - if (md != null) injectMethod(tdParent, md); - } } private EclipseNode generateBuilderAbstractClass(EclipseNode tdParent, String builderClass, @@ -449,7 +462,7 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler<SuperBuilder> { constructor.selector = typeDeclaration.name; if (callBuilderBasedSuperConstructor) { constructor.constructorCall = new ExplicitConstructorCall(ExplicitConstructorCall.Super); - constructor.constructorCall.arguments = new Expression[] {new SingleNameReference("b".toCharArray(), p)}; + constructor.constructorCall.arguments = new Expression[] {new SingleNameReference(BUILDER_VARIABLE_NAME, p)}; } else { constructor.constructorCall = new ExplicitConstructorCall(ExplicitConstructorCall.ImplicitSuper); } @@ -463,7 +476,7 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler<SuperBuilder> { TypeReference[] wildcards = new TypeReference[] {new Wildcard(Wildcard.UNBOUND), new Wildcard(Wildcard.UNBOUND)}; TypeReference builderType = new ParameterizedSingleTypeReference(builderClassName.toCharArray(), mergeToTypeReferences(typeParams, wildcards), 0, p); - constructor.arguments = new Argument[] {new Argument("b".toCharArray(), p, builderType, Modifier.FINAL)}; + constructor.arguments = new Argument[] {new Argument(BUILDER_VARIABLE_NAME, p, builderType, Modifier.FINAL)}; List<Statement> statements = new ArrayList<Statement>(); @@ -476,10 +489,10 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler<SuperBuilder> { Expression assignmentExpr; if (fieldNode.singularData != null && fieldNode.singularData.getSingularizer() != null) { - fieldNode.singularData.getSingularizer().appendBuildCode(fieldNode.singularData, typeNode, statements, fieldNode.name, "b"); + fieldNode.singularData.getSingularizer().appendBuildCode(fieldNode.singularData, typeNode, statements, fieldNode.name, BUILDER_VARIABLE_NAME_STRING); assignmentExpr = new SingleNameReference(fieldNode.name, p); } else { - char[][] variableInBuilder = new char[][] {"b".toCharArray(), fieldName}; + char[][] variableInBuilder = new char[][] {BUILDER_VARIABLE_NAME, fieldName}; long[] positions = new long[] {p, p}; assignmentExpr = new QualifiedNameReference(variableInBuilder, positions, s, e); } @@ -487,7 +500,7 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler<SuperBuilder> { // In case of @Builder.Default, set the value to the default if it was NOT set in the builder. if (fieldNode.nameOfSetFlag != null) { - char[][] setVariableInBuilder = new char[][] {"b".toCharArray(), fieldNode.nameOfSetFlag}; + char[][] setVariableInBuilder = new char[][] {BUILDER_VARIABLE_NAME, fieldNode.nameOfSetFlag}; long[] positions = new long[] {p, p}; QualifiedNameReference setVariableInBuilderRef = new QualifiedNameReference(setVariableInBuilder, positions, s, e); @@ -585,12 +598,12 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler<SuperBuilder> { * <pre> * protected B $fillValuesFrom(final C instance) { * super.$fillValuesFrom(instance); - * this.field(instance.field); + * FoobarBuilderImpl.$fillValuesFromInstanceIntoBuilder(instance, this); * return self(); * } * </pre> */ - private MethodDeclaration generateFillValuesMethod(EclipseNode tdParent, boolean inherited, String builderGenericName, String classGenericName, TypeParameter[] typeParams, java.util.List<BuilderFieldData> builderFields, ASTNode source) { + private MethodDeclaration generateFillValuesMethod(EclipseNode tdParent, boolean inherited, String builderGenericName, String classGenericName, String builderImplClassName, TypeParameter[] typeParams, java.util.List<BuilderFieldData> builderFields, ASTNode source) { MethodDeclaration out = new MethodDeclaration(((CompilationUnitDeclaration) tdParent.top().get()).compilationResult); out.selector = FILL_VALUES_METHOD_NAME; out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; @@ -598,9 +611,8 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler<SuperBuilder> { if (inherited) out.annotations = new Annotation[] {makeMarkerAnnotation(TypeConstants.JAVA_LANG_OVERRIDE, tdParent.get())}; out.returnType = new SingleTypeReference(builderGenericName.toCharArray(), 0); - final String instanceVariableName = "instance"; TypeReference builderType = createTypeReferenceWithTypeParameters(classGenericName, typeParams); - out.arguments = new Argument[] {new Argument(instanceVariableName.toCharArray(), 0, builderType, Modifier.FINAL)}; + out.arguments = new Argument[] {new Argument(INSTANCE_VARIABLE_NAME, 0, builderType, Modifier.FINAL)}; List<Statement> body = new ArrayList<Statement>(); @@ -609,16 +621,18 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler<SuperBuilder> { MessageSend callToSuper = new MessageSend(); callToSuper.receiver = new SuperReference(0, 0); callToSuper.selector = FILL_VALUES_METHOD_NAME; - callToSuper.arguments = new Expression[] {new SingleNameReference(instanceVariableName.toCharArray(), 0)}; + callToSuper.arguments = new Expression[] {new SingleNameReference(INSTANCE_VARIABLE_NAME, 0)}; body.add(callToSuper); } - // Call the builder's setter methods to fill the values from the instance. - for (BuilderFieldData bfd : builderFields) { - MessageSend exec = createSetterCallWithInstanceValue(bfd, instanceVariableName, tdParent, source); - body.add(exec); - } + // Call the builder implemention's helper method that actually fills the values from the instance. + MessageSend callStaticFillValuesMethod = new MessageSend(); + callStaticFillValuesMethod.receiver = new SingleNameReference(builderImplClassName.toCharArray(), 0); + callStaticFillValuesMethod.selector = FILL_VALUES_STATIC_METHOD_NAME; + callStaticFillValuesMethod.arguments = new Expression[] {new SingleNameReference(INSTANCE_VARIABLE_NAME, 0), new ThisReference(0, 0)}; + body.add(callStaticFillValuesMethod); + // Return self(). MessageSend returnCall = new MessageSend(); returnCall.receiver = ThisReference.implicitThis(); returnCall.selector = SELF_METHOD_NAME; @@ -629,40 +643,76 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler<SuperBuilder> { return out; } - private MessageSend createSetterCallWithInstanceValue(BuilderFieldData bfd, final String instanceVariableName, EclipseNode type, ASTNode source) { - char[] setterName = bfd.name; - MessageSend ms = new MessageSend(); - Expression[] tgt = new Expression[bfd.singularData == null ? 1 : 2]; - - if (bfd.obtainVia == null || !bfd.obtainVia.field().isEmpty()) { - char[] fieldName = bfd.obtainVia == null ? bfd.rawName : bfd.obtainVia.field().toCharArray(); - for (int i = 0; i < tgt.length; i++) { - FieldReference fr = new FieldReference(fieldName, 0); - fr.receiver = new SingleNameReference(instanceVariableName.toCharArray(), 0); - tgt[i] = fr; - } - } else { - String obtainName = bfd.obtainVia.method(); - boolean obtainIsStatic = bfd.obtainVia.isStatic(); - for (int i = 0; i < tgt.length; i++) { - MessageSend obtainExpr = new MessageSend(); - obtainExpr.receiver = obtainIsStatic ? new SingleNameReference(type.getName().toCharArray(), 0) : new SingleNameReference(instanceVariableName.toCharArray(), 0); - obtainExpr.selector = obtainName.toCharArray(); - if (obtainIsStatic) obtainExpr.arguments = new Expression[] {new SingleNameReference(instanceVariableName.toCharArray(), 0)}; - tgt[i] = obtainExpr; - } + /** + * Generates a <code>$fillValuesFromInstanceIntoBuilder()</code> method in + * the builder implementation class that copies all fields from the instance + * to the builder. It looks like this: + * + * <pre> + * protected B $fillValuesFromInstanceIntoBuilder(Foobar instance, FoobarBuilder<?, ?> b) { + * b.field(instance.field); + * } + * </pre> + */ + private MethodDeclaration generateStaticFillValuesMethod(EclipseNode tdParent, boolean inherited, String builderClassName, String classGenericName, TypeParameter[] typeParams, java.util.List<BuilderFieldData> builderFields, ASTNode source) { + MethodDeclaration out = new MethodDeclaration(((CompilationUnitDeclaration) tdParent.top().get()).compilationResult); + out.selector = FILL_VALUES_STATIC_METHOD_NAME; + out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + out.modifiers = ClassFileConstants.AccPrivate | ClassFileConstants.AccStatic; + out.returnType = TypeReference.baseTypeReference(TypeIds.T_void, 0); + + TypeReference[] wildcards = new TypeReference[] {new Wildcard(Wildcard.UNBOUND), new Wildcard(Wildcard.UNBOUND)}; + TypeReference builderType = new ParameterizedSingleTypeReference(builderClassName.toCharArray(), mergeToTypeReferences(typeParams, wildcards), 0, 0); + Argument builderArgument = new Argument(BUILDER_VARIABLE_NAME, 0, builderType, Modifier.FINAL); + out.arguments = new Argument[] {new Argument(INSTANCE_VARIABLE_NAME, 0, new SingleTypeReference(tdParent.getName().toCharArray(), 0), Modifier.FINAL), builderArgument}; + + List<Statement> body = new ArrayList<Statement>(); + + // Call the builder's setter methods to fill the values from the instance. + for (BuilderFieldData bfd : builderFields) { + MessageSend exec = createSetterCallWithInstanceValue(bfd, tdParent, source); + body.add(exec); + } + + out.statements = body.isEmpty() ? null : body.toArray(new Statement[body.size()]); + + return out; + } + + private MessageSend createSetterCallWithInstanceValue(BuilderFieldData bfd, EclipseNode type, ASTNode source) { + char[] setterName = bfd.name; + MessageSend ms = new MessageSend(); + Expression[] tgt = new Expression[bfd.singularData == null ? 1 : 2]; + + if (bfd.obtainVia == null || !bfd.obtainVia.field().isEmpty()) { + char[] fieldName = bfd.obtainVia == null ? bfd.rawName : bfd.obtainVia.field().toCharArray(); + for (int i = 0; i < tgt.length; i++) { + FieldReference fr = new FieldReference(fieldName, 0); + fr.receiver = new SingleNameReference(INSTANCE_VARIABLE_NAME, 0); + tgt[i] = fr; } - if (bfd.singularData == null) { - ms.arguments = tgt; - } else { - Expression ifNull = new EqualExpression(tgt[0], new NullLiteral(0, 0), OperatorIds.EQUAL_EQUAL); - MessageSend emptyList = new MessageSend(); - emptyList.receiver = generateQualifiedNameRef(source, TypeConstants.JAVA, TypeConstants.UTIL, "Collections".toCharArray()); - emptyList.selector = EMPTY_LIST; - ms.arguments = new Expression[] {new ConditionalExpression(ifNull, emptyList, tgt[1])}; + } else { + String obtainName = bfd.obtainVia.method(); + boolean obtainIsStatic = bfd.obtainVia.isStatic(); + for (int i = 0; i < tgt.length; i++) { + MessageSend obtainExpr = new MessageSend(); + obtainExpr.receiver = obtainIsStatic ? new SingleNameReference(type.getName().toCharArray(), 0) : new SingleNameReference(INSTANCE_VARIABLE_NAME, 0); + obtainExpr.selector = obtainName.toCharArray(); + if (obtainIsStatic) obtainExpr.arguments = new Expression[] {new SingleNameReference(INSTANCE_VARIABLE_NAME, 0)}; + tgt[i] = obtainExpr; } - ms.receiver = ThisReference.implicitThis(); - ms.selector = setterName; + } + if (bfd.singularData == null) { + ms.arguments = tgt; + } else { + Expression ifNull = new EqualExpression(tgt[0], new NullLiteral(0, 0), OperatorIds.EQUAL_EQUAL); + MessageSend emptyList = new MessageSend(); + emptyList.receiver = generateQualifiedNameRef(source, TypeConstants.JAVA, TypeConstants.UTIL, "Collections".toCharArray()); + emptyList.selector = EMPTY_LIST; + ms.arguments = new Expression[] {new ConditionalExpression(ifNull, emptyList, tgt[1])}; + } + ms.receiver = new SingleNameReference(BUILDER_VARIABLE_NAME, 0); + ms.selector = setterName; return ms; } diff --git a/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java b/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java index c5099480..dc0b6e0c 100644 --- a/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java +++ b/test/transform/resource/after-delombok/SuperBuilderBasicToBuilder.java @@ -79,7 +79,7 @@ public class SuperBuilderBasicToBuilder { @java.lang.SuppressWarnings("all")
private static final class ParentBuilderImpl extends ParentBuilder<Parent, ParentBuilderImpl> {
@java.lang.SuppressWarnings("all")
- private static void $fillValuesFromInstanceIntoBuilder(Parent instance, ParentBuilder<?,?> b) {
+ private static void $fillValuesFromInstanceIntoBuilder(final Parent instance, final ParentBuilder<?,?> b) {
b.field1(instance.field1);
b.obtainViaField(instance.field1);
b.obtainViaMethod(instance.method());
@@ -161,7 +161,7 @@ public class SuperBuilderBasicToBuilder { @java.lang.SuppressWarnings("all")
private static final class ChildBuilderImpl extends ChildBuilder<Child, ChildBuilderImpl> {
@java.lang.SuppressWarnings("all")
- private static void $fillValuesFromInstanceIntoBuilder(Parent instance, ParentBuilder<?,?> b) {
+ private static void $fillValuesFromInstanceIntoBuilder(final Parent instance, final ParentBuilder<?,?> b) {
b.field3(instance.field3);
}
@java.lang.SuppressWarnings("all")
diff --git a/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java b/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java index fac9ab55..be819d05 100644 --- a/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java +++ b/test/transform/resource/after-ecj/SuperBuilderBasicToBuilder.java @@ -57,7 +57,7 @@ public class SuperBuilderBasicToBuilder { private ParentBuilderImpl() {
super();
}
- private static @java.lang.SuppressWarnings("all") void $fillValuesFromInstanceIntoBuilder(Parent instance, ParentBuilder<?,?> b) {
+ private static @java.lang.SuppressWarnings("all") void $fillValuesFromInstanceIntoBuilder(final Parent instance, final ParentBuilder<?, ?> b) {
b.field1(instance.field1);
b.obtainViaField(instance.field1);
b.obtainViaMethod(instance.method());
@@ -101,12 +101,12 @@ public class SuperBuilderBasicToBuilder { }
this.items = items;
}
- public static @java.lang.SuppressWarnings("all") ParentBuilder<?, ?> builder() {
- return new ParentBuilderImpl();
- }
public @java.lang.SuppressWarnings("all") ParentBuilder<?, ?> toBuilder() {
return new ParentBuilderImpl().$fillValuesFrom(this);
}
+ public static @java.lang.SuppressWarnings("all") ParentBuilder<?, ?> builder() {
+ return new ParentBuilderImpl();
+ }
}
public static @lombok.experimental.SuperBuilder(toBuilder = true) class Child extends Parent {
public static abstract @java.lang.SuppressWarnings("all") class ChildBuilder<C extends Child, B extends ChildBuilder<C, B>> extends Parent.ParentBuilder<C, B> {
@@ -133,7 +133,7 @@ public class SuperBuilderBasicToBuilder { private ChildBuilderImpl() {
super();
}
- private static @java.lang.SuppressWarnings("all") void $fillValuesFromInstanceIntoBuilder(Child instance, ChildBuilder<?,?> b) {
+ private static @java.lang.SuppressWarnings("all") void $fillValuesFromInstanceIntoBuilder(final Child instance, final ChildBuilder<?, ?> b) {
b.field3(instance.field3);
}
protected @java.lang.Override @java.lang.SuppressWarnings("all") ChildBuilderImpl self() {
@@ -148,12 +148,12 @@ public class SuperBuilderBasicToBuilder { super(b);
this.field3 = b.field3;
}
- public static @java.lang.SuppressWarnings("all") ChildBuilder<?, ?> builder() {
- return new ChildBuilderImpl();
- }
public @java.lang.SuppressWarnings("all") ChildBuilder<?, ?> toBuilder() {
return new ChildBuilderImpl().$fillValuesFrom(this);
}
+ public static @java.lang.SuppressWarnings("all") ChildBuilder<?, ?> builder() {
+ return new ChildBuilderImpl();
+ }
}
public SuperBuilderBasicToBuilder() {
super();
|