1 /***
2 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3 */
4 package net.sourceforge.pmd.cpd;
5
6 import org.apache.tools.ant.BuildException;
7 import org.apache.tools.ant.DirectoryScanner;
8 import org.apache.tools.ant.Project;
9 import org.apache.tools.ant.Task;
10 import org.apache.tools.ant.types.EnumeratedAttribute;
11 import org.apache.tools.ant.types.FileSet;
12
13 import java.io.File;
14 import java.io.IOException;
15 import java.util.ArrayList;
16 import java.util.Iterator;
17 import java.util.List;
18 import java.util.Properties;
19
20 /***
21 * CPDTask
22 * <p/>
23 * Runs the CPD utility via ant. The ant task looks like this:
24 * <p/>
25 * <project name="CPDProj" default="main" basedir=".">
26 * <taskdef name="cpd" classname="net.sourceforge.pmd.cpd.CPDTask" />
27 * <target name="main">
28 * <cpd language="java" ignoreIdentifiers="true" ignoreLiterals="true" minimumTokenCount="100" outputFile="c:\cpdrun.txt">
29 * <fileset dir="/path/to/my/src">
30 * <include name="*.java"/>
31 * </fileset>
32 * </cpd>
33 * </target>
34 * </project>
35 * <p/>
36 * Required: minimumTokenCount, outputFile, and at least one file
37 */
38 public class CPDTask extends Task {
39
40 private static final String TEXT_FORMAT = "text";
41 private static final String XML_FORMAT = "xml";
42 private static final String CSV_FORMAT = "csv";
43
44 private String format = TEXT_FORMAT;
45 private String language = LanguageFactory.JAVA_KEY;
46 private int minimumTokenCount;
47 private boolean ignoreLiterals;
48 private boolean ignoreIdentifiers;
49 private File outputFile;
50 private List filesets = new ArrayList();
51
52 public void execute() throws BuildException {
53 try {
54 validateFields();
55
56 log("Tokenizing files", Project.MSG_INFO);
57 CPD cpd = new CPD(minimumTokenCount, createLanguage());
58 tokenizeFiles(cpd);
59
60 log("Starting to analyze code", Project.MSG_INFO);
61 long timeTaken = analyzeCode(cpd);
62 log("Done analyzing code; that took " + timeTaken + " milliseconds");
63
64 log("Generating report", Project.MSG_INFO);
65 report(cpd);
66 } catch (IOException ioe) {
67 log(ioe.toString(), Project.MSG_ERR);
68 throw new BuildException("IOException during task execution", ioe);
69 } catch (ReportException re) {
70 re.printStackTrace();
71 log(re.toString(), Project.MSG_ERR);
72 throw new BuildException("ReportException during task execution", re);
73 }
74 }
75
76 private Language createLanguage() {
77 Properties p = new Properties();
78 if (ignoreLiterals) {
79 p.setProperty(JavaTokenizer.IGNORE_LITERALS, "true");
80 }
81 if (ignoreIdentifiers) {
82 p.setProperty(JavaTokenizer.IGNORE_IDENTIFIERS, "true");
83 }
84 return new LanguageFactory().createLanguage(language, p);
85 }
86
87 private void report(CPD cpd) throws ReportException {
88 if (!cpd.getMatches().hasNext()) {
89 log("No duplicates over " + minimumTokenCount + " tokens found", Project.MSG_INFO);
90 }
91 Renderer renderer = createRenderer();
92 if (outputFile.isAbsolute()) {
93 new FileReporter(outputFile).report(renderer.render(cpd.getMatches()));
94 } else {
95 new FileReporter(new File(getProject().getBaseDir(), outputFile.toString()));
96 }
97 }
98
99 private void tokenizeFiles(CPD cpd) throws IOException {
100 for (Iterator iterator = filesets.iterator(); iterator.hasNext();) {
101 FileSet fileSet = (FileSet) iterator.next();
102 DirectoryScanner directoryScanner = fileSet.getDirectoryScanner(getProject());
103 String[] includedFiles = directoryScanner.getIncludedFiles();
104 for (int i = 0; i < includedFiles.length; i++) {
105 File file = new File(directoryScanner.getBasedir() + System.getProperty("file.separator") + includedFiles[i]);
106 log("Tokenizing " + file.getAbsolutePath(), Project.MSG_VERBOSE);
107 cpd.add(file);
108 }
109 }
110 }
111
112 private long analyzeCode(CPD cpd) {
113 long start = System.currentTimeMillis();
114 cpd.go();
115 long stop = System.currentTimeMillis();
116 return stop - start;
117 }
118
119 private Renderer createRenderer() {
120 if (format.equals(TEXT_FORMAT)) {
121 return new SimpleRenderer();
122 } else if (format.equals(CSV_FORMAT)) {
123 return new CSVRenderer();
124 }
125 return new XMLRenderer();
126 }
127
128 private void validateFields() throws BuildException {
129 if (minimumTokenCount == 0) {
130 throw new BuildException("minimumTokenCount is required and must be greater than zero");
131 } else if (outputFile == null) {
132 throw new BuildException("outputFile is a required attribute");
133 } else if (filesets.isEmpty()) {
134 throw new BuildException("Must include at least one FileSet");
135 }
136 }
137
138 public void addFileset(FileSet set) {
139 filesets.add(set);
140 }
141
142 public void setMinimumTokenCount(int minimumTokenCount) {
143 this.minimumTokenCount = minimumTokenCount;
144 }
145
146 public void setIgnoreLiterals(boolean value) {
147 this.ignoreLiterals = value;
148 }
149
150 public void setIgnoreIdentifiers(boolean value) {
151 this.ignoreIdentifiers = value;
152 }
153
154 public void setOutputFile(File outputFile) {
155 this.outputFile = outputFile;
156 }
157
158 public void setFormat(FormatAttribute formatAttribute) {
159 format = formatAttribute.getValue();
160 }
161
162 public void setLanguage(LanguageAttribute languageAttribute) {
163 language = languageAttribute.getValue();
164 }
165
166 public static class FormatAttribute extends EnumeratedAttribute {
167 private static final String[] FORMATS = new String[]{XML_FORMAT, TEXT_FORMAT, CSV_FORMAT};
168 public String[] getValues() {
169 return FORMATS;
170 }
171 }
172
173 public static class LanguageAttribute extends EnumeratedAttribute {
174 private String[] LANGUAGES = new String[]{LanguageFactory.JAVA_KEY, LanguageFactory.JSP_KEY, LanguageFactory.CPP_KEY, LanguageFactory.C_KEY, LanguageFactory.PHP_KEY, LanguageFactory.RUBY_KEY };
175 public String[] getValues() {
176 return LANGUAGES;
177 }
178 }
179 }