Chapter 2. Transformers

2.1. Introduction

A transformer transforms information taken from a source and makes it available at a destination. There are two main types of Transformers: Converters and Copiers. Converters convert an object of one type to a new object of a different type. Copiers copy information from an existing object to an existing object of a different type. Before we get into the reason for having two types of Transformers, let's take a closer look at Converters.

2.2. The Converter Interface

As previously mentioned, Converters allow an object of one type to be converted to an object of a different type. Here is the Converter interface:

public interface Converter extends Transformer {

	public Object convert(Class destinationClass, Object source, Locale locale)
		throws TransformationException;

}
		

As you can see, the Converter interface is very simple. By calling the convert method you are saying, "convert source into a new instance of destinationClass". This is the interface to use when you're doing a simple conversion from one basic type to another. For example, Morph includes converters that will convert a String to an int (TextToNumberConverter), a String to a StringBuffer (TextConverter) and many other converters.

2.3. The Copier Interface

Now let's take a look at the Copier interface:

public interface Copier extends Transformer {

	public void copy(Object destination, Object source, Locale locale)
		throws TransformationException;

}
		

The Copier interface is just as simple as the Converter interface. A call to the copy method basically means, "copy the information from the source to the existing destination. Copiers are used when you want to avoid or cannot create a new instance of the destination object. For example, if you want to copy the information in a Map to a HttpServletRequest's attributes, you can't create a new HttpServletRequest request object, because the servlet container already creates the request object, and you can't create your own. An example of when you could but wouldn't want to create a new instance of the destination object is if you have multiple source objects that you want to be combined into one destination object. For example, if you had information in three different Maps that you would like copied to a single destination business object, you could call the copy operation multiple times with your existing business object as the destination object for all three copy operations.

Now that we've gone over why there are two different types of Transformers, let's make a simple rule of thumb you can use to determine if you should implement a Copier or a Converter: always prefer the Copier interface. In other words, if the transformation you're writing can be expressed as a Copier, you should implement the Copier interface. This is because any copier can easily implement the convert operation: just create a new instance of the destination class, and then call the copy operation. In fact, if you subclass the BaseCopier, you will just have to implement the contract for the copy operation and the Converter interface will be automatically exposed for you.

2.4. Internationalization

You may have noticed that both the convert and copy operations have a locale parameter. This parameter is useful when you need to internationalize your application. For example, to convert a Double to a String, you can use the Morph.convertToString(Object, Locale) method which will delegate to the NumberToTextConverter. Now let's say you want the format of the textual representation of the number to be customized according to the locale of your application's users: English speakers use a period as the decimal separator and Spanish speakers use a comma. By passing in the correct locale, English users will see the Double 3564.12 as 3564.12 and Spanish users will see that same Double as 3564,12. You can customize the NumberToTextConverter by subclassing it and overriding its getNumberFormat method. For example, you could customize the converter to include a thousands separator or round decimals to a certain number of digits.

If you don't know the locale of your user or the locale isn't important, you can simply pass null in as the Locale.

2.5. The Transformer Interface

So far we've skipped over the base interface for Converters and Copiers to highlight the differences between the two interfaces. Now let's look at the similarities by examining the Transformer interface:

public interface Transformer extends Component {

	public Class[] getSourceClasses();

	public Class[] getDestinationClasses();

}
		

These methods allow a transformer to specify the types of transformations it is capable of performing.

This is a different than the one taken by other frameworks. In other frameworks, a transformer is responsible for performing a transformation and a separate registry is used to indicate which transformers can do which transformations. This is like having a restaurant where each person is allowed to eat, but isn't allowed to say what they like to eat. The restaurant's host examines each person and decides what that person will be served without consulting that person. As you can imagine, this gets pretty ugly pretty quick. Logically, each person in the restaurant knows what he or she wants to eat, so why not let them decide?

2.6. Combining Transformers

Transformers are easy to use directly with Morph, but we don't always know exactly what we're converting ahead-of-time. For example, if I have a bunch of objects I want to convert to Strings at once, I don't want to have to write a lengthy if/then statement that picks the right converter. I'd rather just write convert(String.class, source, locale) and have the correct Converter chosen for me. To solve this problem, other frameworks introduced a registry where you state which transformers can be used for which transformations. This solves the problem of choosing how to pick a converter, but as we saw in our restaurant example, it introduces problems of its own.

