001: package net.sourceforge.pmd.dcd;
002:
003: import java.io.File;
004: import java.io.FilenameFilter;
005: import java.util.ArrayList;
006: import java.util.Arrays;
007: import java.util.List;
008:
009: import net.sourceforge.pmd.dcd.graph.UsageGraph;
010: import net.sourceforge.pmd.dcd.graph.UsageGraphBuilder;
011: import net.sourceforge.pmd.util.FileFinder;
012: import net.sourceforge.pmd.util.filter.Filter;
013: import net.sourceforge.pmd.util.filter.Filters;
014:
015: /**
016: * The Dead Code Detector is used to find dead code. What is dead code?
017: * Dead code is code which is not used by other code? It exists, but it not
018: * used. Unused code is clutter, which can generally be a candidate for
019: * removal.
020: * <p>
021: * When performing dead code detection, there are various sets of files/classes
022: * which must be identified. An analogy of the dead code analysis as
023: * a <em>foot race</em> is used to help clarify each of these sets:
024: * <ol>
025: * <li>The <em>direct users</em> is the set of Classes which will always be
026: * parsed to determine what code they use. This set is the starting point of
027: * the race.</li>
028: * <li>The <em>indirect users</em> is the set of Classes which will only be
029: * parsed if they are accessed by code in the <em>direct users</em> set, or
030: * in the <em>indirect users</em> set. This set is the course of the race.</li>
031: * <li>The <em>dead code candidates</em> are the set of Classes which are the
032: * focus of the dead code detection. This set is the finish line of the
033: * race.</li>
034: * </ol>
035: * <p>
036: * Typically there is intersection between the set of <em>direct users</em>,
037: * <em>indirect users</em> and <em>dead code candidates</em>, although it is
038: * not required. If the sets are defined too tightly, there the potential for
039: * a lot of code to be considered as dead code. You may need to expand the
040: * <em>direct users</em> or <em>indirect users</em> sets, or explore using
041: * different options.
042: */
043: public class DCD {
044: //
045: // TODO Implement the direct users, indirect users, and dead code
046: // candidate sets. Use the pmd.util.filter.Filter APIs. Need to come up
047: // with something like Ant's capabilities for <fileset>, it's a decent way
048: // to describe a collection of files in a directory structure. That or we
049: // just adopt Ant, and screw command line/external configuration?
050: //
051: // TODO Better yet, is there a way to enumerate all available classes using
052: // ClassLoaders instead of having to specify Java file names as surrogates
053: // for the Classes we truly desire?
054: //
055: // TODO Methods defined on classes/interfaces not within the scope of
056: // analysis which are implemented/overridden, are not usage violations.
057: //
058: // TODO Static final String and primitive types are often inlined by the
059: // compiler, so there may actually be no explicit usages.
060: //
061: // TODO Ignore "public static void main(String[])"
062: //
063: // TODO Check for method which is always overridden, and never called
064: // directly.
065: //
066: // TODO For methods, record which classes/interfaces methods they are
067: // overriding/implementing.
068: //
069: // TODO Allow recognition of indirect method patterns, like those used by
070: // EJB Home and Remote interfaces with corresponding implementation classes.
071: //
072: // TODO
073: // 1) For each class/member, a set of other class/members which reference.
074: // 2) For every class/member which is part of an interface or super-class,
075: // allocate those references to the interface/super-class.
076: //
077:
078: public static void dump(UsageGraph usageGraph, boolean verbose) {
079: usageGraph.accept(new DumpNodeVisitor(), Boolean
080: .valueOf(verbose));
081: }
082:
083: public static void report(UsageGraph usageGraph, boolean verbose) {
084: usageGraph.accept(new UsageNodeVisitor(), Boolean
085: .valueOf(verbose));
086: }
087:
088: public static void main(String[] args) throws Exception {
089: // 1) Directories
090: List<File> directories = new ArrayList<File>();
091: directories.add(new File("C:/pmd/workspace/pmd-trunk/src"));
092:
093: // Basic filter
094: FilenameFilter javaFilter = new FilenameFilter() {
095: public boolean accept(File dir, String name) {
096: // Recurse on directories
097: if (new File(dir, name).isDirectory()) {
098: return true;
099: } else {
100: // Ignore IBM EJB crap
101: if (name.startsWith("EJS") || name.startsWith("_")
102: || dir.getPath().indexOf("com\\ibm\\") >= 0
103: || dir.getPath().indexOf("org\\omg\\") >= 0) {
104: return false;
105: }
106: return name.endsWith(".java");
107: }
108: }
109: };
110:
111: // 2) Filename filters
112: List<FilenameFilter> filters = new ArrayList<FilenameFilter>();
113: filters.add(javaFilter);
114:
115: assert (directories.size() == filters.size());
116:
117: // Find all files, convert to class names
118: List<String> classes = new ArrayList<String>();
119: for (int i = 0; i < directories.size(); i++) {
120: File directory = directories.get(i);
121: FilenameFilter filter = filters.get(i);
122: List<File> files = new FileFinder().findFilesFrom(directory
123: .getPath(), filter, true);
124: for (File file : files) {
125: String name = file.getPath();
126:
127: // Chop off directory
128: name = name.substring(directory.getPath().length() + 1);
129:
130: // Drop extension
131: name = name.replaceAll("\\.java$", "");
132:
133: // Trim path separators
134: name = name.replace('\\', '.');
135: name = name.replace('/', '.');
136:
137: classes.add(name);
138: }
139: }
140:
141: long start = System.currentTimeMillis();
142:
143: // Define filter for "indirect users" and "dead code candidates".
144: // TODO Need to support these are different concepts.
145: List<String> includeRegexes = Arrays.asList(new String[] {
146: "net\\.sourceforge\\.pmd\\.dcd.*", "us\\..*" });
147: List<String> excludeRegexes = Arrays.asList(new String[] {
148: "java\\..*", "javax\\..*", ".*\\.twa\\..*" });
149: Filter<String> classFilter = Filters
150: .buildRegexFilterExcludeOverInclude(includeRegexes,
151: excludeRegexes);
152: System.out.println("Class filter: " + classFilter);
153:
154: // Index each of the "direct users"
155: UsageGraphBuilder builder = new UsageGraphBuilder(classFilter);
156: int total = 0;
157: for (String clazz : classes) {
158: System.out.println("indexing class: " + clazz);
159: builder.index(clazz);
160: total++;
161: if (total % 20 == 0) {
162: System.out
163: .println(total
164: + " : "
165: + total
166: / ((System.currentTimeMillis() - start) / 1000.0));
167: }
168: }
169:
170: // Reporting
171: boolean dump = true;
172: boolean deadCode = true;
173: UsageGraph usageGraph = builder.getUsageGraph();
174: if (dump) {
175: System.out.println("--- Dump ---");
176: dump(usageGraph, true);
177: }
178: if (deadCode) {
179: System.out.println("--- Dead Code ---");
180: report(usageGraph, true);
181: }
182: long end = System.currentTimeMillis();
183: System.out.println("Time: " + (end - start) / 1000.0);
184: }
185: }
|