/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.dcd;
import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import net.sourceforge.pmd.dcd.graph.UsageGraph;
import net.sourceforge.pmd.dcd.graph.UsageGraphBuilder;
import net.sourceforge.pmd.util.FileFinder;
import net.sourceforge.pmd.util.filter.Filter;
import net.sourceforge.pmd.util.filter.Filters;
/**
* The Dead Code Detector is used to find dead code. What is dead code? Dead
* code is code which is not used by other code? It exists, but it not used.
* Unused code is clutter, which can generally be a candidate for removal.
*
* <p>When performing dead code detection, there are various sets of files/classes
* which must be identified. An analogy of the dead code analysis as a <em>foot
* race</em> is used to help clarify each of these sets:</p>
*
* <ol>
* <li>The <em>direct users</em> is the set of Classes which will always be
* parsed to determine what code they use. This set is the starting point of the
* race.</li>
* <li>The <em>indirect users</em> is the set of Classes which will only be
* parsed if they are accessed by code in the <em>direct users</em> set, or in
* the <em>indirect users</em> set. This set is the course of the race.</li>
* <li>The <em>dead code candidates</em> are the set of Classes which are the
* focus of the dead code detection. This set is the finish line of the
* race.</li>
* </ol>
*
* <p>Typically there is intersection between the set of <em>direct users</em>,
* <em>indirect users</em> and <em>dead code candidates</em>, although it is not
* required. If the sets are defined too tightly, there the potential for a lot
* of code to be considered as dead code. You may need to expand the <em>direct
* users</em> or <em>indirect users</em> sets, or explore using different
* options.</p>
*
* @author Ryan Gustafson <ryan.gustafson@gmail.com>
*/
public class DCD {
//
// TODO Implement the direct users, indirect users, and dead code
// candidate sets. Use the pmd.util.filter.Filter APIs. Need to come up
// with something like Ant's capabilities for <fileset>, it's a decent way
// to describe a collection of files in a directory structure. That or we
// just adopt Ant, and screw command line/external configuration?
//
// TODO Better yet, is there a way to enumerate all available classes using
// ClassLoaders instead of having to specify Java file names as surrogates
// for the Classes we truly desire?
//
// TODO Methods defined on classes/interfaces not within the scope of
// analysis which are implemented/overridden, are not usage violations.
//
// TODO Static final String and primitive types are often inlined by the
// compiler, so there may actually be no explicit usages.
//
// TODO Ignore "public static void main(String[])"
//
// TODO Check for method which is always overridden, and never called
// directly.
//
// TODO For methods, record which classes/interfaces methods they are
// overriding/implementing.
//
// TODO Allow recognition of indirect method patterns, like those used by
// EJB Home and Remote interfaces with corresponding implementation classes.
//
// TODO
// 1) For each class/member, a set of other class/members which reference.
// 2) For every class/member which is part of an interface or super-class,
// allocate those references to the interface/super-class.
//
private DCD() { }
public static void dump(UsageGraph usageGraph, boolean verbose) {
usageGraph.accept(new DumpNodeVisitor(), Boolean.valueOf(verbose));
}
public static void report(UsageGraph usageGraph, boolean verbose) {
usageGraph.accept(new UsageNodeVisitor(), Boolean.valueOf(verbose));
}
public static void main(String[] args) throws Exception {
// 1) Directories
List<File> directories = new ArrayList<>();
directories.add(new File("C:/pmd/workspace/pmd-trunk/src"));
// Basic filter
FilenameFilter javaFilter = new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
// Recurse on directories
if (new File(dir, name).isDirectory()) {
return true;
} else {
return name.endsWith(".java");
}
}
};
// 2) Filename filters
List<FilenameFilter> filters = new ArrayList<>();
filters.add(javaFilter);
assert directories.size() == filters.size();
// Find all files, convert to class names
List<String> classes = new ArrayList<>();
for (int i = 0; i < directories.size(); i++) {
File directory = directories.get(i);
FilenameFilter filter = filters.get(i);
List<File> files = new FileFinder().findFilesFrom(directory, filter, true);
for (File file : files) {
String name = file.getPath();
// Chop off directory
name = name.substring(directory.getPath().length() + 1);
// Drop extension
name = name.replaceAll("\\.java$", "");
// Trim path separators
name = name.replace('\\', '.');
name = name.replace('/', '.');
classes.add(name);
}
}
long start = System.currentTimeMillis();
// Define filter for "indirect users" and "dead code candidates".
// TODO Need to support these are different concepts.
List<String> includeRegexes = Arrays.asList(new String[] { "net\\.sourceforge\\.pmd\\.dcd.*", "us\\..*" });
List<String> excludeRegexes = Arrays.asList(new String[] { "java\\..*", "javax\\..*", ".*\\.twa\\..*" });
Filter<String> classFilter = Filters.buildRegexFilterExcludeOverInclude(includeRegexes, excludeRegexes);
System.out.println("Class filter: " + classFilter);
// Index each of the "direct users"
UsageGraphBuilder builder = new UsageGraphBuilder(classFilter);
int total = 0;
for (String clazz : classes) {
System.out.println("indexing class: " + clazz);
builder.index(clazz);
total++;
if (total % 20 == 0) {
System.out.println(total + " : " + total / ((System.currentTimeMillis() - start) / 1000.0));
}
}
// Reporting
boolean dump = true;
boolean deadCode = true;
UsageGraph usageGraph = builder.getUsageGraph();
if (dump) {
System.out.println("--- Dump ---");
dump(usageGraph, true);
}
if (deadCode) {
System.out.println("--- Dead Code ---");
report(usageGraph, true);
}
long end = System.currentTimeMillis();
System.out.println("Time: " + (end - start) / 1000.0);
}
}