1
2
3
4
5
6 package mockit.internal.faking;
7
8 import static java.lang.reflect.Modifier.isNative;
9
10 import static mockit.internal.util.ObjectMethods.isMethodFromObject;
11
12 import edu.umd.cs.findbugs.annotations.NonNull;
13 import edu.umd.cs.findbugs.annotations.Nullable;
14
15 import java.lang.annotation.Annotation;
16 import java.lang.reflect.Method;
17 import java.lang.reflect.Modifier;
18 import java.lang.reflect.Type;
19 import java.util.ArrayList;
20 import java.util.List;
21
22 import mockit.internal.ClassLoadingBridge;
23 import mockit.internal.reflection.GenericTypeReflection;
24 import mockit.internal.reflection.GenericTypeReflection.GenericSignature;
25 import mockit.internal.state.TestRun;
26 import mockit.internal.util.MethodFormatter;
27 import mockit.internal.util.TypeDescriptor;
28 import mockit.internal.util.Utilities;
29
30
31
32
33 final class FakeMethods {
34 @NonNull
35 private final Class<?> realClass;
36 private final boolean targetTypeIsAClass;
37 private final boolean reentrantRealClass;
38 @NonNull
39 private final List<FakeMethod> methods;
40 @Nullable
41 private FakeMethod adviceMethod;
42 @NonNull
43 private final GenericTypeReflection typeParametersToTypeArguments;
44 @NonNull
45 private String fakeClassInternalName;
46 @Nullable
47 private List<FakeState> fakeStates;
48
49 final class FakeMethod {
50 private final int access;
51 @NonNull
52 final String name;
53 @NonNull
54 final String desc;
55 private final boolean isByNameOnly;
56 final boolean isAdvice;
57 @NonNull
58 final String fakeDescWithoutInvocationParameter;
59 private boolean hasMatchingRealMethod;
60 @Nullable
61 private GenericSignature fakeSignature;
62 private int indexForFakeState;
63 private boolean nativeRealMethod;
64
65 private FakeMethod(int access, @NonNull String name, @NonNull String desc) {
66 this.access = access;
67 this.name = name;
68 this.desc = desc;
69
70 if (desc.contains("Lmockit/Invocation;")) {
71 fakeDescWithoutInvocationParameter = '(' + desc.substring(20);
72 isByNameOnly = name.charAt(0) != '$' && fakeDescWithoutInvocationParameter.startsWith("()");
73 isAdvice = "$advice".equals(name) && "()Ljava/lang/Object;".equals(fakeDescWithoutInvocationParameter);
74 } else {
75 fakeDescWithoutInvocationParameter = desc;
76 isByNameOnly = false;
77 isAdvice = false;
78 }
79
80 hasMatchingRealMethod = false;
81 indexForFakeState = -1;
82 }
83
84 @SuppressWarnings("StringEquality")
85 boolean hasInvocationParameter() {
86 return desc != fakeDescWithoutInvocationParameter;
87 }
88
89 boolean hasInvocationParameterOnly() {
90 return isByNameOnly || isAdvice;
91 }
92
93 boolean isMatch(int realAccess, @NonNull String realName, @NonNull String realDesc,
94 @Nullable String signature) {
95 if (name.equals(realName) && hasMatchingParameters(realDesc, signature)) {
96 hasMatchingRealMethod = true;
97 nativeRealMethod = isNative(realAccess);
98 return true;
99 }
100
101 return false;
102 }
103
104 private boolean hasMatchingParameters(@NonNull String methodDesc, @Nullable String signature) {
105 boolean sameParametersIgnoringGenerics = fakeDescWithoutInvocationParameter.equals(methodDesc);
106
107 if (sameParametersIgnoringGenerics || signature == null) {
108 return sameParametersIgnoringGenerics;
109 }
110
111 if (fakeSignature == null) {
112 fakeSignature = typeParametersToTypeArguments.parseSignature(fakeDescWithoutInvocationParameter);
113 }
114
115 return fakeSignature.satisfiesGenericSignature(signature);
116 }
117
118 boolean isMatchByName(@NonNull String realName) {
119 return isByNameOnly && name.equals(realName);
120 }
121
122 @NonNull
123 Class<?> getRealClass() {
124 return realClass;
125 }
126
127 int getIndexForFakeState() {
128 return indexForFakeState;
129 }
130
131 boolean isStatic() {
132 return Modifier.isStatic(access);
133 }
134
135 boolean isPublic() {
136 return Modifier.isPublic(access);
137 }
138
139 boolean isForGenericMethod() {
140 return fakeSignature != null;
141 }
142
143 boolean isForNativeMethod() {
144 return nativeRealMethod;
145 }
146
147 boolean requiresFakeState() {
148 return hasInvocationParameter() || reentrantRealClass;
149 }
150
151 boolean canBeReentered() {
152 return targetTypeIsAClass && !nativeRealMethod;
153 }
154
155
156 @NonNull
157 String errorMessage(@NonNull String quantifier, int numExpectedInvocations, int timesInvoked) {
158 String nameAndDesc = name + desc;
159 return "Expected " + quantifier + ' ' + numExpectedInvocations + " invocation(s) of "
160 + new MethodFormatter(fakeClassInternalName, nameAndDesc) + ", but was invoked " + timesInvoked
161 + " time(s)";
162 }
163 }
164
165 FakeMethods(@NonNull Class<?> realClass, @Nullable Type targetType) {
166 this.realClass = realClass;
167
168 if (targetType == null || realClass == targetType) {
169 targetTypeIsAClass = true;
170 } else {
171 Class<?> targetClass = Utilities.getClassType(targetType);
172 targetTypeIsAClass = !targetClass.isInterface();
173 }
174
175 reentrantRealClass = targetTypeIsAClass
176 && ClassLoadingBridge.instanceOfClassThatParticipatesInClassLoading(realClass);
177 methods = new ArrayList<>();
178 typeParametersToTypeArguments = new GenericTypeReflection(realClass, targetType);
179 fakeClassInternalName = "";
180 }
181
182 @NonNull
183 Class<?> getRealClass() {
184 return realClass;
185 }
186
187 @Nullable
188 FakeMethod addMethod(boolean fromSuperClass, int access, @NonNull String name, @NonNull String desc) {
189 if (fromSuperClass && isMethodAlreadyAdded(name, desc)) {
190 return null;
191 }
192
193 FakeMethod fakeMethod = new FakeMethod(access, name, desc);
194
195 if (fakeMethod.isAdvice) {
196 adviceMethod = fakeMethod;
197 } else {
198 methods.add(fakeMethod);
199 }
200
201 return fakeMethod;
202 }
203
204 private boolean isMethodAlreadyAdded(@NonNull String name, @NonNull String desc) {
205 int p = desc.lastIndexOf(')');
206 String params = desc.substring(0, p + 1);
207
208 for (FakeMethod fakeMethod : methods) {
209 if (fakeMethod.name.equals(name) && fakeMethod.desc.startsWith(params)) {
210 return true;
211 }
212 }
213
214 return false;
215 }
216
217 void addFakeState(@NonNull FakeState fakeState) {
218 if (fakeStates == null) {
219 fakeStates = new ArrayList<>(4);
220 }
221
222 fakeState.fakeMethod.indexForFakeState = fakeStates.size();
223 fakeStates.add(fakeState);
224 }
225
226
227
228
229
230
231
232
233
234 @Nullable
235 FakeMethod findMethod(int access, @NonNull String name, @NonNull String desc, @Nullable String signature) {
236 FakeMethod fakeMethodMatchingByNameOnly = null;
237
238 for (FakeMethod fakeMethod : methods) {
239 if (fakeMethod.isMatch(access, name, desc, signature)) {
240
241 if (isNative(access) && hasIntrinsicCandidateAnnotation(getRealClass(), name, desc)) {
242 throw new UnsupportedOperationException(
243 "Native methods annotated with IntrinsicCandidate cannot be mocked: "
244 + getRealClass().getSimpleName() + "#" + name);
245 }
246 return fakeMethod;
247 }
248
249 if (fakeMethod.isMatchByName(name)) {
250 fakeMethodMatchingByNameOnly = fakeMethod;
251 }
252 }
253
254 if (fakeMethodMatchingByNameOnly != null) {
255 return fakeMethodMatchingByNameOnly;
256 }
257
258 if (adviceMethod != null && !isNative(access) && !isConstructorOrClassInitialization(name)
259 && !isMethodFromObject(name, desc)) {
260 return adviceMethod;
261 }
262
263 return null;
264 }
265
266 private boolean hasIntrinsicCandidateAnnotation(Class<?> clazz, String methodName, String methodDescriptor) {
267 Class<?>[] parameterTypes = TypeDescriptor.getParameterTypes(methodDescriptor);
268
269 try {
270
271 Method method = clazz.getDeclaredMethod(methodName, parameterTypes);
272 Annotation[] annotations = method.getAnnotations();
273
274 for (Annotation annotation : annotations) {
275 String annotationName = annotation.annotationType().getSimpleName();
276
277
278 if (annotationName.contains("IntrinsicCandidate")) {
279 return true;
280 }
281 }
282 } catch (NoSuchMethodException e) {
283 return false;
284 }
285 return false;
286 }
287
288 private static boolean isConstructorOrClassInitialization(@NonNull String memberName) {
289 return "$init".equals(memberName) || "$clinit".equals(memberName);
290 }
291
292 @NonNull
293 String getFakeClassInternalName() {
294 return fakeClassInternalName;
295 }
296
297 void setFakeClassInternalName(@NonNull String fakeClassInternalName) {
298 this.fakeClassInternalName = fakeClassInternalName.intern();
299 }
300
301 boolean hasUnusedFakes() {
302 if (adviceMethod != null) {
303 return true;
304 }
305
306 for (FakeMethod method : methods) {
307 if (!method.hasMatchingRealMethod) {
308 return true;
309 }
310 }
311
312 return false;
313 }
314
315 void registerFakeStates(@NonNull Object fake, boolean forStartupFake) {
316 if (fakeStates != null) {
317 FakeStates allFakeStates = TestRun.getFakeStates();
318
319 if (forStartupFake) {
320 allFakeStates.addStartupFakeAndItsFakeStates(fake, fakeStates);
321 } else {
322 allFakeStates.addFakeAndItsFakeStates(fake, fakeStates);
323 }
324 }
325 }
326 }