1
2
3
4 package net.sourceforge.pmd.cpd;
5
6 import java.io.File;
7 import java.io.IOException;
8 import java.util.ArrayList;
9 import java.util.Arrays;
10 import java.util.List;
11 import java.util.Properties;
12
13 import org.apache.tools.ant.BuildException;
14 import org.apache.tools.ant.DirectoryScanner;
15 import org.apache.tools.ant.Project;
16 import org.apache.tools.ant.Task;
17 import org.apache.tools.ant.types.EnumeratedAttribute;
18 import org.apache.tools.ant.types.FileSet;
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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 = "java";
46 private int minimumTokenCount;
47 private boolean ignoreLiterals;
48 private boolean ignoreIdentifiers;
49 private boolean ignoreAnnotations;
50 private boolean ignoreUsings;
51 private boolean skipLexicalErrors;
52 private boolean skipDuplicateFiles;
53 private boolean skipBlocks = true;
54 private String skipBlocksPattern = Tokenizer.DEFAULT_SKIP_BLOCKS_PATTERN;
55 private File outputFile;
56 private String encoding = System.getProperty("file.encoding");
57 private List<FileSet> filesets = new ArrayList<FileSet>();
58
59 public void execute() throws BuildException {
60 ClassLoader oldClassloader = Thread.currentThread().getContextClassLoader();
61 Thread.currentThread().setContextClassLoader(CPDTask.class.getClassLoader());
62
63 try {
64 validateFields();
65
66 log("Starting run, minimumTokenCount is " + minimumTokenCount, Project.MSG_INFO);
67
68 log("Tokenizing files", Project.MSG_INFO);
69 CPDConfiguration config = new CPDConfiguration();
70 config.setMinimumTileSize(minimumTokenCount);
71 config.setLanguage(createLanguage());
72 config.setEncoding(encoding);
73 config.setSkipDuplicates(skipDuplicateFiles);
74 config.setSkipLexicalErrors(skipLexicalErrors);
75
76 CPD cpd = new CPD(config);
77 tokenizeFiles(cpd);
78
79 log("Starting to analyze code", Project.MSG_INFO);
80 long timeTaken = analyzeCode(cpd);
81 log("Done analyzing code; that took " + timeTaken + " milliseconds");
82
83 log("Generating report", Project.MSG_INFO);
84 report(cpd);
85 } catch (IOException ioe) {
86 log(ioe.toString(), Project.MSG_ERR);
87 throw new BuildException("IOException during task execution", ioe);
88 } catch (ReportException re) {
89 re.printStackTrace();
90 log(re.toString(), Project.MSG_ERR);
91 throw new BuildException("ReportException during task execution", re);
92 } finally {
93 Thread.currentThread().setContextClassLoader(oldClassloader);
94 }
95 }
96
97 private Language createLanguage() {
98 Properties p = new Properties();
99 if (ignoreLiterals) {
100 p.setProperty(Tokenizer.IGNORE_LITERALS, "true");
101 }
102 if (ignoreIdentifiers) {
103 p.setProperty(Tokenizer.IGNORE_IDENTIFIERS, "true");
104 }
105 if (ignoreAnnotations) {
106 p.setProperty(Tokenizer.IGNORE_ANNOTATIONS, "true");
107 }
108 if (ignoreUsings) {
109 p.setProperty(Tokenizer.IGNORE_USINGS, "true");
110 }
111 p.setProperty(Tokenizer.OPTION_SKIP_BLOCKS, Boolean.toString(skipBlocks));
112 p.setProperty(Tokenizer.OPTION_SKIP_BLOCKS_PATTERN, skipBlocksPattern);
113 return LanguageFactory.createLanguage(language, p);
114 }
115
116 private void report(CPD cpd) throws ReportException {
117 if (!cpd.getMatches().hasNext()) {
118 log("No duplicates over " + minimumTokenCount + " tokens found", Project.MSG_INFO);
119 }
120 Renderer renderer = createRenderer();
121 FileReporter reporter;
122 if (outputFile == null) {
123 reporter = new FileReporter(encoding);
124 } else if (outputFile.isAbsolute()) {
125 reporter = new FileReporter(outputFile, encoding);
126 } else {
127 reporter = new FileReporter(new File(getProject().getBaseDir(), outputFile.toString()), encoding);
128 }
129 reporter.report(renderer.render(cpd.getMatches()));
130 }
131
132 private void tokenizeFiles(CPD cpd) throws IOException {
133 for (FileSet fileSet: filesets) {
134 DirectoryScanner directoryScanner = fileSet.getDirectoryScanner(getProject());
135 String[] includedFiles = directoryScanner.getIncludedFiles();
136 for (int i = 0; i < includedFiles.length; i++) {
137 File file = new File(directoryScanner.getBasedir() + System.getProperty("file.separator") + includedFiles[i]);
138 log("Tokenizing " + file.getAbsolutePath(), Project.MSG_VERBOSE);
139 cpd.add(file);
140 }
141 }
142 }
143
144 private long analyzeCode(CPD cpd) {
145 long start = System.currentTimeMillis();
146 cpd.go();
147 long stop = System.currentTimeMillis();
148 return stop - start;
149 }
150
151 private Renderer createRenderer() {
152 if (format.equals(TEXT_FORMAT)) {
153 return new SimpleRenderer();
154 } else if (format.equals(CSV_FORMAT)) {
155 return new CSVRenderer();
156 }
157 return new XMLRenderer();
158 }
159
160 private void validateFields() throws BuildException {
161 if (minimumTokenCount == 0) {
162 throw new BuildException("minimumTokenCount is required and must be greater than zero");
163 }
164
165 if (filesets.isEmpty()) {
166 throw new BuildException("Must include at least one FileSet");
167 }
168
169 if (!Arrays.asList(LanguageFactory.supportedLanguages).contains(language)) {
170 throw new BuildException("Language " + language + " is not supported. Available languages: "
171 + Arrays.toString(LanguageFactory.supportedLanguages));
172 }
173 }
174
175 public void addFileset(FileSet set) {
176 filesets.add(set);
177 }
178
179 public void setMinimumTokenCount(int minimumTokenCount) {
180 this.minimumTokenCount = minimumTokenCount;
181 }
182
183 public void setIgnoreLiterals(boolean value) {
184 this.ignoreLiterals = value;
185 }
186
187 public void setIgnoreIdentifiers(boolean value) {
188 this.ignoreIdentifiers = value;
189 }
190
191 public void setIgnoreAnnotations(boolean value) {
192 this.ignoreAnnotations = value;
193 }
194
195 public void setIgnoreUsings(boolean value) {
196 this.ignoreUsings = value;
197 }
198
199 public void setSkipLexicalErrors(boolean skipLexicalErrors) {
200 this.skipLexicalErrors = skipLexicalErrors;
201 }
202
203 public void setSkipDuplicateFiles(boolean skipDuplicateFiles) {
204 this.skipDuplicateFiles = skipDuplicateFiles;
205 }
206
207 public void setOutputFile(File outputFile) {
208 this.outputFile = outputFile;
209 }
210
211 public void setFormat(FormatAttribute formatAttribute) {
212 this.format = formatAttribute.getValue();
213 }
214
215 public void setLanguage(String language) {
216 this.language = language;
217 }
218
219 public void setEncoding(String encoding) {
220 this.encoding = encoding;
221 }
222
223 public void setSkipBlocks(boolean skipBlocks) {
224 this.skipBlocks = skipBlocks;
225 }
226
227 public void setSkipBlocksPattern(String skipBlocksPattern) {
228 this.skipBlocksPattern = skipBlocksPattern;
229 }
230
231 public static class FormatAttribute extends EnumeratedAttribute {
232 private static final String[] FORMATS = new String[]{XML_FORMAT, TEXT_FORMAT, CSV_FORMAT};
233 public String[] getValues() {
234 return FORMATS;
235 }
236 }
237 }