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