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.lang.reflect.Array;
12  import java.util.Map;
13  
14  import mockit.internal.expectations.argumentMatching.ArgumentMatcher;
15  import mockit.internal.expectations.argumentMatching.CaptureMatcher;
16  import mockit.internal.expectations.argumentMatching.EqualityMatcher;
17  import mockit.internal.expectations.argumentMatching.LenientEqualityMatcher;
18  
19  import org.checkerframework.checker.index.qual.NonNegative;
20  
21  final class ArgumentValuesAndMatchersWithVarargs extends ArgumentValuesAndMatchers {
22      ArgumentValuesAndMatchersWithVarargs(@NonNull InvocationArguments signature, @NonNull Object[] values) {
23          super(signature, values);
24      }
25  
26      @Override
27      boolean isMatch(@NonNull Object[] replayArgs, @NonNull Map<Object, Object> instanceMap) {
28          if (matchers == null) {
29              return areEqual(replayArgs, instanceMap);
30          }
31  
32          VarargsComparison varargsComparison = new VarargsComparison(replayArgs);
33          int totalArgCount = varargsComparison.getTotalArgumentCountWhenDifferent();
34          int regularArgCount = varargsComparison.regularArgCount;
35  
36          if (totalArgCount < 0) {
37              return false;
38          }
39  
40          for (int i = 0; i < totalArgCount; i++) {
41              Object actual = varargsComparison.getOtherArgument(i);
42              ArgumentMatcher<?> expected = getArgumentMatcher(i);
43  
44              if (expected == null) {
45                  Object arg = varargsComparison.getThisArgument(i);
46                  if (arg == null) {
47                      continue;
48                  }
49                  expected = new LenientEqualityMatcher(arg, instanceMap);
50              } else if (i == regularArgCount && expected instanceof CaptureMatcher<?>) {
51                  actual = varargsComparison.getOtherVarArgs();
52                  i = totalArgCount;
53              }
54  
55              if (!expected.matches(actual)) {
56                  return false;
57              }
58          }
59  
60          return true;
61      }
62  
63      private boolean areEqual(@NonNull Object[] replayArgs, @NonNull Map<Object, Object> instanceMap) {
64          int argCount = replayArgs.length;
65  
66          if (!areEqual(values, replayArgs, argCount - 1, instanceMap)) {
67              return false;
68          }
69  
70          VarargsComparison varargsComparison = new VarargsComparison(replayArgs);
71          Object[] expectedValues = varargsComparison.getThisVarArgs();
72          Object[] actualValues = varargsComparison.getOtherVarArgs();
73  
74          return varargsComparison.sameVarargArrayLength()
75                  && areEqual(expectedValues, actualValues, expectedValues.length, instanceMap);
76      }
77  
78      @Override
79      boolean hasEquivalentMatchers(@NonNull ArgumentValuesAndMatchers other) {
80          @SuppressWarnings("unchecked")
81          int i = indexOfFirstValueAfterEquivalentMatchers(other);
82  
83          if (i < 0) {
84              return false;
85          }
86  
87          VarargsComparison varargsComparison = new VarargsComparison(other.values);
88          int n = varargsComparison.getTotalArgumentCountWhenDifferent();
89  
90          if (n < 0) {
91              return false;
92          }
93  
94          while (i < n) {
95              Object thisArg = varargsComparison.getThisArgument(i);
96              Object otherArg = varargsComparison.getOtherArgument(i);
97  
98              if (!EqualityMatcher.areEqual(thisArg, otherArg)) {
99                  return false;
100             }
101 
102             i++;
103         }
104 
105         return true;
106     }
107 
108     private static final Object[] NULL_VARARGS = {};
109 
110     private final class VarargsComparison {
111         @NonNull
112         private final Object[] otherValues;
113         @Nullable
114         private final Object[] thisVarArgs;
115         @Nullable
116         private final Object[] otherVarArgs;
117         final int regularArgCount;
118 
119         VarargsComparison(@NonNull Object[] otherValues) {
120             this.otherValues = otherValues;
121             thisVarArgs = getVarArgs(values);
122             otherVarArgs = getVarArgs(otherValues);
123             regularArgCount = values.length - 1;
124         }
125 
126         @NonNull
127         Object[] getThisVarArgs() {
128             return thisVarArgs == null ? NULL_VARARGS : thisVarArgs;
129         }
130 
131         @NonNull
132         Object[] getOtherVarArgs() {
133             return otherVarArgs == null ? NULL_VARARGS : otherVarArgs;
134         }
135 
136         @Nullable
137         private Object[] getVarArgs(@NonNull Object[] args) {
138             Object lastArg = args[args.length - 1];
139 
140             if (lastArg == null) {
141                 return null;
142             }
143 
144             if (lastArg instanceof Object[]) {
145                 return (Object[]) lastArg;
146             }
147 
148             int varArgsLength = Array.getLength(lastArg);
149             Object[] results = new Object[varArgsLength];
150 
151             for (int i = 0; i < varArgsLength; i++) {
152                 results[i] = Array.get(lastArg, i);
153             }
154 
155             return results;
156         }
157 
158         int getTotalArgumentCountWhenDifferent() {
159             if (thisVarArgs == null) {
160                 return regularArgCount + 1;
161             }
162 
163             if (!sameVarargArrayLength()) {
164                 return -1;
165             }
166 
167             return regularArgCount + thisVarArgs.length;
168         }
169 
170         boolean sameVarargArrayLength() {
171             return getThisVarArgs().length == getOtherVarArgs().length;
172         }
173 
174         @Nullable
175         Object getThisArgument(@NonNegative int parameter) {
176             if (parameter < regularArgCount) {
177                 return values[parameter];
178             }
179             int p = parameter - regularArgCount;
180             if (thisVarArgs == null || p >= thisVarArgs.length) {
181                 return null;
182             }
183             return thisVarArgs[p];
184         }
185 
186         @Nullable
187         Object getOtherArgument(@NonNegative int parameter) {
188             if (parameter < regularArgCount) {
189                 return otherValues[parameter];
190             }
191             int p = parameter - regularArgCount;
192             if (otherVarArgs == null || p >= otherVarArgs.length) {
193                 return null;
194             }
195             return otherVarArgs[p];
196         }
197     }
198 }