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