View Javadoc
1   /*
2    * MIT License
3    * Copyright (c) 2006-2025 JMockit developers
4    * See LICENSE file for full license text.
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) { // ignore the class if the ".class" file wasn't located
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                      // noinspection NumericCastThatLosesPrecision
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 }