/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.cpd;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.EnumeratedAttribute;
import org.apache.tools.ant.types.FileSet;
/**
* CPDTask
*
* <p>Runs the CPD utility via ant. The ant task looks like this:</p>
*
* <pre>
* <project name="CPDProj" default="main" basedir=".">
* <taskdef name="cpd" classname="net.sourceforge.pmd.cpd.CPDTask" />
* <target name="main">
* <cpd encoding="UTF-16LE" language="java" ignoreIdentifiers="true"
* ignoreLiterals="true" ignoreAnnotations="true" minimumTokenCount="100"
* outputFile="c:\cpdrun.txt">
* <fileset dir="/path/to/my/src">
* <include name="*.java"/>
* </fileset>
* </cpd>
* </target>
* </project>
* </pre>
*
* <p>Required: minimumTokenCount, outputFile, and at least one file</p>
*/
public class CPDTask extends Task {
private static final String TEXT_FORMAT = "text";
private static final String XML_FORMAT = "xml";
private static final String CSV_FORMAT = "csv";
private String format = TEXT_FORMAT;
private String language = "java";
private int minimumTokenCount;
private boolean ignoreLiterals;
private boolean ignoreIdentifiers;
private boolean ignoreAnnotations;
private boolean ignoreUsings;
private boolean skipLexicalErrors;
private boolean skipDuplicateFiles;
private boolean skipBlocks = true;
private String skipBlocksPattern = Tokenizer.DEFAULT_SKIP_BLOCKS_PATTERN;
private File outputFile;
private String encoding = System.getProperty("file.encoding");
private List<FileSet> filesets = new ArrayList<>();
@Override
public void execute() throws BuildException {
ClassLoader oldClassloader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(CPDTask.class.getClassLoader());
try {
validateFields();
log("Starting run, minimumTokenCount is " + minimumTokenCount, Project.MSG_INFO);
log("Tokenizing files", Project.MSG_INFO);
CPDConfiguration config = new CPDConfiguration();
config.setMinimumTileSize(minimumTokenCount);
config.setLanguage(createLanguage());
config.setEncoding(encoding);
config.setSkipDuplicates(skipDuplicateFiles);
config.setSkipLexicalErrors(skipLexicalErrors);
CPD cpd = new CPD(config);
tokenizeFiles(cpd);
log("Starting to analyze code", Project.MSG_INFO);
long timeTaken = analyzeCode(cpd);
log("Done analyzing code; that took " + timeTaken + " milliseconds");
log("Generating report", Project.MSG_INFO);
report(cpd);
} catch (IOException ioe) {
log(ioe.toString(), Project.MSG_ERR);
throw new BuildException("IOException during task execution", ioe);
} catch (ReportException re) {
re.printStackTrace();
log(re.toString(), Project.MSG_ERR);
throw new BuildException("ReportException during task execution", re);
} finally {
Thread.currentThread().setContextClassLoader(oldClassloader);
}
}
private Language createLanguage() {
Properties p = new Properties();
if (ignoreLiterals) {
p.setProperty(Tokenizer.IGNORE_LITERALS, "true");
}
if (ignoreIdentifiers) {
p.setProperty(Tokenizer.IGNORE_IDENTIFIERS, "true");
}
if (ignoreAnnotations) {
p.setProperty(Tokenizer.IGNORE_ANNOTATIONS, "true");
}
if (ignoreUsings) {
p.setProperty(Tokenizer.IGNORE_USINGS, "true");
}
p.setProperty(Tokenizer.OPTION_SKIP_BLOCKS, Boolean.toString(skipBlocks));
p.setProperty(Tokenizer.OPTION_SKIP_BLOCKS_PATTERN, skipBlocksPattern);
return LanguageFactory.createLanguage(language, p);
}
private void report(CPD cpd) throws ReportException {
if (!cpd.getMatches().hasNext()) {
log("No duplicates over " + minimumTokenCount + " tokens found", Project.MSG_INFO);
}
Renderer renderer = createRenderer();
FileReporter reporter;
if (outputFile == null) {
reporter = new FileReporter(encoding);
} else if (outputFile.isAbsolute()) {
reporter = new FileReporter(outputFile, encoding);
} else {
reporter = new FileReporter(new File(getProject().getBaseDir(), outputFile.toString()), encoding);
}
reporter.report(renderer.render(cpd.getMatches()));
}
private void tokenizeFiles(CPD cpd) throws IOException {
for (FileSet fileSet : filesets) {
DirectoryScanner directoryScanner = fileSet.getDirectoryScanner(getProject());
String[] includedFiles = directoryScanner.getIncludedFiles();
for (int i = 0; i < includedFiles.length; i++) {
File file = new File(
directoryScanner.getBasedir() + System.getProperty("file.separator") + includedFiles[i]);
log("Tokenizing " + file.getAbsolutePath(), Project.MSG_VERBOSE);
cpd.add(file);
}
}
}
private long analyzeCode(CPD cpd) {
long start = System.currentTimeMillis();
cpd.go();
long stop = System.currentTimeMillis();
return stop - start;
}
private Renderer createRenderer() {
if (format.equals(TEXT_FORMAT)) {
return new SimpleRenderer();
} else if (format.equals(CSV_FORMAT)) {
return new CSVRenderer();
}
return new XMLRenderer();
}
private void validateFields() throws BuildException {
if (minimumTokenCount == 0) {
throw new BuildException("minimumTokenCount is required and must be greater than zero");
}
if (filesets.isEmpty()) {
throw new BuildException("Must include at least one FileSet");
}
if (!Arrays.asList(LanguageFactory.supportedLanguages).contains(language)) {
throw new BuildException("Language " + language + " is not supported. Available languages: "
+ Arrays.toString(LanguageFactory.supportedLanguages));
}
}
public void addFileset(FileSet set) {
filesets.add(set);
}
public void setMinimumTokenCount(int minimumTokenCount) {
this.minimumTokenCount = minimumTokenCount;
}
public void setIgnoreLiterals(boolean value) {
this.ignoreLiterals = value;
}
public void setIgnoreIdentifiers(boolean value) {
this.ignoreIdentifiers = value;
}
public void setIgnoreAnnotations(boolean value) {
this.ignoreAnnotations = value;
}
public void setIgnoreUsings(boolean value) {
this.ignoreUsings = value;
}
public void setSkipLexicalErrors(boolean skipLexicalErrors) {
this.skipLexicalErrors = skipLexicalErrors;
}
public void setSkipDuplicateFiles(boolean skipDuplicateFiles) {
this.skipDuplicateFiles = skipDuplicateFiles;
}
public void setOutputFile(File outputFile) {
this.outputFile = outputFile;
}
public void setFormat(FormatAttribute formatAttribute) {
this.format = formatAttribute.getValue();
}
public void setLanguage(String language) {
this.language = language;
}
public void setEncoding(String encoding) {
this.encoding = encoding;
}
public void setSkipBlocks(boolean skipBlocks) {
this.skipBlocks = skipBlocks;
}
public void setSkipBlocksPattern(String skipBlocksPattern) {
this.skipBlocksPattern = skipBlocksPattern;
}
public static class FormatAttribute extends EnumeratedAttribute {
private static final String[] FORMATS = new String[] { XML_FORMAT, TEXT_FORMAT, CSV_FORMAT };
@Override
public String[] getValues() {
return FORMATS;
}
}
}