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;
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) { // ignore the class if the ".class" file wasn't located
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                      // noinspection NumericCastThatLosesPrecision
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 }