Thursday 4 September 2008

Abstracting Away Patterns In Java

Today, while hacking away at my blub code in my day job, I noticed a very simple pattern in the code.  My first instinct was to create a utility function to abstract away the pattern, then I thought better of it after thinking for a moment about the inevitable effort and whether it would be worth my time - I have a tight deadline after all!  Then, I thought - sod it - lets see how painful this will be.  The end result?  It was quite painful!  This post explains why....


The pattern

Here's the code with the reoccurring pattern:


public void addReserveDog(String string, int ngrcId, int studBookId) {
    ReserveDogsXmlType xmlReserveDogs = xmlMeeting.getReserveDogs();
    if(xmlReserveDogs == null) {
        xmlReserveDogs = new ReserveDogsXmlType();
    }
    xmlMeeting.setReserveDogs(xmlReserveDogs);
}

All over the code I am doing this with different JAXB types.  In this case, I need to add something to the xmlReserveDogs element but it may be null.  So I need to get the element, check for null and if so create it, then set it again.

This same pattern happens everywhere but for different types.  Ideally, I'd like to abstract this pattern into a function such as this:

ReserveDogsXmlType xmlReserveDogs = getOrCreateXmlObject(xmlMeeting, ReserveDogsXmlType.class)

After messing around for a while I came up with this:

ReserveDogsXmlType xmlReserveDogs = (ReserveDogsXmlType) getOrCreateXmlObject(xmlMeeting, ReserveDogsXmlType.class, "getReserveDogs", "setReserveDogs");

I needed to pass in the names of the getter and setter methods to get it to work, but I can live with that.  The problem was the pain involved in getting this to work.  

Here is the implementation code:

public static Object getOrCreateXmlObject(
    AbstractXmlObject xmlParentObj, 
    Class<?> xmlObjectType, 
    String getterMethodName,
    String setterMethodName) {

    Object newXmlObj = ReflectionUtils.invokeMethod(xmlParentObj, getterMethodName);
    if(newXmlObj == null) {
        newXmlObj = ReflectionUtils.newInstance(xmlObjectType);
    }
    ReflectionUtils.invokeMethod(xmlParentObj, setterMethodName, xmlObjectType, newXmlObj);
    return newXmlObj;
}

Note that I use several utility methods here.  These are listed below:

public static Object invokeMethod(Object obj, String methodName) {
    Object result = null;
    try {
        result = obj.getClass().getMethod(methodName).invoke(obj);
    } catch (Exception e) {
        ////.... error handling .....
    }
}

public static Object invokeMethod(Object obj, String methodName, Class<?> paramType, Object arg) {
    Object result = null;
    try {
        result = obj.getClass().getMethod(methodName, paramType).invoke(obj, arg);
    } catch (Exception e) {
        ////.... error handling ....       
}

public static Object newInstance(Class<?> xmlObjectType) {
    Object newObj = null;
    try {
        newObj = xmlObjectType.newInstance();
    } catch(Exception e) {
        ////.... error handling ....
    }
    return newObj;
}


Summary

It is obvious from this experiment that the effort involved in abstracting away simple patterns is quite high in comparison to the benefits.  However, I still think it's worth it.  As the code base evolves more and more patterns will be abstracted and there will be less and less code duplication which should make code easier to maintain.  Also, the ReflectionUtils can be reused elsewhere in the code.  


4 comments:

Anonymous said...

You can get rid of the cast by putting a type parameter <T> on the method and passing in Class<T> and returning T

Paul Drummond said...

Thanks I didn't think of that!

Moandji Ezana said...

If your getters and setters follow a conventional naming pattern (in this case, the JAXB class name minus XmlType), then you don't have to pass them in as parameters, but can deduce them from the class name itself

Paul Drummond said...

@Mwanji Ezana:

Can you elaborate by showing an example please? I vaguely remember this sort of getter/setter reflection requires a specific (and over-complicated if I remember) JavaBean API unless you roll your own implementation but maybe I'm mistaken.