org.pitest.quickbuilder:quickbuilder

Generates implementations of the Builder Pattern at runtime.

License

License

GroupId

GroupId

org.pitest.quickbuilder
ArtifactId

ArtifactId

quickbuilder
Last Version

Last Version

1.2
Release Date

Release Date

Type

Type

jar
Description

Description

Generates implementations of the Builder Pattern at runtime.
Project URL

Project URL

http://pitest.org
Source Code Management

Source Code Management

https://github.com/hcoles/QuickBuilder

Download quickbuilder

How to add to project

<!-- https://jarcasting.com/artifacts/org.pitest.quickbuilder/quickbuilder/ -->
<dependency>
    <groupId>org.pitest.quickbuilder</groupId>
    <artifactId>quickbuilder</artifactId>
    <version>1.2</version>
</dependency>
// https://jarcasting.com/artifacts/org.pitest.quickbuilder/quickbuilder/
implementation 'org.pitest.quickbuilder:quickbuilder:1.2'
// https://jarcasting.com/artifacts/org.pitest.quickbuilder/quickbuilder/
implementation ("org.pitest.quickbuilder:quickbuilder:1.2")
'org.pitest.quickbuilder:quickbuilder:jar:1.2'
<dependency org="org.pitest.quickbuilder" name="quickbuilder" rev="1.2">
  <artifact name="quickbuilder" type="jar" />
</dependency>
@Grapes(
@Grab(group='org.pitest.quickbuilder', module='quickbuilder', version='1.2')
)
libraryDependencies += "org.pitest.quickbuilder" % "quickbuilder" % "1.2"
[org.pitest.quickbuilder/quickbuilder "1.2"]

Dependencies

test (2)

Group / Artifact Type Version
junit : junit jar 4.11
org.assertj : assertj-core jar 1.5.0

Project Modules

There are no modules declared in this project.

Build Status

QuickBuilder

Builders without boiler plate.

Rationale

The builder pattern helps keep tests readable and maintainable but requires tedious boilerplate. QuickBuilder lets you spend more time on your real code, and less writing boilerplate, by generating fully featured builders on the fly.

You supply an interface, QuickBuilder supplies an implementation.

QuickBuilder can create builders for pretty much any class - it cleanly handles immutable types and other classes not following the bean convention without resorting to dirty tricks like setting fields via reflection. Only the public interface of your class is used.

QuickBuilder is not a code generator. Builders are generated at runtime, there's no autogenerated code for you to maintain.

Download

It's on maven central

<dependency>
    <groupId>org.pitest.quickbuilder</groupId>
    <artifactId>quickbuilder</artifactId>
    <version>1.2</version>
</dependency>

60 second quickstart

For any bean e.g.

public class Person {
    private String name;
    private int age;
    private Person partner;

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

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

    public void setPartner(Person partner) {
        this.partner = partner;
    }

    // etc
}

Create a builder by declaring an interface

interface PersonBuilder extends Builder<Person> {
  PersonBuilder withName(String name);
  PersonBuilder withAge(int age);
  PersonBuilder withPartner(Builder<Person> partner);
  PersonBuilder withFriend(Person partner); 
}

Get an instance by calling QB.builder, then use it like any other builder

    Person p = QB.builder(PersonBuilder.class).withName("Bob").withAge(42).build();

For classes without a public no-args constructor you can supply a "seed" function that creates the instance.

 class Seed implements Generator<PersonBuilder,Person> {
    @Override
    public Person generate(PersonBuilder builder) {
      return Person.create();
    }
 }

 // pass an instance of the seed function to QuickBuilder
 PersonBuilder pb = QB.builder(PersonBuilder.class, new Seed());

For classes that don't provide setter methods (e.g immutable classes) you can tell QuickBuilder that you will take responsibility for a property by declaring an underscore method in your builder.

interface PersonBuilder extends Builder<Person> {
  PersonBuilder withName(String name);
  PersonBuilder withAge(int age);
  PersonBuilder withPartner(PersonBuilder partner);

