BoundedDiagnosingMatcher.java
package androidx.test.espresso.matcher;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.ArrayList;
import java.util.List;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.hamcrest.StringDescription;
/**
* A matcher that allows for a quick creation of a matcher that applies to a given type but only
* processes items of a specific subtype of that matcher. Additional interfaces can be applied. This
* class is syntactic sugar for {@link Matchers#instanceOf(Class)} where the first argument is the
* base class and the remaining optional arguments are interfaces.
*
* @param <S> The desired type of the Matcher (T or a subclass of T).
* @param <T> The actual type that the matcher applies safely to.
*/
public abstract class BoundedDiagnosingMatcher<S, T extends S> extends BaseMatcher<S> {
private final Matcher<Class<?>> matcher;
public BoundedDiagnosingMatcher(Class<? extends S> expectedType) {
matcher = Matchers.instanceOf(checkNotNull(expectedType));
}
public BoundedDiagnosingMatcher(
Class<? extends S> expectedType, Class<?> interfaceType1, Class<?>... otherInterfaces) {
int interfaceCount = otherInterfaces.length + 2;
List<Matcher<? super Class<?>>> instanceMatchers = new ArrayList<>(interfaceCount);
instanceMatchers.add(Matchers.instanceOf(checkNotNull(expectedType)));
checkNotNull(otherInterfaces);
instanceMatchers.add(Matchers.instanceOf(checkNotNull(interfaceType1)));
checkArgument(interfaceType1.isInterface());
for (Class<?> intfType : otherInterfaces) {
instanceMatchers.add(Matchers.instanceOf(checkNotNull(intfType)));
checkArgument(intfType.isInterface());
}
matcher = Matchers.allOf(instanceMatchers);
}
/**
* Subclasses should implement this. The item will already have been checked for the specific
* type, interfaces, and will never be null.
*
* @param item The pre-checked item.
* @param mismatchDescription A {@link Description} to write to for mismatches.
* @return {@code true} if the item matches the expectations for this {@link Matcher}.
*/
protected abstract boolean matchesSafely(T item, Description mismatchDescription);
/**
* Subclasses should implement this. The fine details of the matcher should be added to the
* description. Type checking information will have already been added.
*
* @param description The {@link Description} object to write to.
*/
protected abstract void describeMoreTo(Description description);
@Override
public final void describeTo(Description description) {
matcher.describeTo(description);
Description implDescription = new StringDescription();
describeMoreTo(implDescription);
// workaround for #1351 - javac generates bytecode not handled by older ART versions
String implDescriptionString = ((Object) implDescription).toString();
if (!implDescriptionString.isEmpty()) {
description.appendText(" and ").appendText(implDescriptionString);
}
}
@Override
@SuppressWarnings("unchecked")
public final boolean matches(Object item) {
return item != null && matcher.matches(item) && matchesSafely((T) item, Description.NONE);
}
/**
* This method provides a default implementation for {@code null} check as well as a super type
* and interface checks provided by the constructor. Failing either check provides a default
* mismatch description. Passing both will call into {@link #matchesSafely(Object, Description)}
* which will allow the sub-class to check for a mismatch and describe what went wrong (if
* anything at all).
*
* @param item The item which is assumed to have mismatched and should be described.
* @param mismatchDescription The description builder for the mismatch.
* @see org.hamcrest.TypeSafeDiagnosingMatcher for similar implementation pattern.
*/
@Override
@SuppressWarnings("unchecked")
public final void describeMismatch(Object item, Description mismatchDescription) {
if (item == null) {
mismatchDescription.appendText("was null");
} else if (!matcher.matches(item)) {
matcher.describeMismatch(item, mismatchDescription);
} else {
matchesSafely((T) item, mismatchDescription);
}
}
}