Skip to content

Commit

Permalink
First version of Builder.toBuilder.
Browse files Browse the repository at this point in the history
  • Loading branch information
skinny85 committed Mar 16, 2024
1 parent 48c06b2 commit e5feb39
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 0 deletions.
5 changes: 5 additions & 0 deletions src/main/java/org/jilt/Builder.java
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,11 @@
*/
String buildMethod() default "";

/**
* ToDo add a description here. But also change it to a String
*/
boolean toBuilder() default false;

/**
* Annotation that ignores the given field of a class when generating a Builder for that class.
* Used when {@link Builder} is placed on the class being built itself.
Expand Down
63 changes: 63 additions & 0 deletions src/main/java/org/jilt/internal/AbstractBuilderGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
Expand All @@ -17,6 +18,8 @@

import javax.annotation.processing.Filer;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
Expand Down Expand Up @@ -80,6 +83,9 @@ public final void generateBuilderClass() throws Exception {
.addStatement("return new $T()", this.builderClassTypeName())
.build());

// add a static toBuilder() method to the builder class
this.addToBuilderMethod(builderClassBuilder);

for (VariableElement attribute : attributes) {
String fieldName = attributeSimpleName(attribute);
TypeName fieldType = TypeName.get(attribute.asType());
Expand Down Expand Up @@ -121,6 +127,63 @@ public final void generateBuilderClass() throws Exception {
javaFile.writeTo(filer);
}

private void addToBuilderMethod(TypeSpec.Builder builderClassBuilder) {
if (!this.builderAnnotation.toBuilder()) {
return;
}
String targetClassParam = Utils.deCapitalize(this.targetClassSimpleName().toString());
MethodSpec.Builder toBuilderMethod = MethodSpec
.methodBuilder("toBuilder")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addTypeVariables(this.builderClassTypeParameters())
.returns(this.builderClassTypeName())
.addParameter(ParameterSpec
.builder(this.targetClassTypeName(), targetClassParam)
.build());

CodeBlock.Builder buildStatement = CodeBlock.builder();
buildStatement.add("return new $T()", this.builderClassTypeName());
// iterate through all attributes, and add them to the build statement
for (VariableElement attribute : attributes) {
String attributeAccess = this.accessAttributeOfTargetClass(attribute);
buildStatement.add("\n");
buildStatement.indent();
buildStatement.add(".$L($L.$L)",
this.setterMethodName(attribute),
targetClassParam, attributeAccess);
buildStatement.unindent();
}
buildStatement.add(";\n");

builderClassBuilder.addMethod(toBuilderMethod
.addCode(buildStatement.build())
.build());
}

private String accessAttributeOfTargetClass(VariableElement attribute) {
String fieldName = this.attributeSimpleName(attribute);
String getterName = "get" + Utils.capitalize(fieldName);
for (Element member : this.elements.getAllMembers(this.targetClassType)) {
// if there's a getter method, use it
if (elementIsMethodWithoutArgumentsCalled(member, getterName)) {
return member.getSimpleName().toString() + "()";
}
// if there's a no-argument method with the field name,
// like done in Records, use that
if (elementIsMethodWithoutArgumentsCalled(member, fieldName)) {
return member.getSimpleName().toString() + "()";
}
}
// if we haven't found a sensible method, fall back to the field name
return fieldName;
}

private static boolean elementIsMethodWithoutArgumentsCalled(Element element, String methodName) {
return element.getKind() == ElementKind.METHOD &&
element.getSimpleName().toString().equals(methodName) &&
((ExecutableElement) element).getParameters().isEmpty();
}

private List<String> attributeNames() {
List<String> ret = new ArrayList<String>(attributes.size());
for (VariableElement attribute : attributes) {
Expand Down
20 changes: 20 additions & 0 deletions src/test/java/org/jilt/test/ToBuilderTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.jilt.test;

import org.jilt.test.data.tobuilder.ToBuilderValue;
import org.jilt.test.data.tobuilder.ToBuilderValueBuilder;
import org.junit.Test;

import java.util.Collections;

import static org.assertj.core.api.Assertions.assertThat;

public class ToBuilderTest {
@Test
public void test_to_builder() {
ToBuilderValue value = new ToBuilderValue(1, Collections.singletonList("A"), 'a');
ToBuilderValue builder = ToBuilderValueBuilder.toBuilder(value)
.build();

assertThat(value).isEqualTo(builder);
}
}
42 changes: 42 additions & 0 deletions src/test/java/org/jilt/test/data/tobuilder/ToBuilderValue.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.jilt.test.data.tobuilder;

import org.jilt.Builder;

import java.util.List;

@Builder(toBuilder = true)
public class ToBuilderValue {
private final int getterAttr;
private final List<String> methodAttr;
final char fieldAttr;

public ToBuilderValue(int getterAttr, List<String> methodAttr, char fieldAttr) {
this.getterAttr = getterAttr;
this.methodAttr = methodAttr;
this.fieldAttr = fieldAttr;
}

public int getGetterAttr() {
return getterAttr;
}

public List<String> methodAttr() {
return methodAttr;
}

@Override
public boolean equals(Object obj) {
if (!(obj instanceof ToBuilderValue)) {
return false;
}
ToBuilderValue that = (ToBuilderValue) obj;
return this.getterAttr == that.getterAttr &&
this.methodAttr.equals(that.methodAttr) &&
this.fieldAttr == that.fieldAttr;
}

@Override
public int hashCode() {
return this.getterAttr + 17 * this.methodAttr.hashCode() + 31 * this.fieldAttr;
}
}

0 comments on commit e5feb39

Please sign in to comment.