DEV Community

riversun
riversun

Posted on • Updated on

"Builder Pattern" generator for Java

Introduction

I want to introduce my web app that can generate "Builder Pattern" source code for Java.
With this tool, you can instantly generate Builder patterns: "Effective Java", "GoF", "Telescoping Constructor", "Lombok", "Mandatory Properties"

https://riversun.github.io/java-builder/

(Made only with HTML / CSS / JS , no server side)

As shown below, if you enter "[Type] [Variable]" in Definition, the generated code will be reflected in real time on the right.

animation.gif

When you have finished entering the code, press Copy to Clipboard to copy the code and paste it into your IDE or editor.

Example of generated code

Definition

Just type like as follows and you can get full source code of Builder pattern.

package com.example
class Person
@Required String name
@Required Integer age
@Required String gender
@Required Integer height
String eyeColor
String hairColor
String hobby

When @NotNull annotation is added to the beginning of the variable definition, it becomes a Required parameter and generates a code to check null when building.

Generated code example "Effective Java"

Person.java

package com.example;

public class Person {

  private String name;
  private Integer age;
  private String gender;
  private Integer height;
  private String eyeColor;
  private String hairColor;
  private String hobby;

  public static class Builder {

    private String name;
    private Integer age;
    private String gender;
    private Integer height;
    private String eyeColor;
    private String hairColor;
    private String hobby;

    public Builder() {    
    }

    Builder(String name, Integer age, String gender, Integer height, String eyeColor, String hairColor, String hobby) {    
      this.name = name; 
      this.age = age; 
      this.gender = gender; 
      this.height = height; 
      this.eyeColor = eyeColor; 
      this.hairColor = hairColor; 
      this.hobby = hobby;             
    }

    public Builder name(String name){
      this.name = name;
      return Builder.this;
    }

    public Builder age(Integer age){
      this.age = age;
      return Builder.this;
    }

    public Builder gender(String gender){
      this.gender = gender;
      return Builder.this;
    }

    public Builder height(Integer height){
      this.height = height;
      return Builder.this;
    }

    public Builder eyeColor(String eyeColor){
      this.eyeColor = eyeColor;
      return Builder.this;
    }

    public Builder hairColor(String hairColor){
      this.hairColor = hairColor;
      return Builder.this;
    }

    public Builder hobby(String hobby){
      this.hobby = hobby;
      return Builder.this;
    }

    public Person build() {
        if(this.name == null){
          throw new NullPointerException("The property \"name\" is null. "
              + "Please set the value by \"name()\". "
              + "The properties \"name\", \"age\", \"gender\" and \"height\" are required.");
        }
        if(this.age == null){
          throw new NullPointerException("The property \"age\" is null. "
              + "Please set the value by \"age()\". "
              + "The properties \"name\", \"age\", \"gender\" and \"height\" are required.");
        }
        if(this.gender == null){
          throw new NullPointerException("The property \"gender\" is null. "
              + "Please set the value by \"gender()\". "
              + "The properties \"name\", \"age\", \"gender\" and \"height\" are required.");
        }
        if(this.height == null){
          throw new NullPointerException("The property \"height\" is null. "
              + "Please set the value by \"height()\". "
              + "The properties \"name\", \"age\", \"gender\" and \"height\" are required.");
        }

        return new Person(this);
    }
  }

  private Person(Builder builder) {
    this.name = builder.name; 
    this.age = builder.age; 
    this.gender = builder.gender; 
    this.height = builder.height; 
    this.eyeColor = builder.eyeColor; 
    this.hairColor = builder.hairColor; 
    this.hobby = builder.hobby;     
  }

  public void doSomething() {
      // do something
  }
}

You you want to use this builder cod,
just write like this.

AppMain.java

public static void main(String[] args) {
        new Person.Builder().name("riversun").age(12).gender("male").height(200).build().doSomething();

    }

Generated code example of "GoF"

Person.java

package org.example;
import java.util.ArrayList;
import java.util.List;

public class Person {

  private String name;
  private int age;
  private String sex;
  private List<String> hobby = new ArrayList<String>();

  public Person() {

  }

  public Person(String name, int age, String sex, List<String> hobby) {
    this.name = name; 
    this.age = age; 
    this.sex = sex; 
    this.hobby = hobby; 
  }

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

  public String getName(){
    return this.name;
  }

  public Person setAge(int age){
    this.age = age;
    return Person.this;
  }

  public int getAge(){
    return this.age;
  }

  public Person setSex(String sex){
    this.sex = sex;
    return Person.this;
  }

  public String getSex(){
    return this.sex;
  }

  public Person setHobby(List<String> hobby){
    this.hobby = hobby;
    return Person.this;
  }

