bloginvoke

Data objects for the lazy: A function to auto implement a Java interface

Hannes de Jager java

One of the things I like about a language like Scala is how little you have to type to get a data class with getters and setters for each field. Now in Java this is a bit more painful:

class Person {
   
  private String name;
  private String surname;
  private int age;
   
  public String getName() {
    return name;
  }
   
  public String getSurname() {
    return surname;
  }
   
  public int getAge() {
    return age;
  }
   
  public void setName(String name) {
    this.name = name;
  }
   
  public void setSurname(String surname) {
    this.surname = surname;
  }
   
  public void setAge(int age) {
    this.age = age;
  }
}

All I wanted to say here is that I want a container that stores read/write values for the name, surname and age of a person. Why do I have to type so much? Hmmm, perhaps I can say it a like so:

interface Person {
  public String getName();
  public void setName(String name);
  public String getSurname();
  public void setSurname(String surname);
  public int getAge();
  public void setAge(int age);
}

Brilliant, except that I can’t store anything in it. Its like having an bright idea with no implementation. But I’ve stated what I want did I not, why can’t the compiler fill in the details for me? Is there not a magic wand method that I can pass this interface to and get an implementation back or can’t I have someone type the details out for me? But even if someone over eager typed it out for me then I will have the clutter of the implementation which is just saying again what I already asked the compiler for.

Hmm I just want a container. Perhaps I should just use a map?

Map<String, Object> person = new HashMap<String, Object>();
person.put("name", "Bob");
person.put("surname","Dylan");
person.put("age", 23);

Hey if this was Javascript I could use a map like an object:

person.name = "Bob";
person.surname = "Dylan";
person.age = 23;

This makes sense because isn’t an object doing the same thing as a map conceptually i.e. mapping a string (the field name) to a value? But unfortunately this is not how Java sees things. Hey but what if we blend the concept of a HashMap and a Java interface in a Dynamic Proxy blender to give us a magic wand? Hmm:

class LazyProgrammerToolBox {
 
  /**
   * Creates an object that implements the specified interface
   * and whose data values associated with getter/setter
   * methods are stored in a map.
   *
   * @param <T>
   *          The interface type
   * @param interfaceClass
   *          The interface class
   * @return A new instance of type T
   */
  public static <T> T getMapBackedImplementation(Class<T> interfaceClass) {
    int c = interfaceClass.getDeclaredMethods().length;
    return getMapBackedImplementation(interfaceClass,
        new HashMap<String, Object>(c));
  }
 
  /**
   * Creates an object that implements the specified interface
   * and whose data values associated with getter/setter
   * methods are stored in the specified map.
   *
   * @param <T>
   *          The interface type
   * @param interfaceClass
   *          The interface class
   * @param container
   *          The map where the properties will be stored.
   * @return A new instance of type T
   */
  public static <T> T getMapBackedImplementation(Class<T> interfaceClass,
      final Map<String, Object> container) {
    InvocationHandler handler;
    if (container == null) {
      // Create a black-hole handler...
      handler = new InvocationHandler() {
        public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
          return method.getReturnType().cast(null);
        }
      };
    } else {
      handler = new InvocationHandler() {
 
        public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
          if (method.getReturnType() != void.class) {
            String s = method.getName();
            if (s.equals("toString"))
              return container.toString();
            boolean g = s.startsWith("get");
            boolean i = !g || s.startsWith("is");
            s = g ? s.replaceFirst("get", "") 
                  : i ? s.replaceFirst("is", "")
                  : s;
            if (g || i) {
              s = Character.toLowerCase(
                  s.charAt(0)) + s.substring(1);
            }
            return container.get(s);
          } else {
            if (args.length > 0) {
              String s = method.getName();
              boolean set = s.startsWith("set");
              if (set) {
                s = s.replaceFirst("set", "");
                s = Character.toLowerCase(
                    s.charAt(0)) + s.substring(1);
              }
              container.put(s, args[0]);
            }
            return null;
          }
        }
      };
    }
 
    return interfaceClass.cast(Proxy.newProxyInstance(
        interfaceClass.getClassLoader(), 
        new Class[] { interfaceClass },
        handler));
  }
 
} 

Lets see how do I use this wand:

Person p = LazyProgrammerToolBox.getMapBackedImplementation(Person.class);
p.setName("Bob");
p.setSurname("Dylan");
p.setAge(23);

Aah, thats better 🙂

Hannes de Jager
Software Builder