/** * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ package net.sourceforge.pmd.dcd.graph; import java.util.ArrayList; import java.util.Collections; import java.util.List; import net.sourceforge.pmd.dcd.ClassLoaderUtil; import net.sourceforge.pmd.util.filter.Filter; /** * A UsageGraph tracks usage references between Java classes, based upon a * parsing of the class files. Once the UsageGraph is built, it may be visited * to perform additional post-processing, or usage relationship analysis. * <p> * The UsageGraph is composed of ClassNodes. Each ClassNode has various * MemberNodes, specifically ConstructorNodes, FieldNodes, and MethodNodes. Each * of these MemberNodes keeps track of other MemberNodes which it <em>uses</em> * and other MemberNodes which are <em>users</em> of it. In this sense, the * graph can navigated bi-directionally across the <em>use</em> relationship * between MemberNodes. * <p> * Great effort is taken to keep the bookkeeping of the UsageGraph as tight as * possible, so that rather large code bases can be analyzed. While nodes can * grant access to the underlying Java Reflection APIs (e.g. Class, Constructor, * Field, Member), the results are stored using WeakReferences to assist with * memory usage. * <p> * A class Filter can be specified to limit the set of classes on which usage * references will be tracked. This is often done to limit memory usage to * interesting classes. For example, the <code>java.util</code> package is very * often used, and tracking usages would require a massive bookkeeping effort * which has little value. * * @see UsageGraphBuilder * @see ClassNode * @see MemberNode * @see ConstructorNode * @see FieldNode * @see MethodNode * @see NodeVisitor * @see NodeVisitorAcceptor */ public class UsageGraph implements NodeVisitorAcceptor { private final List<ClassNode> classNodes = new ArrayList<>(); protected final Filter<String> classFilter; public UsageGraph(Filter<String> classFilter) { this.classFilter = classFilter; } @Override public Object accept(NodeVisitor visitor, Object data) { for (ClassNode classNode : classNodes) { visitor.visit(classNode, data); } return data; } public boolean isClass(String className) { checkClassName(className); return Collections.binarySearch(classNodes, className, ClassNodeComparator.INSTANCE) >= 0; } public ClassNode defineClass(String className) { checkClassName(className); int index = Collections.binarySearch(classNodes, className, ClassNodeComparator.INSTANCE); ClassNode classNode; if (index >= 0) { classNode = classNodes.get(index); } else { classNode = new ClassNode(className); classNodes.add(-(index + 1), classNode); } return classNode; } public FieldNode defineField(String className, String name, String desc) { ClassNode classNode = defineClass(className); return classNode.defineField(name, desc); } public MemberNode defineConstructor(String className, String name, String desc) { ClassNode classNode = defineClass(className); return classNode.defineConstructor(name, desc); } public MemberNode defineMethod(String className, String name, String desc) { ClassNode classNode = defineClass(className); if (ClassLoaderUtil.CLINIT.equals(name) || ClassLoaderUtil.INIT.equals(name)) { return classNode.defineConstructor(name, desc); } else { return classNode.defineMethod(name, desc); } } public void usageField(String className, String name, String desc, MemberNode usingMemberNode) { checkClassName(className); if (classFilter.filter(className)) { FieldNode fieldNode = defineField(className, name, desc); usage(fieldNode, usingMemberNode); } } public void usageMethod(String className, String name, String desc, MemberNode usingMemberNode) { checkClassName(className); if (classFilter.filter(className)) { MemberNode memberNode; if (ClassLoaderUtil.CLINIT.equals(name) || ClassLoaderUtil.INIT.equals(name)) { memberNode = defineConstructor(className, name, desc); } else { memberNode = defineMethod(className, name, desc); } usage(memberNode, usingMemberNode); } } private void usage(MemberNode use, MemberNode user) { use.addUser(user); user.addUse(use); } private void checkClassName(String className) { // Make sure it's not in byte code internal format, or file system path. if (className.indexOf('/') >= 0 || className.indexOf('\\') >= 0) { throw new IllegalArgumentException("Invalid class name: " + className); } } }