1 /** 2 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html 3 */ 4 package net.sourceforge.pmd.dcd; 5 6 import java.io.File; 7 import java.io.FilenameFilter; 8 import java.util.ArrayList; 9 import java.util.Arrays; 10 import java.util.List; 11 12 import net.sourceforge.pmd.dcd.graph.UsageGraph; 13 import net.sourceforge.pmd.dcd.graph.UsageGraphBuilder; 14 import net.sourceforge.pmd.util.FileFinder; 15 import net.sourceforge.pmd.util.filter.Filter; 16 import net.sourceforge.pmd.util.filter.Filters; 17 18 /** 19 * The Dead Code Detector is used to find dead code. What is dead code? 20 * Dead code is code which is not used by other code? It exists, but it not 21 * used. Unused code is clutter, which can generally be a candidate for 22 * removal. 23 * <p> 24 * When performing dead code detection, there are various sets of files/classes 25 * which must be identified. An analogy of the dead code analysis as 26 * a <em>foot race</em> is used to help clarify each of these sets: 27 * <ol> 28 * <li>The <em>direct users</em> is the set of Classes which will always be 29 * parsed to determine what code they use. This set is the starting point of 30 * the race.</li> 31 * <li>The <em>indirect users</em> is the set of Classes which will only be 32 * parsed if they are accessed by code in the <em>direct users</em> set, or 33 * in the <em>indirect users</em> set. This set is the course of the race.</li> 34 * <li>The <em>dead code candidates</em> are the set of Classes which are the 35 * focus of the dead code detection. This set is the finish line of the 36 * race.</li> 37 * </ol> 38 * <p> 39 * Typically there is intersection between the set of <em>direct users</em>, 40 * <em>indirect users</em> and <em>dead code candidates</em>, although it is 41 * not required. If the sets are defined too tightly, there the potential for 42 * a lot of code to be considered as dead code. You may need to expand the 43 * <em>direct users</em> or <em>indirect users</em> sets, or explore using 44 * different options. 45 * 46 * @author Ryan Gustafson <ryan.gustafson@gmail.com>, 47 */ 48 public class DCD { 49 // 50 // TODO Implement the direct users, indirect users, and dead code 51 // candidate sets. Use the pmd.util.filter.Filter APIs. Need to come up 52 // with something like Ant's capabilities for <fileset>, it's a decent way 53 // to describe a collection of files in a directory structure. That or we 54 // just adopt Ant, and screw command line/external configuration? 55 // 56 // TODO Better yet, is there a way to enumerate all available classes using 57 // ClassLoaders instead of having to specify Java file names as surrogates 58 // for the Classes we truly desire? 59 // 60 // TODO Methods defined on classes/interfaces not within the scope of 61 // analysis which are implemented/overridden, are not usage violations. 62 // 63 // TODO Static final String and primitive types are often inlined by the 64 // compiler, so there may actually be no explicit usages. 65 // 66 // TODO Ignore "public static void main(String[])" 67 // 68 // TODO Check for method which is always overridden, and never called 69 // directly. 70 // 71 // TODO For methods, record which classes/interfaces methods they are 72 // overriding/implementing. 73 // 74 // TODO Allow recognition of indirect method patterns, like those used by 75 // EJB Home and Remote interfaces with corresponding implementation classes. 76 // 77 // TODO 78 // 1) For each class/member, a set of other class/members which reference. 79 // 2) For every class/member which is part of an interface or super-class, 80 // allocate those references to the interface/super-class. 81 // 82 83 public static void dump(UsageGraph usageGraph, boolean verbose) { 84 usageGraph.accept(new DumpNodeVisitor(), Boolean.valueOf(verbose)); 85 } 86 87 public static void report(UsageGraph usageGraph, boolean verbose) { 88 usageGraph.accept(new UsageNodeVisitor(), Boolean.valueOf(verbose)); 89 } 90 91 public static void main(String[] args) throws Exception { 92 // 1) Directories 93 List<File> directories = new ArrayList<File>(); 94 directories.add(new File("C:/pmd/workspace/pmd-trunk/src")); 95 96 // Basic filter 97 FilenameFilter javaFilter = new FilenameFilter() { 98 public boolean accept(File dir, String name) { 99 // Recurse on directories 100 if (new File(dir, name).isDirectory()) { 101 return true; 102 } else { 103 return name.endsWith(".java"); 104 } 105 } 106 }; 107 108 // 2) Filename filters 109 List<FilenameFilter> filters = new ArrayList<FilenameFilter>(); 110 filters.add(javaFilter); 111 112 assert directories.size() == filters.size(); 113 114 // Find all files, convert to class names 115 List<String> classes = new ArrayList<String>(); 116 for (int i = 0; i < directories.size(); i++) { 117 File directory = directories.get(i); 118 FilenameFilter filter = filters.get(i); 119 List<File> files = new FileFinder().findFilesFrom(directory, filter, true); 120 for (File file : files) { 121 String name = file.getPath(); 122 123 // Chop off directory 124 name = name.substring(directory.getPath().length() + 1); 125 126 // Drop extension 127 name = name.replaceAll("\\.java$", ""); 128 129 // Trim path separators 130 name = name.replace('\\', '.'); 131 name = name.replace('/', '.'); 132 133 classes.add(name); 134 } 135 } 136 137 long start = System.currentTimeMillis(); 138 139 // Define filter for "indirect users" and "dead code candidates". 140 // TODO Need to support these are different concepts. 141 List<String> includeRegexes = Arrays.asList(new String[] { "net\\.sourceforge\\.pmd\\.dcd.*", "us\\..*" }); 142 List<String> excludeRegexes = Arrays.asList(new String[] { "java\\..*", "javax\\..*", ".*\\.twa\\..*" }); 143 Filter<String> classFilter = Filters.buildRegexFilterExcludeOverInclude(includeRegexes, excludeRegexes); 144 System.out.println("Class filter: " + classFilter); 145 146 // Index each of the "direct users" 147 UsageGraphBuilder builder = new UsageGraphBuilder(classFilter); 148 int total = 0; 149 for (String clazz : classes) { 150 System.out.println("indexing class: " + clazz); 151 builder.index(clazz); 152 total++; 153 if (total % 20 == 0) { 154 System.out.println(total + " : " + total / ((System.currentTimeMillis() - start) / 1000.0)); 155 } 156 } 157 158 // Reporting 159 boolean dump = true; 160 boolean deadCode = true; 161 UsageGraph usageGraph = builder.getUsageGraph(); 162 if (dump) { 163 System.out.println("--- Dump ---"); 164 dump(usageGraph, true); 165 } 166 if (deadCode) { 167 System.out.println("--- Dead Code ---"); 168 report(usageGraph, true); 169 } 170 long end = System.currentTimeMillis(); 171 System.out.println("Time: " + (end - start) / 1000.0); 172 } 173 }