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 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.List;
17  import java.util.Properties;
18  
19  /**
20   * CPDTask
21   * <p/>
22   * Runs the CPD utility via ant. The ant task looks like this:
23   * <p/>
24   * <project name="CPDProj" default="main" basedir=".">
25   * <taskdef name="cpd" classname="net.sourceforge.pmd.cpd.CPDTask" />
26   * <target name="main">
27   * <cpd encoding="UTF-16LE" language="java" ignoreIdentifiers="true" ignoreLiterals="true" ignoreAnnotations="true" minimumTokenCount="100" outputFile="c:\cpdrun.txt">
28   * <fileset dir="/path/to/my/src">
29   * <include name="*.java"/>
30   * </fileset>
31   * </cpd>
32   * </target>
33   * </project>
34   * <p/>
35   * Required: minimumTokenCount, outputFile, and at least one file
36   */
37  public class CPDTask extends Task {
38  
39      private static final String TEXT_FORMAT = "text";
40      private static final String XML_FORMAT = "xml";
41      private static final String CSV_FORMAT = "csv";
42  
43      private String format = TEXT_FORMAT;
44      private String language = "java";
45      private int minimumTokenCount;
46      private boolean ignoreLiterals;
47      private boolean ignoreIdentifiers;
48      private boolean ignoreAnnotations;
49      private boolean skipLexicalErrors;
50      private boolean skipDuplicateFiles;
51      private File outputFile;
52      private String encoding = System.getProperty("file.encoding");
53      private List<FileSet> filesets = new ArrayList<FileSet>();
54  
55      public void execute() throws BuildException {
56          try {
57              validateFields();
58  
59              log("Starting run, minimumTokenCount is " + minimumTokenCount, Project.MSG_INFO);
60  
61              log("Tokenizing files", Project.MSG_INFO);
62              CPDConfiguration config = new CPDConfiguration();
63              config.setMinimumTileSize(minimumTokenCount);
64              config.setLanguage(createLanguage());
65              config.setEncoding(encoding);
66              config.setSkipDuplicates(skipDuplicateFiles);
67              config.setSkipLexicalErrors(skipLexicalErrors);
68  
69              CPD cpd = new CPD(config);
70              tokenizeFiles(cpd);
71  
72              log("Starting to analyze code", Project.MSG_INFO);
73              long timeTaken = analyzeCode(cpd);
74              log("Done analyzing code; that took " + timeTaken + " milliseconds");
75  
76              log("Generating report", Project.MSG_INFO);
77              report(cpd);
78          } catch (IOException ioe) {
79              log(ioe.toString(), Project.MSG_ERR);
80              throw new BuildException("IOException during task execution", ioe);
81          } catch (ReportException re) {
82              re.printStackTrace();
83              log(re.toString(), Project.MSG_ERR);
84              throw new BuildException("ReportException during task execution", re);
85          }
86      }
87  
88      private Language createLanguage() {
89          Properties p = new Properties();
90          if (ignoreLiterals) {
91              p.setProperty(JavaTokenizer.IGNORE_LITERALS, "true");
92          }
93          if (ignoreIdentifiers) {
94              p.setProperty(JavaTokenizer.IGNORE_IDENTIFIERS, "true");
95          }
96          if (ignoreAnnotations) {
97              p.setProperty(JavaTokenizer.IGNORE_ANNOTATIONS, "true");
98          }
99          return new LanguageFactory().createLanguage(language, p);
100     }
101 
102     private void report(CPD cpd) throws ReportException {
103         if (!cpd.getMatches().hasNext()) {
104             log("No duplicates over " + minimumTokenCount + " tokens found", Project.MSG_INFO);
105         }
106         Renderer renderer = createRenderer();
107         FileReporter reporter;
108         if (outputFile == null) {
109         	reporter = new FileReporter(encoding);
110         } else if (outputFile.isAbsolute()) {
111             reporter = new FileReporter(outputFile, encoding);
112         } else {
113             reporter = new FileReporter(new File(getProject().getBaseDir(), outputFile.toString()), encoding);
114         }
115         reporter.report(renderer.render(cpd.getMatches()));
116     }
117 
118     private void tokenizeFiles(CPD cpd) throws IOException {
119         for (FileSet fileSet: filesets) {
120             DirectoryScanner directoryScanner = fileSet.getDirectoryScanner(getProject());
121             String[] includedFiles = directoryScanner.getIncludedFiles();
122             for (int i = 0; i < includedFiles.length; i++) {
123                 File file = new File(directoryScanner.getBasedir() + System.getProperty("file.separator") + includedFiles[i]);
124                 log("Tokenizing " + file.getAbsolutePath(), Project.MSG_VERBOSE);
125                 cpd.add(file);
126             }
127         }
128     }
129 
130     private long analyzeCode(CPD cpd) {
131         long start = System.currentTimeMillis();
132         cpd.go();
133         long stop = System.currentTimeMillis();
134         return stop - start;
135     }
136 
137     private Renderer createRenderer() {
138         if (format.equals(TEXT_FORMAT)) {
139             return new SimpleRenderer();
140         } else if (format.equals(CSV_FORMAT)) {
141             return new CSVRenderer();
142         }
143         return new XMLRenderer();
144     }
145 
146     private void validateFields() throws BuildException {
147         if (minimumTokenCount == 0) {
148             throw new BuildException("minimumTokenCount is required and must be greater than zero");
149         } else if (filesets.isEmpty()) {
150             throw new BuildException("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 setIgnoreAnnotations(boolean value) {
171         this.ignoreAnnotations = value;
172     }
173 
174     public void setSkipLexicalErrors(boolean skipLexicalErrors) {
175         this.skipLexicalErrors = skipLexicalErrors;
176     }
177 
178     public void setSkipDuplicateFiles(boolean skipDuplicateFiles) {
179         this.skipDuplicateFiles = skipDuplicateFiles;
180     }
181 
182     public void setOutputFile(File outputFile) {
183         this.outputFile = outputFile;
184     }
185 
186     public void setFormat(FormatAttribute formatAttribute) {
187         format = formatAttribute.getValue();
188     }
189 
190     public void setLanguage(LanguageAttribute languageAttribute) {
191         language = languageAttribute.getValue();
192     }
193 
194     public void setEncoding(String encodingValue) {
195         encoding = encodingValue;
196     }
197 
198     public static class FormatAttribute extends EnumeratedAttribute {
199         private static final String[] FORMATS = new String[]{XML_FORMAT, TEXT_FORMAT, CSV_FORMAT};
200         public String[] getValues() {
201             return FORMATS;
202         }
203     }
204 
205     public static class LanguageAttribute extends EnumeratedAttribute {
206         public String[] getValues() {
207             return LanguageFactory.supportedLanguages;
208         }
209     }
210 }