001 package org.hackystat.sensor.ant.pmd; 002 003 import java.io.File; 004 import java.util.Date; 005 import java.util.HashMap; 006 import java.util.HashSet; 007 import java.util.List; 008 import java.util.Map; 009 import java.util.Set; 010 import java.util.Map.Entry; 011 012 import javax.xml.bind.JAXBContext; 013 import javax.xml.bind.JAXBException; 014 import javax.xml.bind.Unmarshaller; 015 import javax.xml.datatype.XMLGregorianCalendar; 016 017 import org.apache.tools.ant.BuildException; 018 import org.hackystat.sensor.ant.pmd.jaxb.ObjectFactory; 019 import org.hackystat.sensor.ant.pmd.jaxb.Pmd; 020 import org.hackystat.sensor.ant.pmd.jaxb.Violation; 021 import org.hackystat.sensor.ant.task.HackystatSensorTask; 022 import org.hackystat.sensor.ant.util.LongTimeConverter; 023 import org.hackystat.sensorshell.SensorShellException; 024 025 /** 026 * Implements an Ant task that parses the XML files generated by PMD Ant Task and sends the 027 * CodeIssue data to a Hackystat server. 028 * 029 * @author Aaron A. Kagawa, Julie Ann Sakuda, Philip Johnson 030 */ 031 public class PmdSensor extends HackystatSensorTask { 032 033 /** Prefix for type attributes. */ 034 private static final String TYPE = "Type_"; 035 036 /** Tool in UserMap to use. */ 037 private static String tool = "PMD"; 038 039 /** Initialize a new instance of a PmdSensor. */ 040 public PmdSensor() { 041 super(tool); 042 } 043 044 /** 045 * Initialize a new instance of a PmdSensor, passing the host email, and password directly. This 046 * supports testing. 047 * 048 * @param host The hackystat host URL. 049 * @param email The Hackystat email to use. 050 * @param password The Hackystat password to use. 051 */ 052 public PmdSensor(String host, String email, String password) { 053 super(host, email, password, tool); 054 } 055 056 /** 057 * Parses the PMD XML files and sends the resulting code issue results to the hackystat server. 058 * This method is invoked automatically by Ant. 059 * 060 * @throws BuildException If there is an error. 061 */ 062 @Override 063 public void executeInternal() throws BuildException { 064 setupSensorShell(); 065 int numberOfCodeIssues = 0; 066 Date startTime = new Date(); 067 068 // Iterate though each file, extract the PMD data, send to sensorshell. 069 for (File dataFile : getDataFiles()) { 070 verboseInfo("Processing file: " + dataFile); 071 try { 072 numberOfCodeIssues += processPmdXmlFile(dataFile); 073 } 074 catch (Exception e) { 075 signalError("Failure processing: " + dataFile, e); 076 } 077 } 078 this.sendAndQuit(); 079 summaryInfo(startTime, "Code Issue", numberOfCodeIssues); 080 } 081 082 /** 083 * Parses a PMD XML file and sends the code issue instances to the shell. 084 * 085 * @param xmlFile The XML file name to be processed. 086 * @return The number of issues that have been processed in this XML file. 087 * @exception BuildException if any error. 088 */ 089 public int processPmdXmlFile(File xmlFile) throws BuildException { 090 XMLGregorianCalendar runtimeGregorian = LongTimeConverter.convertLongToGregorian(this.runtime); 091 092 try { 093 JAXBContext context = JAXBContext.newInstance(ObjectFactory.class); 094 Unmarshaller unmarshaller = context.createUnmarshaller(); 095 096 List<File> allSourceFiles = this.getSourceFiles(); 097 Set<String> filesWithViolations = new HashSet<String>(); 098 099 Pmd pmdResults = (Pmd) unmarshaller.unmarshal(xmlFile); 100 List<org.hackystat.sensor.ant.pmd.jaxb.File> files = pmdResults.getFile(); 101 102 int codeIssueCount = 0; 103 verboseInfo("Processing information about files that had PMD issues."); 104 for (org.hackystat.sensor.ant.pmd.jaxb.File file : files) { 105 // Base unique timestamp off of the runtime (which is when it started running) 106 long uniqueTstamp = this.tstampSet.getUniqueTstamp(this.runtime); 107 // Get altered time as XMLGregorianCalendar 108 XMLGregorianCalendar uniqueTstampGregorian = LongTimeConverter 109 .convertLongToGregorian(uniqueTstamp); 110 111 // derive the full path name from the file name 112 String fileName = file.getName(); 113 114 String fullFilePath = this.findSrcFile(allSourceFiles, fileName); 115 filesWithViolations.add(fullFilePath); 116 117 Map<String, String> keyValMap = new HashMap<String, String>(); 118 // Required 119 keyValMap.put("Tool", "PMD"); 120 keyValMap.put("SensorDataType", "CodeIssue"); 121 keyValMap.put("Timestamp", uniqueTstampGregorian.toString()); 122 keyValMap.put("Runtime", runtimeGregorian.toString()); 123 keyValMap.put("Resource", fullFilePath); 124 125 HashMap<String, Integer> issueCounts = new HashMap<String, Integer>(); 126 127 List<Violation> violations = file.getViolation(); 128 for (Violation violation : violations) { 129 String rule = violation.getRule(); 130 String ruleset = violation.getRuleset(); 131 132 String key = ruleset + "_" + rule; 133 key = key.replaceAll(" ", ""); // remove spaces 134 if (issueCounts.containsKey(key)) { 135 Integer count = issueCounts.get(key); 136 issueCounts.put(key, ++count); 137 } 138 else { 139 // no previous mapping, add 1st issue to map 140 issueCounts.put(key, 1); 141 } 142 } 143 for (Entry<String, Integer> entry : issueCounts.entrySet()) { 144 String typeKey = TYPE + entry.getKey(); 145 keyValMap.put(typeKey, entry.getValue().toString()); 146 } 147 148 this.sensorShell.add(keyValMap); // add data to sensorshell 149 codeIssueCount++; 150 } 151 152 // process the zero issues 153 verboseInfo("Generating data for files that did not have PMD issues."); 154 for (File srcFile : getSourceFiles()) { 155 // Skip this entry if we've already processed it above. 156 if (filesWithViolations.contains(srcFile.getAbsolutePath())) { 157 continue; 158 } 159 // Alter startTime to guarantee uniqueness. 160 long uniqueTstamp = this.tstampSet.getUniqueTstamp(this.runtime); 161 162 // Get altered time as XMLGregorianCalendar 163 XMLGregorianCalendar uniqueTstampGregorian = LongTimeConverter 164 .convertLongToGregorian(uniqueTstamp); 165 166 Map<String, String> keyValMap = new HashMap<String, String>(); 167 keyValMap.put("Tool", "PMD"); 168 keyValMap.put("SensorDataType", "CodeIssue"); 169 keyValMap.put("Timestamp", uniqueTstampGregorian.toString()); 170 keyValMap.put("Runtime", runtimeGregorian.toString()); 171 keyValMap.put("Resource", srcFile.getAbsolutePath()); 172 173 this.sensorShell.add(keyValMap); // add data to sensorshell 174 codeIssueCount++; 175 } 176 177 return codeIssueCount; 178 } 179 catch (JAXBException e) { 180 throw new BuildException(errMsgPrefix + "Failure in JAXB " + xmlFile, e); 181 } 182 catch (SensorShellException f) { 183 throw new BuildException(errMsgPrefix + "Failure in SensorShell " + xmlFile, f); 184 } 185 } 186 187 /** 188 * Finds the full file path of the source path within the src files collection. For example, 189 * srcFiles could contain: [c:\foo\src\org\Foo.java, c:\foo\src\org\Bar.java] and the sourcePath 190 * could be org\Foo.java. This method will find and return the full path of the Foo.java file. 191 * 192 * @param srcFiles Contains the full path to the files. 193 * @param sourcePath Contains a trimmed version of a file path. 194 * @return The full file path, or null if the path is not found. 195 */ 196 private String findSrcFile(List<File> srcFiles, String sourcePath) { 197 for (File srcFile : srcFiles) { 198 if (srcFile == null) { 199 continue; 200 } 201 String srcFilePath = srcFile.getAbsolutePath(); 202 String alteredSourcePath = sourcePath; 203 if (srcFilePath.contains("\\")) { 204 alteredSourcePath = sourcePath.replace('/', '\\'); 205 } 206 if (srcFile != null && srcFilePath.contains(alteredSourcePath)) { 207 return srcFilePath; 208 } 209 } 210 return null; 211 } 212 }