1/*
2 * Copyright (c) 2007 Mockito contributors
3 * This program is made available under the terms of the MIT License.
4 */
5
6package org.mockito.internal.invocation;
7
8import static org.mockito.internal.invocation.ArgumentsProcessor.argumentsToMatchers;
9import static org.mockito.internal.invocation.MatcherApplicationStrategy.getMatcherApplicationStrategyFor;
10import static org.mockito.internal.invocation.TypeSafeMatching.matchesTypeSafe;
11
12import java.io.Serializable;
13import java.lang.reflect.Method;
14import java.util.Arrays;
15import java.util.Collections;
16import java.util.LinkedList;
17import java.util.List;
18import org.mockito.ArgumentMatcher;
19import org.mockito.internal.matchers.CapturesArguments;
20import org.mockito.internal.reporting.PrintSettings;
21import org.mockito.invocation.DescribedInvocation;
22import org.mockito.invocation.Invocation;
23import org.mockito.invocation.Location;
24import org.mockito.invocation.MatchableInvocation;
25
26/**
27 * In addition to all content of the invocation, the invocation matcher contains the argument matchers. Invocation matcher is used during verification and stubbing. In those cases, the user can provide argument matchers instead of 'raw' arguments. Raw arguments are converted to 'equals' matchers anyway.
28 */
29@SuppressWarnings("serial")
30public class InvocationMatcher implements MatchableInvocation, DescribedInvocation, Serializable {
31
32    private final Invocation invocation;
33    private final List<ArgumentMatcher<?>> matchers;
34
35    @SuppressWarnings({ "rawtypes", "unchecked" })
36    public InvocationMatcher(Invocation invocation, List<ArgumentMatcher> matchers) {
37        this.invocation = invocation;
38        if (matchers.isEmpty()) {
39            this.matchers = (List) argumentsToMatchers(invocation.getArguments());
40        } else {
41            this.matchers = (List) matchers;
42        }
43    }
44
45    @SuppressWarnings("rawtypes")
46    public InvocationMatcher(Invocation invocation) {
47        this(invocation, Collections.<ArgumentMatcher> emptyList());
48    }
49
50    public static List<InvocationMatcher> createFrom(List<Invocation> invocations) {
51        LinkedList<InvocationMatcher> out = new LinkedList<InvocationMatcher>();
52        for (Invocation i : invocations) {
53            out.add(new InvocationMatcher(i));
54        }
55        return out;
56    }
57
58    public Method getMethod() {
59        return invocation.getMethod();
60    }
61
62    @Override
63    public Invocation getInvocation() {
64        return invocation;
65    }
66
67    @Override
68    @SuppressWarnings({ "unchecked", "rawtypes" })
69    public List<ArgumentMatcher> getMatchers() {
70        return (List) matchers;
71    }
72
73    @Override
74    @SuppressWarnings({ "unchecked", "rawtypes" })
75    public String toString() {
76        return new PrintSettings().print((List) matchers, invocation);
77    }
78
79    @Override
80    public boolean matches(Invocation candidate) {
81        return invocation.getMock().equals(candidate.getMock()) && hasSameMethod(candidate) && argumentsMatch(candidate);
82    }
83
84    /**
85     * similar means the same method name, same mock, unverified and: if arguments are the same cannot be overloaded
86     */
87    @Override
88    public boolean hasSimilarMethod(Invocation candidate) {
89        String wantedMethodName = getMethod().getName();
90        String candidateMethodName = candidate.getMethod().getName();
91
92        if (!wantedMethodName.equals(candidateMethodName)) {
93            return false;
94        }
95        if (candidate.isVerified()) {
96            return false;
97        }
98        if (getInvocation().getMock() != candidate.getMock()) {
99            return false;
100        }
101        if (hasSameMethod(candidate)) {
102            return true;
103        }
104
105        return !argumentsMatch(candidate);
106    }
107
108    @Override
109    public boolean hasSameMethod(Invocation candidate) {
110        // not using method.equals() for 1 good reason:
111        // sometimes java generates forwarding methods when generics are in play see JavaGenericsForwardingMethodsTest
112        Method m1 = invocation.getMethod();
113        Method m2 = candidate.getMethod();
114
115        if (m1.getName() != null && m1.getName().equals(m2.getName())) {
116            /* Avoid unnecessary cloning */
117            Class<?>[] params1 = m1.getParameterTypes();
118            Class<?>[] params2 = m2.getParameterTypes();
119            return Arrays.equals(params1, params2);
120        }
121        return false;
122    }
123
124    @Override
125    public Location getLocation() {
126        return invocation.getLocation();
127    }
128
129    @Override
130    public void captureArgumentsFrom(Invocation invocation) {
131        MatcherApplicationStrategy strategy = getMatcherApplicationStrategyFor(invocation, matchers);
132        strategy.forEachMatcherAndArgument(captureArgument());
133    }
134
135    private ArgumentMatcherAction captureArgument() {
136        return new ArgumentMatcherAction() {
137
138            @Override
139            public boolean apply(ArgumentMatcher<?> matcher, Object argument) {
140                if (matcher instanceof CapturesArguments) {
141                    ((CapturesArguments) matcher).captureFrom(argument);
142                }
143
144                return true;
145            }
146        };
147    }
148
149    @SuppressWarnings({ "rawtypes", "unchecked" })
150    private boolean argumentsMatch(Invocation actual) {
151        List matchers = getMatchers();
152        return getMatcherApplicationStrategyFor(actual, matchers).forEachMatcherAndArgument( matchesTypeSafe());
153    }
154}
155