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;
6   
7   import static mockit.internal.injection.InjectionPoint.getQualifiedName;
8   
9   import edu.umd.cs.findbugs.annotations.NonNull;
10  import edu.umd.cs.findbugs.annotations.Nullable;
11  
12  import java.lang.reflect.Field;
13  import java.lang.reflect.Type;
14  import java.util.LinkedHashMap;
15  import java.util.List;
16  import java.util.Map;
17  import java.util.Map.Entry;
18  import java.util.concurrent.ConcurrentHashMap;
19  
20  import mockit.internal.reflection.FieldReflection;
21  import mockit.internal.reflection.GenericTypeReflection;
22  
23  /**
24   * Holds state used throughout the injection process while it's in progress for a given set of tested objects.
25   */
26  public final class InjectionState {
27      @NonNull
28      private static final Map<InjectionPoint, Object> globalObjects = new ConcurrentHashMap<>(2);
29  
30      @NonNull
31      private final Map<InjectionPoint, Object> testedObjects;
32      @NonNull
33      private final Map<InjectionPoint, Object> instantiatedDependencies;
34      @NonNull
35      public final InjectionProviders injectionProviders;
36      @NonNull
37      public final LifecycleMethods lifecycleMethods;
38      @NonNull
39      final InterfaceResolution interfaceResolution;
40      @Nullable
41      private BeanExporter beanExporter;
42      private Object currentTestClassInstance;
43  
44      InjectionState() {
45          testedObjects = new LinkedHashMap<>();
46          instantiatedDependencies = new LinkedHashMap<>();
47          lifecycleMethods = new LifecycleMethods();
48          injectionProviders = new InjectionProviders(lifecycleMethods);
49          interfaceResolution = new InterfaceResolution();
50      }
51  
52      void setInjectables(@NonNull Object testClassInstance, @NonNull List<? extends InjectionProvider> injectables) {
53          currentTestClassInstance = testClassInstance;
54          injectionProviders.setInjectables(injectables);
55          lifecycleMethods.getServletConfigForInitMethodsIfAny(injectables, testClassInstance);
56      }
57  
58      void addInjectables(@NonNull Object testClassInstance,
59              @NonNull List<? extends InjectionProvider> injectablesToAdd) {
60          currentTestClassInstance = testClassInstance;
61          List<InjectionProvider> injectables = injectionProviders.addInjectables(injectablesToAdd);
62          lifecycleMethods.getServletConfigForInitMethodsIfAny(injectables, testClassInstance);
63      }
64  
65      Object getCurrentTestClassInstance() {
66          return currentTestClassInstance;
67      }
68  
69      @Nullable
70      public Object getValueToInject(@NonNull InjectionProvider injectionProvider) {
71          return injectionProviders.getValueToInject(injectionProvider, currentTestClassInstance);
72      }
73  
74      void saveTestedObject(@NonNull InjectionPoint key, @NonNull Object testedObject, boolean global) {
75          Map<InjectionPoint, Object> objects = global ? globalObjects : testedObjects;
76          objects.put(key, testedObject);
77      }
78  
79      @Nullable
80      Object getTestedInstance(@NonNull InjectionPoint injectionPoint, boolean global) {
81          Object testedInstance = instantiatedDependencies.isEmpty() ? null
82                  : findPreviouslyInstantiatedDependency(injectionPoint);
83  
84          if (testedInstance == null) {
85              testedInstance = testedObjects.isEmpty() ? null : getValueFromExistingTestedObject(injectionPoint);
86          }
87  
88          if (testedInstance == null && global) {
89              testedInstance = globalObjects.get(injectionPoint);
90          }
91  
92          return testedInstance;
93      }
94  
95      @Nullable
96      private Object findPreviouslyInstantiatedDependency(@NonNull InjectionPoint injectionPoint) {
97          Object dependency = instantiatedDependencies.get(injectionPoint);
98  
99          if (dependency == null) {
100             InjectionPoint injectionPointWithTypeOnly = new InjectionPoint(injectionPoint.type);
101             dependency = instantiatedDependencies.get(injectionPointWithTypeOnly);
102 
103             if (dependency == null) {
104                 dependency = findMatchingObject(instantiatedDependencies, null, injectionPointWithTypeOnly);
105             }
106         }
107 
108         return dependency;
109     }
110 
111     @Nullable
112     private Object getValueFromExistingTestedObject(@NonNull InjectionPoint injectionPoint) {
113         for (Object testedObject : testedObjects.values()) {
114             Object fieldValue = getValueFromFieldOfEquivalentTypeAndName(injectionPoint, testedObject);
115 
116             if (fieldValue != null) {
117                 return fieldValue;
118             }
119         }
120 
121         return null;
122     }
123 
124     @Nullable
125     private static Object getValueFromFieldOfEquivalentTypeAndName(@NonNull InjectionPoint injectionPoint,
126             @NonNull Object testedObject) {
127         for (Field internalField : testedObject.getClass().getDeclaredFields()) {
128             Type fieldType = internalField.getGenericType();
129             String qualifiedName = getQualifiedName(internalField.getDeclaredAnnotations());
130             boolean qualified = qualifiedName != null;
131             String fieldName = qualified ? qualifiedName : internalField.getName();
132             InjectionPoint internalInjectionPoint = new InjectionPoint(fieldType, fieldName, qualified);
133 
134             if (internalInjectionPoint.equals(injectionPoint)) {
135                 return FieldReflection.getFieldValue(internalField, testedObject);
136             }
137         }
138 
139         return null;
140     }
141 
142     @Nullable
143     @SuppressWarnings("unchecked")
144     public static <D> D getGlobalDependency(@NonNull InjectionPoint key) {
145         return (D) globalObjects.get(key);
146     }
147 
148     @Nullable
149     public Object getTestedValue(@NonNull TestedClass testedClass, @NonNull InjectionPoint injectionPoint) {
150         Object testedValue = testedObjects.get(injectionPoint);
151 
152         if (testedValue == null) {
153             testedValue = findMatchingObject(testedObjects, testedClass, injectionPoint);
154         }
155 
156         return testedValue;
157     }
158 
159     @Nullable
160     public Object getInstantiatedDependency(@Nullable TestedClass testedClass, @NonNull InjectionPoint dependencyKey) {
161         Object dependency = testedObjects.get(dependencyKey);
162 
163         if (dependency == null) {
164             dependency = findMatchingObject(testedObjects, testedClass, dependencyKey);
165 
166             if (dependency == null) {
167                 dependency = instantiatedDependencies.get(dependencyKey);
168 
169                 if (dependency == null) {
170                     dependency = findMatchingObject(instantiatedDependencies, testedClass, dependencyKey);
171 
172                     if (dependency == null) {
173                         dependency = findMatchingObject(globalObjects, testedClass, dependencyKey);
174                     }
175                 }
176             }
177         }
178 
179         return dependency;
180     }
181 
182     @Nullable
183     private static Object findMatchingObject(@NonNull Map<InjectionPoint, Object> availableObjects,
184             @Nullable TestedClass testedClass, @NonNull InjectionPoint injectionPoint) {
185         if (availableObjects.isEmpty()) {
186             return null;
187         }
188 
189         GenericTypeReflection reflection = testedClass == null ? null : testedClass.reflection;
190         Type dependencyType = injectionPoint.type;
191         Object found = null;
192 
193         for (Entry<InjectionPoint, Object> injectionPointAndObject : availableObjects.entrySet()) {
194             InjectionPoint dependencyIP = injectionPointAndObject.getKey();
195             Object dependencyObject = injectionPointAndObject.getValue();
196 
197             if (injectionPoint.equals(dependencyIP)) {
198                 return dependencyObject;
199             }
200 
201             if (reflection != null) {
202                 if (!reflection.areMatchingTypes(dependencyType, dependencyIP.type)) {
203                     continue;
204                 }
205                 found = dependencyObject;
206             }
207 
208             if (injectionPoint.hasSameName(dependencyIP)) {
209                 return dependencyObject;
210             }
211         }
212 
213         return injectionPoint.qualified ? null : found;
214     }
215 
216     public void saveInstantiatedDependency(@NonNull InjectionPoint dependencyKey, @NonNull Object dependency) {
217         instantiatedDependencies.put(dependencyKey, dependency);
218     }
219 
220     public static void saveGlobalDependency(@NonNull InjectionPoint dependencyKey, @NonNull Object dependency) {
221         globalObjects.put(dependencyKey, dependency);
222     }
223 
224     void clearTestedObjectsAndInstantiatedDependencies() {
225         testedObjects.clear();
226         instantiatedDependencies.clear();
227     }
228 
229     @NonNull
230     BeanExporter getBeanExporter() {
231         if (beanExporter == null) {
232             beanExporter = new BeanExporter(this);
233         }
234 
235         return beanExporter;
236     }
237 
238     @Nullable
239     public Class<?> resolveInterface(@NonNull Class<?> anInterface) {
240         return interfaceResolution.resolveInterface(anInterface, currentTestClassInstance);
241     }
242 }