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.Iterator; 17 import java.util.List; 18 import java.util.Properties; 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 ignoreIdentifiers="true" ignoreLiterals="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 int minimumTokenCount; 46 private boolean ignoreLiterals; 47 private boolean ignoreIdentifiers; 48 private File outputFile; 49 private List filesets = new ArrayList(); 50 51 public void execute() throws BuildException { 52 try { 53 validateFields(); 54 55 log("Tokenizing files", Project.MSG_INFO); 56 Properties p = new Properties(); 57 if (ignoreLiterals) { 58 p.setProperty(JavaTokenizer.IGNORE_LITERALS, "true"); 59 } 60 if (ignoreIdentifiers) { 61 p.setProperty(JavaTokenizer.IGNORE_IDENTIFIERS, "true"); 62 } 63 CPD cpd = new CPD(minimumTokenCount, new JavaLanguage(p)); 64 tokenizeFiles(cpd); 65 66 log("Starting to analyze code", Project.MSG_INFO); 67 long timeTaken = analyzeCode(cpd); 68 log("Done analyzing code; that took " + timeTaken + " milliseconds"); 69 70 log("Generating report", Project.MSG_INFO); 71 report(cpd); 72 } catch (IOException ioe) { 73 log(ioe.toString(), Project.MSG_ERR); 74 throw new BuildException("IOException during task execution", ioe); 75 } catch (ReportException re) { 76 re.printStackTrace(); 77 log(re.toString(), Project.MSG_ERR); 78 throw new BuildException("ReportException during task execution", re); 79 } 80 } 81 82 private void report(CPD cpd) throws ReportException { 83 if (!cpd.getMatches().hasNext()) { 84 log("No duplicates over " + minimumTokenCount + " tokens found", Project.MSG_INFO); 85 } 86 Renderer renderer = createRenderer(); 87 if (outputFile.isAbsolute()) { 88 new FileReporter(outputFile).report(renderer.render(cpd.getMatches())); 89 } else { 90 new FileReporter(new File(getProject().getBaseDir(), outputFile.toString())); 91 } 92 } 93 94 95 private void tokenizeFiles(CPD cpd) throws IOException { 96 for (Iterator iterator = filesets.iterator(); iterator.hasNext();) { 97 FileSet fileSet = (FileSet) iterator.next(); 98 DirectoryScanner directoryScanner = fileSet.getDirectoryScanner(getProject()); 99 String[] includedFiles = directoryScanner.getIncludedFiles(); 100 for (int i = 0; i < includedFiles.length; i++) { 101 File file = new File(directoryScanner.getBasedir() + System.getProperty("file.separator") + includedFiles[i]); 102 log("Tokenizing " + file.getAbsolutePath(), Project.MSG_VERBOSE); 103 cpd.add(file); 104 } 105 } 106 } 107 108 private long analyzeCode(CPD cpd) { 109 long start = System.currentTimeMillis(); 110 cpd.go(); 111 long stop = System.currentTimeMillis(); 112 return stop - start; 113 } 114 115 private Renderer createRenderer() { 116 if (format.equals(TEXT_FORMAT)) { 117 return new SimpleRenderer(); 118 } else if (format.equals(CSV_FORMAT)) { 119 return new CSVRenderer(); 120 } 121 return new XMLRenderer(); 122 } 123 124 private void validateFields() throws BuildException { 125 if (minimumTokenCount == 0) { 126 throw new BuildException("minimumTokenCount is required and must be greater than zero"); 127 } else if (outputFile == null) { 128 throw new BuildException("outputFile is a required attribute"); 129 } else if (filesets.isEmpty()) { 130 throw new BuildException("Must include at least one FileSet"); 131 } 132 } 133 134 public void addFileset(FileSet set) { 135 filesets.add(set); 136 } 137 138 public void setMinimumTokenCount(int minimumTokenCount) { 139 this.minimumTokenCount = minimumTokenCount; 140 } 141 142 public void setIgnoreLiterals(boolean value) { 143 this.ignoreLiterals = value; 144 } 145 146 public void setIgnoreIdentifiers(boolean value) { 147 this.ignoreIdentifiers = value; 148 } 149 150 public void setOutputFile(File outputFile) { 151 this.outputFile = outputFile; 152 } 153 154 public void setFormat(FormatAttribute formatAttribute) { 155 format = formatAttribute.getValue(); 156 } 157 158 public static class FormatAttribute extends EnumeratedAttribute { 159 private String[] formats = new String[]{XML_FORMAT, TEXT_FORMAT, CSV_FORMAT}; 160 161 public String[] getValues() { 162 return formats; 163 } 164 } 165 }