View Javadoc

    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.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 }