View Javadoc
1   /*
2    * Copyright (c) 2006 JMockit developers
3    * This file is subject to the terms of the MIT license (see LICENSE.txt).
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          // noinspection SimplifiableIfStatement
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 }