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);
    }
}