1/*
2 * Copyright (c) 2007 Mockito contributors
3 * This program is made available under the terms of the MIT License.
4 */
5package org.mockito.internal.invocation;
6
7import java.lang.reflect.Method;
8
9import org.mockito.ArgumentMatcher;
10
11@SuppressWarnings({"unchecked","rawtypes"})
12public class TypeSafeMatching implements ArgumentMatcherAction {
13
14    private final static ArgumentMatcherAction TYPE_SAFE_MATCHING_ACTION = new TypeSafeMatching();
15
16    private TypeSafeMatching() {}
17
18
19    public static ArgumentMatcherAction matchesTypeSafe(){
20        return TYPE_SAFE_MATCHING_ACTION;
21    }
22    @Override
23    public boolean apply(ArgumentMatcher matcher, Object argument) {
24        return isCompatible(matcher, argument) && matcher.matches(argument);
25    }
26
27
28    /**
29     * Returns <code>true</code> if the given <b>argument</b> can be passed to
30     * the given <code>argumentMatcher</code> without causing a
31     * {@link ClassCastException}.
32     */
33    private static boolean isCompatible(ArgumentMatcher<?> argumentMatcher, Object argument) {
34        if (argument == null)
35            return true;
36
37        Class<?> expectedArgumentType = getArgumentType(argumentMatcher);
38
39        return expectedArgumentType.isInstance(argument);
40    }
41
42    /**
43     * Returns the type of {@link ArgumentMatcher#matches(Object)} of the given
44     * {@link ArgumentMatcher} implementation.
45     */
46    private static Class<?> getArgumentType(ArgumentMatcher<?> argumentMatcher) {
47        Method[] methods = argumentMatcher.getClass().getMethods();
48
49        for (Method method : methods) {
50            if (isMatchesMethod(method)) {
51                return method.getParameterTypes()[0];
52            }
53        }
54        throw new NoSuchMethodError("Method 'matches(T)' not found in ArgumentMatcher: " + argumentMatcher + " !\r\n Please file a bug with this stack trace at: https://github.com/mockito/mockito/issues/new ");
55    }
56
57    /**
58     * Returns <code>true</code> if the given method is
59     * {@link ArgumentMatcher#matches(Object)}
60     */
61    private static boolean isMatchesMethod(Method method) {
62        if (method.getParameterTypes().length != 1) {
63            return false;
64        }
65        if (method.isBridge()) {
66            return false;
67        }
68        return method.getName().equals("matches");
69    }
70}
71