/*
 * Copyright (c) 2007 Mockito contributors
 * This program is made available under the terms of the MIT License.
 */
package org.mockito.internal.invocation;

import java.lang.reflect.Method;

import org.mockito.ArgumentMatcher;

@SuppressWarnings({"unchecked", "rawtypes"})
public class TypeSafeMatching implements ArgumentMatcherAction {

    private static final ArgumentMatcherAction TYPE_SAFE_MATCHING_ACTION = new TypeSafeMatching();

    private TypeSafeMatching() {}

    public static ArgumentMatcherAction matchesTypeSafe() {
        return TYPE_SAFE_MATCHING_ACTION;
    }

    @Override
    public boolean apply(ArgumentMatcher matcher, Object argument) {
        return isCompatible(matcher, argument) && matcher.matches(argument);
    }

    /**
     * Returns <code>true</code> if the given <b>argument</b> can be passed to
     * the given <code>argumentMatcher</code> without causing a
     * {@link ClassCastException}.
     */
    private static boolean isCompatible(ArgumentMatcher<?> argumentMatcher, Object argument) {
        if (argument == null) {
            return true;
        }

        Class<?> expectedArgumentType = getArgumentType(argumentMatcher);

        return expectedArgumentType.isInstance(argument);
    }

    /**
     * Returns the type of {@link ArgumentMatcher#matches(Object)} of the given
     * {@link ArgumentMatcher} implementation.
     */
    private static Class<?> getArgumentType(ArgumentMatcher<?> argumentMatcher) {
        Method[] methods = argumentMatcher.getClass().getMethods();

        for (Method method : methods) {
            if (isMatchesMethod(method)) {
                return method.getParameterTypes()[0];
            }
        }
        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 ");
    }

    /**
     * Returns <code>true</code> if the given method is
     * {@link ArgumentMatcher#matches(Object)}
     */
    private static boolean isMatchesMethod(Method method) {
        if (method.getParameterTypes().length != 1) {
            return false;
        }
        if (method.isBridge()) {
            return false;
        }
        return "matches".equals(method.getName());
    }
}

@@ -155,16 +155,53 @@
 ation;%0A%0A
+import org.mockito.ArgumentMatcher;%0A%0A
 import j
@@ -229,43 +229,96 @@
 od;%0A
-%0A
 import 
-org.mockito.ArgumentMatcher
+java.util.concurrent.ConcurrentHashMap;%0Aimport java.util.concurrent.ConcurrentMap
 ;%0A%0A@
@@ -523,24 +523,521 @@
 atching();%0A%0A
+    /**%0A     * This cache is in theory unbounded. However, its max size is bounded by the number of types of argument matchers%0A     * that are both in the system and being used, which is expected to bound the cache's size to a low number%0A     * (%3C200) in all but the most contrived cases, and form a small percentage of the overall memory usage of those%0A     * classes.%0A     */%0A    private static final ConcurrentMap%3CClass%3C?%3E, Class%3C?%3E%3E argumentTypeCache =%0A            new ConcurrentHashMap%3C%3E();%0A%0A
     private 
@@ -1824,32 +1824,558 @@
 gument);%0A    %7D%0A%0A
+    private static Class%3C?%3E getArgumentType(ArgumentMatcher%3C?%3E matcher) %7B%0A        Class%3C?%3E argumentMatcherType = matcher.getClass();%0A        Class%3C?%3E cached = argumentTypeCache.get(argumentMatcherType);%0A        // Avoids a lambda allocation on invocations %3E=2 for worse perf on invocation 1.%0A        if (cached != null) %7B%0A            return cached;%0A        %7D else %7B%0A            return argumentTypeCache.computeIfAbsent(%0A                    argumentMatcherType, unusedKey -%3E getArgumentTypeUncached(matcher));%0A        %7D%0A    %7D%0A%0A
     /**%0A     * R
@@ -2536,32 +2536,40 @@
  getArgumentType
+Uncached
 (ArgumentMatcher

Optimize `TypeSafeMatching` iteration over class methods (#2729)