DCD.java
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[] argsthrows 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() - start1000.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 - start1000.0);
172   }
173 }