/** * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ package net.sourceforge.pmd.dcd; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import net.sourceforge.pmd.dcd.graph.ClassNode; import net.sourceforge.pmd.dcd.graph.ConstructorNode; import net.sourceforge.pmd.dcd.graph.FieldNode; import net.sourceforge.pmd.dcd.graph.MemberNode; import net.sourceforge.pmd.dcd.graph.MethodNode; import net.sourceforge.pmd.dcd.graph.NodeVisitorAdapter; import net.sourceforge.pmd.dcd.graph.UsageGraph; /** * Perform a visitation a UsageGraph, looking for <em>dead code</em>, which is * essential code which is not used by any other code. There are various options * for configuration how this determination is made. */ public class UsageNodeVisitor extends NodeVisitorAdapter { /** * Configuration options for usage analysis. */ public static final class Options { private boolean ignoreClassAnonymous = true; private boolean ignoreConstructorStaticInitializer = true; private boolean ignoreConstructorSinglePrivateNoArg = true; private boolean ignoreConstructorAllPrivate = false; private boolean ignoreMethodJavaLangObjectOverride = true; private boolean ignoreMethodAllOverride = false; private boolean ignoreMethodMain = true; private boolean ignoreFieldInlinable = true; public boolean isIgnoreClassAnonymous() { return ignoreClassAnonymous; } public void setIgnoreClassAnonymous(boolean ignoreClassAnonymous) { this.ignoreClassAnonymous = ignoreClassAnonymous; } public boolean isIgnoreConstructorStaticInitializer() { return ignoreConstructorStaticInitializer; } public void setIgnoreConstructorStaticInitializer(boolean ignoreConstructorStaticInitializer) { this.ignoreConstructorStaticInitializer = ignoreConstructorStaticInitializer; } public boolean isIgnoreConstructorSinglePrivateNoArg() { return ignoreConstructorSinglePrivateNoArg; } public void setIgnoreConstructorSinglePrivateNoArg(boolean ignoreConstructorSinglePrivateNoArg) { this.ignoreConstructorSinglePrivateNoArg = ignoreConstructorSinglePrivateNoArg; } public boolean isIgnoreConstructorAllPrivate() { return ignoreConstructorAllPrivate; } public void setIgnoreConstructorAllPrivate(boolean ignoreConstructorAllPrivate) { this.ignoreConstructorAllPrivate = ignoreConstructorAllPrivate; } public boolean isIgnoreMethodJavaLangObjectOverride() { return ignoreMethodJavaLangObjectOverride; } public void setIgnoreMethodJavaLangObjectOverride(boolean ignoreMethodJavaLangObjectOverride) { this.ignoreMethodJavaLangObjectOverride = ignoreMethodJavaLangObjectOverride; } public boolean isIgnoreMethodAllOverride() { return ignoreMethodAllOverride; } public void setIgnoreMethodAllOverride(boolean ignoreMethodAllOverride) { this.ignoreMethodAllOverride = ignoreMethodAllOverride; } public boolean isIgnoreMethodMain() { return ignoreMethodMain; } public void setIgnoreMethodMain(boolean ignoreMethodMain) { this.ignoreMethodMain = ignoreMethodMain; } public boolean isIgnoreFieldInlinable() { return ignoreFieldInlinable; } public void setIgnoreFieldInlinable(boolean ignoreFieldInlinable) { this.ignoreFieldInlinable = ignoreFieldInlinable; } } private final Options options = new Options(); @Override public Object visit(UsageGraph usageGraph, Object data) { System.out.println("----------------------------------------"); super.visit(usageGraph, data); System.out.println("----------------------------------------"); return data; } @Override public Object visit(ClassNode classNode, Object data) { boolean log = true; if (options.isIgnoreClassAnonymous() && classNode.getType().isAnonymousClass()) { ignore("class anonymous", classNode); log = false; } if (log) { System.out.println("--- " + classNode.getName() + " ---"); return super.visit(classNode, data); } else { return data; } } @Override public Object visit(FieldNode fieldNode, Object data) { if (fieldNode.getUsers().isEmpty()) { boolean log = true; // A field is inlinable if: // 1) It is final // 2) It is a primitive, or a java.lang.String if (options.isIgnoreFieldInlinable()) { if (Modifier.isFinal(fieldNode.getMember().getModifiers()) && fieldNode.getMember().getType().isPrimitive() || fieldNode.getMember().getType().getName().equals("java.lang.String")) { ignore("field inlinable", fieldNode); log = false; } } if (log) { System.out.println("\t" + fieldNode.toStringLong()); } } return super.visit(fieldNode, data); } @Override public Object visit(ConstructorNode constructorNode, Object data) { if (constructorNode.getUsers().isEmpty()) { boolean log = true; if (constructorNode.isStaticInitializer()) { if (options.isIgnoreConstructorStaticInitializer()) { ignore("constructor static initializer", constructorNode); log = false; } } else if (constructorNode.isInstanceInitializer()) { if (Modifier.isPrivate(constructorNode.getMember().getModifiers())) { if (options.isIgnoreConstructorAllPrivate()) { ignore("constructor all private", constructorNode); log = false; } else if (options.isIgnoreConstructorSinglePrivateNoArg() && constructorNode.getMember().getParameterTypes().length == 0 && constructorNode.getClassNode().getConstructorNodes().size() == 1) { ignore("constructor single private no-arg", constructorNode); log = false; } } } if (log) { System.out.println("\t" + constructorNode.toStringLong()); } } return super.visit(constructorNode, data); } private static boolean isMainMethod(MethodNode node) { final Method method = node.getMember(); return method.getName().equals("main") && Modifier.isPublic(method.getModifiers()) && Modifier.isStatic(method.getModifiers()) && method.getReturnType() == Void.TYPE && method.getParameterTypes().length == 1 && method.getParameterTypes()[0].isArray() && method.getParameterTypes()[0].getComponentType().equals(java.lang.String.class); } @Override public Object visit(MethodNode methodNode, Object data) { if (methodNode.getUsers().isEmpty()) { boolean log = true; if (options.isIgnoreMethodAllOverride()) { if (ClassLoaderUtil.isOverridenMethod(methodNode.getClassNode().getClass(), methodNode.getMember(), false)) { ignore("method all override", methodNode); log = false; } } else if (options.isIgnoreMethodJavaLangObjectOverride()) { if (ClassLoaderUtil.isOverridenMethod(java.lang.Object.class, methodNode.getMember(), true)) { ignore("method java.lang.Object override", methodNode); log = false; } } if (options.isIgnoreMethodMain()) { if (isMainMethod(methodNode)) { ignore("method public static void main(String[])", methodNode); log = false; } } if (log) { System.out.println("\t" + methodNode.toStringLong()); } } return super.visit(methodNode, data); } private void ignore(String description, ClassNode classNode) { System.out.println("Ignoring " + description + ": " + classNode.getName()); } private void ignore(String description, MemberNode memberNode) { System.out.println("Ignoring " + description + ": " + memberNode.toStringLong()); } }