/** * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ package net.sourceforge.pmd.lang.java.typeresolution; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.objectweb.asm.ClassReader; import net.sourceforge.pmd.lang.java.typeresolution.visitors.PMDASMVisitor; /* * I've refactored this class to not cache the results any more. This is a * tradeoff in testing I've found the CPU tradeoff is negligeable. With the * cache, large codebases consumed a lot of memory and slowed down greatly when * approaching 3,000 classes. I'm adding this comment in case someone is looking * at this code and thinks a cache may help. * * see: git show 9e7deee88f63870a1de2cd86458278a027deb6d6 * * However, there seems to be a big performance improvement by caching * the negative cases only. The cache is shared between loadClass and getImportedClasses, * as they are using the same (parent) class loader, e.g. if the class foo.Bar cannot be loaded, * then the resource foo/Bar.class will not exist, too. * * Note: since git show 46ad3a4700b7a233a177fa77d08110127a85604c the cache is using * a concurrent hash map to avoid synchronizing on the class loader instance. */ public final class PMDASMClassLoader extends ClassLoader { private static PMDASMClassLoader cachedPMDASMClassLoader; private static ClassLoader cachedClassLoader; /** * Caches the names of the classes that we can't load or that don't exist. */ private final ConcurrentMap<String, Boolean> dontBother = new ConcurrentHashMap<>(); static { registerAsParallelCapable(); } private PMDASMClassLoader(ClassLoader parent) { super(parent); } /** * A new PMDASMClassLoader is created for each compilation unit, this method * allows to reuse the same PMDASMClassLoader across all the compilation * units. */ public static synchronized PMDASMClassLoader getInstance(ClassLoader parent) { if (parent.equals(cachedClassLoader)) { return cachedPMDASMClassLoader; } cachedClassLoader = parent; cachedPMDASMClassLoader = new PMDASMClassLoader(parent); return cachedPMDASMClassLoader; } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { if (dontBother.containsKey(name)) { throw new ClassNotFoundException(name); } try { return super.loadClass(name); } catch (ClassNotFoundException e) { dontBother.put(name, Boolean.TRUE); throw e; } catch (NoClassDefFoundError e) { dontBother.put(name, Boolean.TRUE); // rethrow as ClassNotFoundException, as the remaining part just // deals with that // see also: https://sourceforge.net/p/pmd/bugs/1319/ throw new ClassNotFoundException(name, e); } } /** * Checks if the class loader could resolve a given class name (ie: it * doesn't know for sure it will fail). Notice, that the ability to resolve * a class does not imply that the class will actually be found and * resolved. * * @param name * the name of the class * @return whether the class can be resolved */ public boolean couldResolve(String name) { return !dontBother.containsKey(name); } public synchronized Map<String, String> getImportedClasses(String name) throws ClassNotFoundException { if (dontBother.containsValue(name)) { throw new ClassNotFoundException(name); } try (InputStream classResource = getResourceAsStream(name.replace('.', '/') + ".class")) { ClassReader reader = new ClassReader(classResource); PMDASMVisitor asmVisitor = new PMDASMVisitor(name); reader.accept(asmVisitor, 0); List<String> inner = asmVisitor.getInnerClasses(); if (inner != null && !inner.isEmpty()) { // to avoid ConcurrentModificationException inner = new ArrayList<>(inner); for (String str : inner) { try (InputStream innerClassStream = getResourceAsStream(str.replace('.', '/') + ".class")) { if (innerClassStream != null) { reader = new ClassReader(innerClassStream); reader.accept(asmVisitor, 0); } } } } return asmVisitor.getPackages(); } catch (IOException e) { dontBother.put(name, Boolean.TRUE); throw new ClassNotFoundException(name, e); } } }