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