Simple Builder Example

An annotation to mark classes
package com.derivandi.builder;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@interface BuilderPattern {}
A Processor creating a simple Builder companion object
package com.derivandi.builder;

import com.derivandi.api.D;
import com.derivandi.api.dsl.class_.ClassBodyStep;
import com.derivandi.api.processor.Processor;
import com.derivandi.api.processor.ProcessorBuilder;
import com.derivandi.api.processor.ProcessorConfiguration;
import com.derivandi.api.processor.SimpleContext;

import java.util.ArrayList;
import java.util.List;

import static com.derivandi.api.dsl.JavaDsl.*;
import static java.lang.String.join;
import static org.apache.commons.lang3.StringUtils.capitalize;
import static org.apache.commons.lang3.StringUtils.uncapitalize;

/// Builds a companion Builder class for each annotated class
public class DerivandiBuilderProcessor
      extends Processor
{
   private static final String BUILDER_PATTERN = "com.derivandi.builder.BuilderPattern";

   @Override
   public ProcessorConfiguration buildProcessor(ProcessorBuilder processorBuilder)
   {
      return processorBuilder.process(DerivandiBuilderProcessor::process);
   }

   private static void process(SimpleContext context)
   {
      // iterate over every class annotated with the BuilderPattern annotation
      for (D.Class toBuild : context.getClassesAnnotatedWith(BUILDER_PATTERN))
      {
         // create the Builder Class
         ClassBodyStep step = class_().package_(toBuild.getPackage().getQualifiedName())
                                      .name(toBuild.getName() + "DerivandiBuilder");

         String toBuildVariableName = uncapitalize(toBuild.getName());
         List<String> setterInvocations = new ArrayList<>();

         // loop over all needed properties
         for (D.Property property : toBuild.getProperties()
                                           .stream()
                                           .filter(D.Property::isMutable)
                                           .toList())
         {
            String propertyName = property.getName();

            // render the existing field if possible, otherwise create a new one
            step = property.getField().map(step::field)
                           .orElse(step.field(field().private_()
                                                     .type(property.getType())
                                                     .name(propertyName)));
            // render the wither
            step = step.method(method().public_()
                                       .resultType(step)//use the builder type
                                       .name("with" + capitalize(propertyName))
                                       .parameter(parameter(property.getType(), propertyName))
                                       .body("""
                                             this.%1$s = %1$s;
                                             return this;""".formatted(propertyName)));

            // collect all setter invocations for the object being build
            setterInvocations.add(toBuildVariableName + "." +
                                  property.getSetterOrThrow().getName() +
                                  "(" + propertyName + ");");
         }

         // render the build method
         step = step.method(method().public_().resultType(toBuild).name("build")
                                    .body("""
                                         %1$s %2$s = new %1$s();
                                         %3$s
                                         return %2$s;
                                         """.formatted(toBuild.getName(),
                                                       toBuildVariableName,
                                                       join("\n\n", setterInvocations))));

         //writes the builder
         context.writeAndCompileSourceFile(step);
      }
   }
}
For a simple pojo like
package com.derivandi.builder;

import java.util.Objects;

@BuilderPattern
public class Customer
{
   private String name;

   public String getName()
   {
      return name;
   }

   public void setName(String name)
   {
      this.name = name;
   }

   @Override
   public boolean equals(Object o)
   {
      return this == o ||
             o instanceof Customer customer &&
             Objects.equals(getName(), customer.getName());
   }

   @Override
   public int hashCode()
   {
      return Objects.hash(getName());
   }

   @Override
   public String toString()
   {
      return "Customer{" +
             "name='" + name + '\'' +
             '}';
   }
}
This builder would be generated
package com.derivandi.builder;

import builder.com.derivandi.Customer;

class CustomerDerivandiBuilder {
   private String name;
   public CustomerDerivandiBuilder withName(String name) {
      this.name = name;
      return this;
   }
   public Customer build() {
      Customer customer = new Customer();
      customer.setName(name);
      return customer;
   }

}