Java Builder generator problem
Asked Answered
P

2

8

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.

Passport answered 23/11, 2010 at 14:33 Comment(8)
Aside comment: when I need to generate Java code (especially huge sets of unit tests), I use grep/sed+python...Beata
I like to use a technology that understands the language. Which limits it to either a) reflection b) a source code parser c) a byte code tool like ASMPassport
Out of curiousity, what Maven plugin generates Builder classes for you?Diplosis
@matt b: none yet. I am thinking about writing one. I have working groovy code that I call from maven using GMaven and it would be trivial to turn this mechanism into a maven plugin if not for the two problems I am mentioning.Passport
the existing plugin I was talking about ts an eclipse plugin, not a maven pluginPassport
ok - whats the existing eclipse plugin then? :) i'm just curious how you are auto-generating the Builders.Diplosis
eclipse plugin: fluent-builders-generator-eclipse-plugin.googlecode.com but I'm not using it. I'll extend my question to give you some details on what I do in my codePassport
@matt b: updated question to give you an idea of what I doPassport
O
1

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.
Overthrust answered 17/12, 2011 at 22:9 Comment(0)
C
2

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)

Crescendo answered 23/11, 2010 at 15:28 Comment(1)
OK, so they are beans, not POJOs. But they do need setters because several external tools we use rely on setters being there (even if we don't use them ourselves). You are answering a different question (I'm not saying your answer is invalid, just irrelevant to me). The question was not about immutability, but about two specific problems, none of which are addressed by your answer.Passport
O
1

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.
Overthrust answered 17/12, 2011 at 22:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.