1 /* 2 * Copyright (c) 2016 Mockito contributors 3 * This program is made available under the terms of the MIT License. 4 */ 5 package org.mockito.internal.invocation; 6 7 import static org.mockito.internal.invocation.MatcherApplicationStrategy.MatcherApplicationType.ERROR_UNSUPPORTED_NUMBER_OF_MATCHERS; 8 import static org.mockito.internal.invocation.MatcherApplicationStrategy.MatcherApplicationType.MATCH_EACH_VARARGS_WITH_LAST_MATCHER; 9 import static org.mockito.internal.invocation.MatcherApplicationStrategy.MatcherApplicationType.ONE_MATCHER_PER_ARGUMENT; 10 11 import java.util.ArrayList; 12 import java.util.List; 13 14 import org.mockito.ArgumentMatcher; 15 import org.mockito.internal.hamcrest.HamcrestArgumentMatcher; 16 import org.mockito.internal.matchers.CapturingMatcher; 17 import org.mockito.internal.matchers.VarargMatcher; 18 import org.mockito.invocation.Invocation; 19 20 public class MatcherApplicationStrategy { 21 22 private final Invocation invocation; 23 private final List<ArgumentMatcher<?>> matchers; 24 private final MatcherApplicationType matchingType; 25 26 27 MatcherApplicationStrategy(Invocation invocation, List<ArgumentMatcher<?>> matchers, MatcherApplicationType matchingType)28 private MatcherApplicationStrategy(Invocation invocation, List<ArgumentMatcher<?>> matchers, MatcherApplicationType matchingType) { 29 this.invocation = invocation; 30 if (matchingType == MATCH_EACH_VARARGS_WITH_LAST_MATCHER) { 31 int times = varargLength(invocation); 32 this.matchers = appendLastMatcherNTimes(matchers, times); 33 } else { 34 this.matchers = matchers; 35 } 36 37 this.matchingType = matchingType; 38 } 39 40 /** 41 * Returns the {@link MatcherApplicationStrategy} that must be used to capture the 42 * arguments of the given <b>invocation</b> using the given <b>matchers</b>. 43 * 44 * @param invocation 45 * that contain the arguments to capture 46 * @param matchers 47 * that will be used to capture the arguments of the invocation, 48 * the passed {@link List} is not required to contain a 49 * {@link CapturingMatcher} 50 * @return never <code>null</code> 51 */ getMatcherApplicationStrategyFor(Invocation invocation, List<ArgumentMatcher<?>> matchers)52 public static MatcherApplicationStrategy getMatcherApplicationStrategyFor(Invocation invocation, List<ArgumentMatcher<?>> matchers) { 53 54 MatcherApplicationType type = getMatcherApplicationType(invocation, matchers); 55 return new MatcherApplicationStrategy(invocation, matchers, type); 56 } 57 58 /** 59 * Applies the given {@link ArgumentMatcherAction} to all arguments and 60 * corresponding matchers 61 * 62 * @param action 63 * must not be <code>null</code> 64 * @return 65 * <ul> 66 * <li><code>true</code> if the given <b>action</b> returned 67 * <code>true</code> for all arguments and matchers passed to it. 68 * <li><code>false</code> if the given <b>action</b> returned 69 * <code>false</code> for one of the passed arguments and matchers 70 * <li><code>false</code> if the given matchers don't fit to the given invocation 71 * because too many or to few matchers are available. 72 * </ul> 73 */ forEachMatcherAndArgument(ArgumentMatcherAction action)74 public boolean forEachMatcherAndArgument(ArgumentMatcherAction action) { 75 if (matchingType == ERROR_UNSUPPORTED_NUMBER_OF_MATCHERS) 76 return false; 77 78 Object[] arguments = invocation.getArguments(); 79 for (int i = 0; i < arguments.length; i++) { 80 ArgumentMatcher<?> matcher = matchers.get(i); 81 Object argument = arguments[i]; 82 83 if (!action.apply(matcher, argument)) { 84 return false; 85 } 86 } 87 return true; 88 } 89 getMatcherApplicationType(Invocation invocation, List<ArgumentMatcher<?>> matchers)90 private static MatcherApplicationType getMatcherApplicationType(Invocation invocation, List<ArgumentMatcher<?>> matchers) { 91 final int rawArguments = invocation.getRawArguments().length; 92 final int expandedArguments = invocation.getArguments().length; 93 final int matcherCount = matchers.size(); 94 95 if (expandedArguments == matcherCount) { 96 return ONE_MATCHER_PER_ARGUMENT; 97 } 98 99 if (rawArguments == matcherCount && isLastMatcherVarargMatcher(matchers)) { 100 return MATCH_EACH_VARARGS_WITH_LAST_MATCHER; 101 } 102 103 return ERROR_UNSUPPORTED_NUMBER_OF_MATCHERS; 104 } 105 isLastMatcherVarargMatcher(final List<ArgumentMatcher<?>> matchers)106 private static boolean isLastMatcherVarargMatcher(final List<ArgumentMatcher<?>> matchers) { 107 ArgumentMatcher<?> argumentMatcher = lastMatcher(matchers); 108 if (argumentMatcher instanceof HamcrestArgumentMatcher<?>) { 109 return ((HamcrestArgumentMatcher<?>) argumentMatcher).isVarargMatcher(); 110 } 111 return argumentMatcher instanceof VarargMatcher; 112 } 113 appendLastMatcherNTimes(List<ArgumentMatcher<?>> matchers, int timesToAppendLastMatcher)114 private static List<ArgumentMatcher<?>> appendLastMatcherNTimes(List<ArgumentMatcher<?>> matchers, int timesToAppendLastMatcher) { 115 ArgumentMatcher<?> lastMatcher = lastMatcher(matchers); 116 117 List<ArgumentMatcher<?>> expandedMatchers = new ArrayList<ArgumentMatcher<?>>(matchers); 118 for (int i = 0; i < timesToAppendLastMatcher; i++) { 119 expandedMatchers.add(lastMatcher); 120 } 121 return expandedMatchers; 122 } 123 varargLength(Invocation invocation)124 private static int varargLength(Invocation invocation) { 125 int rawArgumentCount = invocation.getRawArguments().length; 126 int expandedArgumentCount = invocation.getArguments().length; 127 return expandedArgumentCount - rawArgumentCount; 128 } 129 lastMatcher(List<ArgumentMatcher<?>> matchers)130 private static ArgumentMatcher<?> lastMatcher(List<ArgumentMatcher<?>> matchers) { 131 return matchers.get(matchers.size() - 1); 132 } 133 134 enum MatcherApplicationType { 135 ONE_MATCHER_PER_ARGUMENT, MATCH_EACH_VARARGS_WITH_LAST_MATCHER, ERROR_UNSUPPORTED_NUMBER_OF_MATCHERS; 136 } 137 } 138