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_INJECT_CLASS;
9   import static mockit.internal.injection.InjectionPoint.JAVAX_INJECT_CLASS;
10  import static mockit.internal.util.Utilities.getClassType;
11  
12  import edu.umd.cs.findbugs.annotations.NonNull;
13  import edu.umd.cs.findbugs.annotations.Nullable;
14  
15  import java.lang.reflect.ParameterizedType;
16  import java.lang.reflect.Type;
17  import java.util.ArrayList;
18  import java.util.Collections;
19  import java.util.List;
20  
21  import mockit.internal.injection.InjectionPoint.KindOfInjectionPoint;
22  import mockit.internal.reflection.GenericTypeReflection;
23  
24  public final class InjectionProviders {
25      @NonNull
26      private List<InjectionProvider> injectables;
27      @NonNull
28      private List<InjectionProvider> consumedInjectionProviders;
29      private Type typeOfInjectionPoint;
30      private KindOfInjectionPoint kindOfInjectionPoint;
31  
32      InjectionProviders(@NonNull LifecycleMethods lifecycleMethods) {
33          injectables = Collections.emptyList();
34          consumedInjectionProviders = new ArrayList<>();
35      }
36  
37      boolean setInjectables(
38              @SuppressWarnings("ParameterHidesMemberVariable") @NonNull List<? extends InjectionProvider> injectables) {
39          if (injectables.isEmpty()) {
40              this.injectables = Collections.emptyList();
41              return false;
42          }
43  
44          this.injectables = new ArrayList<>(injectables);
45          return true;
46      }
47  
48      @NonNull
49      List<InjectionProvider> addInjectables(@NonNull List<? extends InjectionProvider> injectablesToAdd) {
50          if (!injectablesToAdd.isEmpty()) {
51              if (injectables.isEmpty()) {
52                  injectables = new ArrayList<>(injectablesToAdd);
53              } else {
54                  injectables.addAll(injectablesToAdd);
55              }
56          }
57  
58          return injectables;
59      }
60  
61      public void setTypeOfInjectionPoint(@NonNull Type typeOfInjectionPoint,
62              @NonNull KindOfInjectionPoint kindOfInjectionPoint) {
63          this.typeOfInjectionPoint = typeOfInjectionPoint;
64          this.kindOfInjectionPoint = kindOfInjectionPoint;
65      }
66  
67      @Nullable
68      public InjectionProvider getProviderByTypeAndOptionallyName(@NonNull String nameOfInjectionPoint,
69              @NonNull TestedClass testedClass) {
70          if (kindOfInjectionPoint == KindOfInjectionPoint.Required) {
71              Type elementTypeOfIterable = getElementTypeIfIterable(typeOfInjectionPoint);
72  
73              if (elementTypeOfIterable != null) {
74                  return findInjectablesByTypeOnly(elementTypeOfIterable, testedClass);
75              }
76          }
77  
78          return findInjectableByTypeAndOptionallyName(nameOfInjectionPoint, testedClass);
79      }
80  
81      @Nullable
82      private static Type getElementTypeIfIterable(@NonNull Type injectableType) {
83          if (injectableType instanceof ParameterizedType) {
84              ParameterizedType parameterizedType = (ParameterizedType) injectableType;
85              Class<?> classOfInjectionPoint = (Class<?>) parameterizedType.getRawType();
86  
87              if (Iterable.class.isAssignableFrom(classOfInjectionPoint)) {
88                  return parameterizedType.getActualTypeArguments()[0];
89              }
90          }
91  
92          return null;
93      }
94  
95      @Nullable
96      public InjectionProvider findNextInjectableForInjectionPoint(@NonNull TestedClass testedClass) {
97          for (InjectionProvider injectable : injectables) {
98              if (hasTypeAssignableToInjectionPoint(injectable, testedClass)
99                      && !consumedInjectionProviders.contains(injectable)) {
100                 return injectable;
101             }
102         }
103 
104         return null;
105     }
106 
107     private boolean hasTypeAssignableToInjectionPoint(@NonNull InjectionProvider injectable,
108             @NonNull TestedClass testedClass) {
109         Type declaredType = injectable.getDeclaredType();
110         return isAssignableToInjectionPoint(declaredType, testedClass);
111     }
112 
113     boolean isAssignableToInjectionPoint(@NonNull Type injectableType, @NonNull TestedClass testedClass) {
114         if (testedClass.reflection.areMatchingTypes(typeOfInjectionPoint, injectableType)) {
115             return true;
116         }
117 
118         if (typeOfInjectionPoint instanceof ParameterizedType) {
119             ParameterizedType parameterizedType = (ParameterizedType) typeOfInjectionPoint;
120             Class<?> classOfInjectionPoint = (Class<?>) parameterizedType.getRawType();
121 
122             if (kindOfInjectionPoint == KindOfInjectionPoint.Required
123                     && Iterable.class.isAssignableFrom(classOfInjectionPoint)
124                     || (JAKARTA_INJECT_CLASS != null
125                             && jakarta.inject.Provider.class.isAssignableFrom(classOfInjectionPoint)
126                             || JAVAX_INJECT_CLASS != null
127                                     && javax.inject.Provider.class.isAssignableFrom(classOfInjectionPoint))) {
128                 Type providedType = parameterizedType.getActualTypeArguments()[0];
129 
130                 if (providedType.equals(injectableType)) {
131                     return true;
132                 }
133 
134                 Class<?> injectableClass = getClassType(injectableType);
135                 Class<?> providedClass = getClassType(providedType);
136 
137                 return providedClass.isAssignableFrom(injectableClass);
138             }
139         }
140 
141         return false;
142     }
143 
144     @Nullable
145     private InjectionProvider findInjectablesByTypeOnly(@NonNull Type elementType, @NonNull TestedClass testedClass) {
146         GenericTypeReflection typeReflection = testedClass.reflection;
147         MultiValuedProvider found = null;
148 
149         for (InjectionProvider injectable : injectables) {
150             Type injectableType = injectable.getDeclaredType();
151             Type elementTypeOfIterable = getElementTypeIfIterable(injectableType);
152 
153             if (elementTypeOfIterable != null && typeReflection.areMatchingTypes(elementType, elementTypeOfIterable)) {
154                 return injectable;
155             }
156 
157             if (isAssignableToInjectionPoint(injectableType, testedClass)) {
158                 if (found == null) {
159                     found = new MultiValuedProvider(elementType);
160                 }
161 
162                 found.addInjectable(injectable);
163             }
164         }
165 
166         return found;
167     }
168 
169     @Nullable
170     private InjectionProvider findInjectableByTypeAndOptionallyName(@NonNull String nameOfInjectionPoint,
171             @NonNull TestedClass testedClass) {
172         InjectionProvider foundInjectable = null;
173 
174         for (InjectionProvider injectable : injectables) {
175             if (hasTypeAssignableToInjectionPoint(injectable, testedClass)) {
176                 if (nameOfInjectionPoint.equals(injectable.getName())) {
177                     return injectable;
178                 }
179 
180                 if (foundInjectable == null) {
181                     foundInjectable = injectable;
182                 }
183             }
184         }
185 
186         return foundInjectable;
187     }
188 
189     @Nullable
190     InjectionProvider findInjectableByTypeAndName(@NonNull String nameOfInjectionPoint,
191             @NonNull TestedClass testedClass) {
192         for (InjectionProvider injectable : injectables) {
193             if (hasTypeAssignableToInjectionPoint(injectable, testedClass)
194                     && nameOfInjectionPoint.equals(injectable.getName())) {
195                 return injectable;
196             }
197         }
198 
199         return null;
200     }
201 
202     @Nullable
203     Object getValueToInject(@NonNull InjectionProvider injectionProvider, @Nullable Object currentTestClassInstance) {
204         if (consumedInjectionProviders.contains(injectionProvider)) {
205             return null;
206         }
207 
208         Object value = injectionProvider.getValue(currentTestClassInstance);
209 
210         if (value != null) {
211             consumedInjectionProviders.add(injectionProvider);
212         }
213 
214         return value;
215     }
216 
217     void resetConsumedInjectionProviders() {
218         consumedInjectionProviders.clear();
219     }
220 
221     @NonNull
222     public List<InjectionProvider> saveConsumedInjectionProviders() {
223         List<InjectionProvider> previouslyConsumed = consumedInjectionProviders;
224         consumedInjectionProviders = new ArrayList<>();
225         return previouslyConsumed;
226     }
227 
228     public void restoreConsumedInjectionProviders(@NonNull List<InjectionProvider> previouslyConsumed) {
229         consumedInjectionProviders = previouslyConsumed;
230     }
231 }