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