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