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