1
2
3
4
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 }