View Javadoc
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 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   * 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 encoding="UTF-16LE" language="java" ignoreIdentifiers="true" ignoreLiterals="true" ignoreAnnotations="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 = "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<>();
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 }