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