1
2
3
4
5 package mockit.internal.expectations.state;
6
7 import static java.lang.reflect.Modifier.INTERFACE;
8 import static java.lang.reflect.Modifier.PUBLIC;
9 import static java.lang.reflect.Modifier.isPublic;
10 import static java.util.Collections.synchronizedList;
11
12 import static mockit.asm.types.JavaType.getInternalName;
13 import static mockit.internal.util.Utilities.containsReference;
14 import static mockit.internal.util.Utilities.getClassType;
15
16 import edu.umd.cs.findbugs.annotations.NonNull;
17 import edu.umd.cs.findbugs.annotations.Nullable;
18
19 import java.lang.reflect.Method;
20 import java.lang.reflect.ParameterizedType;
21 import java.lang.reflect.Type;
22 import java.lang.reflect.TypeVariable;
23 import java.util.ArrayList;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.concurrent.ConcurrentHashMap;
27
28 import mockit.internal.expectations.MockingFilters;
29 import mockit.internal.expectations.mocking.CascadingTypeRedefinition;
30 import mockit.internal.expectations.mocking.InstanceFactory;
31 import mockit.internal.reflection.GenericTypeReflection;
32 import mockit.internal.reflection.RealMethodOrConstructor;
33 import mockit.internal.state.TestRun;
34 import mockit.internal.util.DefaultValues;
35
36 public final class MockedTypeCascade {
37 @NonNull
38 private static final CascadingTypes CASCADING_TYPES = TestRun.getExecutingTest().getCascadingTypes();
39 private static final int PUBLIC_INTERFACE = PUBLIC + INTERFACE;
40
41 final boolean fromMockField;
42 @NonNull
43 private final Type mockedType;
44 @NonNull
45 final String mockedTypeDesc;
46 @Nullable
47 Class<?> mockedClass;
48 @Nullable
49 private GenericTypeReflection genericReflection;
50 @NonNull
51 private final Map<String, Type> cascadedTypesAndMocks;
52 @NonNull
53 private final List<Object> cascadingInstances;
54
55 MockedTypeCascade(boolean fromMockField, @NonNull Type mockedType, @NonNull String mockedTypeDesc) {
56 this.fromMockField = fromMockField;
57 this.mockedType = mockedType;
58 this.mockedTypeDesc = mockedTypeDesc;
59 cascadedTypesAndMocks = new ConcurrentHashMap<>(4);
60 cascadingInstances = synchronizedList(new ArrayList<>());
61 }
62
63 @Nullable
64 public static Object getMock(@NonNull String mockedTypeDesc, @NonNull String mockedMethodNameAndDesc,
65 @Nullable Object mockInstance, @NonNull String returnTypeDesc, @NonNull Class<?> returnType) {
66 MockedTypeCascade cascade = CASCADING_TYPES.getCascade(mockedTypeDesc, mockInstance);
67
68 if (cascade == null) {
69 return null;
70 }
71
72 String cascadedReturnTypeDesc = getReturnTypeIfCascadingSupportedForIt(returnTypeDesc);
73
74 if (cascadedReturnTypeDesc == null) {
75 return null;
76 }
77
78 return cascade.getCascadedInstance(mockedMethodNameAndDesc, cascadedReturnTypeDesc, returnType);
79 }
80
81 @Nullable
82 public static Object getMock(@NonNull String mockedTypeDesc, @NonNull String mockedMethodNameAndDesc,
83 @Nullable Object mockInstance, @NonNull String returnTypeDesc, @Nullable String genericSignature) {
84 char typeCode = returnTypeDesc.charAt(0);
85
86 if (typeCode != 'L') {
87 return null;
88 }
89
90 MockedTypeCascade cascade = CASCADING_TYPES.getCascade(mockedTypeDesc, mockInstance);
91
92 if (cascade == null) {
93 return null;
94 }
95
96 String resolvedReturnTypeDesc = null;
97
98 if (genericSignature != null) {
99 resolvedReturnTypeDesc = cascade.getGenericReturnType(mockedTypeDesc, genericSignature);
100 }
101
102 if (resolvedReturnTypeDesc == null) {
103 resolvedReturnTypeDesc = getReturnTypeIfCascadingSupportedForIt(returnTypeDesc);
104
105 if (resolvedReturnTypeDesc == null) {
106 return null;
107 }
108 } else if (resolvedReturnTypeDesc.charAt(0) == '[') {
109 return DefaultValues.computeForArrayType(resolvedReturnTypeDesc);
110 }
111
112 return cascade.getCascadedInstance(mockedMethodNameAndDesc, resolvedReturnTypeDesc, mockInstance);
113 }
114
115 @Nullable
116 private String getGenericReturnType(@NonNull String ownerTypeDesc, @NonNull String genericSignature) {
117 String resolvedSignature = getGenericReflection().resolveSignature(ownerTypeDesc, genericSignature);
118 String returnTypeDesc = resolvedSignature.substring(resolvedSignature.indexOf(')') + 1);
119
120 if (returnTypeDesc.charAt(0) == '[') {
121 return returnTypeDesc;
122 }
123
124 String returnTypeName = returnTypeDesc.substring(1, returnTypeDesc.length() - 1);
125 return isTypeSupportedForCascading(returnTypeName) ? returnTypeName : null;
126 }
127
128 @NonNull
129 private synchronized GenericTypeReflection getGenericReflection() {
130 GenericTypeReflection reflection = genericReflection;
131
132 if (reflection == null) {
133 Class<?> ownerClass = getClassWithCalledMethod();
134 reflection = new GenericTypeReflection(ownerClass, mockedType);
135 genericReflection = reflection;
136 }
137
138 return reflection;
139 }
140
141 private static boolean isReturnTypeNotSupportedForCascading(@NonNull Class<?> returnType) {
142 return MockingFilters.isSubclassOfUnmockable(returnType)
143 || !isTypeSupportedForCascading(getInternalName(returnType));
144 }
145
146 @SuppressWarnings("OverlyComplexMethod")
147 private static boolean isTypeSupportedForCascading(@NonNull String typeName) {
148
149 if (typeName.contains("/Process") || typeName.endsWith("/Runnable")) {
150 return true;
151 }
152
153 return (!typeName.startsWith("java/lang/") || typeName.contains("management"))
154 && !typeName.startsWith("java/math/")
155 && (!typeName.startsWith("java/util/") || typeName.endsWith("/Date") || typeName.endsWith("/Callable")
156 || typeName.endsWith("Future") || typeName.contains("logging"))
157 && !"java/time/Duration".equals(typeName);
158 }
159
160 @Nullable
161 private static String getReturnTypeIfCascadingSupportedForIt(@NonNull String typeDesc) {
162 String typeName = typeDesc.substring(1, typeDesc.length() - 1);
163 return isTypeSupportedForCascading(typeName) ? typeName : null;
164 }
165
166 @Nullable
167 private Object getCascadedInstance(@NonNull String methodNameAndDesc, @NonNull String returnTypeInternalName,
168 @NonNull Class<?> returnClass) {
169 MockedTypeCascade nextLevel = this;
170
171 if (!cascadedTypesAndMocks.containsKey(returnTypeInternalName)) {
172 cascadedTypesAndMocks.put(returnTypeInternalName, returnClass);
173 nextLevel = CASCADING_TYPES.add(returnTypeInternalName, false, returnClass);
174 }
175
176 return nextLevel.createNewCascadedInstanceOrUseNonCascadedOneIfAvailable(methodNameAndDesc, returnClass);
177 }
178
179 @Nullable
180 private Object getCascadedInstance(@NonNull String methodNameAndDesc, @NonNull String returnTypeInternalName,
181 @Nullable Object mockInstance) {
182 MockedTypeCascade nextLevel = this;
183 Type returnType = cascadedTypesAndMocks.get(returnTypeInternalName);
184
185 if (returnType == null) {
186 Class<?> cascadingClass = getClassWithCalledMethod();
187 Type genericReturnType = getGenericReturnType(cascadingClass, methodNameAndDesc);
188
189 if (genericReturnType == null) {
190 return null;
191 }
192
193 Class<?> resolvedReturnType = getClassType(genericReturnType);
194
195 if (resolvedReturnType.isAssignableFrom(cascadingClass)) {
196 if (mockInstance != null) {
197 return mockInstance;
198 }
199
200 returnType = mockedType;
201 } else if (nonPublicTypeReturnedFromPublicInterface(cascadingClass, resolvedReturnType)
202 || isReturnTypeNotSupportedForCascading(resolvedReturnType)) {
203 return null;
204 } else {
205 Object defaultReturnValue = DefaultValues.computeForType(resolvedReturnType);
206
207 if (defaultReturnValue != null) {
208 return defaultReturnValue;
209 }
210
211 cascadedTypesAndMocks.put(returnTypeInternalName, genericReturnType);
212 nextLevel = CASCADING_TYPES.add(returnTypeInternalName, false, genericReturnType);
213 returnType = genericReturnType;
214 }
215 } else {
216 nextLevel = CASCADING_TYPES.getCascade(returnType);
217 }
218
219 return nextLevel.createNewCascadedInstanceOrUseNonCascadedOneIfAvailable(methodNameAndDesc, returnType);
220 }
221
222 private static boolean nonPublicTypeReturnedFromPublicInterface(@NonNull Class<?> cascadingClass,
223 @NonNull Class<?> resolvedReturnType) {
224 return cascadingClass.isInterface() && !isPublic(resolvedReturnType.getModifiers())
225 && cascadingClass.getClassLoader() != null && (cascadingClass.getModifiers() & PUBLIC_INTERFACE) != 0
226 && !resolvedReturnType.isMemberClass();
227 }
228
229 @NonNull
230 private Class<?> getClassWithCalledMethod() {
231 if (mockedClass != null) {
232 return mockedClass;
233 }
234
235 if (mockedType instanceof Class<?>) {
236 return (Class<?>) mockedType;
237 }
238
239 return (Class<?>) ((ParameterizedType) mockedType).getRawType();
240 }
241
242 @Nullable
243 private Type getGenericReturnType(@NonNull Class<?> cascadingClass, @NonNull String methodNameAndDesc) {
244 RealMethodOrConstructor realMethod;
245
246 try {
247 realMethod = new RealMethodOrConstructor(cascadingClass, methodNameAndDesc);
248 } catch (NoSuchMethodException e) {
249 return null;
250 }
251
252 Method cascadingMethod = realMethod.getMember();
253 Type genericReturnType = cascadingMethod.getGenericReturnType();
254
255 if (genericReturnType instanceof TypeVariable<?>) {
256 genericReturnType = getGenericReflection().resolveTypeVariable((TypeVariable<?>) genericReturnType);
257 }
258
259 return genericReturnType == Object.class ? null : genericReturnType;
260 }
261
262 @Nullable
263 private Object createNewCascadedInstanceOrUseNonCascadedOneIfAvailable(@NonNull String methodNameAndDesc,
264 @NonNull Type mockedReturnType) {
265 InstanceFactory instanceFactory = TestRun.mockFixture().findInstanceFactory(mockedReturnType);
266
267 if (instanceFactory == null) {
268 String methodName = methodNameAndDesc.substring(0, methodNameAndDesc.indexOf('('));
269 CascadingTypeRedefinition typeRedefinition = new CascadingTypeRedefinition(methodName, mockedReturnType);
270 instanceFactory = typeRedefinition.redefineType();
271
272 if (instanceFactory == null) {
273 return null;
274 }
275 } else {
276 Object lastInstance = instanceFactory.getLastInstance();
277
278 if (lastInstance != null) {
279 return lastInstance;
280 }
281 }
282
283 Object cascadedInstance = instanceFactory.create();
284 instanceFactory.clearLastInstance();
285 addInstance(cascadedInstance);
286 TestRun.getExecutingTest().addInjectableMock(cascadedInstance);
287 return cascadedInstance;
288 }
289
290 void discardCascadedMocks() {
291 cascadedTypesAndMocks.clear();
292 cascadingInstances.clear();
293 }
294
295 void addInstance(@NonNull Object cascadingInstance) {
296 cascadingInstances.add(cascadingInstance);
297 }
298
299 boolean hasInstance(@NonNull Object cascadingInstance) {
300 return containsReference(cascadingInstances, cascadingInstance);
301 }
302 }