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.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             // As defined in MockedClassModifier, some critical JRE classes have all methods excluded from mocking by
207             // default. This exception occurs when they are visited.
208             // In this case, we simply stop class redefinition for the rest of the class hierarchy.
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         // noinspection ConstantConditions
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 }