While setting up Hudson for continous integration testing (on a JeOS server), I've come across some strange behaviour I'm hoping the fine people at SO can explain to me.
Our unit tests depend heavily on the use of domain objects, with lots of properties that must be set (due to null constraints in the database). In order to keep our tests readable, we have created a class InstantiationUtils that can instantiate an object and set a series of properties through reflection:
public static <T> T newInstance(final Class<T> type, final KeyValuePair<?>... propertyValues) {
return ReflectionUtils.reflectionOperation(new ReflectionOperation<T>() {
@Override
public T perform() throws Exception {
T object = type.newInstance();
for (KeyValuePair<?> propertyValue : propertyValues) {
String propertyName = propertyValue.getKey();
Object value = propertyValue.getValue();
String setterName = "set" + StringUtils.capitalize(propertyName);
ReflectionUtils.invoke(object, setterName, value);
}
return object;
}
});
}
public static void invoke(final Object target, final String methodName, final Object... params) {
List<Class<?>> parameterTypes = ListUtils.map(asList(params), "class");
Class<?> targetClass = target.getClass();
Method method = MethodUtils.getMatchingAccessibleMethod(targetClass, methodName,
parameterTypes.toArray(new Class<?>[] {}));
invoke(target, method, params);
}
public class Foo {
private String foo;
public void setFoo(final String foo) {
this.foo = foo;
}
}
public class Bar extends Foo {
private String bar;
public void setBar(final String bar) {
this.bar = bar;
}
}
The person who wrote this code unfortunately no longer works for us, but as far as I can see, there is nothing wrong with it. Which is also true for Windows - we use InstantiationUtils throughout our unit tests without any problems.
Linux, however, is different. It turns out that in Linux, the newInstance() method only works for direct (i.e. not inherited) members of the class we want to instantiate.
InstantiationUtils.newInstance(Bar.class, "bar", "12345"); will work, while InstantiationUtils.newInstance(Bar.class, "foo", "98765"); will fail on Linux, with the following exception:
xxx.xxx.xxx.ReflectionUtils$ReflectionException: java.lang.NoSuchMethodException: Property 'foo' has no setter method
On Windows, both calls will work (I know the newInstance signature doesn't match; we have several overloaded newInstance() methods that convert the parameters to KeyValuePairs).
I had a hard time accepting that inherited public methods are treated differently, so I have tested this in all ways I can think of. And it always ends up with the conclusion that under Linux, at least with the above usage of Reflection, we can't access public inherited methods.
On Windows, I use Sun's JRE 1.6.0.11, in Linux it's also Sun, but version 1.6.0.7.
Can anyone confirm if this is correct? Or is the Reflection usage somehow flawed?