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