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.


5 Responses to “Beautiful Java: Reflection and the BeanCopier”  

  1. Gravatar Icon 1 ben

    copy = xstream.fromXML(xstream.toXml(object));

    Easier, faster, more robust.

  2. Gravatar Icon 2 stephan

    Nice solution. But I guess it’s slower and more diffcult to understand for developers who don’t know xstream. It’s also more complex to configure. XStream is more robust though, you’re right. And of course your solution doesn’t copy properties but creates a new copy of an object. At least I would write a small wrapper with a copy copy method to change the semantics.

  3. Gravatar Icon 3 ben

    XStream should be used by every java developer imho. It vastly simplifies copying of objects, serialisation, and is FASTER than normal serialisation without the drawbacks.

    It could also be trivially enhanced to pass an object into it:

    templateObject = new …
    xstream.fromXml(templateObject, xml);

    xstream.codehaus.org

    There are many, many problems with doing your own approach - god knows I have tried. Its a large can of worms, as I am sure the xstream guys know.

    There is another solution though - I think jboss have a similar object serialisation technology that would work similarly.

    Lastly, if you want to restrict to beans, you can use the internal java classes xmlencoder,xmldecoder, but those are horrid imho.

  4. Gravatar Icon 4 stephan

    I still don’t think xstream is faster than copying properties, I can’t see why it should.

    And as I said, copying attributes to an object is something different than creating a copy of an object.

    “There is another solution though - I think jboss have a similar object serialisation technology that would work similarly.”

    If you’ve read the post, I wrote “JBoss provides a fast implementation of this technique.”

    And yes, I have been using XStream in several projects.

    And yes, I’ve said it is perhaps best to use Commons BeanUtils to copy properties.

    :-)

  1. 1 How-to: copying an object with several ways « When IE meets SE


Leave a Reply



RSS