Monday, 26 May 2008

Java code to concatenate values from get__ methods of a class dynamically

Ended up discarding this code from a project, but this was a good
exercise to learn a little about Java reflection methods.

This was based on code from HashCodeBuilder in Apache commons-lang,
which can generate hashcodes via reflection - ie,
HashCodeBuilder.reflectionHashCode(this);


/**
* Given an object of type clazz, go through the fields in that
class and concatenate the
* values of all the fields, excluding the ones listed in excludedFields.
* This will only work on bean-type getter methods that 1. do not
have parameters
* and 2. return a String.
* This code does not do any lookup on inherited methods.
* @param object instance where we are getting field values from.
* @param clazz the Class of the object.
* @param builder StringBuilder object to contain our concatenated values.
* @param excludeMethods fields that are not to be included in our builder.
* @throws InvocationTargetException when an error occurs while
calling a method in our object.
*/
public static void concatenateFieldValues(Object object, Class
clazz, StringBuilder builder,
String[]
excludeMethods) throws InvocationTargetException
{

List excludedMethodList = excludeMethods != null ?
Arrays.asList(excludeMethods) : Collections.EMPTY_LIST;

Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods)
{
// only get values from public getter methods that are not
in exclusion list
if ((method.getName().indexOf("get") == 0)
&& !excludedMethodList.contains(method.getName())
&& (Modifier.isPublic(method.getModifiers()))
) {
try {

// this will only deal with bean-type get methods
without params
assert method.getParameterTypes().length == 0;

// TODO - shouldnt we explicitly check and throw
an exception here?
Object fieldValue = method.invoke(object);

if (fieldValue instanceof String) {

builder.append(StringUtils.remove(fieldValue.toString(), ' '));
}
} catch (InvocationTargetException e)
{
throw new InvocationTargetException(e, "Unable to
invoke the method: " + method.getName() +
" from
instance of: " + clazz.getName());
} catch (IllegalAccessException e) {
// this can't happen. Would get a Security exception instead
// throw a runtime exception in case the impossible happens.
throw new InternalError("Unexpected
IllegalAccessException");
}
}
}

}

2 comments:

Chris Marx said...

good deal, thanks!

to make it a little more helpful, i added a few lines

try {

//this will only deal with bean-type get methods without params
assert method.getParameterTypes().length == 0;

// TODO - shouldnt we explicitly check and throw an exception here?
Object fieldValue = method.invoke(object);
builder.append(method.getName()+"=");

if (fieldValue instanceof String) {
builder.append(StringUtils.remove(fieldValue.toString(), ' '));
} else if (fieldValue instanceof Integer) {
builder.append(fieldValue);
}
//regardless add delimiter
builder.append(",");
}

krangsquared said...

Good stuff! Just realised that in the original code, there will be cases where you end up with the same concatenated string as a result.

ie,

Bean1
-field 1 = null
-field 2 = value

Bean2
-field 1 = value
-field 2 = null

in both cases, you get a string of "value" - so it's not a correct value for a hash.

Having the method names in there removes that possibility.

Thanks Chris!