15 August 2009

Type Parameters and Reflection

Last week I had an opportunity to participate in the development of XMAdsl, an Xtext based model driven development extension of openXMA. In the new release we want to use Value Objects for all relevant values. E.g. the birth date of a person should not be an arbitrary java.util.Date, but a specific BirthDateValue instance. The Value Object hierarchy looks like that:
abstract class ValueObject<T> {
private final T value;
public ValueObject(T pValue) {
value = pValue;
}
// shortened for brevity...
}

class ValueObjectDate extends ValueObject<Date> {
public ValueObjectDate(Date pDate) {
super((Date) pDate.clone()); // Date is mutable
}
// shortened for brevity...
}

class BirthDateValue extends ValueObjectDate {
public BirthDateValue(Date pValue) {
super(pValue);
}
}
ValueObject, ValueObjectDate and all other Value Object base classes are packaged in the platform used by the generator. BirthDateValue and all other concrete subclasses are generated depending on the model. Such method signatures are more readable and passing a date of payment as a birth date by accident gets impossible. (Value Objects have other advantages, e.g. being read only, but that's not the point here.)

Reflection PoolAll Value Objects use the ValueObjectType Hibernate User Type for persistence. Obviously the User Type has to know the type of the Value Object to create new instances and the type of the value inside it to map it onto the database column. But how to get the inner type? Our first approach was to configure it. As the Hibernate configuration is generated as well, that's no big deal. Nevertheless it's a bit lame and definitely not DRY.

So the question is how to find the type parameter of a generic super class. In the given case the inner value is passed as constructor argument, so we can easily do better using reflection on the constructor parameter:
for (Constructor<?> c : pValue.getConstructors()) {
if (c.getParameterTypes().length == 1) {
return c.getParameterTypes()[0];
}
}
// throw an exception
Nice. That fixed our problem but made me think further. What if there would would be no method or field with the concrete type of the type parameter, e.g. when using collections as super classes and not defining any new methods? It's also possible to find the type using reflection:
public Class<?> findType(Class<?> pValue) {
if (pValue == Object.class) {
// throw an exception
}

// is it generic itself?
if (pValue.getTypeParameters().length > 0) {
return (Class<?>) pValue.getTypeParameters()[0].getBounds()[0];
}

// is the super class typed?
if (pValue.getSuperclass().getTypeParameters().length > 0) {
Type superClass = pValue.getGenericSuperclass();
ParameterizedType type = (ParameterizedType) superClass;
return (Class<?>) type.getActualTypeArguments()[0];
}

// go up to super class
return findType(pValue.getSuperclass());
}
A class knows if it's super class had type parameters using getGenericSuperclass(). If the class has a type parameter itself we don't know it due to erasure, but at least we get its lower bound. In all other cases we go up the class hierarchy. Finally a little test if it works as expected...
@Test
public void testFindType() {
assertSame(Date.class,
userType.findType(ValueObjectDate.class));
assertSame(Date.class,
userType.findType(BirthDateValue.class));
}

No comments: