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