/** * The TypeSafe classes, and their descendants, need a mechanism to find out what type has been used as a parameter * for the concrete matcher. Unfortunately, this type is lost during type erasure so we need to use reflection * to get it back, by picking out the type of a known parameter to a known method. * The catch is that, with bridging methods, this type is only visible in the class that actually implements * the expected method, so the ReflectiveTypeFinder needs to be applied to that class or a subtype. * * For example, the abstract <code>TypeSafeDiagnosingMatcher<T></code> defines an abstract method * <pre>protected abstract boolean matchesSafely(T item, Description mismatchDescription);</pre> * By default it uses <code>new ReflectiveTypeFinder("matchesSafely", 2, 0); </code> to find the * parameterised type. If we create a <code>TypeSafeDiagnosingMatcher<String></code>, the type * finder will return <code>String.class</code>. * * A <code>FeatureMatcher</code> is an abstract subclass of <code>TypeSafeDiagnosingMatcher</code>. * Although it has a templated implementation of <code>matchesSafely(<T>, Description);</code>, the * actual run-time signature of this is <code>matchesSafely(Object, Description);</code>. Instead, * we must find the type by reflecting on the concrete implementation of * <pre>protected abstract U featureValueOf(T actual);</pre> * a method which is declared in <code>FeatureMatcher</code>. * * In short, use this to extract a type from a method in the leaf class of a templated class hierarchy. * * @author Steve Freeman * @author Nat Pryce */ package org.hamcrest.internal; import java.lang.reflect.Method; public class ReflectiveTypeFinder { private final String methodName; private final int expectedNumberOfParameters; private final int typedParameter; public ReflectiveTypeFinder(String methodName, int expectedNumberOfParameters, int typedParameter) { this.methodName = methodName; this.expectedNumberOfParameters = expectedNumberOfParameters; this.typedParameter = typedParameter; } public Class<?> findExpectedType(Class<?> fromClass) { for (Class<?> c = fromClass; c != Object.class; c = c.getSuperclass()) { for (Method method : c.getDeclaredMethods()) { if (canObtainExpectedTypeFrom(method)) { return expectedTypeFrom(method); } } } throw new Error("Cannot determine correct type for " + methodName + "() method."); } /** * @param method The method to examine. * @return true if this method references the relevant type */ protected boolean canObtainExpectedTypeFrom(Method method) { return method.getName().equals(methodName) && method.getParameterTypes().length == expectedNumberOfParameters && !method.isSynthetic(); } /** * @param method The method from which to extract * @return The type we're looking for */ protected Class<?> expectedTypeFrom(Method method) { return method.getParameterTypes()[typedParameter]; } }