View Javadoc
1   /*
2    * Copyright (c) 2006 JMockit developers
3    * This file is subject to the terms of the MIT license (see LICENSE.txt).
4    */
5   package mockit.internal.expectations.invocation;
6   
7   import edu.umd.cs.findbugs.annotations.NonNull;
8   import edu.umd.cs.findbugs.annotations.Nullable;
9   
10  import java.util.List;
11  import java.util.Map;
12  
13  import mockit.internal.expectations.argumentMatching.AlwaysTrueMatcher;
14  import mockit.internal.expectations.argumentMatching.ArgumentMatcher;
15  import mockit.internal.expectations.argumentMatching.ArgumentMismatch;
16  import mockit.internal.expectations.argumentMatching.EqualityMatcher;
17  import mockit.internal.expectations.argumentMatching.HamcrestAdapter;
18  import mockit.internal.expectations.argumentMatching.ReflectiveMatcher;
19  
20  import org.checkerframework.checker.index.qual.NonNegative;
21  
22  abstract class ArgumentValuesAndMatchers {
23      @NonNull
24      final InvocationArguments signature;
25      @NonNull
26      Object[] values;
27      @Nullable
28      List<ArgumentMatcher<?>> matchers;
29  
30      ArgumentValuesAndMatchers(@NonNull InvocationArguments signature, @NonNull Object[] values) {
31          this.signature = signature;
32          this.values = values;
33      }
34  
35      final void setValuesWithNoMatchers(@NonNull Object[] argsToVerify) {
36          setValuesAndMatchers(argsToVerify, null);
37      }
38  
39      @NonNull
40      final Object[] prepareForVerification(@NonNull Object[] argsToVerify,
41              @Nullable List<ArgumentMatcher<?>> matchersToUse) {
42          Object[] replayArgs = values;
43          setValuesAndMatchers(argsToVerify, matchersToUse);
44          return replayArgs;
45      }
46  
47      final void setValuesAndMatchers(@NonNull Object[] argsToVerify, @Nullable List<ArgumentMatcher<?>> matchersToUse) {
48          values = argsToVerify;
49          matchers = matchersToUse;
50      }
51  
52      @Nullable
53      final ArgumentMatcher<?> getArgumentMatcher(@NonNegative int parameterIndex) {
54          if (matchers == null) {
55              return null;
56          }
57  
58          ArgumentMatcher<?> matcher = parameterIndex < matchers.size() ? matchers.get(parameterIndex) : null;
59  
60          if (matcher == null && parameterIndex < values.length && values[parameterIndex] == null) {
61              matcher = AlwaysTrueMatcher.ANY_VALUE;
62          }
63  
64          return matcher;
65      }
66  
67      abstract boolean isMatch(@NonNull Object[] replayArgs, @NonNull Map<Object, Object> instanceMap);
68  
69      static boolean areEqual(@NonNull Object[] expectedValues, @NonNull Object[] actualValues, @NonNegative int count,
70              @NonNull Map<Object, Object> instanceMap) {
71          for (int i = 0; i < count; i++) {
72              if (isNotEqual(expectedValues[i], actualValues[i], instanceMap)) {
73                  return false;
74              }
75          }
76  
77          return true;
78      }
79  
80      private static boolean isNotEqual(@Nullable Object expected, @Nullable Object actual,
81              @NonNull Map<Object, Object> instanceMap) {
82          return actual == null == (expected != null) || actual != null && actual != expected
83                  && expected != instanceMap.get(actual) && !EqualityMatcher.areEqualWhenNonNull(actual, expected);
84      }
85  
86      abstract boolean hasEquivalentMatchers(@NonNull ArgumentValuesAndMatchers other);
87  
88      private static boolean equivalentMatches(@NonNull ArgumentMatcher<?> matcher1, @Nullable Object arg1,
89              @NonNull ArgumentMatcher<?> matcher2, @Nullable Object arg2) {
90          boolean matcher1MatchesArg2 = matcher1.matches(arg2);
91          boolean matcher2MatchesArg1 = matcher2.matches(arg1);
92  
93          if (arg1 != null && arg2 != null && matcher1MatchesArg2 && matcher2MatchesArg1) {
94              return true;
95          }
96  
97          if (arg1 == arg2 && matcher1MatchesArg2 == matcher2MatchesArg1) { // both matchers fail
98              ArgumentMismatch desc1 = new ArgumentMismatch();
99              matcher1.writeMismatchPhrase(desc1);
100             ArgumentMismatch desc2 = new ArgumentMismatch();
101             matcher2.writeMismatchPhrase(desc2);
102             return desc1.toString().equals(desc2.toString());
103         }
104 
105         return false;
106     }
107 
108     @SuppressWarnings("unchecked")
109     final <M1 extends ArgumentMatcher<M1>, M2 extends ArgumentMatcher<M2>> int indexOfFirstValueAfterEquivalentMatchers(
110             @NonNull ArgumentValuesAndMatchers other) {
111         List<ArgumentMatcher<?>> otherMatchers = other.matchers;
112 
113         if (hasDifferentAmountOfMatchers(otherMatchers)) {
114             return -1;
115         }
116 
117         // noinspection ConstantConditions
118         int m = matchers.size();
119         int i;
120 
121         for (i = 0; i < m; i++) {
122             M1 matcher1 = (M1) matchers.get(i);
123             M2 matcher2 = (M2) otherMatchers.get(i);
124 
125             if (matcher1 == null || matcher2 == null) {
126                 if (!EqualityMatcher.areEqual(values[i], other.values[i])) {
127                     return -1;
128                 }
129             } else if (matcher1 != matcher2 && (matcher1.getClass() != matcher2.getClass()
130                     || !matcher1.same((M1) matcher2) && areNonEquivalentMatches(other, matcher1, matcher2, i))) {
131                 return -1;
132             }
133         }
134 
135         return i;
136     }
137 
138     private boolean hasDifferentAmountOfMatchers(@Nullable List<ArgumentMatcher<?>> otherMatchers) {
139         return otherMatchers == null || matchers == null || otherMatchers.size() != matchers.size();
140     }
141 
142     private boolean areNonEquivalentMatches(@NonNull ArgumentValuesAndMatchers other,
143             @NonNull ArgumentMatcher<?> matcher1, @NonNull ArgumentMatcher<?> matcher2, @NonNegative int matcherIndex) {
144         Class<?> matcherClass = matcher1.getClass();
145         return matcherClass == ReflectiveMatcher.class || matcherClass == HamcrestAdapter.class
146                 || !equivalentMatches(matcher1, values[matcherIndex], matcher2, other.values[matcherIndex]);
147     }
148 
149     @NonNull
150     final String toString(@NonNull List<String> parameterTypes) {
151         ArgumentMismatch desc = new ArgumentMismatch();
152         int parameterCount = values.length;
153 
154         if (parameterCount > 0) {
155             if (matchers == null) {
156                 desc.appendFormatted(values);
157             } else {
158                 String sep = "";
159 
160                 for (int i = 0; i < parameterCount; i++) {
161                     ArgumentMatcher<?> matcher = getArgumentMatcher(i);
162                     String parameterType = parameterTypes.get(i);
163                     desc.append(sep).appendFormatted(parameterType, values[i], matcher);
164                     sep = ", ";
165                 }
166             }
167 
168             desc.append(')');
169         }
170 
171         return desc.toString();
172     }
173 }