/** * Filter on methods not annotated with the given annotation type. */ @SafeVarargs public final Builder<T> annotNotPresent(Class<? extends Annotation>... annotationTypes) { String message = "annotationNotPresent=" + Arrays.toString(annotationTypes); addFilter(message, method -> { if (annotationTypes.length != 0) { return Arrays.stream(annotationTypes).noneMatch(annotType -> AnnotatedElementUtils.findMergedAnnotation(method, annotType) != null); } else { return method.getAnnotations().length == 0; } }); return this; }
@Override public String toString() { return "ResolvableMethod.Builder[\n" + "\tobjectClass = " + this.objectClass.getName() + ",\n" + "\tfilters = " + formatFilters() + "\n]"; }
/** * Build a {@code ResolvableMethod} from the provided filters which must * resolve to a unique, single method. * <p>See additional resolveXxx shortcut methods going directly to * {@link Method} or return type parameter. * @throws IllegalStateException for no match or multiple matches */ public ResolvableMethod build() { Set<Method> methods = MethodIntrospector.selectMethods(this.objectClass, this::isMatch); Assert.state(!methods.isEmpty(), () -> "No matching method: " + this); Assert.state(methods.size() == 1, () -> "Multiple matching methods: " + this + formatMethods(methods)); return new ResolvableMethod(methods.iterator().next()); }
@Test public void invocationTargetException() throws Exception { Handler handler = new Handler(); Method method = ResolvableMethod.on(Handler.class).argTypes(Throwable.class).resolveMethod(); Throwable expected = null; try {
@Test public void illegalArgumentException() throws Exception { this.resolvers.addResolver(new StubArgumentResolver(Integer.class, "__not_an_int__")); this.resolvers.addResolver(new StubArgumentResolver("value")); try { Method method = ResolvableMethod.on(Handler.class).mockCall(c -> c.handle(0, "")).method(); invoke(new Handler(), method); fail("Expected exception"); } catch (IllegalStateException ex) { assertNotNull("Exception not wrapped", ex.getCause()); assertTrue(ex.getCause() instanceof IllegalArgumentException); assertTrue(ex.getMessage().contains("Endpoint [")); assertTrue(ex.getMessage().contains("Method [")); assertTrue(ex.getMessage().contains("with argument values:")); assertTrue(ex.getMessage().contains("[0] [type=java.lang.String] [value=__not_an_int__]")); assertTrue(ex.getMessage().contains("[1] [type=java.lang.String] [value=value")); } }
@Test public void resolveArg() throws Exception { this.resolvers.addResolver(new StubArgumentResolver(99)); this.resolvers.addResolver(new StubArgumentResolver("value")); Method method = ResolvableMethod.on(Handler.class).mockCall(c -> c.handle(0, "")).method(); Object value = invoke(new Handler(), method); assertEquals(1, getStubResolver(0).getResolvedParameters().size()); assertEquals(1, getStubResolver(1).getResolvedParameters().size()); assertEquals("99-value", value); assertEquals("intArg", getStubResolver(0).getResolvedParameters().get(0).getParameterName()); assertEquals("stringArg", getStubResolver(1).getResolvedParameters().get(0).getParameterName()); }
/** * Filter on methods returning the given type with generics. * @param returnType the return type * @param generic at least one generic type * @param generics optional extra generic types */ public Builder<T> returning(Class<?> returnType, ResolvableType generic, ResolvableType... generics) { return returning(toResolvableType(returnType, generic, generics)); }
/** * Create a {@code ResolvableMethod} builder for the given handler class. */ public static <T> Builder<T> on(Class<T> objectClass) { return new Builder<>(objectClass); }
/** * Filter on methods returning the given type. * @param returnType the return type */ public Builder<T> returning(ResolvableType returnType) { String expected = returnType.toString(); String message = "returnType=" + expected; addFilter(message, m -> expected.equals(ResolvableType.forMethodReturnType(m).toString())); return this; }
/** * Filter on methods annotated with the given annotation type. * @see #annot(Predicate[]) */ @SafeVarargs public final Builder<T> annotPresent(Class<? extends Annotation>... annotationTypes) { String message = "annotationPresent=" + Arrays.toString(annotationTypes); addFilter(message, method -> Arrays.stream(annotationTypes).allMatch(annotType -> AnnotatedElementUtils.findMergedAnnotation(method, annotType) != null)); return this; }
/** * Filter on methods with the given name. */ public Builder<T> named(String methodName) { addFilter("methodName=" + methodName, method -> method.getName().equals(methodName)); return this; }
/** * Filter on methods returning the given type. * @param returnType the return type * @param generics optional array of generic types */ public Builder<T> returning(Class<?> returnType, Class<?>... generics) { return returning(toResolvableType(returnType, generics)); }
@Test public void resolveProvidedArgFirst() throws Exception { this.resolvers.addResolver(new StubArgumentResolver(1)); this.resolvers.addResolver(new StubArgumentResolver("value1")); Method method = ResolvableMethod.on(Handler.class).mockCall(c -> c.handle(0, "")).method(); Object value = invoke(new Handler(), method, 2, "value2"); assertEquals("2-value2", value); }
@Test // Based on SPR-13917 (spring-web) public void invocationErrorMessage() throws Exception { this.resolvers.addResolver(new StubArgumentResolver(double.class)); try { Method method = ResolvableMethod.on(Handler.class).mockCall(c -> c.handle(0.0)).method(); invoke(new Handler(), method); fail(); } catch (IllegalStateException ex) { assertThat(ex.getMessage(), containsString("Illegal argument")); } }
@Test public void resolveProvidedArg() throws Exception { Method method = ResolvableMethod.on(Handler.class).mockCall(c -> c.handle(0, "")).method(); Object value = invoke(new Handler(), method, 99, "value"); assertNotNull(value); assertEquals(String.class, value.getClass()); assertEquals("99-value", value); }
/** * Resolve and return the declared return type equivalent to: * <p>{@code build().returnType()} */ public final MethodParameter resolveReturnType() { return build().returnType(); }
/** * Resolve and return the {@code Method} equivalent to: * <p>{@code build().method()} */ public final Method resolveMethod() { return build().method(); }
/** * Filter on methods with the given parameter types. */ public Builder<T> argTypes(Class<?>... argTypes) { addFilter("argTypes=" + Arrays.toString(argTypes), method -> ObjectUtils.isEmpty(argTypes) ? method.getParameterCount() == 0 : Arrays.equals(method.getParameterTypes(), argTypes)); return this; }
@Test public void exceptionInResolvingArg() throws Exception { this.resolvers.addResolver(new ExceptionRaisingArgumentResolver()); try { Method method = ResolvableMethod.on(Handler.class).mockCall(c -> c.handle(0, "")).method(); invoke(new Handler(), method); fail("Expected exception"); } catch (IllegalArgumentException ex) { // expected - allow HandlerMethodArgumentResolver exceptions to propagate } }