summaryrefslogtreecommitdiff
path: root/src/main/java/org/mockito/internal/invocation/InvocationMatcher.java
blob: 83a28ff9de551cb440d798efcac41b7421789c89 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/*
 * Copyright (c) 2007 Mockito contributors
 * This program is made available under the terms of the MIT License.
 */

package org.mockito.internal.invocation;

import static org.mockito.internal.invocation.ArgumentsProcessor.argumentsToMatchers;
import static org.mockito.internal.invocation.MatcherApplicationStrategy.getMatcherApplicationStrategyFor;
import static org.mockito.internal.invocation.TypeSafeMatching.matchesTypeSafe;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import org.mockito.ArgumentMatcher;
import org.mockito.internal.matchers.CapturesArguments;
import org.mockito.internal.reporting.PrintSettings;
import org.mockito.invocation.DescribedInvocation;
import org.mockito.invocation.Invocation;
import org.mockito.invocation.Location;
import org.mockito.invocation.MatchableInvocation;

/**
 * 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.
 */
@SuppressWarnings("serial")
public class InvocationMatcher implements MatchableInvocation, DescribedInvocation, Serializable {

    private final Invocation invocation;
    private final List<ArgumentMatcher<?>> matchers;

    @SuppressWarnings({ "rawtypes", "unchecked" })
    public InvocationMatcher(Invocation invocation, List<ArgumentMatcher> matchers) {
        this.invocation = invocation;
        if (matchers.isEmpty()) {
            this.matchers = (List) argumentsToMatchers(invocation.getArguments());
        } else {
            this.matchers = (List) matchers;
        }
    }

    @SuppressWarnings("rawtypes")
    public InvocationMatcher(Invocation invocation) {
        this(invocation, Collections.<ArgumentMatcher> emptyList());
    }

    public static List<InvocationMatcher> createFrom(List<Invocation> invocations) {
        LinkedList<InvocationMatcher> out = new LinkedList<InvocationMatcher>();
        for (Invocation i : invocations) {
            out.add(new InvocationMatcher(i));
        }
        return out;
    }

    public Method getMethod() {
        return invocation.getMethod();
    }

    @Override
    public Invocation getInvocation() {
        return invocation;
    }

    @Override
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public List<ArgumentMatcher> getMatchers() {
        return (List) matchers;
    }

    @Override
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public String toString() {
        return new PrintSettings().print((List) matchers, invocation);
    }

    @Override
    public boolean matches(Invocation candidate) {
        return invocation.getMock().equals(candidate.getMock()) && hasSameMethod(candidate) && argumentsMatch(candidate);
    }

    /**
     * similar means the same method name, same mock, unverified and: if arguments are the same cannot be overloaded
     */
    @Override
    public boolean hasSimilarMethod(Invocation candidate) {
        String wantedMethodName = getMethod().getName();
        String candidateMethodName = candidate.getMethod().getName();

        if (!wantedMethodName.equals(candidateMethodName)) {
            return false;
        }
        if (candidate.isVerified()) {
            return false;
        }
        if (getInvocation().getMock() != candidate.getMock()) {
            return false;
        }
        if (hasSameMethod(candidate)) {
            return true;
        }

        return !argumentsMatch(candidate);
    }

    @Override
    public boolean hasSameMethod(Invocation candidate) {
        // not using method.equals() for 1 good reason:
        // sometimes java generates forwarding methods when generics are in play see JavaGenericsForwardingMethodsTest
        Method m1 = invocation.getMethod();
        Method m2 = candidate.getMethod();

        if (m1.getName() != null && m1.getName().equals(m2.getName())) {
            /* Avoid unnecessary cloning */
            Class<?>[] params1 = m1.getParameterTypes();
            Class<?>[] params2 = m2.getParameterTypes();
            return Arrays.equals(params1, params2);
        }
        return false;
    }

    @Override
    public Location getLocation() {
        return invocation.getLocation();
    }

    @Override
    public void captureArgumentsFrom(Invocation invocation) {
        MatcherApplicationStrategy strategy = getMatcherApplicationStrategyFor(invocation, matchers);
        strategy.forEachMatcherAndArgument(captureArgument());
    }

    private ArgumentMatcherAction captureArgument() {
        return new ArgumentMatcherAction() {

            @Override
            public boolean apply(ArgumentMatcher<?> matcher, Object argument) {
                if (matcher instanceof CapturesArguments) {
                    ((CapturesArguments) matcher).captureFrom(argument);
                }

                return true;
            }
        };
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    private boolean argumentsMatch(Invocation actual) {
        List matchers = getMatchers();
        return getMatcherApplicationStrategyFor(actual, matchers).forEachMatcherAndArgument( matchesTypeSafe());
    }
}