Generic Builder Pattern class using generics + reflection

Submitted by Derrick on

Starting with this SO answer, I built out a fully generic Unit Test POJO builder:

package com.derrickbowen.testutils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import com.google.common.testing.ArbitraryInstances;

public class Builder<T>
{

private final Class<?> clazz;
private Map<String, Object> map;

public Builder(Class<T> clazz)
{
super();
this.clazz = clazz;
this.map = new HashMap<>();
}

public static Builder<?> start(Class<?> clazz)
{
return new Builder<>(clazz);
}

public Builder<T> with(String name, Object value)
{
map.put(name, value);
return this;
}

/**
* Attempts to find the setter for the given property name
*
* @param instance
* @param name
*            the property
* @param value
* @return
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws InvocationTargetException
* @throws NoSuchMethodException
* @throws SecurityException
*/
private Builder<T> setProperty(T instance, String name, Object value)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException, NoSuchMethodException, SecurityException
{
try
{
if (value != null)
{
invoke(instance, name, value, value.getClass());
}
else
{
findMethodAndInvoke(instance, name, value);
}
}
catch (NoSuchMethodException nm)
{
if (value.getClass() == java.lang.Integer.class)
{
invoke(instance, name, value, int.class);
}
else if (value.getClass() == java.lang.Long.class)
{
invoke(instance, name, value, long.class);
}
else if (value.getClass() == java.lang.Float.class)
{
invoke(instance, name, value, float.class);
}
else if (value.getClass() == java.lang.Double.class)
{
invoke(instance, name, value, double.class);
}
else if (value.getClass() == java.lang.Boolean.class)
{
invoke(instance, name, value, boolean.class);
}
else
{
findMethodAndInvoke(instance, name, value);
}
}
return this;
}

/**
* Iterates through all methods on the class to find the setter
*
* @param instance
* @param name
* @param value
* @throws IllegalAccessException
* @throws InvocationTargetException
* @throws NoSuchMethodError
*/
private void findMethodAndInvoke(T instance, String name, Object value)
throws IllegalAccessException, InvocationTargetException,
NoSuchMethodError
{
Method[] methods = clazz.getMethods();
String setterName = getSetterName(name);
boolean invoked = false;
for (int i = 0; i < methods.length; i++)
{
Method method = methods[i];
if (method.getName().equals(setterName))
{
method.invoke(instance, value);
invoked = true;
}
}
if (!invoked)
{
throw new NoSuchMethodError(
"Cannot find method with name " + setterName);
}
}

private String getSetterName(String name)
{
return "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
}

private void invoke(T instance, String name, Object value, Class<?> claz)
throws NoSuchMethodException, SecurityException,
IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
Method method = clazz.getMethod(getSetterName(name), claz);
method.invoke(instance, value);
}

public T build()
{
@SuppressWarnings("unchecked")
T instance = (T) ArbitraryInstances.get(clazz);

try
{
for (Entry<String, Object> val : map.entrySet())
{
setProperty(instance, val.getKey(), val.getValue());
}
}
catch (Exception e)
{
throw new RuntimeException("Unable to set value with builder", e);
}
return instance;
}
}

I am using it along with Guava testlibs and Lombok to get better coverage of POJOs without much effort:

public class StateProvinceTest
{

@Test
public void sanityTests()
{
StateProvince sp1 = (StateProvince) Builder.start(StateProvince.class)
.with("stateProvinceValue", 2).with("stateProvinceName", "TX")
.build();
StateProvince sp2 = (StateProvince) Builder.start(StateProvince.class)
.with("stateProvinceValue", 3).with("stateProvinceName", "LA")
.build();

EqualsTester etester = new EqualsTester();
etester.addEqualityGroup(sp1,
(StateProvince) Builder.start(StateProvince.class)
.with("stateProvinceValue", 2)
.with("stateProvinceName", "TX")
.with("stateProvinceAbbreviation", "Big T").build());
etester.addEqualityGroup(sp2);
etester.testEquals();
}

@Test
public void testCreateAndSerialize()
throws ClassNotFoundException, IOException
{
// arrange
StateProvince sp1 = (StateProvince) Builder.start(StateProvince.class)
.with("stateProvinceValue", 2).with("stateProvinceName", "TX")
.build();
// act & assert
SerializableTester.reserializeAndAssert(sp1);
}
}