1
2
3
4
5
6 package mockit.internal.injection;
7
8 import static mockit.internal.injection.InjectionPoint.JAKARTA_POST_CONSTRUCT_CLASS;
9 import static mockit.internal.injection.InjectionPoint.JAKARTA_SERVLET_CLASS;
10 import static mockit.internal.injection.InjectionPoint.JAVAX_POST_CONSTRUCT_CLASS;
11 import static mockit.internal.injection.InjectionPoint.JAVAX_SERVLET_CLASS;
12 import static mockit.internal.injection.InjectionPoint.isJakartaServlet;
13 import static mockit.internal.injection.InjectionPoint.isJavaxServlet;
14 import static mockit.internal.reflection.ParameterReflection.getParameterCount;
15 import static mockit.internal.util.Utilities.NO_ARGS;
16
17 import edu.umd.cs.findbugs.annotations.NonNull;
18 import edu.umd.cs.findbugs.annotations.Nullable;
19
20 import java.lang.annotation.Annotation;
21 import java.lang.reflect.Method;
22 import java.util.ArrayList;
23 import java.util.IdentityHashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Map.Entry;
27
28 import mockit.internal.reflection.MethodReflection;
29 import mockit.internal.state.TestRun;
30
31 public final class LifecycleMethods {
32
33 @NonNull
34 private final List<Class<?>> classesSearched;
35
36 @NonNull
37 private final Map<Class<?>, Method> initializationMethods;
38
39 @NonNull
40 private final Map<Class<?>, Method> terminationMethods;
41
42 @NonNull
43 private final Map<Class<?>, Object> objectsWithTerminationMethodsToExecute;
44
45 @Nullable
46 private Object servletConfig;
47
48 LifecycleMethods() {
49 classesSearched = new ArrayList<>();
50 initializationMethods = new IdentityHashMap<>();
51 terminationMethods = new IdentityHashMap<>();
52 objectsWithTerminationMethodsToExecute = new IdentityHashMap<>();
53 }
54
55 public void findLifecycleMethods(@NonNull Class<?> testedClass) {
56 if (testedClass.isInterface() || classesSearched.contains(testedClass)) {
57 return;
58 }
59
60 boolean isServlet = isJakartaServlet(testedClass);
61 if (!isServlet) {
62 isServlet = isJavaxServlet(testedClass);
63 }
64 Class<?> classWithLifecycleMethods = testedClass;
65
66 do {
67 findLifecycleMethodsInSingleClass(isServlet, classWithLifecycleMethods);
68 classWithLifecycleMethods = classWithLifecycleMethods.getSuperclass();
69 } while (classWithLifecycleMethods != Object.class);
70
71 classesSearched.add(testedClass);
72 }
73
74 private void findLifecycleMethodsInSingleClass(boolean isServlet, @NonNull Class<?> classWithLifecycleMethods) {
75 Method initializationMethod = null;
76 Method terminationMethod = null;
77 int methodsFoundInSameClass = 0;
78
79 for (Method method : classWithLifecycleMethods.getDeclaredMethods()) {
80 if (method.isSynthetic()) {
81 continue;
82 }
83
84 if (initializationMethod == null && isInitializationMethod(method, isServlet)) {
85 initializationMethods.put(classWithLifecycleMethods, method);
86 initializationMethod = method;
87 methodsFoundInSameClass++;
88 } else if (terminationMethod == null && isTerminationMethod(method, isServlet)) {
89 terminationMethods.put(classWithLifecycleMethods, method);
90 terminationMethod = method;
91 methodsFoundInSameClass++;
92 }
93
94 if (methodsFoundInSameClass == 2) {
95 break;
96 }
97 }
98 }
99
100 private static boolean isInitializationMethod(@NonNull Method method, boolean isServlet) {
101 if (hasLifecycleAnnotationJakarta(method, true) || hasLifecycleAnnotationJavax(method, true)) {
102 return true;
103 }
104
105 if (isServlet && "init".equals(method.getName())) {
106 Class<?>[] parameterTypes = method.getParameterTypes();
107
108 if (parameterTypes.length != 1) {
109 return false;
110 }
111 return (JAKARTA_SERVLET_CLASS != null && parameterTypes[0] == jakarta.servlet.ServletConfig.class)
112 || (JAVAX_SERVLET_CLASS != null && parameterTypes[0] == javax.servlet.ServletConfig.class);
113 }
114
115 return false;
116 }
117
118 private static boolean hasLifecycleAnnotationJakarta(@NonNull Method method, boolean postConstruct) {
119 if (JAKARTA_POST_CONSTRUCT_CLASS == null) {
120 return false;
121 }
122
123 try {
124 Class<? extends Annotation> lifecycleAnnotation = postConstruct ? jakarta.annotation.PostConstruct.class
125 : jakarta.annotation.PreDestroy.class;
126
127 if (method.isAnnotationPresent(lifecycleAnnotation)) {
128 return true;
129 }
130 } catch (NoClassDefFoundError ignore) {
131 }
132
133 return false;
134 }
135
136 private static boolean hasLifecycleAnnotationJavax(@NonNull Method method, boolean postConstruct) {
137 if (JAVAX_POST_CONSTRUCT_CLASS == null) {
138 return false;
139 }
140
141 try {
142 Class<? extends Annotation> lifecycleAnnotation = postConstruct ? javax.annotation.PostConstruct.class
143 : javax.annotation.PreDestroy.class;
144
145 if (method.isAnnotationPresent(lifecycleAnnotation)) {
146 return true;
147 }
148 } catch (NoClassDefFoundError ignore) {
149 }
150
151 return false;
152 }
153
154 private static boolean isTerminationMethod(@NonNull Method method, boolean isServlet) {
155 return hasLifecycleAnnotationJakarta(method, false) || hasLifecycleAnnotationJavax(method, false)
156 || isServlet && "destroy".equals(method.getName()) && getParameterCount(method) == 0;
157 }
158
159 public void executeInitializationMethodsIfAny(@NonNull Class<?> testedClass, @NonNull Object testedObject) {
160 Class<?> superclass = testedClass.getSuperclass();
161
162 if (superclass != Object.class) {
163 executeInitializationMethodsIfAny(superclass, testedObject);
164 }
165
166 Method postConstructMethod = initializationMethods.get(testedClass);
167
168 if (postConstructMethod != null) {
169 executeInitializationMethod(testedObject, postConstructMethod);
170 }
171
172 Method preDestroyMethod = terminationMethods.get(testedClass);
173
174 if (preDestroyMethod != null) {
175 objectsWithTerminationMethodsToExecute.put(testedClass, testedObject);
176 }
177 }
178
179 private void executeInitializationMethod(@NonNull Object testedObject, @NonNull Method initializationMethod) {
180 Object[] args = NO_ARGS;
181
182 if ("init".equals(initializationMethod.getName()) && getParameterCount(initializationMethod) == 1) {
183 args = new Object[] { servletConfig };
184 }
185
186 TestRun.exitNoMockingZone();
187
188 try {
189 MethodReflection.invoke(testedObject, initializationMethod, args);
190 } finally {
191 TestRun.enterNoMockingZone();
192 }
193 }
194
195 void executeTerminationMethodsIfAny() {
196 try {
197 for (Entry<Class<?>, Object> testedClassAndObject : objectsWithTerminationMethodsToExecute.entrySet()) {
198 executeTerminationMethod(testedClassAndObject.getKey(), testedClassAndObject.getValue());
199 }
200 } finally {
201 objectsWithTerminationMethodsToExecute.clear();
202 }
203 }
204
205 private void executeTerminationMethod(@NonNull Class<?> testedClass, @NonNull Object testedObject) {
206 Method terminationMethod = terminationMethods.get(testedClass);
207 TestRun.exitNoMockingZone();
208
209 try {
210 MethodReflection.invoke(testedObject, terminationMethod);
211 } catch (RuntimeException | AssertionError ignore) {
212 } finally {
213 TestRun.enterNoMockingZone();
214 }
215 }
216
217 void getServletConfigForInitMethodsIfAny(@NonNull List<? extends InjectionProvider> injectables,
218 @NonNull Object testClassInstance) {
219 for (InjectionProvider injectable : injectables) {
220
221 if (JAKARTA_SERVLET_CLASS != null && injectable.getDeclaredType() == jakarta.servlet.ServletConfig.class) {
222 servletConfig = injectable.getValue(testClassInstance);
223 break;
224 }
225
226
227 if (JAVAX_SERVLET_CLASS != null && injectable.getDeclaredType() == javax.servlet.ServletConfig.class) {
228 servletConfig = injectable.getValue(testClassInstance);
229 break;
230 }
231 }
232 }
233
234 }