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.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             /* can occur on JDK 9 */ }
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             /* can occur on JDK 9 */ }
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             // Try Jakarta first
221             if (JAKARTA_SERVLET_CLASS != null && injectable.getDeclaredType() == jakarta.servlet.ServletConfig.class) {
222                 servletConfig = injectable.getValue(testClassInstance);
223                 break;
224             }
225 
226             // Then try Javax
227             if (JAVAX_SERVLET_CLASS != null && injectable.getDeclaredType() == javax.servlet.ServletConfig.class) {
228                 servletConfig = injectable.getValue(testClassInstance);
229                 break;
230             }
231         }
232     }
233 
234 }