  public Person addHobby(String hobby){
    this.hobby.add(hobby);
    return Person.this;
  }

  public List<String> getHobby(){
    return this.hobby;
  }

  public void doSomething(){
    System.out.println("Person's properties");
    System.out.println("name="+name);
    System.out.println("age="+age);
    System.out.println("sex="+sex);
    System.out.println("hobby="+hobby);  
  }
}

Builder.java

package org.example;
import java.util.List;

public interface Builder {

  public void name(String name);
  public void age(int age);
  public void sex(String sex);
  public void hobby(List<String> hobby);

  Person getResult();
}

PersonBuilder.java

package org.example;
import java.util.List;

public class PersonBuilder implements Builder {
  private Person person;

  public PersonBuilder() {
    this.person = new Person();
  }

  @Override
  public void name(String name) {
    this.person.setName(name);
  }

  @Override
  public void age(int age) {
    this.person.setAge(age);
  }

  @Override
  public void sex(String sex) {
    this.person.setSex(sex);
  }

  @Override
  public void hobby(List<String> hobby) {
    this.person.setHobby(hobby);
  }


  @Override
  public Person getResult() {

    if(this.person.getName() == null){
      throw new NullPointerException("The property \"name\" is null. "
          + "Please set the value by \"builder.name()\" at Director class. "
          + "The property \"name\" is required.");
    }

    return this.person;
  }
}

Director.java

package org.example;

public class Director {

  private Builder builder;

  public Director(Builder builder) {
    this.builder = builder;
  }

  public void construct() {
    builder.name("something"); // required property
    builder.age(0); // optional property
    builder.sex("something"); // optional property
    //builder.hobby(new ArrayList<>());
  }
}

AppMain.java

package org.example;

public class AppMain {

  public static void main(String[] args) {

    Builder builder = new PersonBuilder();
    Director director = new Director(builder);
    director.construct();
    Person person = builder.getResult();
    person.doSomething();

  }
}

Generated source code of "Telescoping Constructor"

Person.java

package org.example;
import java.util.ArrayList;
import java.util.List;

public class Person {

  private String name;
  private int age;
  private String sex;
  private List<String> hobby = new ArrayList<String>();

  public Person() {

  }

  public Person(String name, int age, String sex, List<String> hobby) {
    this.name = name; 
    this.age = age; 
    this.sex = sex; 
    this.hobby = hobby; 
  }

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

  public String getName() {
    return this.name;
  }

  public Person setAge(int age) {
    this.age = age;
    return Person.this;
  }

  public int getAge() {
    return this.age;
  }

  public Person setSex(String sex) {
    this.sex = sex;
    return Person.this;
  }

  public String getSex() {
    return this.sex;
  }

  public Person setHobby(List<String> hobby) {
    this.hobby = hobby;
    return Person.this;
  }

  public Person addHobby(String hobby){
    this.hobby.add(hobby);
    return Person.this;
  }

  public List<String> getHobby() {
    return this.hobby;
  }

}

Generated source code of "Lombok"

Person.java

package org.example;
import java.util.ArrayList;
import java.util.List;

public class Person {

  private String name;
  private int age;
  private String sex;
  private List<String> hobby;

  Person(String name, int age, String sex, List<String> hobby) {

    if(name == null){
      throw new NullPointerException("The property \"name\" is null. "
          + "Please set the value by \"name()\". "
          + "The property \"name\" is required.");
    }

    this.name = name; 
    this.age = age; 
    this.sex = sex; 
    this.hobby = hobby; 
  }

  public static PersonBuilder builder(){
    return new PersonBuilder();
  }  
  public static class PersonBuilder {

    private String name;
    private int age;
    private String sex;
    private List<String> hobby = new ArrayList<String>();

    PersonBuilder() {    
    }

    public PersonBuilder name(String name){
      this.name = name;
      return PersonBuilder.this;
    }

    public PersonBuilder age(int age){
      this.age = age;
      return PersonBuilder.this;
    }

    public PersonBuilder sex(String sex){
      this.sex = sex;
      return PersonBuilder.this;
    }

    public PersonBuilder hobby(List<String> hobby){
      this.hobby = hobby;
      return PersonBuilder.this;
    }

    public PersonBuilder addHobby(String hobby){
      this.hobby.add(hobby);
      return PersonBuilder.this;
    }

    public Person build() {
      return new Person(this.name, this.age, this.sex, this.hobby);
    }
    @Override
    public String toString() {
      return "Person.PersonBuilder(name=" + this.name + ", age=" + this.age + ", sex=" + this.sex + ", hobby=" + this.hobby + ")";
    }
  }

  @Override
  public String toString() {
    return "Person(name=" + this.name + ", age=" + this.age + ", sex=" + this.sex + ", hobby=" + this.hobby + ")";
  }