Morph's solution to this problem is the DelegatingTransformer. It's a Transformer just like the other Transformers we've looked at, but instead of doing transformations itself, it delegates to other Transformers. Continuing with our restaurant example, the DelegatingTransformer is like a buffet. Each person that enters the restaurant gets in line for the buffet and each person gets to choose what they would like to eat. Now to really stretch this metaphor: the trick is to arrange the line in such a way that everyone's happy. Put the picky eaters in the front of the line so they can get what they like to eat, and put your puppy that will eat anything at the back of the line so that everything gets eaten.

Now let's flee from this crazy restaurant and talk about transformations again. Morph includes both a NumberToTextConverter and a ObjectToTextConverter. The ObjectToTextConverter just calls an Object's toString method, whereas the NumberToTextConverter nicely formats a number based on a user's locale. Clearly, if we're converting a bunch of objects to Strings, we want the NumberToTextConverter to get chosen if the object to be converted is a number. If the object is not a number, we can fall back to the ObjectToTextConverter. We specify all this behavior simply by setting the delegates property of the DelegatingTransformer. The delegates are arranged in order of precedence. When the DelegatingTransformer does a transformation, it goes to each transformer in turn and asks if it can perform the requested transformation. If the transformer reaches the end of the list but couldn't find any transformers to do the requested transformation, a TransformationException is thrown.

2.7. Transformer Implementations

Morph comes with many Transformers pre-built so that hopefully you won't have to implement any yourself. In this section we'll briefly examine the transformers that are bundled with Morph, and see how to write our own.

2.7.1. Pre-Built Converters

The Converters included with Morph work with all the basic Java types: primitives, Characters, Strings, StringBuffers, Dates, Calendars, Numbers, Iterators, and Enumerators. For a complete list, see the JavaDoc of the net.sf.morph.transform.converters package. To get an idea at a glance of what you can convert to what, see the chart below. An arrow from one type to another indicates that a conversion in that direction is possible. For example, Numbers can be converted to Booleans, but not the other way around.

Converters included with Morph

2.7.2. Pre-Built Copiers

The Copiers included with Morph are focused on transferring information between bean-like objects and container-like objects. Bean-like objects can be copied using the PropertyNameMatchingCopier, which copies information from one object to another based on matching up property names in the source and destination objects. For example, if you had a PersonDAO data access object and a Person domain object that each had the properties firstName, middleName and lastName, the PropertyNameMatchingCopier would take care of copying the information to and from those two objects automatically.

If the properties don't match, you can use the PropertyNameMappingCopier. For example, if PersonDAO used firstName, middleName and lastName as property names and Person used firstName middleName and familyName, the PropertyNameMappingCopier can be customized to do this conversion by setting its mapping property.

2.7.3. Writing Custom Transformers

If you need to write a custom transformer, it's easy since Copiers and Converters have such simple interfaces. We recommend you try to extend an existing, pre-built transformer, but if you can't find one that does what you need you can also directly subclass BaseConverter or BaseCopier. See the JavaDoc for BaseTransformer for more information.

2.8. Transforming Arbitrary Object Graphs

2.8.1. Introduction

When information is passed between different tiers of an application, it often needs to be transformed into a different format. Essentially, what you need to do is transform one graph of objects into a different graph of objects with similar information. Without Morph, this type of code can quickly become a big mess that is difficult to modify when the structure of either object graph is changed. Morph helps isolate each of the different types of transformations that are happening using a divide-and-conquer approach. Instead of writing one massive method that does the transformation, you write several Transformer classes, each of which is concerned only with transforming one node in the object graph from one type to another. You then combine all these Transformers using the DelegatingTransformer.

2.8.2. Example

In this section we'll look at an example of transforming a data access object that holds information from a database into a value object to be exposed as part of a web service. Note that this example has been made intentionally as difficult as possible. Most use cases will require far fewer custom transformers to be written. You can see this example in action by examining the net.sf.morph.examples.person.PersonExampleTestCase.

