How's this sound? Weird / open issues are in all caps so you can quickly spot the hard decisions we have to make here.
@Builder: Annotate any class 'XType' with this (error if on interface, enum).
Optional parameters: "via", which can specify a static method in XType that is called. If via is not there, the constructor is called.
_EVERY FIELD_ is included, even weird ones like fields with a $ in it. There is no way to specify alternate signatures (your only option is to make a private static method that takes each field in order and calls the 'right' real constructing method/constructor). This does mean it won't actually work with @AllArgsConstructor if you have fields that start with a $ or other weirdness. Static fields are of course skipped. We won't check the signature of the constructor / static method. We just call it, and if it doesn't exist, ecj/javac will emit the error, not us.
Optional parameter 2: "rebuilder=boolean". False by default. If true, an instance 'builder' method is generated which returns a builder that is initialized to all values of the instance. This method is called 'rebuilder' and the name cannot be changed (possible alternate plan is @Builder(rebuilder="myName"), but if we go that route that means you HAVE to specify some name, and the only way we can try and create some sort of convention here is if (rebuilder="") means it'll be called 'rebuilder'. That's why I prefer the boolean, and then we can all have a nice session of paint-the-bikeshed as to what it'll be called.
The builder itself will be an inner static class named XTypeBuilder. It's okay if this inner static class is already present; in that case, we'll inject all the methods we would have generated in there, and simply skip whatever already exists (this means you get to override whatever you want and add whatever you want to the builder).
inside XTypeBuilder, you'll find 1 method for each field. The method's name is equal to the field's name, it has 1 argument (equal to the field's type), and returns XTypeBuilder. There's also a build() method that returns XType and just calls the constructor which each field in order. This method never itself errors (so there is no intrinsic handling of mandatory fields. You'll have to error out in your constructor. IT WILL NOT BE POSSIBLE TO TELL THE DIFFERENCE between a build session where some field wasn't initialized at all, and a build session where that field was initialized to null / false / 0 / 0.0 / '\0'.
XTypeBuilder will also get a sane toString. __NO SANE EQUALS AND HASHCODE WILL BE GENERATED__. Builders are not considered inherently comparable like that. You are supposed to use them to make an instance and then get rid of em. It's somewhat suspect to start passing a builder around, and it would be completely crazy to try and shove them into lists and sets and the like, so let's not even try to go there. These are not intended as mutable standins for XType. If you must have it, you can always write @EqualsAndHashCode public static class XTypeBuilder {}. We'll make sure that that'll work.
THERE IS NO WAY TO CHANGE THE NAME of build() or builder(). We can always add this later, and I really would like to create a convention here. guava has already set the tone on builder().foo().bar().build().
NO SUPERFLUENCY. (superfluency is where you have a huge list of types, and each type has only 1 build method and returns the next type in the chain. This way, you just ctrl+space (autocomplete) your way through the entire deal, and there won't even be a build() method to call unless you've actually initialized each mandatory value. It's what lombok-pg's builder does. It's a neat idea but way too much typespace clutter and it looks ridiculous if delomboked).
example:
@Builder(via="_build", rebuilder=true) @Value
public class Person {
String firstName, lastName;
private static Person _build(String a, String b) {
return (a.equals("Bruce") && b.equals("Wayne")) : Superheroes.SUPERMAN : new Person(a, b);
}
}
results in all the usual things @Value would do, and:
public class Person {
private final String firstName, lastName;
public static PersonBuilder builder() { return new PersonBuilder(); }
public PersonBuilder rebuilder() {
return builder().firstName(firstName).lastName(lastName);
}
public static class PersonBuilder {
private String firstName, lastName;
PersonBuilder() {}
public PersonBuilder firstName(String firstName) { this.firstName = firstName; }
public PersonBuilder lastName(String firstName) { this.lastName = lastName; }
public Person build() {
return Person._build(firstName, lastName);
}
@java.lang.Override public java.lang.String toString() {
return "PersonBuilder(firstName = " + firstName + ", lastName = " + lastName + ")";
}
}
}
I have no idea if it is possible to generate new inner classes in both ecj and javac (this isn't something we have ever done before), but lombok-pg does it so presumably it's possible.