View Javadoc
1   /*
2    * Copyright (c) 2006 JMockit developers
3    * This file is subject to the terms of the MIT license (see LICENSE.txt).
4    */
5   package mockit.internal.injection.full;
6   
7   import static java.lang.reflect.Modifier.isStatic;
8   
9   import static mockit.internal.injection.InjectionPoint.CONVERSATION_CLASS;
10  import static mockit.internal.injection.InjectionPoint.INJECT_CLASS;
11  import static mockit.internal.injection.InjectionPoint.PERSISTENCE_UNIT_CLASS;
12  import static mockit.internal.injection.InjectionPoint.SERVLET_CLASS;
13  import static mockit.internal.reflection.ConstructorReflection.newInstanceUsingDefaultConstructorIfAvailable;
14  import static mockit.internal.util.Utilities.getClassType;
15  
16  import edu.umd.cs.findbugs.annotations.NonNull;
17  import edu.umd.cs.findbugs.annotations.Nullable;
18  
19  import java.lang.annotation.Annotation;
20  import java.lang.reflect.ParameterizedType;
21  import java.lang.reflect.Type;
22  import java.lang.reflect.TypeVariable;
23  import java.util.logging.Logger;
24  
25  import javax.annotation.Resource;
26  import javax.enterprise.context.Conversation;
27  import javax.inject.Provider;
28  import javax.inject.Singleton;
29  import javax.sql.CommonDataSource;
30  
31  import mockit.asm.jvmConstants.Access;
32  import mockit.internal.injection.InjectionPoint;
33  import mockit.internal.injection.InjectionProvider;
34  import mockit.internal.injection.InjectionState;
35  import mockit.internal.injection.Injector;
36  import mockit.internal.injection.TestedClass;
37  import mockit.internal.injection.TestedObjectCreation;
38  
39  /**
40   * Responsible for recursive injection of dependencies into a <code>@Tested(fullyInitialized = true)</code> object.
41   */
42  public final class FullInjection {
43      private static final int INVALID_TYPES = Access.ABSTRACT + Access.ANNOTATION + Access.ENUM;
44  
45      @NonNull
46      private final InjectionState injectionState;
47      @NonNull
48      private final String testedClassName;
49      @NonNull
50      private final String testedName;
51      @Nullable
52      private final ServletDependencies servletDependencies;
53      @Nullable
54      private final JPADependencies jpaDependencies;
55      @Nullable
56      private Class<?> dependencyClass;
57      @Nullable
58      private InjectionProvider parentInjectionProvider;
59  
60      public FullInjection(@NonNull InjectionState injectionState, @NonNull Class<?> testedClass,
61              @NonNull String testedName) {
62          this.injectionState = injectionState;
63          testedClassName = testedClass.getSimpleName();
64          this.testedName = testedName;
65          servletDependencies = SERVLET_CLASS == null ? null : new ServletDependencies(injectionState);
66          jpaDependencies = PERSISTENCE_UNIT_CLASS == null ? null : new JPADependencies(injectionState);
67      }
68  
69      @Nullable
70      public Object createOrReuseInstance(@NonNull TestedClass testedClass, @NonNull Injector injector,
71              @Nullable InjectionProvider injectionProvider, @Nullable String qualifiedName) {
72          setInjectionProvider(injectionProvider);
73  
74          InjectionPoint injectionPoint = getInjectionPoint(testedClass, injectionProvider, qualifiedName);
75          Object dependency = injectionState.getInstantiatedDependency(testedClass, injectionPoint);
76  
77          if (dependency != null) {
78              return dependency;
79          }
80  
81          Class<?> typeToInject = dependencyClass;
82  
83          if (typeToInject == Logger.class) {
84              return createLogger(testedClass);
85          }
86  
87          if (typeToInject == null || !isInstantiableType(typeToInject)) {
88              return null;
89          }
90  
91          return createInstance(testedClass, injector, injectionProvider, injectionPoint);
92      }
93  
94      public void setInjectionProvider(@Nullable InjectionProvider injectionProvider) {
95          if (injectionProvider != null) {
96              injectionProvider.parent = parentInjectionProvider;
97          }
98  
99          parentInjectionProvider = injectionProvider;
100     }
101 
102     @NonNull
103     private InjectionPoint getInjectionPoint(@NonNull TestedClass testedClass,
104             @Nullable InjectionProvider injectionProvider, @Nullable String qualifiedName) {
105         if (injectionProvider == null) {
106             dependencyClass = testedClass.targetClass;
107             return new InjectionPoint(dependencyClass, qualifiedName, true);
108         }
109 
110         Type dependencyType = injectionProvider.getDeclaredType();
111 
112         if (dependencyType instanceof TypeVariable<?>) {
113             dependencyType = testedClass.reflection.resolveTypeVariable((TypeVariable<?>) dependencyType);
114             dependencyClass = getClassType(dependencyType);
115         } else {
116             dependencyClass = injectionProvider.getClassOfDeclaredType();
117         }
118 
119         if (qualifiedName != null && !qualifiedName.isEmpty()) {
120             return new InjectionPoint(dependencyClass, qualifiedName, true);
121         }
122 
123         if (jpaDependencies != null && JPADependencies.isApplicable(dependencyClass)) {
124             for (Annotation annotation : injectionProvider.getAnnotations()) {
125                 InjectionPoint injectionPoint = jpaDependencies.getInjectionPointIfAvailable(annotation);
126 
127                 if (injectionPoint != null) {
128                     return injectionPoint;
129                 }
130             }
131         }
132 
133         return new InjectionPoint(dependencyType, injectionProvider.getName(), false);
134     }
135 
136     @NonNull
137     private static Object createLogger(@NonNull TestedClass testedClass) {
138         TestedClass testedClassWithLogger = testedClass.parent;
139         assert testedClassWithLogger != null;
140         return Logger.getLogger(testedClassWithLogger.nameOfTestedClass);
141     }
142 
143     public static boolean isInstantiableType(@NonNull Class<?> type) {
144         if (type.isPrimitive() || type.isArray() || type.isAnnotation()) {
145             return false;
146         }
147 
148         if (type.isInterface()) {
149             return true;
150         }
151 
152         int typeModifiers = type.getModifiers();
153 
154         if ((typeModifiers & INVALID_TYPES) != 0 || !isStatic(typeModifiers) && type.isMemberClass()) {
155             return false;
156         }
157 
158         return type.getClassLoader() != null;
159     }
160 
161     @Nullable
162     private Object createInstance(@NonNull TestedClass testedClass, @NonNull Injector injector,
163             @Nullable InjectionProvider injectionProvider, @NonNull InjectionPoint injectionPoint) {
164         @SuppressWarnings("ConstantConditions")
165         @NonNull
166         Class<?> typeToInject = dependencyClass;
167         Object dependency = null;
168 
169         if (typeToInject.isInterface()) {
170             dependency = createInstanceOfSupportedInterfaceIfApplicable(testedClass, typeToInject, injectionPoint,
171                     injectionProvider);
172 
173             if (dependency == null && typeToInject.getClassLoader() != null) {
174                 Class<?> resolvedType = injectionState.resolveInterface(typeToInject);
175 
176                 if (resolvedType != null && !resolvedType.isInterface()) {
177                     // noinspection AssignmentToMethodParameter
178                     testedClass = new TestedClass(resolvedType, resolvedType);
179                     typeToInject = resolvedType;
180                 }
181             }
182         }
183 
184         if (dependency == null) {
185             dependency = createAndRegisterNewInstance(typeToInject, testedClass, injector, injectionPoint,
186                     injectionProvider);
187         }
188 
189         return dependency;
190     }
191 
192     @Nullable
193     private Object createInstanceOfSupportedInterfaceIfApplicable(@NonNull TestedClass testedClass,
194             @NonNull Class<?> typeToInject, @NonNull InjectionPoint injectionPoint,
195             @Nullable InjectionProvider injectionProvider) {
196         Object dependency = null;
197 
198         if (CommonDataSource.class.isAssignableFrom(typeToInject)) {
199             dependency = createAndRegisterDataSource(testedClass, injectionPoint, injectionProvider);
200         } else if (INJECT_CLASS != null && typeToInject == Provider.class) {
201             assert injectionProvider != null;
202             dependency = createProviderInstance(injectionProvider);
203         } else if (CONVERSATION_CLASS != null && typeToInject == Conversation.class) {
204             dependency = createAndRegisterConversationInstance();
205         } else if (servletDependencies != null && ServletDependencies.isApplicable(typeToInject)) {
206             dependency = servletDependencies.createAndRegisterDependency(typeToInject);
207         } else if (jpaDependencies != null && JPADependencies.isApplicable(typeToInject)) {
208             dependency = jpaDependencies.createAndRegisterDependency(typeToInject, injectionPoint, injectionProvider);
209         }
210 
211         return dependency;
212     }
213 
214     @Nullable
215     private Object createAndRegisterDataSource(@NonNull TestedClass testedClass, @NonNull InjectionPoint injectionPoint,
216             @Nullable InjectionProvider injectionProvider) {
217         if (injectionProvider == null || !injectionProvider.hasAnnotation(Resource.class)) {
218             return null;
219         }
220 
221         TestDataSource dsCreation = new TestDataSource(injectionPoint);
222         CommonDataSource dataSource = dsCreation.createIfDataSourceDefinitionAvailable(testedClass);
223 
224         if (dataSource != null) {
225             injectionState.saveInstantiatedDependency(injectionPoint, dataSource);
226         }
227 
228         return dataSource;
229     }
230 
231     @NonNull
232     private Object createProviderInstance(@NonNull InjectionProvider injectionProvider) {
233         ParameterizedType genericType = (ParameterizedType) injectionProvider.getDeclaredType();
234         final Class<?> providedClass = (Class<?>) genericType.getActualTypeArguments()[0];
235 
236         if (providedClass.isAnnotationPresent(Singleton.class)) {
237             return new Provider<Object>() {
238                 private Object dependency;
239 
240                 @Override
241                 public synchronized Object get() {
242                     if (dependency == null) {
243                         dependency = createNewInstance(providedClass, true);
244                     }
245 
246                     return dependency;
247                 }
248             };
249         }
250 
251         return (Provider<Object>) () -> createNewInstance(providedClass, false);
252     }
253 
254     @Nullable
255     private Object createNewInstance(@NonNull Class<?> classToInstantiate, boolean required) {
256         if (classToInstantiate.isInterface()) {
257             return null;
258         }
259 
260         if (classToInstantiate.getClassLoader() == null) {
261             return newInstanceUsingDefaultConstructorIfAvailable(classToInstantiate);
262         }
263 
264         return new TestedObjectCreation(injectionState, this, classToInstantiate).create(required, false);
265     }
266 
267     @NonNull
268     private Object createAndRegisterConversationInstance() {
269         Conversation conversation = new TestConversation();
270 
271         InjectionPoint injectionPoint = new InjectionPoint(Conversation.class);
272         injectionState.saveInstantiatedDependency(injectionPoint, conversation);
273         return conversation;
274     }
275 
276     @Nullable
277     private Object createAndRegisterNewInstance(@NonNull Class<?> typeToInstantiate, @NonNull TestedClass testedClass,
278             @NonNull Injector injector, @NonNull InjectionPoint injectionPoint,
279             @Nullable InjectionProvider injectionProvider) {
280         Object dependency = createNewInstance(typeToInstantiate,
281                 injectionProvider != null && injectionProvider.isRequired());
282 
283         if (dependency != null) {
284             if (injectionPoint.name == null) {
285                 assert injectionProvider != null;
286                 injectionPoint = new InjectionPoint(injectionPoint.type, injectionProvider.getName());
287             }
288 
289             registerNewInstance(testedClass, injector, injectionPoint, dependency);
290         }
291 
292         return dependency;
293     }
294 
295     private void registerNewInstance(@NonNull TestedClass testedClass, @NonNull Injector injector,
296             @NonNull InjectionPoint injectionPoint, @NonNull Object dependency) {
297         injectionState.saveInstantiatedDependency(injectionPoint, dependency);
298 
299         Class<?> instantiatedClass = dependency.getClass();
300 
301         if (testedClass.isClassFromSameModuleOrSystemAsTestedClass(instantiatedClass)) {
302             injector.fillOutDependenciesRecursively(dependency, testedClass);
303             injectionState.lifecycleMethods.findLifecycleMethods(instantiatedClass);
304             injectionState.lifecycleMethods.executeInitializationMethodsIfAny(instantiatedClass, dependency);
305         }
306     }
307 
308     @Override
309     public String toString() {
310         String description = "@Tested object \"" + testedClassName + ' ' + testedName + '"';
311 
312         if (parentInjectionProvider != null) {
313             InjectionProvider injectionProvider = parentInjectionProvider.parent;
314 
315             if (injectionProvider != null) {
316                 description = injectionProvider + "\r\n  of " + description;
317             }
318         }
319 
320         return description;
321     }
322 
323     public void clear() {
324         parentInjectionProvider = null;
325     }
326 }