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 java.lang.reflect.Modifier.isFinal;
9   import static java.lang.reflect.Modifier.isStatic;
10  import static java.lang.reflect.Modifier.isVolatile;
11  import static java.util.regex.Pattern.compile;
12  
13  import static mockit.internal.injection.InjectionPoint.JAKARTA_PERSISTENCE_UNIT_CLASS;
14  import static mockit.internal.injection.InjectionPoint.JAVAX_PERSISTENCE_UNIT_CLASS;
15  import static mockit.internal.injection.InjectionPoint.convertToLegalJavaIdentifierIfNeeded;
16  import static mockit.internal.injection.InjectionPoint.getQualifiedName;
17  import static mockit.internal.injection.InjectionPoint.isJakartaServlet;
18  import static mockit.internal.injection.InjectionPoint.isJavaxServlet;
19  import static mockit.internal.injection.InjectionPoint.kindOfInjectionPoint;
20  import static mockit.internal.injection.InjectionPoint.wrapInProviderIfNeeded;
21  import static mockit.internal.injection.InjectionProvider.NULL;
22  
23  import edu.umd.cs.findbugs.annotations.NonNull;
24  import edu.umd.cs.findbugs.annotations.Nullable;
25  
26  import java.lang.reflect.Field;
27  import java.lang.reflect.Type;
28  import java.lang.reflect.TypeVariable;
29  import java.util.ArrayList;
30  import java.util.List;
31  import java.util.regex.Pattern;
32  
33  import mockit.internal.injection.InjectionPoint.KindOfInjectionPoint;
34  import mockit.internal.injection.field.FieldToInject;
35  import mockit.internal.injection.full.FullInjection;
36  import mockit.internal.reflection.FieldReflection;
37  import mockit.internal.util.DefaultValues;
38  
39  public class Injector {
40      private static final Pattern TYPE_NAME = compile("class |interface |java\\.lang\\.");
41  
42      @NonNull
43      protected final InjectionState injectionState;
44      @Nullable
45      protected final FullInjection fullInjection;
46  
47      protected Injector(@NonNull InjectionState state, @Nullable FullInjection fullInjection) {
48          injectionState = state;
49          this.fullInjection = fullInjection;
50      }
51  
52      @NonNull
53      static List<Field> findAllTargetInstanceFieldsInTestedClassHierarchy(@NonNull Class<?> actualTestedClass,
54              @NonNull TestedClass testedClass) {
55          List<Field> targetFields = new ArrayList<>();
56          Class<?> classWithFields = actualTestedClass;
57  
58          do {
59              addEligibleFields(targetFields, classWithFields);
60              classWithFields = classWithFields.getSuperclass();
61          } while (testedClass.isClassFromSameModuleOrSystemAsTestedClass(classWithFields)
62                  || isJakartaServlet(classWithFields) || isJavaxServlet(classWithFields));
63  
64          return targetFields;
65      }
66  
67      private static void addEligibleFields(@NonNull List<Field> targetFields, @NonNull Class<?> classWithFields) {
68          Field[] fields = classWithFields.getDeclaredFields();
69  
70          for (Field field : fields) {
71              if (isEligibleForInjection(field)) {
72                  targetFields.add(field);
73              }
74          }
75      }
76  
77      private static boolean isEligibleForInjection(@NonNull Field field) {
78          int modifiers = field.getModifiers();
79  
80          if (isFinal(modifiers)) {
81              return false;
82          }
83  
84          if (kindOfInjectionPoint(field) != KindOfInjectionPoint.NotAnnotated) {
85              return true;
86          }
87  
88          if (JAKARTA_PERSISTENCE_UNIT_CLASS != null
89                  && field.getType().isAnnotationPresent(jakarta.persistence.Entity.class)) {
90              return false;
91          }
92  
93          if (JAVAX_PERSISTENCE_UNIT_CLASS != null
94                  && field.getType().isAnnotationPresent(javax.persistence.Entity.class)) {
95              return false;
96          }
97  
98          return !isStatic(modifiers) && !isVolatile(modifiers);
99      }
100 
101     public final void fillOutDependenciesRecursively(@NonNull Object dependency, @NonNull TestedClass testedClass) {
102         Class<?> dependencyClass = dependency.getClass();
103         List<Field> targetFields = findAllTargetInstanceFieldsInTestedClassHierarchy(dependencyClass, testedClass);
104 
105         if (!targetFields.isEmpty()) {
106             List<InjectionProvider> currentlyConsumedInjectables = injectionState.injectionProviders
107                     .saveConsumedInjectionProviders();
108             injectIntoEligibleFields(targetFields, dependency, testedClass);
109             injectionState.injectionProviders.restoreConsumedInjectionProviders(currentlyConsumedInjectables);
110         }
111     }
112 
113     public final void injectIntoEligibleFields(@NonNull List<Field> targetFields, @NonNull Object testedObject,
114             @NonNull TestedClass testedClass) {
115         for (Field field : targetFields) {
116             if (targetFieldWasNotAssignedByConstructor(testedObject, field)) {
117                 Object injectableValue = getValueForFieldIfAvailable(targetFields, testedClass, field);
118 
119                 if (injectableValue != null && injectableValue != NULL) {
120                     injectableValue = wrapInProviderIfNeeded(field.getGenericType(), injectableValue);
121                     FieldReflection.setFieldValue(field, testedObject, injectableValue);
122                 }
123             }
124         }
125     }
126 
127     private static boolean targetFieldWasNotAssignedByConstructor(@NonNull Object testedObject,
128             @NonNull Field targetField) {
129         if (kindOfInjectionPoint(targetField) != KindOfInjectionPoint.NotAnnotated) {
130             return true;
131         }
132 
133         Object fieldValue = FieldReflection.getFieldValue(targetField, testedObject);
134 
135         if (fieldValue == null) {
136             return true;
137         }
138 
139         Class<?> fieldType = targetField.getType();
140 
141         if (!fieldType.isPrimitive()) {
142             return false;
143         }
144 
145         Object defaultValue = DefaultValues.defaultValueForPrimitiveType(fieldType);
146 
147         return fieldValue.equals(defaultValue);
148     }
149 
150     @Nullable
151     private Object getValueForFieldIfAvailable(@NonNull List<Field> targetFields, @NonNull TestedClass testedClass,
152             @NonNull Field targetField) {
153         @Nullable
154         String qualifiedFieldName = getQualifiedName(targetField.getDeclaredAnnotations());
155         InjectionProvider injectable = findAvailableInjectableIfAny(targetFields, qualifiedFieldName, testedClass,
156                 targetField);
157 
158         if (injectable != null) {
159             return injectionState.getValueToInject(injectable);
160         }
161 
162         InjectionProvider fieldToInject = new FieldToInject(targetField);
163         Type typeToInject = fieldToInject.getDeclaredType();
164         InjectionPoint injectionPoint = new InjectionPoint(typeToInject, fieldToInject.getName(), qualifiedFieldName);
165         TestedClass nextTestedClass = typeToInject instanceof TypeVariable<?> ? testedClass
166                 : new TestedClass(typeToInject, fieldToInject.getClassOfDeclaredType(), testedClass);
167         Object testedValue = injectionState.getTestedValue(nextTestedClass, injectionPoint);
168 
169         if (testedValue != null) {
170             return testedValue;
171         }
172 
173         if (fullInjection != null) {
174             Object newInstance = fullInjection.createOrReuseInstance(nextTestedClass, this, fieldToInject,
175                     qualifiedFieldName);
176 
177             if (newInstance != null) {
178                 return newInstance;
179             }
180         }
181 
182         KindOfInjectionPoint kindOfInjectionPoint = kindOfInjectionPoint(targetField);
183         throwExceptionIfUnableToInjectRequiredTargetField(kindOfInjectionPoint, targetField, qualifiedFieldName);
184         return null;
185     }
186 
187     @Nullable
188     private InjectionProvider findAvailableInjectableIfAny(@NonNull List<Field> targetFields,
189             @Nullable String qualifiedTargetFieldName, @NonNull TestedClass testedClass, @NonNull Field targetField) {
190         KindOfInjectionPoint kindOfInjectionPoint = kindOfInjectionPoint(targetField);
191         InjectionProviders injectionProviders = injectionState.injectionProviders;
192         injectionProviders.setTypeOfInjectionPoint(targetField.getGenericType(), kindOfInjectionPoint);
193 
194         if (qualifiedTargetFieldName != null && !qualifiedTargetFieldName.isEmpty()) {
195             String injectableName = convertToLegalJavaIdentifierIfNeeded(qualifiedTargetFieldName);
196             InjectionProvider matchingInjectable = injectionProviders.findInjectableByTypeAndName(injectableName,
197                     testedClass);
198 
199             if (matchingInjectable != null) {
200                 return matchingInjectable;
201             }
202         }
203 
204         String targetFieldName = targetField.getName();
205 
206         if (withMultipleTargetFieldsOfSameType(targetFields, testedClass, targetField, injectionProviders)) {
207             return injectionProviders.findInjectableByTypeAndName(targetFieldName, testedClass);
208         }
209 
210         return injectionProviders.getProviderByTypeAndOptionallyName(targetFieldName, testedClass);
211     }
212 
213     private static boolean withMultipleTargetFieldsOfSameType(@NonNull List<Field> targetFields,
214             @NonNull TestedClass testedClass, @NonNull Field targetField,
215             @NonNull InjectionProviders injectionProviders) {
216         for (Field anotherTargetField : targetFields) {
217             if (anotherTargetField != targetField && injectionProviders
218                     .isAssignableToInjectionPoint(anotherTargetField.getGenericType(), testedClass)) {
219                 return true;
220             }
221         }
222 
223         return false;
224     }
225 
226     private void throwExceptionIfUnableToInjectRequiredTargetField(@NonNull KindOfInjectionPoint kindOfInjectionPoint,
227             @NonNull Field targetField, @Nullable String qualifiedFieldName) {
228         if (kindOfInjectionPoint == KindOfInjectionPoint.Required) {
229             Type fieldType = targetField.getGenericType();
230             String fieldTypeName = fieldType.toString();
231             fieldTypeName = TYPE_NAME.matcher(fieldTypeName).replaceAll("");
232             String kindOfInjectable = "@Tested or @Injectable";
233 
234             if (fullInjection != null) {
235                 if (targetField.getType().isInterface()) {
236                     kindOfInjectable = "@Tested instance of an implementation class";
237                 } else {
238                     kindOfInjectable = "@Tested object";
239                 }
240             }
241 
242             throw new IllegalStateException("Missing " + kindOfInjectable + " for field " + fieldTypeName + ' '
243                     + targetField.getName() + (qualifiedFieldName == null ? "" : " (\"" + qualifiedFieldName + "\")")
244                     + " in " + targetField.getDeclaringClass().getSimpleName());
245         }
246     }
247 }