Now let's get started. Below are our example objects, a PersonDAO (Person data access object) and a PersonVO (Person value object):

The PersonDAO and PersonVO classes

Below is an example PersonDAO object that represents John A. Smith. As we can see, his firstName is John, his middleName is A. and his lastName is Smith. His credit card number is 5555 5555 5555 5555. He has two children, Matthew and Natalie, a home and work address, a Ford Taurus, and a Honda Civic.

John A. Smith represented as a PersonDAO

We would like to convert John's PersonDAO into a PersonVO, like the one shown below. Notice the creditCardNumber information is removed and his firstName, middleName and lastName have been combined to provide a single name. Similarly, all his address information was squished into a single String by listing only his primaryAddress, and converting it to a String representation. Finally, his Ford Taurus and Honda Civic are now just a Taurus and a Civic, because in our VehicleVO we decided we didn't need to include information about the vehicle's manufacturer.

John A. Smith represented as a PersonVO

2.8.2.1. Transforming VehicleDAO[] to VehicleVO[]

First we'll focus on converting the vehicles property of the PersonDAO to the vehicles property of the PersonVO. We'll assume for this example that the VehicleDAO can be converted to a VehicleVO by simply using the PropertyNameMatchingCopier. If this is the case, the ContainerCopier will be able to use the PropertyNameMatchingCopier to convert the VehicleDAO[] to a VehicleVO[] without any further effort on our part.

2.8.2.2. Transforming PersonDAO[] to String[]

For the children property of the PersonDAO, we will need to convert a PersonDAO[] array to String[]. If we assume that a PersonDAO can be converted to a String by simply calling the object's toString method, the ContainerCopier can do this conversion. It will automatically delegate to the ObjectToTextConverter to handle the PersonDAO to String conversion. If we want to write a different converter to handle the PersonDAO to String conversion, we can configure a ContainerCopier to use it by setting the ContainerCopier's graphTransformer property.

2.8.2.3. Transforming AddressDAO[] to String

Now we'll focus on copying the PersonDAO.addresses property to the VehicleVO.primaryAddress property. We will also assume that a PersonDAO can be converted to a String by calling the object's toString method. We will have to write our own converter that takes a PersonDAO[] and transforms it to a String:

public class AddressDAOArrayToStringConverter extends BaseConverter {

	protected Object convertImpl(Class destinationClass, Object source,
		Locale locale) throws Exception {
		
		// the BaseConverter will make sure the source is of the correct type
		// for us, so we can just do a cast here with no error checking
		AddressDAO[] addresses = (AddressDAO[]) source;
		// we can also assume the source is not null, because we didn't
		// explicitly state that null was a valid source class
		AddressDAO address = addresses[0];
		// now we convert the first address to a String
		return address.toString();
		
	}

	protected Class[] getSourceClassesImpl() throws Exception {
		// if we wanted this converter to also handle converting null values
		// to Strings, we could write this line as:
		//
		//      return new Class[] { AddressDAO[].class, null };
		return new Class[] { AddressDAO[].class };
	}

	protected Class[] getDestinationClassesImpl() throws Exception {
		return new Class[] { String.class };
	}

}
				

2.8.2.4. Transforming PersonDAO[] to PersonVO[]

Now that we know which converters we need to transform the properties of a PersonDAO to the properties of a PersonVO, we are ready to transform our top-level PersonDAO object into a top-level PersonVO object. We will be able to use the PropertyNameMappingCopier to do most of the work, but we will need to subclass it to handle the conversion of the PersonDAO.firstName, personDAO.middleName and personDAO.lastName properties into a single PersonVO.name property. Here is our top-level converter:

public class PersonDAOToPersonVOCopier extends PropertyNameMappingCopier {

	protected void copyImpl(Object destination, Object source, Locale locale)
		throws TransformationException {
		
		super.copyImpl(destination, source, locale);
		
		// this cast is safe because our superclass makes sure the source is of
		// the correct type and not null
		PersonDAO personDAO = (PersonDAO) source;
		// construct the name
		String name = personDAO.getFirstName() + " "
			+ personDAO.getMiddleName() + " " + personDAO.getLastName();
		
		// this cast is safe because our superclass makes sure the destination
		// is of the correct type and not null
		PersonVO personVO = (PersonVO) destination;
		// save the name
		personVO.setName(name);
		
	}
	
