1
2
3
4
5
6 package mockit.internal.faking;
7
8 import static mockit.asm.jvmConstants.Access.ABSTRACT;
9 import static mockit.asm.jvmConstants.Access.BRIDGE;
10 import static mockit.asm.jvmConstants.Access.NATIVE;
11 import static mockit.asm.jvmConstants.Access.SYNTHETIC;
12
13 import edu.umd.cs.findbugs.annotations.NonNull;
14 import edu.umd.cs.findbugs.annotations.Nullable;
15
16 import java.lang.reflect.Method;
17 import java.util.EnumSet;
18 import java.util.List;
19
20 import mockit.Mock;
21 import mockit.MockUp;
22 import mockit.asm.metadata.ClassMetadataReader;
23 import mockit.asm.metadata.ClassMetadataReader.Attribute;
24 import mockit.asm.metadata.ClassMetadataReader.MethodInfo;
25 import mockit.asm.types.JavaType;
26 import mockit.internal.ClassFile;
27 import mockit.internal.faking.FakeMethods.FakeMethod;
28 import mockit.internal.util.ClassLoad;
29 import mockit.internal.util.TypeDescriptor;
30
31
32
33
34
35 final class FakeMethodCollector {
36 private static final int INVALID_METHOD_ACCESSES = BRIDGE + SYNTHETIC + ABSTRACT + NATIVE;
37 private static final EnumSet<Attribute> ANNOTATIONS = EnumSet.of(Attribute.Annotations);
38
39 @NonNull
40 private final FakeMethods fakeMethods;
41 private boolean collectingFromSuperClass;
42
43 FakeMethodCollector(@NonNull FakeMethods fakeMethods) {
44 this.fakeMethods = fakeMethods;
45 }
46
47 void collectFakeMethods(@NonNull Class<?> fakeClass) {
48 ClassLoad.registerLoadedClass(fakeClass);
49 fakeMethods.setFakeClassInternalName(JavaType.getInternalName(fakeClass));
50
51 Class<?> classToCollectFakesFrom = fakeClass;
52
53 do {
54 byte[] classfileBytes = ClassFile.readBytesFromClassFile(classToCollectFakesFrom);
55 ClassMetadataReader cmr = new ClassMetadataReader(classfileBytes, ANNOTATIONS);
56 List<MethodInfo> methods = cmr.getMethods();
57 addFakeMethods(classToCollectFakesFrom, methods);
58
59 classToCollectFakesFrom = classToCollectFakesFrom.getSuperclass();
60 collectingFromSuperClass = true;
61 } while (classToCollectFakesFrom != MockUp.class);
62 }
63
64 private void addFakeMethods(@NonNull Class<?> fakeClass, @NonNull List<MethodInfo> methods) {
65 for (MethodInfo method : methods) {
66 int access = method.accessFlags;
67
68 if ((access & INVALID_METHOD_ACCESSES) == 0 && method.isMethod() && method.hasAnnotation("Lmockit/Mock;")) {
69 FakeMethod fakeMethod = fakeMethods.addMethod(collectingFromSuperClass, access, method.name,
70 method.desc);
71
72 if (fakeMethod != null) {
73 FakeState fakeState = createFakeStateIfRequired(fakeMethod);
74 applyInvocationConstraintsIfAny(fakeClass, method, fakeMethod, fakeState);
75 }
76 }
77 }
78 }
79
80 @Nullable
81 private FakeState createFakeStateIfRequired(@NonNull FakeMethod fakeMethod) {
82 if (!fakeMethod.requiresFakeState()) {
83 return null;
84 }
85
86 FakeState fakeState = new FakeState(fakeMethod);
87 fakeMethods.addFakeState(fakeState);
88 return fakeState;
89 }
90
91 private void applyInvocationConstraintsIfAny(@NonNull Class<?> fakeClass, @NonNull MethodInfo methodInfo,
92 @NonNull FakeMethod fakeMethod, @Nullable FakeState existingFakeState) {
93 Method javaMethod = findJavaMethod(fakeClass, methodInfo);
94 Mock annotation = javaMethod.getAnnotation(Mock.class);
95
96 if (annotation == null) {
97 return;
98 }
99
100 int expectedInvocations = annotation.invocations();
101 int minInvocations = annotation.minInvocations();
102 int maxInvocations = annotation.maxInvocations();
103
104 boolean hasConstraints = expectedInvocations >= 0 || minInvocations > 0 || maxInvocations >= 0;
105
106 if (!hasConstraints) {
107 return;
108 }
109
110 FakeState fakeState = existingFakeState;
111
112 if (fakeState == null) {
113 fakeState = new FakeState(fakeMethod);
114 }
115
116 if (expectedInvocations >= 0) {
117 fakeState.setExpectedInvocations(expectedInvocations);
118 }
119
120 if (minInvocations > 0) {
121 fakeState.setMinExpectedInvocations(minInvocations);
122 }
123
124 if (maxInvocations >= 0) {
125 fakeState.setMaxExpectedInvocations(maxInvocations);
126 }
127
128 if (existingFakeState == null) {
129 fakeMethods.addFakeState(fakeState);
130 }
131 }
132
133 @NonNull
134 private Method findJavaMethod(@NonNull Class<?> fakeClass, @NonNull MethodInfo methodInfo) {
135 Class<?>[] parameterTypes = TypeDescriptor.getParameterTypes(methodInfo.desc);
136
137 try {
138 Method method = fakeClass.getDeclaredMethod(methodInfo.name, parameterTypes);
139
140 try {
141 method.setAccessible(true);
142 } catch (SecurityException ignore) {
143
144 }
145
146 return method;
147 } catch (NoSuchMethodException e) {
148 throw new IllegalStateException(
149 "Unable to resolve @Mock method " + fakeClass.getName() + '#' + methodInfo.name, e);
150 }
151 }
152 }