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