001/** 002 * The TypeSafe classes, and their descendants, need a mechanism to find out what type has been used as a parameter 003 * for the concrete matcher. Unfortunately, this type is lost during type erasure so we need to use reflection 004 * to get it back, by picking out the type of a known parameter to a known method. 005 * The catch is that, with bridging methods, this type is only visible in the class that actually implements 006 * the expected method, so the ReflectiveTypeFinder needs to be applied to that class or a subtype. 007 * 008 * For example, the abstract <code>TypeSafeDiagnosingMatcher<T></code> defines an abstract method 009 * <pre>protected abstract boolean matchesSafely(T item, Description mismatchDescription);</pre> 010 * By default it uses <code>new ReflectiveTypeFinder("matchesSafely", 2, 0); </code> to find the 011 * parameterised type. If we create a <code>TypeSafeDiagnosingMatcher<String></code>, the type 012 * finder will return <code>String.class</code>. 013 * 014 * A <code>FeatureMatcher</code> is an abstract subclass of <code>TypeSafeDiagnosingMatcher</code>. 015 * Although it has a templated implementation of <code>matchesSafely(<T>, Decription);</code>, the 016 * actualy run-time signature of this is <code>matchesSafely(Object, Description);</code>. Instead, 017 * we must find the type by reflecting on the concrete implementation of 018 * <pre>protected abstract U featureValueOf(T actual);</pre> 019 * a method which is declared in <code>FeatureMatcher</code>. 020 * 021 * In short, use this to extract a type from a method in the leaf class of a templated class hierarchy. 022 * 023 * @author Steve Freeman 024 * @author Nat Pryce 025 */ 026package org.hamcrest.internal; 027 028import java.lang.reflect.Method; 029 030public class ReflectiveTypeFinder { 031 private final String methodName; 032 private final int expectedNumberOfParameters; 033 private final int typedParameter; 034 035 public ReflectiveTypeFinder(String methodName, int expectedNumberOfParameters, int typedParameter) { 036 this.methodName = methodName; 037 this.expectedNumberOfParameters = expectedNumberOfParameters; 038 this.typedParameter = typedParameter; 039 } 040 041 public Class<?> findExpectedType(Class<?> fromClass) { 042 for (Class<?> c = fromClass; c != Object.class; c = c.getSuperclass()) { 043 for (Method method : c.getDeclaredMethods()) { 044 if (canObtainExpectedTypeFrom(method)) { 045 return expectedTypeFrom(method); 046 } 047 } 048 } 049 throw new Error("Cannot determine correct type for " + methodName + "() method."); 050 } 051 052 /** 053 * @param method The method to examine. 054 * @return true if this method references the relevant type 055 */ 056 protected boolean canObtainExpectedTypeFrom(Method method) { 057 return method.getName().equals(methodName) 058 && method.getParameterTypes().length == expectedNumberOfParameters 059 && !method.isSynthetic(); 060 } 061 062 063 /** 064 * @param method The method from which to extract 065 * @return The type we're looking for 066 */ 067 protected Class<?> expectedTypeFrom(Method method) { 068 return method.getParameterTypes()[typedParameter]; 069 } 070}