1
2
3
4
5 package mockit.internal.capturing;
6
7 import static mockit.internal.capturing.CapturedType.isNotToBeCaptured;
8
9 import edu.umd.cs.findbugs.annotations.NonNull;
10 import edu.umd.cs.findbugs.annotations.Nullable;
11
12 import java.lang.instrument.ClassFileTransformer;
13 import java.security.ProtectionDomain;
14 import java.util.Collections;
15 import java.util.HashMap;
16 import java.util.Map;
17
18 import mockit.asm.classes.ClassReader;
19 import mockit.asm.classes.ClassVisitor;
20 import mockit.asm.metadata.ClassMetadataReader;
21 import mockit.asm.types.JavaType;
22 import mockit.internal.ClassFile;
23 import mockit.internal.ClassIdentification;
24 import mockit.internal.startup.Startup;
25 import mockit.internal.state.TestRun;
26
27 public final class CaptureTransformer<M> implements ClassFileTransformer {
28 @NonNull
29 private final CapturedType capturedType;
30 @NonNull
31 private final String capturedTypeDesc;
32 @NonNull
33 private final CaptureOfImplementations<M> captureOfImplementations;
34 @NonNull
35 private final Map<ClassIdentification, byte[]> transformedClasses;
36 @NonNull
37 private final Map<String, Boolean> superTypesSearched;
38 @Nullable
39 private final M typeMetadata;
40 private boolean inactive;
41
42 CaptureTransformer(@NonNull CapturedType capturedType,
43 @NonNull CaptureOfImplementations<M> captureOfImplementations, boolean registerTransformedClasses,
44 @Nullable M typeMetadata) {
45 this.capturedType = capturedType;
46 capturedTypeDesc = JavaType.getInternalName(capturedType.baseType);
47 this.captureOfImplementations = captureOfImplementations;
48 transformedClasses = registerTransformedClasses ? new HashMap<>(2)
49 : Collections.<ClassIdentification, byte[]> emptyMap();
50 superTypesSearched = new HashMap<>();
51 this.typeMetadata = typeMetadata;
52 }
53
54 public void deactivate() {
55 inactive = true;
56
57 if (!transformedClasses.isEmpty()) {
58 for (Map.Entry<ClassIdentification, byte[]> classNameAndOriginalBytecode : transformedClasses.entrySet()) {
59 ClassIdentification classId = classNameAndOriginalBytecode.getKey();
60 byte[] originalBytecode = classNameAndOriginalBytecode.getValue();
61
62 Startup.redefineMethods(classId, originalBytecode);
63 }
64
65 transformedClasses.clear();
66 }
67 }
68
69 @Nullable
70 @Override
71 public byte[] transform(@Nullable ClassLoader loader, @NonNull String classDesc,
72 @Nullable Class<?> classBeingRedefined, @Nullable ProtectionDomain protectionDomain,
73 @NonNull byte[] classfileBuffer) {
74 if (classBeingRedefined != null || inactive || isNotToBeCaptured(protectionDomain, classDesc)) {
75 return null;
76 }
77
78 if (isClassToBeCaptured(loader, classfileBuffer)) {
79 String className = classDesc.replace('/', '.');
80 ClassReader cr = new ClassReader(classfileBuffer);
81 return modifyAndRegisterClass(loader, className, cr);
82 }
83
84 return null;
85 }
86
87 private boolean isClassToBeCaptured(@Nullable ClassLoader loader, @NonNull byte[] classfileBuffer) {
88 ClassMetadataReader cmr = new ClassMetadataReader(classfileBuffer);
89 String superName = cmr.getSuperClass();
90
91 if (capturedTypeDesc.equals(superName)) {
92 return true;
93 }
94
95 String[] interfaces = cmr.getInterfaces();
96
97 if (interfaces != null && isClassWhichImplementsACapturingInterface(interfaces)) {
98 return true;
99 }
100
101 return superName != null && searchSuperTypes(loader, superName, interfaces);
102 }
103
104 private boolean isClassWhichImplementsACapturingInterface(@NonNull String[] interfaces) {
105 for (String implementedInterface : interfaces) {
106 if (capturedTypeDesc.equals(implementedInterface)) {
107 return true;
108 }
109 }
110
111 return false;
112 }
113
114 private boolean searchSuperTypes(@Nullable ClassLoader loader, @NonNull String superName,
115 @Nullable String[] interfaces) {
116 if (!"java/lang/Object".equals(superName) && !superName.startsWith("mockit/")
117 && searchSuperType(loader, superName)) {
118 return true;
119 }
120
121 if (interfaces != null && interfaces.length > 0) {
122 for (String itf : interfaces) {
123 if (!itf.startsWith("java/") && !itf.startsWith("javax/") && searchSuperType(loader, itf)) {
124 return true;
125 }
126 }
127 }
128
129 return false;
130 }
131
132 private boolean searchSuperType(@Nullable ClassLoader loader, @NonNull String superName) {
133 Boolean extendsCapturedType = superTypesSearched.get(superName);
134
135 if (extendsCapturedType == null) {
136 byte[] classfileBytes = ClassFile.getClassFile(loader, superName);
137 extendsCapturedType = isClassToBeCaptured(loader, classfileBytes);
138 superTypesSearched.put(superName, extendsCapturedType);
139 }
140
141 return extendsCapturedType;
142 }
143
144 @NonNull
145 private byte[] modifyAndRegisterClass(@Nullable ClassLoader loader, @NonNull String className,
146 @NonNull ClassReader cr) {
147 ClassVisitor modifier = captureOfImplementations.createModifier(loader, cr, capturedType.baseType,
148 typeMetadata);
149 cr.accept(modifier);
150
151 ClassIdentification classId = new ClassIdentification(loader, className);
152 byte[] originalBytecode = cr.getBytecode();
153
154 if (transformedClasses == Collections.<ClassIdentification, byte[]> emptyMap()) {
155 TestRun.mockFixture().addTransformedClass(classId, originalBytecode);
156 } else {
157 transformedClasses.put(classId, originalBytecode);
158 }
159
160 TestRun.mockFixture().registerMockedClass(capturedType.baseType);
161 return modifier.toByteArray();
162 }
163
164 @Nullable
165 public <C extends CaptureOfImplementations<?>> C getCaptureOfImplementationsIfApplicable(@NonNull Class<?> aType) {
166 if (typeMetadata != null && capturedType.baseType.isAssignableFrom(aType)) {
167
168 return (C) captureOfImplementations;
169 }
170
171 return null;
172 }
173
174 public boolean areCapturedClasses(@NonNull Class<?> mockedClass1, @NonNull Class<?> mockedClass2) {
175 Class<?> baseType = capturedType.baseType;
176 return baseType.isAssignableFrom(mockedClass1) && baseType.isAssignableFrom(mockedClass2);
177 }
178 }