  // underscore method telling QuickBuilder not to handle this property
  String _Name();
}

The underscore method can then be used in your seed function to route the value to the right place.

 class Seed implements Generator<Person, PersonBuilder> {
    @Override
    public Person generate(PersonBuilder builder) {
      return Person.create(builder._Name());
    }
 }

You can of course choose to supply a seed function even when QuickBuilder does not require it and thereby move some some errors to being compile time errors rather than runtime.

When a seed function is used to set all properties the only types of runtime error that can occur are if :-

  • The name of an underscore method does not match with a property declared on the builder
  • The type of an underscore method does not match the type of the corresponding property

Note that these are mistakes within the builder interface definition - these probelms cannot be introduced by making changes to the built class.

2 minute overview of features

In addition to being much easier to write, the builders that QuickBuilder creates are probably better behaved and more richly featured than the ones you might have been building by hand.

Fully immutable

QuickBuilder creates immutable builders - their internal state is never updated, instead they return updated shallow copies of themselves.

Builders may therefore be reused without unexpected side effects

interface PersonBuilder extends Builder<Person> {
  // ... etc
}

PersonBuilder youngPerson = QB.builder(PersonBuilder.class).withAge(18);

Person rita = youngPerson.withName("Rita").withPartner(bob).build();
Person sue = youngPerson.withName("Sue").build();

// Rita and Sue do not share Bob

Respects default values

If you don't supply a value for a property QuickBuilder will not try to overwrite it.

Given the bean

public class Person {

    private int age;

    public Person() {
      age = 18; // set a default
    }

    // etc
}

Common hand rolled builder implementations would overwrite the default age with primitive int default of 0 when no value was supplied, but calling

   Person p = QB.builder(PersonBuilder).withName("Tom").build();

Will return a person called Tom with the default age of 18.

Sequences

Occasionally you may want to create a series of objects that differ from each other on one or more property. Allthough you could create these by calling with methods generating objects from a sequence may result in more readable code.

QuickBuilder currently provides one simple sequence implementation that takes it's values from a list that you supply.

import static org.pitest.quickbuilder.common.ElementSequence.from;
import static java.util.Arrays.asList;

interface PersonBuilder extends SequenceBuilder<Person> {
  // ... etc
}


PersonBuilder person = QB.builder(PersonBuilder.class)
                            .withName(from(asList("Paul", "Barry", "Sarah")))
                            .withAge(from(asList(20,29,42)))

Iterator<Person> people = person.iterator(); 

people.next(); // a person named Paul with age 20
people.next(); // a person named Barry with age 29
people.next(); // a person named Sarah with age 42

person.buildAll() // a list with Paul(20), Barry(29) and Sarah(42)
person.build(2) // a list with Paul(20) and Barry(29)

Notice that the SequenceBuilders are also immutable.

Features

  • Automatically creates builders for beans - you just supply an interface
  • Creates builders for non-beans (e.g. immutable classes) by defining interface and small seeder function
  • Builder methods may take normal types or other builders as parameters
  • Supports any lowercase prefix for property methods eg "withName", "andName", "usingName"
  • Builders are immutable and thread safe

Design principles

  1. Well behaved - only public interface of built type used
  2. Non invasive - no changes or annotations needed on built type
  3. Fail as early as possible - error on type creation or compile if possible

TODO

  • Separate interface for underscore methods
  • Collect list entries individually
  • Auto generation for classes with single factory method
  • Auto generation for classes with constructor without repeated types

Alternatives

Other approaches you might want to consider

Releases

1.2

  • SequenceBuilder implement Iterable
  • Report type mismatch of Builder parameter with methods
  • Composing operations repeat, once, limit, theSame
  • Convience builders integersFrom, nullValue
  • Converting builders

1.1

  • Builders immutable when generating sequences

1.0

Initial release

Versions

Version
1.2
1.1
1.0
0.0.2
0.0.1