CPDTask.java
001 /**
002  * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003  */
004 package net.sourceforge.pmd.cpd;
005 
006 import org.apache.tools.ant.BuildException;
007 import org.apache.tools.ant.DirectoryScanner;
008 import org.apache.tools.ant.Project;
009 import org.apache.tools.ant.Task;
010 import org.apache.tools.ant.types.EnumeratedAttribute;
011 import org.apache.tools.ant.types.FileSet;
012 
013 import java.io.File;
014 import java.io.IOException;
015 import java.util.ArrayList;
016 import java.util.List;
017 import java.util.Properties;
018 
019 /**
020  * CPDTask
021  <p/>
022  * Runs the CPD utility via ant. The ant task looks like this:
023  <p/>
024  <project name="CPDProj" default="main" basedir=".">
025  <taskdef name="cpd" classname="net.sourceforge.pmd.cpd.CPDTask" />
026  <target name="main">
027  <cpd encoding="UTF-16LE" language="java" ignoreIdentifiers="true" ignoreLiterals="true" minimumTokenCount="100" outputFile="c:\cpdrun.txt">
028  <fileset dir="/path/to/my/src">
029  <include name="*.java"/>
030  </fileset>
031  </cpd>
032  </target>
033  </project>
034  <p/>
035  * Required: minimumTokenCount, outputFile, and at least one file
036  */
037 public class CPDTask extends Task {
038 
039     private static final String TEXT_FORMAT = "text";
040     private static final String XML_FORMAT = "xml";
041     private static final String CSV_FORMAT = "csv";
042 
043     private String format = TEXT_FORMAT;
044     private String language = "java";
045     private int minimumTokenCount;
046     private boolean ignoreLiterals;
047     private boolean ignoreIdentifiers;
048     private File outputFile;
049     private String encoding = System.getProperty("file.encoding");
050     private List<FileSet> filesets = new ArrayList<FileSet>();
051 
052     public void execute() throws BuildException {
053         try {
054             validateFields();
055 
056             log("Starting run, minimumTokenCount is " + minimumTokenCount, Project.MSG_INFO);
057 
058             log("Tokenizing files", Project.MSG_INFO);
059             CPD cpd = new CPD(minimumTokenCount, createLanguage());
060             cpd.setEncoding(encoding);
061             tokenizeFiles(cpd);
062 
063             log("Starting to analyze code", Project.MSG_INFO);
064             long timeTaken = analyzeCode(cpd);
065             log("Done analyzing code; that took " + timeTaken + " milliseconds");
066 
067             log("Generating report", Project.MSG_INFO);
068             report(cpd);
069         catch (IOException ioe) {
070             log(ioe.toString(), Project.MSG_ERR);
071             throw new BuildException("IOException during task execution", ioe);
072         catch (ReportException re) {
073             re.printStackTrace();
074             log(re.toString(), Project.MSG_ERR);
075             throw new BuildException("ReportException during task execution", re);
076         }
077     }
078 
079     private Language createLanguage() {
080         Properties p = new Properties();
081         if (ignoreLiterals) {
082             p.setProperty(JavaTokenizer.IGNORE_LITERALS, "true");
083         }
084         if (ignoreIdentifiers) {
085             p.setProperty(JavaTokenizer.IGNORE_IDENTIFIERS, "true");
086         }
087         return new LanguageFactory().createLanguage(language, p);
088     }
089 
090     private void report(CPD cpdthrows ReportException {
091         if (!cpd.getMatches().hasNext()) {
092             log("No duplicates over " + minimumTokenCount + " tokens found", Project.MSG_INFO);
093         }
094         Renderer renderer = createRenderer();
095         FileReporter reporter;
096         if (outputFile == null) {
097           reporter = new FileReporter(encoding);
098         else if (outputFile.isAbsolute()) {
099             reporter = new FileReporter(outputFile, encoding);
100         else {
101             reporter = new FileReporter(new File(getProject().getBaseDir(), outputFile.toString()), encoding);
102         }
103         reporter.report(renderer.render(cpd.getMatches()));
104     }
105 
106     private void tokenizeFiles(CPD cpdthrows IOException {
107         for (FileSet fileSet: filesets) {
108             DirectoryScanner directoryScanner = fileSet.getDirectoryScanner(getProject());
109             String[] includedFiles = directoryScanner.getIncludedFiles();
110             for (int i = 0; i < includedFiles.length; i++) {
111                 File file = new File(directoryScanner.getBasedir() + System.getProperty("file.separator"+ includedFiles[i]);
112                 log("Tokenizing " + file.getAbsolutePath(), Project.MSG_VERBOSE);
113                 cpd.add(file);
114             }
115         }
116     }
117 
118     private long analyzeCode(CPD cpd) {
119         long start = System.currentTimeMillis();
120         cpd.go();
121         long stop = System.currentTimeMillis();
122         return stop - start;
123     }
124 
125     private Renderer createRenderer() {
126         if (format.equals(TEXT_FORMAT)) {
127             return new SimpleRenderer();
128         else if (format.equals(CSV_FORMAT)) {
129             return new CSVRenderer();
130         }
131         return new XMLRenderer(encoding);
132     }
133 
134     private void validateFields() throws BuildException {
135         if (minimumTokenCount == 0) {
136             throw new BuildException("minimumTokenCount is required and must be greater than zero");
137         else if (filesets.isEmpty()) {
138             throw new BuildException("Must include at least one FileSet");
139         }
140     }
141 
142     public void addFileset(FileSet set) {
143         filesets.add(set);
144     }
145 
146     public void setMinimumTokenCount(int minimumTokenCount) {
147         this.minimumTokenCount = minimumTokenCount;
148     }
149 
150     public void setIgnoreLiterals(boolean value) {
151         this.ignoreLiterals = value;
152     }
153 
154     public void setIgnoreIdentifiers(boolean value) {
155         this.ignoreIdentifiers = value;
156     }
157 
158     public void setOutputFile(File outputFile) {
159         this.outputFile = outputFile;
160     }
161 
162     public void setFormat(FormatAttribute formatAttribute) {
163         format = formatAttribute.getValue();
164     }
165 
166     public void setLanguage(LanguageAttribute languageAttribute) {
167         language = languageAttribute.getValue();
168     }
169 
170     public void setEncoding(String encodingValue) {
171         encoding = encodingValue;
172     }
173 
174     public static class FormatAttribute extends EnumeratedAttribute {
175         private static final String[] FORMATS = new String[]{XML_FORMAT, TEXT_FORMAT, CSV_FORMAT};
176         public String[] getValues() {
177             return FORMATS;
178         }
179     }
180 
181     /*
182      * FIXME Can't we do something cleaner and
183      * more dynamic ? Maybe externalise to a properties files that will
184      * be generated when building pmd ? This will not have to add manually
185      * new language here ?
186     */
187     public static class LanguageAttribute extends EnumeratedAttribute {
188         private static final String[] LANGUAGES = new String[]{"java","jsp","cpp""c","php""ruby""fortran"};
189         public String[] getValues() {
190             return LANGUAGES;
191         }
192     }
193 }