package com.vtence.molecule.support; import org.hamcrest.Condition; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeDiagnosingMatcher; import java.lang.reflect.Method; import static org.hamcrest.Condition.matched; import static org.hamcrest.Condition.notMatched; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.beans.PropertyUtil.NO_ARGUMENTS; public class HasMethodWithValue<T> extends TypeSafeDiagnosingMatcher<T> { private static final Condition.Step<Method, Method> WITH_READABLE_METHOD = readableMethod(); private final String methodName; private final Matcher<Object> valueMatcher; public HasMethodWithValue(String methodName, Matcher<?> valueMatcher) { this.methodName = methodName; this.valueMatcher = nastyGenericsWorkaround(valueMatcher); } public boolean matchesSafely(T target, Description mismatch) { return methodOn(target, mismatch) .and(WITH_READABLE_METHOD) .and(withReturnValue(target)) .matching(valueMatcher, "method " + methodName + " "); } @Override public void describeTo(Description description) { description.appendText("has method ").appendValue(methodName).appendText(" with value ") .appendDescriptionOf(valueMatcher); } private Method getMethod(Class<?> clazz, String name) { // first check up the superclass chain for (Class<?> each = clazz; each != null && each != Object.class; each = each.getSuperclass()) { Method candidate = getMethodOn(each, name); if (candidate != null) return candidate; } return null; } private Method getMethodOn(Class<?> clazz, String name) { try { return clazz.getDeclaredMethod(name); } catch (Exception notFound) { } return null; } private Condition<Method> methodOn(T target, Description mismatch) { Method method = getMethod(target.getClass(), methodName); if (method == null) { mismatch.appendText("No method \"" + methodName + "\""); return notMatched(); } return matched(method, mismatch); } private static Condition.Step<Method, Method> readableMethod() { return (method, mismatch) -> { if (method.getReturnType().equals(void.class)) { mismatch.appendText("method \"" + method.getName() + "\" is not readable"); return notMatched(); } return matched(method, mismatch); }; } private Condition.Step<Method, Object> withReturnValue(final T value) { return (method, mismatch) -> { try { return matched(method.invoke(value, NO_ARGUMENTS), mismatch); } catch (Exception e) { mismatch.appendText(e.getMessage()); return notMatched(); } }; } @SuppressWarnings("unchecked") private static Matcher<Object> nastyGenericsWorkaround(Matcher<?> valueMatcher) { return (Matcher<Object>) valueMatcher; } public static <T> Matcher<T> hasMethod(String methodName, Object value) { return hasMethod(methodName, equalTo(value)); } /** * Creates a matcher that matches when the examined object has a method * with the specified name whose value satisfies the specified matcher. * * @param methodName the name of the method to look for * @param valueMatcher a matcher for the return value of the specified method */ public static <T> Matcher<T> hasMethod(String methodName, Matcher<?> valueMatcher) { return new HasMethodWithValue<T>(methodName, valueMatcher); } }