Immutables: Generate Builder with static compile-time safe checking of mandatory attributes

Created on 6 Sep 2016  Â·  7Comments  Â·  Source: immutables/immutables

Have you considered generating Builders which allow some nullable attributes yet enforce the presence of all other mandatory (required) attributes at compile time, instead of by exception at runtime by validation on build() ?

See e.g. my Person.java for a (hand-written, example) of the pattern I mean. -- According to @eamonnmcmanus apparently it may even be possible to use Java generics to lift the requirement that properties need to be set in a particular order, with the generics keep track of which properties have been set (I do not know how to do this).

FTR: I've originally proposed the same idea to google/auto as well, but it's less of a fit there because with AutoValue the user still has to write the public facing API as it never generate APIs, only implement APIs that the user has written.

enhancement question

Most helpful comment

@elucash :+1: - thx

All 7 comments

Hi @vorburger, thank you for proposal! You may find interesting to read previous discussions on this: https://groups.google.com/forum/#!topic/immutables/4VqoSvB0IRQ
The trade of seems to be a lot of generated classes/interfaces for large amount of attributes, for small number, the advantage of using builder rather than constructor is questionable. There was related ideas to generate lots of interfaces (needed for compile-safety) and remove redundant ones (rewriting bytecode) as post compile optimization.

IMO this would be a better style of implementation (as referenced on https://github.com/immutables/immutables/issues/450)

http://blog.crisp.se/2013/10/09/perlundholm/another-builder-pattern-for-java

@elucash one of the comments from @androidfred provided link mention an even older block post Step Builder pattern with the same idea. Currently with @Value.Style(strictBuilder = true) a strict builder is generated. What do you think about @Value.Style(stepBuilder = true) to generate such a step builder?
Also @svlada has a blog post on this topic of step builder pattern http://www.svlada.com/step-builder-pattern.

So far, I haven't found a plug-in for Eclipse that provides Step Builder code generation feature.

I have created a github repository with the intention of creating an Eclipse plug-in that will provide
support for Step Builder pattern generation: https://github.com/svlada/alyx

IMO a plugin isn't the right tool :wink:

@agebhar1 @androidfred @vorburger org.immutables:value:2.3.3 @Value.Style(stagedBuilder = true)

@elucash :+1: - thx

@elucash Can't thank you enough! Works exactly as desired as far as I can tell!

import org.immutables.value.Value;
import java.util.Optional;

@Value.Immutable
@Value.Style(strictBuilder = true)
interface Person {
    String name();
    String title();
    Optional<String> department();
}
import org.immutables.value.Value;
import java.util.Optional;

@Value.Immutable
@Value.Style(stagedBuilder = true)
interface Individual {
    String name();
    String title();
    Optional<String> department();
}
import org.junit.Assert;
import org.junit.Test;
import java.util.Optional;

public class PersonAndIndividualTests {

    @Test(expected = IllegalStateException.class)
    public void personBuildCalledWithoutRequiredArguments() throws Exception {
     ImmutablePerson.builder().build();
    }

    @Test(expected = NullPointerException.class)
    public void personBuildCalledWithRequiredArgumentsNull() throws Exception {
        ImmutablePerson.builder().name(null).title(null).build();
    }

//    @Test
//    public void personBuildCalledWithNonRequiredArgumentsNull() throws Exception {
//        ImmutablePerson.builder().name("John").title("Dev").department(null).build(); doesn't compile
//    }

    @Test
    public void personBuildCalledWithoutNonrequiredArguments() throws Exception {
        ImmutablePerson john = ImmutablePerson.builder().name("John").title("Dev").build();
        Assert.assertEquals("John", john.name());
        Assert.assertEquals("Dev", john.title());
        Assert.assertEquals(Optional.empty(), john.department());
    }

    @Test
    public void personBuildCalledWithAllArguments() throws Exception {
        ImmutablePerson john = ImmutablePerson.builder().name("John").title("Dev").department("IT").build();
        Assert.assertEquals("John", john.name());
        Assert.assertEquals("Dev", john.title());
        Assert.assertEquals(Optional.of("IT"), john.department());
    }

//    @Test
//    public void individualBuildCalledWithoutRequiredArguments() throws Exception {
//        ImmutableIndividual.builder().build(); doesn't compile (YES THANK YOU SO MUCH)
//    }


    @Test(expected = NullPointerException.class)
    public void individualBuildCalledWithRequiredArgumentsNull() throws Exception {
        ImmutableIndividual.builder().name(null).title(null).build();
    }

//    @Test
//    public void individualBuildCalledWithNonRequiredArgumentsNull() throws Exception {
//        ImmutableIndividual.builder().name("John").title("Dev").department(null).build(); doesn't compile
//    }

    @Test
    public void individualBuildCalledWithoutNonrequiredArguments() throws Exception {
        ImmutableIndividual john = ImmutableIndividual.builder().name("John").title("Dev").build();
        Assert.assertEquals("John", john.name());
        Assert.assertEquals("Dev", john.title());
        Assert.assertEquals(Optional.empty(), john.department());
    }

    @Test
    public void individualBuildCalledWithAllArguments() throws Exception {
        ImmutableIndividual john = ImmutableIndividual.builder().name("John").title("Dev").department("IT").build();
        Assert.assertEquals("John", john.name());
        Assert.assertEquals("Dev", john.title());
        Assert.assertEquals(Optional.of("IT"), john.department());
    }
}

@elucash you rock! Tx for implementing this. Trying out Immutables more seriously now.. ;-)

Was this page helpful?
0 / 5 - 0 ratings