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&lt;T&gt;</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&lt;String&gt;</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(&lt;T&gt;, 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}