001: /**
002: * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003: */package net.sourceforge.pmd.cpd;
004:
005: import org.apache.tools.ant.BuildException;
006: import org.apache.tools.ant.DirectoryScanner;
007: import org.apache.tools.ant.Project;
008: import org.apache.tools.ant.Task;
009: import org.apache.tools.ant.types.EnumeratedAttribute;
010: import org.apache.tools.ant.types.FileSet;
011:
012: import java.io.File;
013: import java.io.IOException;
014: import java.util.ArrayList;
015: import java.util.List;
016: import java.util.Properties;
017:
018: /**
019: * CPDTask
020: * <p/>
021: * Runs the CPD utility via ant. The ant task looks like this:
022: * <p/>
023: * <project name="CPDProj" default="main" basedir=".">
024: * <taskdef name="cpd" classname="net.sourceforge.pmd.cpd.CPDTask" />
025: * <target name="main">
026: * <cpd encoding="UTF-16LE" language="java" ignoreIdentifiers="true" ignoreLiterals="true" minimumTokenCount="100" outputFile="c:\cpdrun.txt">
027: * <fileset dir="/path/to/my/src">
028: * <include name="*.java"/>
029: * </fileset>
030: * </cpd>
031: * </target>
032: * </project>
033: * <p/>
034: * Required: minimumTokenCount, outputFile, and at least one file
035: */
036: public class CPDTask extends Task {
037:
038: private static final String TEXT_FORMAT = "text";
039: private static final String XML_FORMAT = "xml";
040: private static final String CSV_FORMAT = "csv";
041:
042: private String format = TEXT_FORMAT;
043: private String language = "java";
044: private int minimumTokenCount;
045: private boolean ignoreLiterals;
046: private boolean ignoreIdentifiers;
047: private File outputFile;
048: private String encoding = System.getProperty("file.encoding");
049: private List<FileSet> filesets = new ArrayList<FileSet>();
050:
051: public void execute() throws BuildException {
052: try {
053: validateFields();
054:
055: log("Starting run, minimumTokenCount is "
056: + minimumTokenCount, Project.MSG_INFO);
057:
058: log("Tokenizing files", Project.MSG_INFO);
059: CPD cpd = new CPD(minimumTokenCount, createLanguage());
060: cpd.setEncoding(encoding);
061: tokenizeFiles(cpd);
062:
063: log("Starting to analyze code", Project.MSG_INFO);
064: long timeTaken = analyzeCode(cpd);
065: log("Done analyzing code; that took " + timeTaken
066: + " milliseconds");
067:
068: log("Generating report", Project.MSG_INFO);
069: report(cpd);
070: } catch (IOException ioe) {
071: log(ioe.toString(), Project.MSG_ERR);
072: throw new BuildException(
073: "IOException during task execution", ioe);
074: } catch (ReportException re) {
075: re.printStackTrace();
076: log(re.toString(), Project.MSG_ERR);
077: throw new BuildException(
078: "ReportException during task execution", re);
079: }
080: }
081:
082: private Language createLanguage() {
083: Properties p = new Properties();
084: if (ignoreLiterals) {
085: p.setProperty(JavaTokenizer.IGNORE_LITERALS, "true");
086: }
087: if (ignoreIdentifiers) {
088: p.setProperty(JavaTokenizer.IGNORE_IDENTIFIERS, "true");
089: }
090: return new LanguageFactory().createLanguage(language, p);
091: }
092:
093: private void report(CPD cpd) throws ReportException {
094: if (!cpd.getMatches().hasNext()) {
095: log("No duplicates over " + minimumTokenCount
096: + " tokens found", Project.MSG_INFO);
097: }
098: Renderer renderer = createRenderer();
099: FileReporter reporter;
100: if (outputFile == null) {
101: reporter = new FileReporter(encoding);
102: } else if (outputFile.isAbsolute()) {
103: reporter = new FileReporter(outputFile, encoding);
104: } else {
105: reporter = new FileReporter(new File(getProject()
106: .getBaseDir(), outputFile.toString()), encoding);
107: }
108: reporter.report(renderer.render(cpd.getMatches()));
109: }
110:
111: private void tokenizeFiles(CPD cpd) throws IOException {
112: for (FileSet fileSet : filesets) {
113: DirectoryScanner directoryScanner = fileSet
114: .getDirectoryScanner(getProject());
115: String[] includedFiles = directoryScanner
116: .getIncludedFiles();
117: for (int i = 0; i < includedFiles.length; i++) {
118: File file = new File(directoryScanner.getBasedir()
119: + System.getProperty("file.separator")
120: + includedFiles[i]);
121: log("Tokenizing " + file.getAbsolutePath(),
122: Project.MSG_VERBOSE);
123: cpd.add(file);
124: }
125: }
126: }
127:
128: private long analyzeCode(CPD cpd) {
129: long start = System.currentTimeMillis();
130: cpd.go();
131: long stop = System.currentTimeMillis();
132: return stop - start;
133: }
134:
135: private Renderer createRenderer() {
136: if (format.equals(TEXT_FORMAT)) {
137: return new SimpleRenderer();
138: } else if (format.equals(CSV_FORMAT)) {
139: return new CSVRenderer();
140: }
141: return new XMLRenderer(encoding);
142: }
143:
144: private void validateFields() throws BuildException {
145: if (minimumTokenCount == 0) {
146: throw new BuildException(
147: "minimumTokenCount is required and must be greater than zero");
148: } else if (filesets.isEmpty()) {
149: throw new BuildException(
150: "Must include at least one FileSet");
151: }
152: }
153:
154: public void addFileset(FileSet set) {
155: filesets.add(set);
156: }
157:
158: public void setMinimumTokenCount(int minimumTokenCount) {
159: this .minimumTokenCount = minimumTokenCount;
160: }
161:
162: public void setIgnoreLiterals(boolean value) {
163: this .ignoreLiterals = value;
164: }
165:
166: public void setIgnoreIdentifiers(boolean value) {
167: this .ignoreIdentifiers = value;
168: }
169:
170: public void setOutputFile(File outputFile) {
171: this .outputFile = outputFile;
172: }
173:
174: public void setFormat(FormatAttribute formatAttribute) {
175: format = formatAttribute.getValue();
176: }
177:
178: public void setLanguage(LanguageAttribute languageAttribute) {
179: language = languageAttribute.getValue();
180: }
181:
182: public void setEncoding(String encodingValue) {
183: encoding = encodingValue;
184: }
185:
186: public static class FormatAttribute extends EnumeratedAttribute {
187: private static final String[] FORMATS = new String[] {
188: XML_FORMAT, TEXT_FORMAT, CSV_FORMAT };
189:
190: public String[] getValues() {
191: return FORMATS;
192: }
193: }
194:
195: /*
196: * FIXME Can't we do something cleaner and
197: * more dynamic ? Maybe externalise to a properties files that will
198: * be generated when building pmd ? This will not have to add manually
199: * new language here ?
200: */
201: public static class LanguageAttribute extends EnumeratedAttribute {
202: private static final String[] LANGUAGES = new String[] {
203: "java", "jsp", "cpp", "c", "php", "ruby", "fortran" };
204:
205: public String[] getValues() {
206: return LANGUAGES;
207: }
208: }
209: }
|