	protected Class[] getDestinationClassesImpl() throws Exception {
		return new Class[] { PersonVO.class };
	}
	
	protected Class[] getSourceClassesImpl() throws Exception {
		return new Class[] { PersonDAO.class };
	}
	
}
				

Now that we have all our transformers written, we can go about performing our graph transformation. We can do everything programmatically, or we can use a dependency injection framework. Here is the code we'll need to do things programmatically:

// this is the overall transformer we'll use to do the graph copy
DelegatingTransformer graphTransformer = new DelegatingTransformer();

// AddressDAO[] to String
AddressDAOArrayToStringConverter addressConverter =
	new AddressDAOArrayToStringConverter();		
// PersonDAO[] to PersonVO[]
PropertyNameMappingCopier personCopier = new PersonDAOToPersonVOCopier();
Map personMapping = new HashMap();
personMapping.put("children", "children");
personMapping.put("addresses", "primaryAddress");
personMapping.put("vehicles", "vehicles");		
personCopier.setMapping(personMapping);
personCopier.setGraphTransformer(graphTransformer);

// the list of transformers that are involved in our overall graph
// transformation
List transformers = new ArrayList();
// always put your custom transformers first
transformers.add(personCopier);
transformers.add(addressConverter);

// then put in the default set of transformers as listed in the
// DelegatingTransformer.  this makes sure all the normal conversions
// you would expect from Morph are available (e.g. Integer 1 -> Long 1)
transformers.add(new DefaultToBooleanConverter());
transformers.add(new NullConverter());
transformers.add(new IdentityConverter());
transformers.add(new DefaultToTextConverter());
transformers.add(new TextToNumberConverter());
transformers.add(new NumberConverter());
transformers.add(new TraverserConverter());
transformers.add(new TextConverter());
// will automatically take care of PersonDAO[] to String[]
transformers.add(new ContainerCopier());
// will automatically take care of VehicleDAO[] to VehicleVO[]
transformers.add(new PropertyNameMatchingCopier());

// convert our list of transformers into an array
Transformer[] transformerArray = (Transformer[]) transformers.toArray(
	new Transformer[transformers.size()]);
graphTransformer.setComponents(transformerArray);

// copy the information from personDAO to personVO
graphTransformer.copy(personVO, personDAO);
				

Below is essentially the same code using Spring. The code may not be much shorter, but I feel it's clearer

<beans>

	<!-- VehicleDAO[] to VehicleVO[] -->
	<bean
		id="vehicleCopier"
		class="net.sf.morph.transform.copiers.PropertyNameMatchingCopier"/>
	<!-- PersonDAO[] to String[] -->
	<bean
		id="childrenCopier"
		class="net.sf.morph.transform.copiers.ContainerCopier"/>
	<!-- AddressDAO[] to String -->
	<bean
		id="addressCopier"
		class="net.sf.morph.examples.person.AddressDAOArrayToStringConverter"/>
	<!-- PersonDAO[] to PersonVO[] -->
	<bean
		id="personCopier"
		class="net.sf.morph.examples.person.PersonDAOToPersonVOCopier">
		<property name="mapping">
			<map>
				<entry key="children" value="children"/>
				<entry key="address" value="primaryAddress"/>
				<entry key="vehicles" value="vehicles"/>
			</list>
		</property>
		<property name="graphTransformer">
			<ref bean="graphTransformer"/>
		</property>
	</bean>

	<!-- the overall transformer we'll use to do the graph copy -->
	<bean
		id="graphTransformer"
		class="net.sf.morph.transform.DelegatingCopier">
		<property name="components">
			<list>
				<ref bean="personCopier"/>
				<ref bean="vehicleCopier"/>
				<ref bean="childrenCopier"/>
				<ref bean="addressConverter"/>
			</list>
		</property>
	</bean>
	
</beans>