diff --git a/common/source/java/ch/systemsx/cisd/common/utilities/ClassUtils.java b/common/source/java/ch/systemsx/cisd/common/utilities/ClassUtils.java index 6c222ec345f285719085a2b8e2e9fbc8f7c32303..ee45ad63f533878e3f0fe8796128a05446334636 100644 --- a/common/source/java/ch/systemsx/cisd/common/utilities/ClassUtils.java +++ b/common/source/java/ch/systemsx/cisd/common/utilities/ClassUtils.java @@ -17,8 +17,13 @@ package ch.systemsx.cisd.common.utilities; import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Proxy; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import ch.systemsx.cisd.common.annotation.Mandatory; @@ -87,11 +92,157 @@ public final class ClassUtils return method; } } - // SecurityException, ClassNotFoundException + // SecurityException, ClassNotFoundException } catch (Exception ex) { throw CheckedExceptionTunnel.wrapIfNecessary(ex); } return null; } + + /** + * The {@link InvocationHandler} for the {@link ClassUtils#createProxy(Object, Class, Class[])} method. + * + * @author Bernd Rinn + */ + private static final class ProxyingInvocationHandler implements InvocationHandler + { + private final Object proxiedObject; + + ProxyingInvocationHandler(Object proxiedObject) + { + this.proxiedObject = proxiedObject; + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable + { + final Method proxyThroughMethod = + proxiedObject.getClass().getMethod(method.getName(), method.getParameterTypes()); + return proxyThroughMethod.invoke(proxiedObject, args); + } + } + + private static boolean fitsInterfaces(Class classToCheck, List<Class> interfaces) + { + for (Class ifs : interfaces) + { + if (ifs.isInterface() == false) + { + System.err.println("'" + ifs.getCanonicalName() + "' is not an interface."); + return false; + } + for (Method method : ifs.getMethods()) + { + try + { + classToCheck.getMethod(method.getName(), method.getParameterTypes()); + } catch (NoSuchMethodException ex) + { + System.err.println("'" + classToCheck.getCanonicalName() + "' does not have a method '" + method + + "'"); + return false; + } + } + } + return true; + } + + /** + * Returns a {@link Proxy} for <var>objectToBeProxied</var>. This allows to retroactively fit a class into a couple + * of interfaces. + * + * @param objectToProxy The object to proxy through to. + * @param mainInterfaceToProvideProxyFor The main interface to provide a proxy for and to cast to. + * @param otherInterfacesToProvideProxyFor Other interfaces to provide a proxy for. + */ + // Proxy guarantees that the type cast is OK. + @SuppressWarnings("unchecked") + public final static <T> T createProxy(Object objectToProxy, Class<T> mainInterfaceToProvideProxyFor, + Class... otherInterfacesToProvideProxyFor) + { + final List<Class> interfaces = new ArrayList<Class>(); + interfaces.add(mainInterfaceToProvideProxyFor); + interfaces.addAll(Arrays.asList(otherInterfacesToProvideProxyFor)); + assert fitsInterfaces(objectToProxy.getClass(), interfaces); + return (T) Proxy.newProxyInstance(ClassUtils.class.getClassLoader(), interfaces.toArray(new Class[interfaces + .size()]), new ProxyingInvocationHandler(objectToProxy)); + } + + private static boolean isNull(Object objectToCheck) + { + return (objectToCheck instanceof Number) && ((Number) objectToCheck).longValue() == 0; + } + + /** + * Checks the list of bean objects item by item for public getters which return <code>null</code> or 0. + * + * @param beanListToCheck The list of beans to check. Can be <code>null</code>. + * @return <var>beanListToCheck</var> (the parameter itself) + * @see #checkGettersNotNull(Object) + * @throws IllegalStateException If at least one of the public getters returns <code>null</code> or 0. + */ + public final static <T> List<T> checkGettersNotNull(List<T> beanListToCheck) + { + if (beanListToCheck == null) + { + return beanListToCheck; + } + for (Object bean : beanListToCheck) + { + checkGettersNotNull(bean); + } + return beanListToCheck; + } + + /** + * Checks bean object for public getters which return <code>null</code> or 0. + * + * @param beanToCheck The bean to check. Can be <code>null</code>. Must not be an array type. + * @return <var>beanToCheck</var> (the parameter itself) + * @throws IllegalArgumentException If the <var>beanToCheck</var> is an array type. + * @throws IllegalStateException If at least one of the public getters returns <code>null</code> or 0. + */ + public final static <T> T checkGettersNotNull(T beanToCheck) + { + if (beanToCheck == null) + { + return beanToCheck; + } + if (beanToCheck.getClass().isArray()) + { + throw new IllegalArgumentException("Arrays are not supported."); + } + for (Method method : beanToCheck.getClass().getMethods()) + { + if (method.getName().startsWith("get") && method.getParameterTypes().length == 0 + && Modifier.isPublic(method.getModifiers())) + { + try + { + final Object result = method.invoke(beanToCheck, new Object[0]); + if (result == null) + { + throw new IllegalStateException("Method '" + method.getName() + "' returns null."); + } else if (isNull(result)) + { + throw new IllegalStateException("Method '" + method.getName() + "' returns 0."); + } + } catch (InvocationTargetException ex) + { + final Throwable cause = ex.getCause(); + if (cause instanceof Error) + { + throw (Error) cause; + } + throw CheckedExceptionTunnel.wrapIfNecessary((Exception) cause); + } catch (IllegalAccessException ex) + { + // Can't happen since we checked for isAccessible() + throw new Error("Cannot call method '" + method.getName() + "'."); + } + } + } + return beanToCheck; + } + } diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/utilities/ClassUtilsTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/utilities/ClassUtilsTest.java index a00ed2a2852943d0849fbe8731e05b4bf4ddd429..2a65473aaad310effeb811b0b73e01fde09a2d75 100644 --- a/common/sourceTest/java/ch/systemsx/cisd/common/utilities/ClassUtilsTest.java +++ b/common/sourceTest/java/ch/systemsx/cisd/common/utilities/ClassUtilsTest.java @@ -17,6 +17,11 @@ package ch.systemsx.cisd.common.utilities; import static org.testng.AssertJUnit.*; + +import java.util.Arrays; +import java.util.List; + +import org.testng.AssertJUnit; import org.testng.annotations.Test; /** @@ -35,5 +40,83 @@ public class ClassUtilsTest { assertEquals("testGetCurrentMethod", ClassUtils.getCurrentMethod().getName()); } + + private static class SimpleBean + { + private final int number; + private final String string; + + SimpleBean(int number, String string) + { + this.number = number; + this.string = string; + } + + public int getNumber() + { + return number; + } + + public String getString() + { + return string; + } + + String getIgnoreThisBecauseItIsNotPublic() + { + AssertJUnit.fail("Should be ignore because not public"); + return null; + } + } + + @Test + public void testCheckGettersForNullOK() + { + final SimpleBean bean = new SimpleBean(1, ""); + assert ClassUtils.checkGettersNotNull(bean) == bean; + } + + @Test + public void testCheckGettersForNullOKNullBean() + { + assertNull(ClassUtils.checkGettersNotNull(null)); + } + + @Test(expectedExceptions = IllegalStateException.class) + public void testCheckGettersForNullStringNull() + { + final SimpleBean bean = new SimpleBean(1, null); + ClassUtils.checkGettersNotNull(bean); + } + + @Test(expectedExceptions = IllegalStateException.class) + public void testCheckGettersForNullInt0() + { + final SimpleBean bean = new SimpleBean(0, "test"); + ClassUtils.checkGettersNotNull(bean); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testCheckGettersForNullForbiddenArray() + { + ClassUtils.checkGettersNotNull(new Object[0]); + } + + @Test + public void testCheckGettersForNullListOK() + { + final SimpleBean bean1 = new SimpleBean(1, "test"); + final SimpleBean bean2 = new SimpleBean(5, "test2"); + final List<SimpleBean> beanList = Arrays.asList(bean1, bean2); + assert ClassUtils.checkGettersNotNull(beanList) == beanList; + } + + @Test(expectedExceptions = IllegalStateException.class) + public void testCheckGettersForNullListInt0() + { + final SimpleBean bean1 = new SimpleBean(1, "test"); + final SimpleBean bean2 = new SimpleBean(0, "test2"); + ClassUtils.checkGettersNotNull(Arrays.asList(bean1, bean2)); + } }