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