Skip to content

Commit

Permalink
Unified logic of argument matching and capturing
Browse files Browse the repository at this point in the history
  • Loading branch information
Christian Schwarz committed Oct 2, 2016
1 parent 0fcad43 commit 026ba1d
Show file tree
Hide file tree
Showing 9 changed files with 1,028 additions and 409 deletions.
@@ -0,0 +1,28 @@
package org.mockito.internal.invocation;

import org.mockito.ArgumentMatcher;

public interface ArgumentMatcherAction {
/**
* Implementations must apply the given matcher to the argument and return
* <code>true</code> if the operation was successful or <code>false</code>
* if not. In this case no more matchers and arguments will be passed by
* {@link MatcherApplicationStrategy#forEachMatcherAndArgument(ArgumentMatcherAction)} to this method.
* .
*
* @param matcher
* to process the argument, never <code>null</code>
* @param argument
* to be processed by the matcher, can be <code>null</code>
* @return
* <ul>
* <li><code>true</code> if the <b>matcher</b> was successfully
* applied to the <b>argument</b> and the next pair of matcher and
* argument should be passed
* <li><code>false</code> otherwise
* </ul>
*
*
*/
boolean apply(ArgumentMatcher<?> matcher, Object argument);
}
117 changes: 0 additions & 117 deletions src/main/java/org/mockito/internal/invocation/ArgumentsComparator.java

This file was deleted.

154 changes: 67 additions & 87 deletions src/main/java/org/mockito/internal/invocation/InvocationMatcher.java
Expand Up @@ -5,98 +5,103 @@

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.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 static org.mockito.internal.invocation.ArgumentsComparator.argumentsMatch;

import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.*;

@SuppressWarnings("unchecked")
/**
* 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.
* 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 DescribedInvocation, CapturesArgumentsFromInvocation, Serializable {

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

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

@SuppressWarnings("rawtypes")
public InvocationMatcher(Invocation invocation) {
this(invocation, Collections.<ArgumentMatcher>emptyList());
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();
}

public Invocation getInvocation() {
return this.invocation;
return invocation;
}

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

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

public boolean matches(Invocation actual) {
return invocation.getMock().equals(actual.getMock())
&& hasSameMethod(actual)
&& argumentsMatch(this, actual);
}

private boolean safelyArgumentsMatch(Object[] actualArgs) {
try {
return argumentsMatch(this, actualArgs);
} catch (Throwable t) {
return false;
}
return invocation.getMock().equals(actual.getMock()) && hasSameMethod(actual) && argumentsMatch(actual);
}

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

final boolean methodNameEquals = wantedMethodName.equals(currentMethodName);
final boolean isUnverified = !candidate.isVerified();
final boolean mockIsTheSame = getInvocation().getMock() == candidate.getMock();
final boolean methodEquals = hasSameMethod(candidate);
String candidateMethodName = candidate.getMethod().getName();

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

final boolean overloadedButSameArgs = !methodEquals && safelyArgumentsMatch(candidate.getArguments());

return !overloadedButSameArgs;
return !argumentsMatch(candidate);
}

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();

Expand All @@ -115,59 +120,34 @@ public boolean hasSameMethod(Invocation candidate) {
return false;
}

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

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

private void captureRegularArguments(Invocation invocation) {
for (int position = 0; position < regularArgumentsSize(invocation); position++) {
ArgumentMatcher m = matchers.get(position);
if (m instanceof CapturesArguments) {
((CapturesArguments) m).captureFrom(invocation.getArgument(position));
}
}
}

private void captureVarargsPart(Invocation invocation) {
if (!invocation.getMethod().isVarArgs()) {
return;
}
int indexOfVararg = invocation.getRawArguments().length - 1;
for (ArgumentMatcher m : uniqueMatcherSet(indexOfVararg)) {
if (m instanceof CapturesArguments) {
Object rawArgument = invocation.getRawArguments()[indexOfVararg];
for (int i = 0; i < Array.getLength(rawArgument); i++) {
((CapturesArguments) m).captureFrom(Array.get(rawArgument, i));
private ArgumentMatcherAction captureArgument() {
return new ArgumentMatcherAction() {

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

return true;
}
}
}

private int regularArgumentsSize(Invocation invocation) {
return invocation.getMethod().isVarArgs() ?
invocation.getRawArguments().length - 1 // ignores vararg holder array
: matchers.size();
}

private Set<ArgumentMatcher> uniqueMatcherSet(int indexOfVararg) {
HashSet<ArgumentMatcher> set = new HashSet<ArgumentMatcher>();
for (int position = indexOfVararg; position < matchers.size(); position++) {
ArgumentMatcher matcher = matchers.get(position);
set.add(matcher);
}
return set;
};
}

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;

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

0 comments on commit 026ba1d

Please sign in to comment.