Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Builder generator problem

In a project of mine I have two packages full of DTOs, POJOs with just getters and setters. While it's important that they are simple java beans (e.g. because Apache CXF uses them to create Web Service XSDs etc.), it's also awful and error-prone to program like that.

Foo foo = new Foo();
foo.setBar("baz");
foo.setPhleem(123);
return foo;

I prefer fluent interfaces and builder objects, so I use maven / gmaven to automatically create builders for the DTOs. So for the above code, a FooBuilder is automatically generated, which I can use like this:

Foo foo = new FooBuilder()
           .bar("baz")
           .phleem(123)
           .build();

I also automatically generates Unit tests for the generated Builders. A unit test would generate both of the above codes (builder version and non builder version) and assert that both versions are equivalent in terms of equals() and hashcode(). The way I can achieve that is to have a globally accessible Map with defaults for every property type. Something like this:

public final class Defaults{
    private Defaults(){}
    private static final Map<Class<?>, Object> DEFAULT_VALUES =
        new HashMap<Class<?>, Object>();
    static{
        DEFAULT_VALUES.put(String.class, "baz");
        // argh, autoboxing is necessary :-)
        DEFAULT_VALUES.put(int.class, 123);
        // etc. etc.
    }
    public static getPropertyValue(Class<?> type){
        return DEFAULT_VALUES.get(type);
    }
}

Another non-trivial aspect is that the pojos sometimes have collection members. e.g.:

foo.setBings(List<Bing> bings)

but in my builder I would like this to generate two methods from this case: a set method and an add method:

fooBuilder.bings(List<Bing> bings); // set method
fooBuilder.addBing(Bing bing); // add method

I have solved this by adding a custom annotation to the property fields in Foo

@ComponentType(Bing.class)
private List<Bing> bings;

The builder builder (sic) reads the annotation and uses the value as the generic type of the methods to generate.

We are now getting closer to the question (sorry, brevity is not one of my merits :-)).

I have realized that this builder approach could be used in more than one project, so I am thinking of turning it into a maven plugin. I am perfectly clear about how to generate a maven plugin, so that's not part of the question (nor is how to generate valid Java source code). My problem is: how can I deal with the two above problems without introducing any common dependencies (between Project and Plugin):

<Question>

  1. I need a Defaults class (or a similar mechanism) for getting default values for generated unit tests (this is a key part of the concept, I would not trust automatically generated builders if they weren't fully tested). Please help me come up with a good and generic way to solve this problem, given that each project will have it's own domain objects.

  2. I need a common way of communicating generic types to the builder generator. The current annotation based version I am using is not satisfactory, as both project and plugin need to be aware of the same annotation.

</Question>

Any Ideas?

BTW: I know that the real key point of using builders is making objects immutable. I can't make mine immutable, because standard java beans are necessary, but I use AspectJ to enforce that neither set-methods nor constructors are called anywhere in my code base except in the builders, so for practical purposes, the resulting objects are immutable.

Also: Yes, I am aware of existing Builder-generator IDE plugins. That doesn't fit my purpose, I want an automated solution, that's always up to date whenever the underlying code has changed.


Matt B requested some info about how I generate my builders. Here's what I do:

I read a class per reflection, use Introspector.getBeanInfo(clazz).getPropertyDescriptors() to get an array of property descriptors. All my builders have a base class AbstractBuilder<T> where T would be Foo in the above case. Here's the code of the Abstract Builder class. For every property in the PropertyDescriptor array, a method is generated with the name of the property. This would be the implementation of FooBuilder.bar(String):

public FooBuilder bar(String bar){
    setProperty("bar", bar);
    return this;
}

the build() method in AbstractBuilder instantiates the object and assigns all properties in it's property map.

like image 412
Sean Patrick Floyd Avatar asked Nov 23 '10 14:11

Sean Patrick Floyd


2 Answers

A POJO is an object which doesn't follow the Java Bean spoec. ie. it doesn't have setters/getters.

JavaBeans are not required to have setters, if you don't want them to be called, don't generate them. (Your builder can call a package local or private constructor to create your immutable objects)

like image 94
Peter Lawrey Avatar answered Oct 22 '22 13:10

Peter Lawrey


Have you looked at Diezel ? It's a Builder generator.

  1. It handles generic types, so it might be helpful here for the question 2
  2. It generates all the interfaces, and implementation boiler plate based on a description XML file. You might be able, through introspection to generate this XML (or even goes directly into lower API )
  3. It is bundled as a maven plugin.
like image 44
eric Avatar answered Oct 22 '22 14:10

eric