  public void doSomething() {
      // do something
  }
}

You you want to use this builder cod,
just write like this.

AppMain.java

public class AppMain {
    public static void main(String[] args) {
        System.out.println(Person.builder().name("Tom").age(12).sex("male").build());
    }
}

Generated source code of "Mandatory Properties"

It is a Builder pattern that can set mandatory properties in the correct order.

Person.java

package com.example;
import java.util.Optional;

public class Person {

  private final String name;// Required
  private final Integer age;// Required
  private final String gender;// Required
  private final Integer height;// Required
  private final Optional<String> eyeColor;// Optional(use Optional<String>)
  private final Optional<String> hairColor;// Optional(use Optional<String>)
  private final Optional<String> hobby;// Optional(use Optional<String>)

  Person(Builder.Builder1 builder) {
    this.name = builder.name; 
    this.age = builder.age; 
    this.gender = builder.gender; 
    this.height = builder.height; 
    this.eyeColor = builder.eyeColor; 
    this.hairColor = builder.hairColor; 
    this.hobby = builder.hobby; 
  }
  public static Builder builder() {
    return new Builder();
  }

  public static final class Builder {
    public Builder1 name(String name) {
      return new Builder1(name);
    }
    public static final class Builder1 {
      final String name;
      Integer age;
      String gender;
      Integer height;
      Optional<String> eyeColor;
      Optional<String> hairColor;
      Optional<String> hobby;

      private Builder1(String name) {
        this.name = name;
      }
      public Builder2 age(Integer age) {
        this.age = age;
        return new Builder2(Builder1.this);
      }
    }
    public static final class Builder2 {
      final Builder1 builder;

      private Builder2(Builder1 builder) {
        this.builder = builder;
      }
      public Builder3 gender(String gender) {
        this.builder.gender = gender;
        return new Builder3(this.builder);
      }
    }
    public static final class Builder3 {
      final Builder1 builder;

      private Builder3(Builder1 builder) {
        this.builder = builder;
      }
      public Builder4 height(Integer height) {
        this.builder.height = height;
        return new Builder4(this.builder);
      }
    }
    public static final class Builder4 {
      final Builder1 builder;

      private Builder4(Builder1 builder) {
        this.builder = builder;
      }
      public Builder4 eyeColor(String eyeColor){
        this.builder.eyeColor = Optional.of(eyeColor);
        return this;
      }
      public Builder4 hairColor(String hairColor){
        this.builder.hairColor = Optional.of(hairColor);
        return this;
      }
      public Builder4 hobby(String hobby){
        this.builder.hobby = Optional.of(hobby);
        return this;
      }
      public Person build() {
        return new Person(this.builder);
      }
    }
  }

  public String name() {
    return this.name;
  }
  public Integer age() {
    return this.age;
  }
  public String gender() {
    return this.gender;
  }
  public Integer height() {
    return this.height;
  }
  public Optional<String> eyeColor() {
    return this.eyeColor;
  }
  public Optional<String> hairColor() {
    return this.hairColor;
  }
  public Optional<String> hobby() {
    return this.hobby;
  }

  @Override
  public String toString() {
    return "Person(name=" + this.name + ", age=" + this.age + ", gender=" + this.gender + ", height=" + this.height + ", eyeColor=" + this.eyeColor + ", hairColor=" + this.hairColor + ", hobby=" + this.hobby + ")";
  }

  public void doSomething() {
      // do something
  }
}

Generator Tips

The following ideas have been put in place with the aim of typical code output where the generated code can be used as is as possible without setting the details.

Effective Java Template

  • For type of List<> like as List<String> hobby;, generate code initialized with new ArrayList();
  • Also, in the case of List, generate a method like addHobby(String hobby) so that items can be added individually.

GoF Template

  • GoF's Builder pattern is more complicated than other patterns in construction, generate main class named AppMain.java.
  • Added temporary code to construct() method of Director.java.
public void construct() {
    builder.name("something");
    builder.age(0);
    builder.sex("something");
    //builder.hobby(new ArrayList<>());
  }

I hope it helps.

Top comments (6)

Collapse
 
michelemauro profile image
michelemauro • Edited

Interesting tool. It's a pain to write these things by hand.

However, a note (or you may call it a feature request): with these templates, there is no check done by the builder that all or a mandatory subset of parameters have been given a value. You could use Optional<> to encode arguments that may not be necessary to construct the object; after all, one of the use cases of the Builder pattern is managing construction-time validation of the constructor arguments.

Another idea: there is at least another implementation style for the pattern; the one in which each step is encoded in a Class (or better, an Interface) so that the user must follow a precise call sequence in building the object. I.e:


