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