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.full;
7   
8   import static java.lang.reflect.Modifier.isStatic;
9   
10  import static mockit.internal.injection.InjectionPoint.JAKARTA_CONVERSATION_CLASS;
11  import static mockit.internal.injection.InjectionPoint.JAKARTA_INJECT_CLASS;
12  import static mockit.internal.injection.InjectionPoint.JAKARTA_PERSISTENCE_UNIT_CLASS;
13  import static mockit.internal.injection.InjectionPoint.JAKARTA_RESOURCE_CLASS;
14  import static mockit.internal.injection.InjectionPoint.JAKARTA_SERVLET_CLASS;
15  import static mockit.internal.injection.InjectionPoint.JAVAX_CONVERSATION_CLASS;
16  import static mockit.internal.injection.InjectionPoint.JAVAX_INJECT_CLASS;
17  import static mockit.internal.injection.InjectionPoint.JAVAX_PERSISTENCE_UNIT_CLASS;
18  import static mockit.internal.injection.InjectionPoint.JAVAX_RESOURCE_CLASS;
19  import static mockit.internal.injection.InjectionPoint.JAVAX_SERVLET_CLASS;
20  import static mockit.internal.reflection.ConstructorReflection.newInstanceUsingDefaultConstructorIfAvailable;
21  import static mockit.internal.util.Utilities.getClassType;
22  
23  import edu.umd.cs.findbugs.annotations.NonNull;
24  import edu.umd.cs.findbugs.annotations.Nullable;
25  
26  import java.lang.annotation.Annotation;
27  import java.lang.reflect.ParameterizedType;
28  import java.lang.reflect.Type;
29  import java.lang.reflect.TypeVariable;
30  import java.util.logging.Logger;
31  
32  import javax.sql.CommonDataSource;
33  
34  import mockit.asm.jvmConstants.Access;
35  import mockit.internal.injection.InjectionPoint;
36  import mockit.internal.injection.InjectionProvider;
37  import mockit.internal.injection.InjectionState;
38  import mockit.internal.injection.Injector;
39  import mockit.internal.injection.TestedClass;
40  import mockit.internal.injection.TestedObjectCreation;
41  
42  /**
43   * Responsible for recursive injection of dependencies into a <code>@Tested(fullyInitialized = true)</code> object.
44   */
45  public final class FullInjection {
46      private static final int INVALID_TYPES = Access.ABSTRACT + Access.ANNOTATION + Access.ENUM;
47  
48      @NonNull
49      private final InjectionState injectionState;
50  
51      @NonNull
52      private final String testedClassName;
53  
54      @NonNull
55      private final String testedName;
56  
57      @Nullable
58      private final ServletJakartaDependencies servletJakartaDependencies;
59      @Nullable
60      private final ServletJavaxDependencies servletJavaxDependencies;
61  
62      @Nullable
63      private final JPAJakartaDependencies jpaJakartaDependencies;
64      @Nullable
65      private final JPAJavaxDependencies jpaJavaxDependencies;
66  
67      @Nullable
68      private Class<?> dependencyClass;
69  
70      @Nullable
71      private InjectionProvider parentInjectionProvider;
72  
73      public FullInjection(@NonNull InjectionState injectionState, @NonNull Class<?> testedClass,
74              @NonNull String testedName) {
75          this.injectionState = injectionState;
76          testedClassName = testedClass.getSimpleName();
77          this.testedName = testedName;
78          servletJakartaDependencies = JAKARTA_SERVLET_CLASS == null ? null
79                  : new ServletJakartaDependencies(injectionState);
80          servletJavaxDependencies = JAVAX_SERVLET_CLASS == null ? null : new ServletJavaxDependencies(injectionState);
81          jpaJakartaDependencies = JAKARTA_PERSISTENCE_UNIT_CLASS == null ? null
82                  : new JPAJakartaDependencies(injectionState);
83          jpaJavaxDependencies = JAVAX_PERSISTENCE_UNIT_CLASS == null ? null : new JPAJavaxDependencies(injectionState);
84      }
85  
86      @Nullable
87      public Object createOrReuseInstance(@NonNull TestedClass testedClass, @NonNull Injector injector,
88              @Nullable InjectionProvider injectionProvider, @Nullable String qualifiedName) {
89          setInjectionProvider(injectionProvider);
90  
91          InjectionPoint injectionPoint = getInjectionPoint(testedClass, injectionProvider, qualifiedName);
92          Object dependency = injectionState.getInstantiatedDependency(testedClass, injectionPoint);
93  
94          if (dependency != null) {
95              return dependency;
96          }
97  
98          Class<?> typeToInject = dependencyClass;
99  
100         if (typeToInject == Logger.class) {
101             return createLogger(testedClass);
102         }
103 
104         if (typeToInject == null || !isInstantiableType(typeToInject)) {
105             return null;
106         }
107 
108         return createInstance(testedClass, injector, injectionProvider, injectionPoint);
109     }
110 
111     public void setInjectionProvider(@Nullable InjectionProvider injectionProvider) {
112         if (injectionProvider != null) {
113             injectionProvider.parent = parentInjectionProvider;
114         }
115 
116         parentInjectionProvider = injectionProvider;
117     }
118 
119     @NonNull
120     private InjectionPoint getInjectionPoint(@NonNull TestedClass testedClass,
121             @Nullable InjectionProvider injectionProvider, @Nullable String qualifiedName) {
122         if (injectionProvider == null) {
123             dependencyClass = testedClass.targetClass;
124             return new InjectionPoint(dependencyClass, qualifiedName, true);
125         }
126 
127         Type dependencyType = injectionProvider.getDeclaredType();
128 
129         if (dependencyType instanceof TypeVariable<?>) {
130             dependencyType = testedClass.reflection.resolveTypeVariable((TypeVariable<?>) dependencyType);
131             dependencyClass = getClassType(dependencyType);
132         } else {
133             dependencyClass = injectionProvider.getClassOfDeclaredType();
134         }
135 
136         if (qualifiedName != null && !qualifiedName.isEmpty()) {
137             return new InjectionPoint(dependencyClass, qualifiedName, true);
138         }
139 
140         if (jpaJakartaDependencies != null && JPAJakartaDependencies.isApplicable(dependencyClass)) {
141             for (Annotation annotation : injectionProvider.getAnnotations()) {
142                 InjectionPoint injectionPoint = jpaJakartaDependencies.getInjectionPointIfAvailable(annotation);
143 
144                 if (injectionPoint != null) {
145                     return injectionPoint;
146                 }
147             }
148         }
149 
150         if (jpaJavaxDependencies != null && JPAJavaxDependencies.isApplicable(dependencyClass)) {
151             for (Annotation annotation : injectionProvider.getAnnotations()) {
152                 InjectionPoint injectionPoint = jpaJavaxDependencies.getInjectionPointIfAvailable(annotation);
153 
154                 if (injectionPoint != null) {
155                     return injectionPoint;
156                 }
157             }
158         }
159 
160         return new InjectionPoint(dependencyType, injectionProvider.getName(), false);
161     }
162 
163     @NonNull
164     private static Object createLogger(@NonNull TestedClass testedClass) {
165         TestedClass testedClassWithLogger = testedClass.parent;
166         assert testedClassWithLogger != null;
167         return Logger.getLogger(testedClassWithLogger.nameOfTestedClass);
168     }
169 
170     public static boolean isInstantiableType(@NonNull Class<?> type) {
171         if (type.isPrimitive() || type.isArray() || type.isAnnotation()) {
172             return false;
173         }
174 
175         if (type.isInterface()) {
176             return true;
177         }
178 
179         int typeModifiers = type.getModifiers();
180 
181         if ((typeModifiers & INVALID_TYPES) != 0 || !isStatic(typeModifiers) && type.isMemberClass()) {
182             return false;
183         }
184 
185         return type.getClassLoader() != null;
186     }
187 
188     @Nullable
189     private Object createInstance(@NonNull TestedClass testedClass, @NonNull Injector injector,
190             @Nullable InjectionProvider injectionProvider, @NonNull InjectionPoint injectionPoint) {
191         @SuppressWarnings("ConstantConditions")
192         @NonNull
193         Class<?> typeToInject = dependencyClass;
194         Object dependency = null;
195 
196         if (typeToInject.isInterface()) {
197             dependency = createInstanceOfSupportedInterfaceIfApplicable(testedClass, typeToInject, injectionPoint,
198                     injectionProvider);
199 
200             if (dependency == null && typeToInject.getClassLoader() != null) {
201                 Class<?> resolvedType = injectionState.resolveInterface(typeToInject);
202 
203                 if (resolvedType != null && !resolvedType.isInterface()) {
204                     // noinspection AssignmentToMethodParameter
205                     testedClass = new TestedClass(resolvedType, resolvedType);
206                     typeToInject = resolvedType;
207                 }
208             }
209         }
210 
211         if (dependency == null) {
212             dependency = createAndRegisterNewInstance(typeToInject, testedClass, injector, injectionPoint,
213                     injectionProvider);
214         }
215 
216         return dependency;
217     }
218 
219     @Nullable
220     private Object createInstanceOfSupportedInterfaceIfApplicable(@NonNull TestedClass testedClass,
221             @NonNull Class<?> typeToInject, @NonNull InjectionPoint injectionPoint,
222             @Nullable InjectionProvider injectionProvider) {
223         Object dependency = null;
224 
225         if (CommonDataSource.class.isAssignableFrom(typeToInject)) {
226             dependency = createAndRegisterDataSource(testedClass, injectionPoint, injectionProvider);
227         } else if (JAKARTA_INJECT_CLASS != null && typeToInject == jakarta.inject.Provider.class) {
228             assert injectionProvider != null;
229             dependency = createProviderJakartaInstance(injectionProvider);
230         } else if (JAVAX_INJECT_CLASS != null && typeToInject == javax.inject.Provider.class) {
231             assert injectionProvider != null;
232             dependency = createProviderJavaxInstance(injectionProvider);
233         } else if (JAKARTA_CONVERSATION_CLASS != null
234                 && typeToInject == jakarta.enterprise.context.Conversation.class) {
235             dependency = createAndRegisterConversationJakartaInstance();
236         } else if (JAVAX_CONVERSATION_CLASS != null && typeToInject == javax.enterprise.context.Conversation.class) {
237             dependency = createAndRegisterConversationJavaxInstance();
238         } else if (servletJakartaDependencies != null && ServletJakartaDependencies.isApplicable(typeToInject)) {
239             dependency = servletJakartaDependencies.createAndRegisterDependency(typeToInject);
240         } else if (servletJavaxDependencies != null && ServletJavaxDependencies.isApplicable(typeToInject)) {
241             dependency = servletJavaxDependencies.createAndRegisterDependency(typeToInject);
242         } else if (jpaJakartaDependencies != null && JPAJakartaDependencies.isApplicable(typeToInject)) {
243             dependency = jpaJakartaDependencies.createAndRegisterDependency(typeToInject, injectionPoint,
244                     injectionProvider);
245         } else if (jpaJavaxDependencies != null && JPAJavaxDependencies.isApplicable(typeToInject)) {
246             dependency = jpaJavaxDependencies.createAndRegisterDependency(typeToInject, injectionPoint,
247                     injectionProvider);
248         }
249 
250         return dependency;
251     }
252 
253     @Nullable
254     private Object createAndRegisterDataSource(@NonNull TestedClass testedClass, @NonNull InjectionPoint injectionPoint,
255             @Nullable InjectionProvider injectionProvider) {
256         if (injectionProvider == null) {
257             return null;
258         }
259 
260         // Check annotation is present (both jars)
261         if ((JAKARTA_RESOURCE_CLASS != null && injectionProvider.hasAnnotation(jakarta.annotation.Resource.class))
262                 || (JAVAX_RESOURCE_CLASS != null && injectionProvider.hasAnnotation(javax.annotation.Resource.class))) {
263             TestDataSource dsCreation = new TestDataSource(injectionPoint);
264             CommonDataSource dataSource = dsCreation.createIfDataSourceDefinitionAvailable(testedClass);
265 
266             if (dataSource != null) {
267                 injectionState.saveInstantiatedDependency(injectionPoint, dataSource);
268             }
269 
270             return dataSource;
271         }
272 
273         return null;
274     }
275 
276     @NonNull
277     private Object createProviderJakartaInstance(@NonNull InjectionProvider injectionProvider) {
278         ParameterizedType genericType = (ParameterizedType) injectionProvider.getDeclaredType();
279         final Class<?> providedClass = (Class<?>) genericType.getActualTypeArguments()[0];
280 
281         if (providedClass.isAnnotationPresent(jakarta.inject.Singleton.class)) {
282             return new jakarta.inject.Provider<Object>() {
283                 private Object dependency;
284 
285                 @Override
286                 public synchronized Object get() {
287                     if (dependency == null) {
288                         dependency = createNewInstance(providedClass, true);
289                     }
290 
291                     return dependency;
292                 }
293             };
294         }
295 
296         return (jakarta.inject.Provider<Object>) () -> createNewInstance(providedClass, false);
297     }
298 
299     @NonNull
300     private Object createProviderJavaxInstance(@NonNull InjectionProvider injectionProvider) {
301         ParameterizedType genericType = (ParameterizedType) injectionProvider.getDeclaredType();
302         final Class<?> providedClass = (Class<?>) genericType.getActualTypeArguments()[0];
303 
304         if (providedClass.isAnnotationPresent(javax.inject.Singleton.class)) {
305             return new javax.inject.Provider<Object>() {
306                 private Object dependency;
307 
308                 @Override
309                 public synchronized Object get() {
310                     if (dependency == null) {
311                         dependency = createNewInstance(providedClass, true);
312                     }
313 
314                     return dependency;
315                 }
316             };
317         }
318 
319         return (javax.inject.Provider<Object>) () -> createNewInstance(providedClass, false);
320     }
321 
322     @Nullable
323     private Object createNewInstance(@NonNull Class<?> classToInstantiate, boolean required) {
324         if (classToInstantiate.isInterface()) {
325             return null;
326         }
327 
328         if (classToInstantiate.getClassLoader() == null) {
329             return newInstanceUsingDefaultConstructorIfAvailable(classToInstantiate);
330         }
331 
332         return new TestedObjectCreation(injectionState, this, classToInstantiate).create(required, false);
333     }
334 
335     @NonNull
336     private Object createAndRegisterConversationJakartaInstance() {
337         jakarta.enterprise.context.Conversation conversation = new TestConversationJakarta();
338 
339         InjectionPoint injectionPoint = new InjectionPoint(jakarta.enterprise.context.Conversation.class);
340         injectionState.saveInstantiatedDependency(injectionPoint, conversation);
341         return conversation;
342     }
343 
344     @NonNull
345     private Object createAndRegisterConversationJavaxInstance() {
346         javax.enterprise.context.Conversation conversation = new TestConversationJavax();
347 
348         InjectionPoint injectionPoint = new InjectionPoint(javax.enterprise.context.Conversation.class);
349         injectionState.saveInstantiatedDependency(injectionPoint, conversation);
350         return conversation;
351     }
352 
353     @Nullable
354     private Object createAndRegisterNewInstance(@NonNull Class<?> typeToInstantiate, @NonNull TestedClass testedClass,
355             @NonNull Injector injector, @NonNull InjectionPoint injectionPoint,
356             @Nullable InjectionProvider injectionProvider) {
357         Object dependency = createNewInstance(typeToInstantiate,
358                 injectionProvider != null && injectionProvider.isRequired());
359 
360         if (dependency != null) {
361             if (injectionPoint.name == null) {
362                 assert injectionProvider != null;
363                 injectionPoint = new InjectionPoint(injectionPoint.type, injectionProvider.getName());
364             }
365 
366             registerNewInstance(testedClass, injector, injectionPoint, dependency);
367         }
368 
369         return dependency;
370     }
371 
372     private void registerNewInstance(@NonNull TestedClass testedClass, @NonNull Injector injector,
373             @NonNull InjectionPoint injectionPoint, @NonNull Object dependency) {
374         injectionState.saveInstantiatedDependency(injectionPoint, dependency);
375 
376         Class<?> instantiatedClass = dependency.getClass();
377 
378         if (testedClass.isClassFromSameModuleOrSystemAsTestedClass(instantiatedClass)) {
379             injector.fillOutDependenciesRecursively(dependency, testedClass);
380             injectionState.lifecycleMethods.findLifecycleMethods(instantiatedClass);
381             injectionState.lifecycleMethods.executeInitializationMethodsIfAny(instantiatedClass, dependency);
382         }
383     }
384 
385     @Override
386     public String toString() {
387         String description = "@Tested object \"" + testedClassName + ' ' + testedName + '"';
388 
389         if (parentInjectionProvider != null) {
390             InjectionProvider injectionProvider = parentInjectionProvider.parent;
391 
392             if (injectionProvider != null) {
393                 description = injectionProvider + "\r\n  of " + description;
394             }
395         }
396 
397         return description;
398     }
399 
400     public void clear() {
401         parentInjectionProvider = null;
402     }
403 }