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.expectations.mocking;
6   
7   import static java.lang.reflect.Modifier.isFinal;
8   
9   import edu.umd.cs.findbugs.annotations.NonNull;
10  import edu.umd.cs.findbugs.annotations.Nullable;
11  
12  import java.lang.annotation.Annotation;
13  import java.lang.reflect.Field;
14  import java.lang.reflect.ParameterizedType;
15  import java.lang.reflect.Type;
16  import java.lang.reflect.TypeVariable;
17  
18  import mockit.Capturing;
19  import mockit.Injectable;
20  import mockit.Mocked;
21  import mockit.internal.expectations.MockingFilters;
22  import mockit.internal.expectations.state.CascadingTypes;
23  import mockit.internal.expectations.state.ExecutingTest;
24  import mockit.internal.injection.InjectionProvider;
25  import mockit.internal.reflection.FieldReflection;
26  import mockit.internal.state.ParameterNames;
27  import mockit.internal.state.TestRun;
28  import mockit.internal.util.DefaultValues;
29  import mockit.internal.util.TestMethod;
30  import mockit.internal.util.TypeConversion;
31  import mockit.internal.util.Utilities;
32  
33  import org.checkerframework.checker.index.qual.NonNegative;
34  
35  @SuppressWarnings("EqualsAndHashcode")
36  public final class MockedType extends InjectionProvider {
37      @Mocked
38      private static final Object DUMMY = null;
39      private static final int DUMMY_HASHCODE;
40  
41      static {
42          int h = 0;
43  
44          try {
45              Field dummyField = MockedType.class.getDeclaredField("DUMMY");
46              Mocked mocked = dummyField.getAnnotation(Mocked.class);
47              h = mocked.hashCode();
48          } catch (NoSuchFieldException ignore) {
49          }
50  
51          DUMMY_HASHCODE = h;
52      }
53  
54      @Nullable
55      public final Field field;
56      final boolean fieldFromTestClass;
57      private final int accessModifiers;
58      @Nullable
59      private final Mocked mocked;
60      @Nullable
61      private final Capturing capturing;
62      @Nullable
63      private final Class<?> parameterImplementationClass;
64      public final boolean injectable;
65      @Nullable
66      Object providedValue;
67  
68      public MockedType(@NonNull Field field) {
69          super(field.getGenericType(), field.getName());
70          this.field = field;
71          fieldFromTestClass = true;
72          accessModifiers = field.getModifiers();
73          mocked = field.getAnnotation(Mocked.class);
74          capturing = field.getAnnotation(Capturing.class);
75          parameterImplementationClass = null;
76          Injectable injectableAnnotation = field.getAnnotation(Injectable.class);
77          injectable = injectableAnnotation != null;
78          providedValue = getProvidedInjectableValue(injectableAnnotation);
79          registerCascadingAsNeeded();
80      }
81  
82      @Nullable
83      private Object getProvidedInjectableValue(@Nullable Injectable annotation) {
84          if (annotation != null) {
85              String value = annotation.value();
86  
87              if (!value.isEmpty()) {
88                  Class<?> injectableClass = getClassType();
89  
90                  if (injectableClass != TypeVariable.class) {
91                      return TypeConversion.convertFromString(injectableClass, value);
92                  }
93              }
94          }
95  
96          return null;
97      }
98  
99      private void registerCascadingAsNeeded() {
100         if (isMockableType()) {
101             Type mockedType = declaredType;
102 
103             if (!(mockedType instanceof TypeVariable<?>)) {
104                 ExecutingTest executingTest = TestRun.getExecutingTest();
105                 CascadingTypes types = executingTest.getCascadingTypes();
106                 types.add(fieldFromTestClass, mockedType);
107             }
108         }
109     }
110 
111     MockedType(@NonNull TestMethod testMethod, @NonNegative int paramIndex, @NonNull Type parameterType,
112             @NonNull Annotation[] annotationsOnParameter, @Nullable Class<?> parameterImplementationClass) {
113         super(parameterType, ParameterNames.getName(testMethod, paramIndex));
114         field = null;
115         fieldFromTestClass = false;
116         accessModifiers = 0;
117         mocked = getAnnotation(annotationsOnParameter, Mocked.class);
118         capturing = getAnnotation(annotationsOnParameter, Capturing.class);
119         this.parameterImplementationClass = parameterImplementationClass;
120         Injectable injectableAnnotation = getAnnotation(annotationsOnParameter, Injectable.class);
121         injectable = injectableAnnotation != null;
122         providedValue = getProvidedInjectableValue(injectableAnnotation);
123 
124         if (providedValue == null && parameterType instanceof Class<?>) {
125             Class<?> parameterClass = (Class<?>) parameterType;
126 
127             if (parameterClass.isPrimitive()) {
128                 providedValue = DefaultValues.defaultValueForPrimitiveType(parameterClass);
129             }
130         }
131 
132         registerCascadingAsNeeded();
133     }
134 
135     @Nullable
136     private static <A extends Annotation> A getAnnotation(@NonNull Annotation[] annotations,
137             @NonNull Class<A> annotation) {
138         for (Annotation paramAnnotation : annotations) {
139             if (paramAnnotation.annotationType() == annotation) {
140                 // noinspection unchecked
141                 return (A) paramAnnotation;
142             }
143         }
144 
145         return null;
146     }
147 
148     MockedType(@NonNull String cascadingMethodName, @NonNull Type cascadedType) {
149         super(cascadedType, cascadingMethodName);
150         field = null;
151         fieldFromTestClass = false;
152         accessModifiers = 0;
153         mocked = null;
154         capturing = null;
155         injectable = true;
156         parameterImplementationClass = null;
157     }
158 
159     @NonNull
160     @Override
161     public Class<?> getClassOfDeclaredType() {
162         return getClassType();
163     }
164 
165     /**
166      * @return the class object corresponding to the type to be mocked, or <code>TypeVariable.class</code> in case the
167      *         mocked type is a type variable (which usually occurs when the mocked type implements/extends multiple
168      *         types)
169      */
170     @NonNull
171     public Class<?> getClassType() {
172         if (parameterImplementationClass != null) {
173             return parameterImplementationClass;
174         }
175 
176         Type mockedType = declaredType;
177 
178         if (mockedType instanceof Class<?>) {
179             return (Class<?>) mockedType;
180         }
181 
182         if (mockedType instanceof ParameterizedType) {
183             ParameterizedType parameterizedType = (ParameterizedType) mockedType;
184             return (Class<?>) parameterizedType.getRawType();
185         }
186 
187         // Occurs when declared type is a TypeVariable, usually having two or more bound types.
188         // In such cases, there isn't a single class type.
189         return TypeVariable.class;
190     }
191 
192     boolean isMockableType() {
193         if (mocked == null && !injectable && capturing == null) {
194             return false;
195         }
196 
197         Class<?> classType = Utilities.getClassType(declaredType);
198 
199         if (isUnmockableJREType(classType)) {
200             return false;
201         }
202 
203         MockingFilters.validateAsMockable(classType);
204 
205         if (injectable) {
206             return !isJREValueType(classType) && !classType.isEnum();
207         }
208 
209         return true;
210     }
211 
212     private static boolean isUnmockableJREType(@NonNull Class<?> type) {
213         return type.isPrimitive() || type.isArray() || type == Integer.class || type == String.class;
214     }
215 
216     private static boolean isJREValueType(@NonNull Class<?> type) {
217         return type == String.class || type == Boolean.class || type == Character.class
218                 || Number.class.isAssignableFrom(type);
219     }
220 
221     boolean isFinalFieldOrParameter() {
222         return field == null || isFinal(accessModifiers);
223     }
224 
225     boolean isClassInitializationToBeStubbedOut() {
226         return mocked != null && mocked.stubOutClassInitialization();
227     }
228 
229     boolean withInstancesToCapture() {
230         return getMaxInstancesToCapture() > 0;
231     }
232 
233     public int getMaxInstancesToCapture() {
234         return capturing == null ? 0 : capturing.maxInstances();
235     }
236 
237     @Nullable
238     @Override
239     public Object getValue(@Nullable Object owner) {
240         if (field == null) {
241             return providedValue;
242         }
243 
244         Object value = FieldReflection.getFieldValue(field, owner);
245 
246         if (!injectable) {
247             return value;
248         }
249 
250         Class<?> fieldType = field.getType();
251 
252         if (value == null) {
253             if (providedValue != null) {
254                 return providedValue;
255             }
256 
257             if (isFinalFieldOrParameter()) {
258                 return NULL;
259             }
260 
261             if (fieldType == String.class) {
262                 return "";
263             }
264 
265             return null;
266         }
267 
268         if (providedValue == null || !fieldType.isPrimitive()) {
269             return value;
270         }
271 
272         Object defaultValue = DefaultValues.defaultValueForPrimitiveType(fieldType);
273 
274         return value.equals(defaultValue) ? providedValue : value;
275     }
276 
277     @Override
278     public int hashCode() {
279         int result = declaredType.hashCode();
280 
281         if (isFinal(accessModifiers)) {
282             result *= 31;
283         }
284 
285         if (injectable) {
286             result *= 37;
287         }
288 
289         if (mocked != null) {
290             int h = mocked.hashCode();
291 
292             if (h != DUMMY_HASHCODE) {
293                 result = 31 * result + h;
294             }
295         }
296 
297         return result;
298     }
299 }