1
2
3
4
5 package mockit.internal.expectations.mocking;
6
7 import static java.lang.reflect.Modifier.isAbstract;
8 import static java.lang.reflect.Modifier.isPublic;
9
10 import static mockit.internal.util.GeneratedClasses.getNameForGeneratedClass;
11 import static mockit.internal.util.Utilities.JAVA8;
12 import static mockit.internal.util.Utilities.getClassType;
13
14 import edu.umd.cs.findbugs.annotations.NonNull;
15 import edu.umd.cs.findbugs.annotations.Nullable;
16
17 import java.lang.instrument.ClassDefinition;
18 import java.lang.reflect.ParameterizedType;
19 import java.lang.reflect.Proxy;
20 import java.lang.reflect.Type;
21 import java.lang.reflect.TypeVariable;
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Map;
26
27 import mockit.asm.classes.ClassReader;
28 import mockit.asm.classes.ClassVisitor;
29 import mockit.asm.jvmConstants.ClassVersion;
30 import mockit.internal.ClassFile;
31 import mockit.internal.classGeneration.ImplementationClass;
32 import mockit.internal.expectations.mocking.InstanceFactory.ClassInstanceFactory;
33 import mockit.internal.expectations.mocking.InstanceFactory.InterfaceInstanceFactory;
34 import mockit.internal.reflection.ConstructorReflection;
35 import mockit.internal.reflection.EmptyProxy.Impl;
36 import mockit.internal.state.TestRun;
37 import mockit.internal.util.ClassLoad;
38 import mockit.internal.util.VisitInterruptedException;
39
40 class BaseTypeRedefinition {
41 private static final ClassDefinition[] CLASS_DEFINITIONS = {};
42
43 private static final class MockedClass {
44 @Nullable
45 final InstanceFactory instanceFactory;
46 @NonNull
47 final ClassDefinition[] mockedClassDefinitions;
48
49 MockedClass(@Nullable InstanceFactory instanceFactory, @NonNull ClassDefinition[] classDefinitions) {
50 this.instanceFactory = instanceFactory;
51 mockedClassDefinitions = classDefinitions;
52 }
53
54 void redefineClasses() {
55 TestRun.mockFixture().redefineClasses(mockedClassDefinitions);
56 }
57 }
58
59 @NonNull
60 private static final Map<Integer, MockedClass> mockedClasses = new HashMap<>();
61 @NonNull
62 private static final Map<Type, Class<?>> mockImplementations = new HashMap<>();
63
64 Class<?> targetClass;
65 @Nullable
66 MockedType typeMetadata;
67 @Nullable
68 private InstanceFactory instanceFactory;
69 @Nullable
70 private List<ClassDefinition> mockedClassDefinitions;
71
72 BaseTypeRedefinition() {
73 }
74
75 BaseTypeRedefinition(@NonNull MockedType typeMetadata) {
76 targetClass = typeMetadata.getClassType();
77 this.typeMetadata = typeMetadata;
78 }
79
80 @Nullable
81 final InstanceFactory redefineType(@NonNull Type typeToMock) {
82 if (targetClass == TypeVariable.class || targetClass.isInterface()) {
83 createMockedInterfaceImplementationAndInstanceFactory(typeToMock);
84 } else {
85 if (typeMetadata == null || !typeMetadata.isClassInitializationToBeStubbedOut()) {
86 TestRun.ensureThatClassIsInitialized(targetClass);
87 }
88 redefineTargetClassAndCreateInstanceFactory(typeToMock);
89 }
90
91 if (instanceFactory != null) {
92 Class<?> mockedType = getClassType(typeToMock);
93 TestRun.mockFixture().registerInstanceFactoryForMockedType(mockedType, instanceFactory);
94 }
95
96 return instanceFactory;
97 }
98
99 private void createMockedInterfaceImplementationAndInstanceFactory(@NonNull Type interfaceToMock) {
100 Class<?> mockedInterface = interfaceToMock(interfaceToMock);
101 Object mockedInstance;
102
103 if (mockedInterface == null) {
104 mockedInstance = createMockInterfaceImplementationUsingStandardProxy(interfaceToMock);
105 } else {
106 mockedInstance = createMockInterfaceImplementationDirectly(interfaceToMock);
107 }
108
109 redefinedImplementedInterfacesIfRunningOnJava8(targetClass);
110 instanceFactory = new InterfaceInstanceFactory(mockedInstance);
111 }
112
113 @Nullable
114 private static Class<?> interfaceToMock(@NonNull Type typeToMock) {
115 while (true) {
116 if (typeToMock instanceof Class<?>) {
117 Class<?> theInterface = (Class<?>) typeToMock;
118
119 if (isPublic(theInterface.getModifiers()) && !theInterface.isAnnotation()) {
120 return theInterface;
121 }
122 } else if (typeToMock instanceof ParameterizedType) {
123 typeToMock = ((ParameterizedType) typeToMock).getRawType();
124 continue;
125 }
126
127 return null;
128 }
129 }
130
131 @NonNull
132 private Object createMockInterfaceImplementationUsingStandardProxy(@NonNull Type typeToMock) {
133 ClassLoader loader = getClass().getClassLoader();
134 Object mockedInstance = Impl.newEmptyProxy(loader, typeToMock);
135 targetClass = mockedInstance.getClass();
136 redefineClass(targetClass);
137 return mockedInstance;
138 }
139
140 @NonNull
141 private Object createMockInterfaceImplementationDirectly(@NonNull Type interfaceToMock) {
142 Class<?> previousMockImplementationClass = mockImplementations.get(interfaceToMock);
143
144 if (previousMockImplementationClass == null) {
145 generateNewMockImplementationClassForInterface(interfaceToMock);
146 mockImplementations.put(interfaceToMock, targetClass);
147 } else {
148 targetClass = previousMockImplementationClass;
149 }
150
151 return ConstructorReflection.newInstanceUsingDefaultConstructor(targetClass);
152 }
153
154 private void redefineClass(@NonNull Class<?> realClass) {
155 ClassReader classReader = ClassFile.createReaderOrGetFromCache(realClass);
156
157 if (realClass.isInterface() && classReader.getVersion() < ClassVersion.V8) {
158 return;
159 }
160
161 ClassLoader loader = realClass.getClassLoader();
162 MockedClassModifier modifier = createClassModifier(loader, classReader);
163 redefineClass(realClass, classReader, modifier);
164 }
165
166 @NonNull
167 private MockedClassModifier createClassModifier(@Nullable ClassLoader loader, @NonNull ClassReader classReader) {
168 MockedClassModifier modifier = new MockedClassModifier(loader, classReader, typeMetadata);
169 configureClassModifier(modifier);
170 return modifier;
171 }
172
173 void configureClassModifier(@NonNull MockedClassModifier modifier) {
174 }
175
176 private void generateNewMockImplementationClassForInterface(@NonNull final Type interfaceToMock) {
177 ImplementationClass<?> implementationGenerator = new ImplementationClass<>(interfaceToMock) {
178 @NonNull
179 @Override
180 protected ClassVisitor createMethodBodyGenerator(@NonNull ClassReader cr) {
181 return new InterfaceImplementationGenerator(cr, interfaceToMock, generatedClassName);
182 }
183 };
184
185 targetClass = implementationGenerator.generateClass();
186 }
187
188 private void redefinedImplementedInterfacesIfRunningOnJava8(@NonNull Class<?> aClass) {
189 if (JAVA8) {
190 redefineImplementedInterfaces(aClass.getInterfaces());
191 }
192 }
193
194 final boolean redefineMethodsAndConstructorsInTargetType() {
195 return redefineClassAndItsSuperClasses(targetClass);
196 }
197
198 private boolean redefineClassAndItsSuperClasses(@NonNull Class<?> realClass) {
199 ClassLoader loader = realClass.getClassLoader();
200 ClassReader classReader = ClassFile.createReaderOrGetFromCache(realClass);
201 MockedClassModifier modifier = createClassModifier(loader, classReader);
202
203 try {
204 redefineClass(realClass, classReader, modifier);
205 } catch (VisitInterruptedException ignore) {
206
207
208
209 return false;
210 }
211
212 redefineElementSubclassesOfEnumTypeIfAny(modifier.enumSubclasses);
213 redefinedImplementedInterfacesIfRunningOnJava8(realClass);
214
215 Class<?> superClass = realClass.getSuperclass();
216 boolean redefined = true;
217
218 if (superClass != null && superClass != Object.class && superClass != Proxy.class && superClass != Enum.class) {
219 redefined = redefineClassAndItsSuperClasses(superClass);
220 }
221
222 return redefined;
223 }
224
225 private void redefineClass(@NonNull Class<?> realClass, @NonNull ClassReader classReader,
226 @NonNull MockedClassModifier modifier) {
227 classReader.accept(modifier);
228
229 if (modifier.wasModified()) {
230 byte[] modifiedClass = modifier.toByteArray();
231 applyClassRedefinition(realClass, modifiedClass);
232 }
233 }
234
235 void applyClassRedefinition(@NonNull Class<?> realClass, @NonNull byte[] modifiedClass) {
236 ClassDefinition classDefinition = new ClassDefinition(realClass, modifiedClass);
237 TestRun.mockFixture().redefineClasses(classDefinition);
238
239 if (mockedClassDefinitions != null) {
240 mockedClassDefinitions.add(classDefinition);
241 }
242 }
243
244 private void redefineElementSubclassesOfEnumTypeIfAny(@Nullable List<String> enumSubclasses) {
245 if (enumSubclasses != null) {
246 for (String enumSubclassDesc : enumSubclasses) {
247 Class<?> enumSubclass = ClassLoad.loadByInternalName(enumSubclassDesc);
248 redefineClass(enumSubclass);
249 }
250 }
251 }
252
253 private void redefineImplementedInterfaces(@NonNull Class<?>[] implementedInterfaces) {
254 for (Class<?> implementedInterface : implementedInterfaces) {
255 redefineClass(implementedInterface);
256 redefineImplementedInterfaces(implementedInterface.getInterfaces());
257 }
258 }
259
260 private void redefineTargetClassAndCreateInstanceFactory(@NonNull Type typeToMock) {
261 Integer mockedClassId = redefineClassesFromCache();
262
263 if (mockedClassId == null) {
264 return;
265 }
266
267 boolean redefined = redefineMethodsAndConstructorsInTargetType();
268 instanceFactory = createInstanceFactory(typeToMock);
269
270 if (redefined) {
271 storeRedefinedClassesInCache(mockedClassId);
272 }
273 }
274
275 @NonNull
276 final InstanceFactory createInstanceFactory(@NonNull Type typeToMock) {
277 Class<?> classToInstantiate = targetClass;
278
279 if (isAbstract(classToInstantiate.getModifiers())) {
280 classToInstantiate = generateConcreteSubclassForAbstractType(typeToMock);
281 }
282
283 return new ClassInstanceFactory(classToInstantiate);
284 }
285
286 @Nullable
287 private Integer redefineClassesFromCache() {
288
289 Integer mockedClassId = typeMetadata.hashCode();
290 MockedClass mockedClass = mockedClasses.get(mockedClassId);
291
292 if (mockedClass != null) {
293 mockedClass.redefineClasses();
294 instanceFactory = mockedClass.instanceFactory;
295 return null;
296 }
297
298 mockedClassDefinitions = new ArrayList<>();
299 return mockedClassId;
300 }
301
302 private void storeRedefinedClassesInCache(@NonNull Integer mockedClassId) {
303 assert mockedClassDefinitions != null;
304 ClassDefinition[] classDefs = mockedClassDefinitions.toArray(CLASS_DEFINITIONS);
305 MockedClass mockedClass = new MockedClass(instanceFactory, classDefs);
306
307 mockedClasses.put(mockedClassId, mockedClass);
308 }
309
310 @NonNull
311 private Class<?> generateConcreteSubclassForAbstractType(@NonNull final Type typeToMock) {
312 final String subclassName = getNameForConcreteSubclassToCreate();
313
314 return new ImplementationClass<>(targetClass, subclassName) {
315 @NonNull
316 @Override
317 protected ClassVisitor createMethodBodyGenerator(@NonNull ClassReader cr) {
318 return new SubclassGenerationModifier(targetClass, typeToMock, cr, subclassName, false);
319 }
320 }.generateClass();
321 }
322
323 @NonNull
324 private String getNameForConcreteSubclassToCreate() {
325 String mockId = typeMetadata == null ? null : typeMetadata.getName();
326 return getNameForGeneratedClass(targetClass, mockId);
327 }
328 }