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