001 package org.hamcrest;
002
003 import org.hamcrest.internal.ReflectiveTypeFinder;
004
005 /**
006 * Convenient base class for Matchers that require a non-null value of a specific type.
007 * This simply implements the null check, checks the type and then casts.
008 *
009 * @author Joe Walnes
010 * @author Steve Freeman
011 * @author Nat Pryce
012 */
013 public abstract class TypeSafeMatcher<T> extends BaseMatcher<T> {
014 private static final ReflectiveTypeFinder TYPE_FINDER = new ReflectiveTypeFinder("matchesSafely", 1, 0);
015
016 final private Class<?> expectedType;
017
018 /**
019 * The default constructor for simple sub types
020 */
021 protected TypeSafeMatcher() {
022 this(TYPE_FINDER);
023 }
024
025 /**
026 * Use this constructor if the subclass that implements <code>matchesSafely</code>
027 * is <em>not</em> the class that binds <T> to a type.
028 * @param expectedType The expectedType of the actual value.
029 */
030 protected TypeSafeMatcher(Class<?> expectedType) {
031 this.expectedType = expectedType;
032 }
033
034 /**
035 * Use this constructor if the subclass that implements <code>matchesSafely</code>
036 * is <em>not</em> the class that binds <T> to a type.
037 * @param typeFinder A type finder to extract the type
038 */
039 protected TypeSafeMatcher(ReflectiveTypeFinder typeFinder) {
040 this.expectedType = typeFinder.findExpectedType(getClass());
041 }
042
043 /**
044 * Subclasses should implement this. The item will already have been checked for
045 * the specific type and will never be null.
046 */
047 protected abstract boolean matchesSafely(T item);
048
049 /**
050 * Subclasses should override this. The item will already have been checked for
051 * the specific type and will never be null.
052 */
053 protected void describeMismatchSafely(T item, Description mismatchDescription) {
054 super.describeMismatch(item, mismatchDescription);
055 }
056
057 /**
058 * Methods made final to prevent accidental override.
059 * If you need to override this, there's no point on extending TypeSafeMatcher.
060 * Instead, extend the {@link BaseMatcher}.
061 */
062 @Override
063 @SuppressWarnings({"unchecked"})
064 public final boolean matches(Object item) {
065 return item != null
066 && expectedType.isInstance(item)
067 && matchesSafely((T) item);
068 }
069
070 @SuppressWarnings("unchecked")
071 @Override
072 final public void describeMismatch(Object item, Description description) {
073 if (item == null) {
074 super.describeMismatch(item, description);
075 } else if (! expectedType.isInstance(item)) {
076 description.appendText("was a ")
077 .appendText(item.getClass().getName())
078 .appendText(" (")
079 .appendValue(item)
080 .appendText(")");
081 } else {
082 describeMismatchSafely((T)item, description);
083 }
084 }
085 }