package org.hamcrest;

import org.hamcrest.internal.ReflectiveTypeFinder;

/**
 * Convenient base class for Matchers that require a non-null value of a specific type
 * and that will report why the received value has been rejected.
 * This implements the null check, checks the type and then casts.
 * To use, implement <pre>matchesSafely()</pre>.
 *
 * @param <T>
 *     the matcher type.
 * @author Neil Dunn
 * @author Nat Pryce
 * @author Steve Freeman
 */
public abstract class TypeSafeDiagnosingMatcher<T> extends BaseMatcher<T> {

    private static final ReflectiveTypeFinder TYPE_FINDER = new ReflectiveTypeFinder("matchesSafely", 2, 0);
    private final Class<?> expectedType;

    /**
     * Subclasses should implement this. The item will already have been checked
     * for the specific type and will never be null.
     *
     * @param item
     *     the item.
     * @param mismatchDescription
     *     the mismatch description.
     * @return boolean true/false depending if item matches matcher.
     */
    protected abstract boolean matchesSafely(T item, Description mismatchDescription);

    /**
     * Use this constructor if the subclass that implements <code>matchesSafely</code>
     * is <em>not</em> the class that binds &lt;T&gt; to a type.
     *
     * @param expectedType The expectedType of the actual value.
     */
    protected TypeSafeDiagnosingMatcher(Class<?> expectedType) {
      this.expectedType = expectedType;
    }

    /**
     * Use this constructor if the subclass that implements <code>matchesSafely</code>
     * is <em>not</em> the class that binds &lt;T&gt; to a type.
     *
     * @param typeFinder A type finder to extract the type
     */
    protected TypeSafeDiagnosingMatcher(ReflectiveTypeFinder typeFinder) {
      this.expectedType = typeFinder.findExpectedType(getClass());
    }

    /**
     * The default constructor for simple sub types
     */
    protected TypeSafeDiagnosingMatcher() {
      this(TYPE_FINDER);
    }

    @Override
    @SuppressWarnings("unchecked")
    public final boolean matches(Object item) {
        return item != null
            && expectedType.isInstance(item)
            && matchesSafely((T) item, new Description.NullDescription());
    }

    @SuppressWarnings("unchecked")
    @Override
    public final void describeMismatch(Object item, Description mismatchDescription) {
      if (item == null) {
        mismatchDescription.appendText("was null");
    } else if (!expectedType.isInstance(item)) {
      mismatchDescription.appendText("was ")
            .appendText(item.getClass().getSimpleName())
            .appendText(" ")
            .appendValue(item);
    } else {
        matchesSafely((T) item, mismatchDescription);
      }
    }

}