class Point { 
  final int x, y; 
  private Point(int x, int y) {
    this.x = x; this.y = y;
  }

  static class PointY {
    final int x;
    PointY(int x) {
      this.x = x;
    }
    Point py(int y) {
      return new Point(x, y);
    }
  }

  static PointY px(int x) { 
    return new PointY(x); 
  } 
};

The Point class can only be instantiated in this way:

Point pt= Point.px(42).py(20);

This style may be useful when you want to thightly constrain how an object is created.

Collapse
 
riversun profile image
riversun

Thanks for your helpful comments.

I added @NotNull annotation to specify required properties and not required ones.
They are used for null checks in build methods.
What do you think?
The tool works with @Required and @NonNull as aliases for @NotNull as well.

Your "Another idea" is also interesting as a design that forces the coder to set the required parameters in order.

Best.

Collapse
 
moaxcp profile image
John Mercier

I like the idea of using Optional but I thought it caused problems when declaring a field as Optional. Was this something in Effective Java? I'm not so sure it is an issue but it is possible to wrap the object in the getter instead.

Collapse
 
riversun profile image
riversun

I supported the source code generation of the Builder pattern that can forcibly set the mandatory property in the correct order.

Here is an example code auto generated with my tool.(select Mandatory Properties)

package com.example;
import java.util.Optional;

public class Person {

  private final String name;// Required
  private final Integer age;// Required
  private final String gender;// Required
  private final Integer height;// Required
  private final Optional<String> eyeColor;// Optional(use Optional<String>)
  private final Optional<String> hairColor;// Optional(use Optional<String>)
  private final Optional<String> hobby;// Optional(use Optional<String>)

  Person(Builder.Builder1 builder) {
    this.name = builder.name; 
    this.age = builder.age; 
    this.gender = builder.gender; 
    this.height = builder.height; 
    this.eyeColor = builder.eyeColor; 
    this.hairColor = builder.hairColor; 
    this.hobby = builder.hobby; 
  }
  public static Builder builder() {
    return new Builder();
  }

  public static final class Builder {
    public Builder1 name(String name) {
      return new Builder1(name);
    }
    public static final class Builder1 {
      final String name;
      Integer age;
      String gender;
      Integer height;
      Optional<String> eyeColor;
      Optional<String> hairColor;
      Optional<String> hobby;

      private Builder1(String name) {
        this.name = name;
      }
      public Builder2 age(Integer age) {
        this.age = age;
        return new Builder2(Builder1.this);
      }
    }
    public static final class Builder2 {
      final Builder1 builder;

      private Builder2(Builder1 builder) {
        this.builder = builder;
      }
      public Builder3 gender(String gender) {
        this.builder.gender = gender;
        return new Builder3(this.builder);
      }
    }
    public static final class Builder3 {
      final Builder1 builder;

      private Builder3(Builder1 builder) {
        this.builder = builder;
      }
      public Builder4 height(Integer height) {
        this.builder.height = height;
        return new Builder4(this.builder);
      }
    }
    public static final class Builder4 {
      final Builder1 builder;

      private Builder4(Builder1 builder) {
        this.builder = builder;
      }
      public Builder4 eyeColor(String eyeColor){
        this.builder.eyeColor = Optional.of(eyeColor);
        return this;
      }
      public Builder4 hairColor(String hairColor){
        this.builder.hairColor = Optional.of(hairColor);
        return this;
      }
      public Builder4 hobby(String hobby){
        this.builder.hobby = Optional.of(hobby);
        return this;
      }
      public Person build() {
        return new Person(this.builder);
      }
    }
  }

  public String name() {
    return this.name;
  }
  public Integer age() {
    return this.age;
  }
  public String gender() {
    return this.gender;
  }
  public Integer height() {
    return this.height;
  }
  public Optional<String> eyeColor() {
    return this.eyeColor;
  }
  public Optional<String> hairColor() {
    return this.hairColor;
  }
  public Optional<String> hobby() {
    return this.hobby;
  }

  @Override
  public String toString() {
    return "Person(name=" + this.name + ", age=" + this.age + ", gender=" + this.gender + ", height=" + this.height + ", eyeColor=" + this.eyeColor + ", hairColor=" + this.hairColor + ", hobby=" + this.hobby + ")";
  }

  public void doSomething() {
      // do something
  }
}
Collapse
 
olivierjaquemet profile image
Olivier Jaquemet

Thanks for providing this nice tool @riversun

One bug totally unrelated to the builder generation itself : the canonical URL used by riversun.github.io/java-builder/ is wrong and links to getbootstrap site.
PS : I could not find your github repo for this project to fill an issue/PR

Collapse
 
riversun profile image
riversun

builder_man.gif