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 cpd) throws 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 cpd) throws 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 }
|