1
2
3
4
5
6 package mockit.internal;
7
8 import edu.umd.cs.findbugs.annotations.NonNull;
9 import edu.umd.cs.findbugs.annotations.Nullable;
10
11 import java.io.IOException;
12 import java.io.InputStream;
13 import java.util.Map;
14 import java.util.concurrent.ConcurrentHashMap;
15
16 import mockit.asm.classes.ClassReader;
17 import mockit.internal.state.CachedClassfiles;
18 import mockit.internal.state.TestRun;
19
20 public final class ClassFile {
21 private static final Map<String, ClassReader> CLASS_FILES = new ConcurrentHashMap<>();
22
23 private ClassFile() {
24 }
25
26 public static final class NotFoundException extends RuntimeException {
27 private static final long serialVersionUID = 1L;
28
29 private NotFoundException(@NonNull String classNameOrDesc) {
30 super("Unable to find class file for " + classNameOrDesc.replace('/', '.'));
31 }
32 }
33
34 private static void verifyClassFileFound(@Nullable InputStream classFile, @NonNull String classNameOrDesc) {
35 if (classFile == null) {
36 throw new NotFoundException(classNameOrDesc);
37 }
38 }
39
40 @Nullable
41 public static ClassReader createClassReader(@NonNull ClassLoader cl, @NonNull String internalClassName) {
42 String classFileName = internalClassName + ".class";
43 InputStream classFile = cl.getResourceAsStream(classFileName);
44
45 if (classFile != null) {
46 try {
47 byte[] bytecode = readClass(classFile);
48 return new ClassReader(bytecode);
49 } catch (IOException ignore) {
50 }
51 }
52
53 return null;
54 }
55
56 @NonNull
57 private static byte[] readClass(@NonNull InputStream is) throws IOException {
58 try {
59 byte[] bytecode = new byte[is.available()];
60 int len = 0;
61
62 while (true) {
63 int n = is.read(bytecode, len, bytecode.length - len);
64
65 if (n == -1) {
66 if (len < bytecode.length) {
67 byte[] truncatedCopy = new byte[len];
68 System.arraycopy(bytecode, 0, truncatedCopy, 0, len);
69 bytecode = truncatedCopy;
70 }
71
72 return bytecode;
73 }
74
75 len += n;
76
77 if (len == bytecode.length) {
78 int last = is.read();
79
80 if (last < 0) {
81 return bytecode;
82 }
83
84 byte[] lengthenedCopy = new byte[bytecode.length + 1000];
85 System.arraycopy(bytecode, 0, lengthenedCopy, 0, len);
86
87 lengthenedCopy[len] = (byte) last;
88 len++;
89 bytecode = lengthenedCopy;
90 }
91 }
92 } finally {
93 is.close();
94 }
95 }
96
97 @NonNull
98 public static ClassReader createReaderOrGetFromCache(@NonNull Class<?> aClass) {
99 byte[] cachedClassfile = CachedClassfiles.getClassfile(aClass);
100
101 if (cachedClassfile != null) {
102 return new ClassReader(cachedClassfile);
103 }
104
105 String classDesc = aClass.getName().replace('.', '/');
106 ClassReader reader = CLASS_FILES.get(classDesc);
107
108 if (reader == null) {
109 reader = readFromFileSavingInCache(classDesc);
110 }
111
112 return reader;
113 }
114
115 @NonNull
116 private static ClassReader readFromFileSavingInCache(@NonNull String classDesc) {
117 byte[] classfileBytes = readBytesFromClassFile(classDesc);
118 ClassReader cr = new ClassReader(classfileBytes);
119 CLASS_FILES.put(classDesc, cr);
120 return cr;
121 }
122
123 @NonNull
124 public static ClassReader createReaderFromLastRedefinitionIfAny(@NonNull Class<?> aClass) {
125 byte[] classfile = TestRun.mockFixture().getRedefinedClassfile(aClass);
126
127 if (classfile == null) {
128 classfile = CachedClassfiles.getClassfile(aClass);
129 }
130
131 if (classfile != null) {
132 return new ClassReader(classfile);
133 }
134
135 String classDesc = aClass.getName().replace('.', '/');
136 return readFromFileSavingInCache(classDesc);
137 }
138
139 @NonNull
140 public static byte[] getClassFile(@NonNull String internalClassName) {
141 byte[] classfileBytes = CachedClassfiles.getClassfile(internalClassName);
142
143 if (classfileBytes == null) {
144 classfileBytes = readBytesFromClassFile(internalClassName);
145 }
146
147 return classfileBytes;
148 }
149
150 @NonNull
151 public static byte[] getClassFile(@Nullable ClassLoader loader, @NonNull String internalClassName) {
152 byte[] classfileBytes = CachedClassfiles.getClassfile(loader, internalClassName);
153
154 if (classfileBytes == null) {
155 classfileBytes = readBytesFromClassFile(internalClassName);
156 }
157
158 return classfileBytes;
159 }
160
161 @NonNull
162 public static byte[] getClassFile(@NonNull Class<?> aClass) {
163 byte[] classfileBytes = CachedClassfiles.getClassfile(aClass);
164
165 if (classfileBytes == null) {
166 classfileBytes = readBytesFromClassFile(aClass);
167 }
168
169 return classfileBytes;
170 }
171
172 @NonNull
173 public static byte[] readBytesFromClassFile(@NonNull String classDesc) {
174 if (classDesc.startsWith("java/") || classDesc.startsWith("javax/") || classDesc.startsWith("jakarta/")) {
175 byte[] classfile = CachedClassfiles.getClassfile(classDesc);
176
177 if (classfile != null) {
178 return classfile;
179 }
180 }
181
182 InputStream classFile = readClassFromClasspath(classDesc);
183
184 try {
185 return readClass(classFile);
186 } catch (IOException e) {
187 throw new RuntimeException("Failed to read class file for " + classDesc.replace('/', '.'), e);
188 }
189 }
190
191 @NonNull
192 public static byte[] readBytesFromClassFile(@NonNull Class<?> aClass) {
193 String classDesc = aClass.getName().replace('.', '/');
194 return readBytesFromClassFile(classDesc);
195 }
196
197 @NonNull
198 @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
199 private static InputStream readClassFromClasspath(@NonNull String classDesc) {
200 String classFileName = classDesc + ".class";
201 ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
202 InputStream inputStream = null;
203
204 if (contextClassLoader != null) {
205 inputStream = contextClassLoader.getResourceAsStream(classFileName);
206 }
207
208 if (inputStream == null) {
209 ClassLoader thisClassLoader = ClassFile.class.getClassLoader();
210
211 if (thisClassLoader != contextClassLoader) {
212 inputStream = thisClassLoader.getResourceAsStream(classFileName);
213
214 if (inputStream == null) {
215 Class<?> testClass = TestRun.getCurrentTestClass();
216
217 if (testClass != null) {
218 inputStream = testClass.getClassLoader().getResourceAsStream(classFileName);
219 }
220 }
221 }
222 }
223
224 verifyClassFileFound(inputStream, classDesc);
225 return inputStream;
226 }
227 }