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.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         // noinspection SimplifiableIfStatement
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 }