1e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson/*
2e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson * Copyright (c) 2007 Mockito contributors
3e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson * This program is made available under the terms of the MIT License.
4e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson */
5e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson
6e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinsonpackage org.mockito.internal.invocation;
7e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson
8e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinsonimport java.io.Serializable;
9e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinsonimport java.lang.reflect.Method;
10e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinsonimport java.util.Collections;
11e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinsonimport java.util.LinkedList;
12e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinsonimport java.util.List;
13e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson
14e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinsonimport org.hamcrest.Matcher;
15e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinsonimport org.mockito.internal.matchers.CapturesArguments;
16e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinsonimport org.mockito.internal.matchers.MatcherDecorator;
17e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinsonimport org.mockito.internal.matchers.VarargMatcher;
18e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinsonimport org.mockito.internal.reporting.PrintSettings;
19e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinsonimport org.mockito.invocation.DescribedInvocation;
20e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinsonimport org.mockito.invocation.Invocation;
21e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinsonimport org.mockito.invocation.Location;
22e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson
23e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson@SuppressWarnings("unchecked")
24e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinsonpublic class InvocationMatcher implements DescribedInvocation, CapturesArgumensFromInvocation, Serializable {
25e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson
26e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    private static final long serialVersionUID = -3047126096857467610L;
27e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    private final Invocation invocation;
28e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    private final List<Matcher> matchers;
29e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson
30e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    public InvocationMatcher(Invocation invocation, List<Matcher> matchers) {
31e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        this.invocation = invocation;
32e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        if (matchers.isEmpty()) {
33e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson            this.matchers = ArgumentsProcessor.argumentsToMatchers(invocation.getArguments());
34e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        } else {
35e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson            this.matchers = matchers;
36e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        }
37e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    }
38e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson
39e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    public InvocationMatcher(Invocation invocation) {
40e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        this(invocation, Collections.<Matcher>emptyList());
41e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    }
42e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson
43e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    public Method getMethod() {
44e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        return invocation.getMethod();
45e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    }
46e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson
47e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    public Invocation getInvocation() {
48e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        return this.invocation;
49e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    }
50e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson
51e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    public List<Matcher> getMatchers() {
52e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        return this.matchers;
53e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    }
54e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson
55e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    public String toString() {
56e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        return new PrintSettings().print(matchers, invocation);
57e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    }
58e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson
59e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    public boolean matches(Invocation actual) {
60e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        return invocation.getMock().equals(actual.getMock())
61e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson                && hasSameMethod(actual)
62e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson                && new ArgumentsComparator().argumentsMatch(this, actual);
63e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    }
64e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson
65e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    private boolean safelyArgumentsMatch(Object[] actualArgs) {
66e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        try {
67e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson            return new ArgumentsComparator().argumentsMatch(this, actualArgs);
68e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        } catch (Throwable t) {
69e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson            return false;
70e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        }
71e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    }
72e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson
73e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    /**
74e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson     * similar means the same method name, same mock, unverified
75e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson     * and: if arguments are the same cannot be overloaded
76e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson     */
77e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    public boolean hasSimilarMethod(Invocation candidate) {
78e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        String wantedMethodName = getMethod().getName();
79e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        String currentMethodName = candidate.getMethod().getName();
80e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson
81e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        final boolean methodNameEquals = wantedMethodName.equals(currentMethodName);
82e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        final boolean isUnverified = !candidate.isVerified();
83e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        final boolean mockIsTheSame = getInvocation().getMock() == candidate.getMock();
84e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        final boolean methodEquals = hasSameMethod(candidate);
85e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson
86e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        if (!methodNameEquals || !isUnverified || !mockIsTheSame) {
87e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson            return false;
88e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        }
89e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson
90e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        final boolean overloadedButSameArgs = !methodEquals && safelyArgumentsMatch(candidate.getArguments());
91e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson
92e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        return !overloadedButSameArgs;
93e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    }
94e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson
95e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    public boolean hasSameMethod(Invocation candidate) {
96e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        //not using method.equals() for 1 good reason:
97e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        //sometimes java generates forwarding methods when generics are in play see JavaGenericsForwardingMethodsTest
98e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        Method m1 = invocation.getMethod();
99e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        Method m2 = candidate.getMethod();
100e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson
101e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        if (m1.getName() != null && m1.getName().equals(m2.getName())) {
102e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        	/* Avoid unnecessary cloning */
103e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        	Class[] params1 = m1.getParameterTypes();
104e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        	Class[] params2 = m2.getParameterTypes();
105e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        	if (params1.length == params2.length) {
106e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        	    for (int i = 0; i < params1.length; i++) {
107e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        		if (params1[i] != params2[i])
108e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        		    return false;
109e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        	    }
110e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        	    return true;
111e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        	}
112e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        }
113e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        return false;
114e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    }
115e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson
116e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    public Location getLocation() {
117e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        return invocation.getLocation();
118e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    }
119e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson
120e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    public void captureArgumentsFrom(Invocation invocation) {
121e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        for (int position = 0; position < matchers.size(); position++) {
122e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson            Matcher m = matchers.get(position);
123e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson            if (m instanceof CapturesArguments && invocation.getArguments().length > position) {
124e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson                if(isVariableArgument(invocation, position) && isVarargMatcher(m)) {
125e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson                    ((CapturesArguments) m).captureFrom(invocation.getRawArguments()[position]);
126e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson                } else {
127e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson                    ((CapturesArguments) m).captureFrom(invocation.getArguments()[position]);
128e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson                }
129e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson            }
130e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        }
131e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    }
132e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson
133e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    private boolean isVarargMatcher(Matcher matcher) {
134e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        Matcher actualMatcher = matcher;
135e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        if (actualMatcher instanceof MatcherDecorator) {
136e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson            actualMatcher = ((MatcherDecorator) actualMatcher).getActualMatcher();
137e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        }
138e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        return actualMatcher instanceof VarargMatcher;
139e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    }
140e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson
141e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    private boolean isVariableArgument(Invocation invocation, int position) {
142e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        return invocation.getRawArguments().length - 1 == position
143e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson                && invocation.getRawArguments()[position] != null
144e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson                && invocation.getRawArguments()[position].getClass().isArray()
145e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson                && invocation.getMethod().isVarArgs();
146e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    }
147e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson
148e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    public static List<InvocationMatcher> createFrom(List<Invocation> invocations) {
149e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        LinkedList<InvocationMatcher> out = new LinkedList<InvocationMatcher>();
150e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson
151e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        for (Invocation i : invocations) {
152e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson            out.add(new InvocationMatcher(i));
153e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        }
154e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson
155e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson        return out;
156e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson    }
157e0ae5d7e87b1dd6e789803c1b9615a84bd7488b7Ian Parkinson}