001 /**
002 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003 */
004 package net.sourceforge.pmd.dcd;
005
006 import java.io.File;
007 import java.io.FilenameFilter;
008 import java.util.ArrayList;
009 import java.util.Arrays;
010 import java.util.List;
011
012 import net.sourceforge.pmd.dcd.graph.UsageGraph;
013 import net.sourceforge.pmd.dcd.graph.UsageGraphBuilder;
014 import net.sourceforge.pmd.util.FileFinder;
015 import net.sourceforge.pmd.util.filter.Filter;
016 import net.sourceforge.pmd.util.filter.Filters;
017
018 /**
019 * The Dead Code Detector is used to find dead code. What is dead code?
020 * Dead code is code which is not used by other code? It exists, but it not
021 * used. Unused code is clutter, which can generally be a candidate for
022 * removal.
023 * <p>
024 * When performing dead code detection, there are various sets of files/classes
025 * which must be identified. An analogy of the dead code analysis as
026 * a <em>foot race</em> is used to help clarify each of these sets:
027 * <ol>
028 * <li>The <em>direct users</em> is the set of Classes which will always be
029 * parsed to determine what code they use. This set is the starting point of
030 * the race.</li>
031 * <li>The <em>indirect users</em> is the set of Classes which will only be
032 * parsed if they are accessed by code in the <em>direct users</em> set, or
033 * in the <em>indirect users</em> set. This set is the course of the race.</li>
034 * <li>The <em>dead code candidates</em> are the set of Classes which are the
035 * focus of the dead code detection. This set is the finish line of the
036 * race.</li>
037 * </ol>
038 * <p>
039 * Typically there is intersection between the set of <em>direct users</em>,
040 * <em>indirect users</em> and <em>dead code candidates</em>, although it is
041 * not required. If the sets are defined too tightly, there the potential for
042 * a lot of code to be considered as dead code. You may need to expand the
043 * <em>direct users</em> or <em>indirect users</em> sets, or explore using
044 * different options.
045 *
046 * @author Ryan Gustafson <ryan.gustafson@gmail.com>,
047 */
048 public class DCD {
049 //
050 // TODO Implement the direct users, indirect users, and dead code
051 // candidate sets. Use the pmd.util.filter.Filter APIs. Need to come up
052 // with something like Ant's capabilities for <fileset>, it's a decent way
053 // to describe a collection of files in a directory structure. That or we
054 // just adopt Ant, and screw command line/external configuration?
055 //
056 // TODO Better yet, is there a way to enumerate all available classes using
057 // ClassLoaders instead of having to specify Java file names as surrogates
058 // for the Classes we truly desire?
059 //
060 // TODO Methods defined on classes/interfaces not within the scope of
061 // analysis which are implemented/overridden, are not usage violations.
062 //
063 // TODO Static final String and primitive types are often inlined by the
064 // compiler, so there may actually be no explicit usages.
065 //
066 // TODO Ignore "public static void main(String[])"
067 //
068 // TODO Check for method which is always overridden, and never called
069 // directly.
070 //
071 // TODO For methods, record which classes/interfaces methods they are
072 // overriding/implementing.
073 //
074 // TODO Allow recognition of indirect method patterns, like those used by
075 // EJB Home and Remote interfaces with corresponding implementation classes.
076 //
077 // TODO
078 // 1) For each class/member, a set of other class/members which reference.
079 // 2) For every class/member which is part of an interface or super-class,
080 // allocate those references to the interface/super-class.
081 //
082
083 public static void dump(UsageGraph usageGraph, boolean verbose) {
084 usageGraph.accept(new DumpNodeVisitor(), Boolean.valueOf(verbose));
085 }
086
087 public static void report(UsageGraph usageGraph, boolean verbose) {
088 usageGraph.accept(new UsageNodeVisitor(), Boolean.valueOf(verbose));
089 }
090
091 public static void main(String[] args) throws Exception {
092 // 1) Directories
093 List<File> directories = new ArrayList<File>();
094 directories.add(new File("C:/pmd/workspace/pmd-trunk/src"));
095
096 // Basic filter
097 FilenameFilter javaFilter = new FilenameFilter() {
098 public boolean accept(File dir, String name) {
099 // 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.getPath(), 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 }
|