the blog for developers

Beautiful Java: Reflection and the BeanCopier

When reading about reflection on the beautiful code website I thought about solving some problems with reflection and finding new solutions to old problems. One problem is boring code when writing a copy constructor. A copy constructor is a constructor which takes another object and copies it’s attributes. This is often useful when copying objects or moving objects to different system layers. One example would look like this:

public class Employee {
  public Employee(Employee other) {
      this.name = other.name;
  }
}

There are some pitfalls, especially that you only create shallow copies. And there is always clone() and Serializable, which can also be used to create deep copies without such pitfalls. JBoss provides a fast implementation of this technique. But often copy constructors are quite useful if you’re careful and know what you do. I recently had a discussion on how to support domain objects in different parts of a system. One part might call an entity Employee, another part might call it Person. To make them talk together you can either create a composite, an adapter, a wrapper or use a copy constructor (or find a different solution). In such a case a shallow copy is all you want, because the new object should have the same “identity” as the first and not be a copy.

public class Employee {
  public Employee(Person person) {
      this.lastName = person.getLastName();
      this.name= person.getFirstName();
  }
}

This should be easier to implement. Copying bean attributes for large objects isn’t much fun, leads to long constructors and unmaintanable code. What about changes in Person? New attributes? So I tinkered with Java reflection and ended with this:

 public Employee(Person person) {
    BeanCopier copier = new BeanCopier();
    copier.copy(person, this, "name:firstName", "age:");
 }

I’ve written a BeanCopier which can – well you guessed it – copy beans. The syntax is easy to learn. A BeanCopier has a copy method which takes a source, a copy and the attributes which should be specially handled. By default all attributes which have the same name and type in source and copy are copied. But if they have different names, like name and firstName, you need to specify “name:firstName”. If a property shouldn’t be copied, you can specify “property:” without a target attribute.

The copy method looks like this (the generics are not needed but I was also tinkering with caching where it was quite handy):

public <T, U>void copy(U source, T destination, String... attributes) {
  if (destination.getClass().getName().equals(source.getClass().getName())) {
    // copy all fields
  } else {
    // copy all bean properties
    try {
      BeanInfo sourceInfo = Introspector.getBeanInfo(source.getClass());
      PropertyDescriptor[] sourceDescriptors = sourceInfo.getPropertyDescriptors();
      for (PropertyDescriptor descriptor : sourceDescriptors) {
        String name = descriptor.getName();
        if (!"class".equals(name)) {
          String attribute = this.find(attributes, name);
          if (attribute == null) {
            copyAttribute(source, destination, name);
          } else if ((attribute.indexOf(':') >= 0) && !attribute.endsWith(":")) {
            int colonIndex = attribute.indexOf(':');
            String sourceName = attribute.substring(0, colonIndex);
            String copyName = attribute.substring(colonIndex + 1);
            copyAttribute(source, destination, sourceName, copyName);
          }
        }
      }
    } catch (IntrospectionException e) {
      e.printStackTrace();
    }
  }
}

and the copyAttribute method:

private void copyAttribute(Object source,
      Object copy,
      String sourceName,
      String copyName) {

  PropertyDescriptor writer;
  PropertyDescriptor reader;
  try {
    writer = new PropertyDescriptor(copyName, copy.getClass());
    reader = new PropertyDescriptor(sourceName, source.getClass());

    Object value = reader.getReadMethod().invoke(source);
    Class sourceType = reader.getPropertyType();
    Class destionationType = writer.getPropertyType();
    if (sourceType.getName().equals(destionationType.getName())) {
      writer.getWriteMethod().invoke(copy, value);
    }
}

The code is rough but works. The type test can be improved to work with super types.BeanCopier can be used outside of constructors, often a wise choice. And be careful with “copying” other objects, you might only create a shallow copy.

Of course, I could have taken a look at commons BeanUtils, but isn’t it much more fun to tinker and find a nice solution yourself sometimes?

Thanks for listening.

About the author

stephan Stephan Schmidt has been working with internet technologies for the last 20 years. He was head of development, consultant and CTO and is a speaker, author and blog writer. He specializes in organizing and optimizing software development helping companies by increasing productivity with lean software development and agile methodologies. Want to know more? All views are only his own. You can find him on Google +

Discuss on Hacker News Vote on HN