View Javadoc

1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.lang.java.typeresolution;
5   
6   import java.io.IOException;
7   import java.util.ArrayList;
8   import java.util.Collections;
9   import java.util.HashSet;
10  import java.util.List;
11  import java.util.Map;
12  import java.util.Set;
13  
14  import net.sourceforge.pmd.lang.java.typeresolution.visitors.PMDASMVisitor;
15  
16  import org.objectweb.asm.ClassReader;
17  
18  /*
19   * I've refactored this class to not cache the results any more. This is a
20   * tradeoff in testing I've found the CPU tradeoff is negligeable. With the
21   * cache, large codebases consumed a lot of memory and slowed down greatly when
22   * approaching 3,000 classes. I'm adding this comment in case someone is looking
23   * at this code and thinks a cache may help.
24   *
25   * see: git show 9e7deee88f63870a1de2cd86458278a027deb6d6
26   *
27   * However, there seems to be a big performance improvement by caching
28   * the negative cases only. The cache is shared between loadClass and getImportedClasses,
29   * as they are using the same (parent) class loader, e.g. if the class foo.Bar cannot be loaded,
30   * then the resource foo/Bar.class will not exist, too.
31   */
32  public class PMDASMClassLoader extends ClassLoader {
33      
34      private static PMDASMClassLoader cachedPMDASMClassLoader;
35      private static ClassLoader cachedClassLoader;
36      
37      /**
38       * A new PMDASMClassLoader is created for each compilation unit, this method allows to reuse the same
39       * PMDASMClassLoader across all the compilation units.
40       */
41      public static synchronized PMDASMClassLoader getInstance(ClassLoader parent) {
42          if (parent == cachedClassLoader) return cachedPMDASMClassLoader;
43          cachedClassLoader = parent;
44          cachedPMDASMClassLoader = new PMDASMClassLoader(parent);
45          return cachedPMDASMClassLoader;
46      }
47      
48      //
49  
50      private PMDASMClassLoader(ClassLoader parent) {
51      	super(parent);
52      }
53  
54      /** Caches the names of the classes that we can't load or that don't exist. */
55      private final Set<String> dontBother = new HashSet<String>();
56  
57      @Override
58      public synchronized Class<?> loadClass(String name) throws ClassNotFoundException {
59  	if (dontBother.contains(name)) {
60  	    throw new ClassNotFoundException(name);
61  	}
62  	try {
63  	    return super.loadClass(name);
64  	} catch (ClassNotFoundException e) {
65  	    dontBother.add(name);
66  	    throw e;
67  	}
68      }
69  
70      public synchronized Map<String, String> getImportedClasses(String name) throws ClassNotFoundException {
71  
72          if (dontBother.contains(name)) {
73              throw new ClassNotFoundException(name);
74          }
75          try {
76              ClassReader reader = new ClassReader(getResourceAsStream(name.replace('.', '/') + ".class"));
77              PMDASMVisitor asmVisitor = new PMDASMVisitor();
78              reader.accept(asmVisitor, 0);
79  
80              List<String> inner = asmVisitor.getInnerClasses();
81              if (inner != null && !inner.isEmpty()) {
82                  inner = new ArrayList<String>(inner); // to avoid ConcurrentModificationException
83                  for (String str: inner) {
84                      reader = new ClassReader(getResourceAsStream(str.replace('.', '/') + ".class"));
85                      reader.accept(asmVisitor, 0);
86                  }
87              }
88              return asmVisitor.getPackages();
89          } catch (IOException e) {
90              dontBother.add(name);
91              throw new ClassNotFoundException(name, e);
92          }
93      }
94  }