001 package org.hackystat.sensor.ant.checkstyle; 002 003 import java.io.File; 004 import java.util.Date; 005 import java.util.HashMap; 006 import java.util.List; 007 import java.util.Map; 008 import java.util.Map.Entry; 009 010 import javax.xml.bind.JAXBContext; 011 import javax.xml.bind.JAXBException; 012 import javax.xml.bind.Unmarshaller; 013 import javax.xml.datatype.XMLGregorianCalendar; 014 015 import org.apache.tools.ant.BuildException; 016 import org.hackystat.sensor.ant.checkstyle.jaxb.Checkstyle; 017 import org.hackystat.sensor.ant.checkstyle.jaxb.Error; 018 import org.hackystat.sensor.ant.task.HackystatSensorTask; 019 import org.hackystat.sensor.ant.util.LongTimeConverter; 020 import org.hackystat.sensorshell.SensorShellException; 021 022 /** 023 * Implements an Ant task that parses the XML files generated by Checkstyle. The Ant Task sends the 024 * CodeIssue data to a Hackystat server. 025 * 026 * @author Julie Ann Sakuda, Philip Johnson 027 */ 028 public class CheckstyleSensor extends HackystatSensorTask { 029 030 /** The type prefix for all attributes representing code issue errors. */ 031 private static final String TYPE = "Type_"; 032 /** The name of this tool. */ 033 private static String tool = "Checkstyle"; 034 035 /** Initialize a new instance of a CheckstyleSensor. */ 036 public CheckstyleSensor() { 037 super(tool); 038 } 039 040 /** 041 * Initialize a new instance of a CheckstyleSensor for testing purposes. 042 * 043 * @param host The SensorBase host URL. 044 * @param email The SensorBase email to use. 045 * @param password The SensorBase password to use. 046 */ 047 public CheckstyleSensor(String host, String email, String password) { 048 super(host, email, password, tool); 049 } 050 051 052 /** 053 * Parses the Checkstyle XML files and sends the resulting code issue results to the SensorBase 054 * server. 055 * 056 * @throws BuildException If there is an error. 057 */ 058 @Override 059 public void executeInternal() throws BuildException { 060 setupSensorShell(); 061 062 int numberOfCodeIssues = 0; 063 Date startTime = new Date(); 064 // Iterate though each file, extract the Checkstyle data, send to sensorshell. 065 for (File dataFile : getDataFiles()) { 066 verboseInfo("Processing Checkstyle file: " + dataFile); 067 try { 068 numberOfCodeIssues += this.processIssueXmlFile(dataFile); 069 } 070 catch (Exception e) { 071 signalError("Failure processing: " + dataFile, e); 072 } 073 } 074 this.sendAndQuit(); 075 summaryInfo(startTime, "Checkstyle", numberOfCodeIssues); 076 } 077 078 /** 079 * Parses a Checkstyle XML file and sends the code issue instances to the shell. 080 * 081 * @param xmlFile The XML file name to be processed. 082 * @return The number of issues that have been processed in this XML file. 083 * @exception BuildException thrown if it fails to process a file. 084 */ 085 public int processIssueXmlFile(File xmlFile) throws BuildException { 086 XMLGregorianCalendar runtimeGregorian = LongTimeConverter.convertLongToGregorian(this.runtime); 087 try { 088 JAXBContext context = JAXBContext 089 .newInstance(org.hackystat.sensor.ant.checkstyle.jaxb.ObjectFactory.class); 090 Unmarshaller unmarshaller = context.createUnmarshaller(); 091 092 Checkstyle checkstyle = (Checkstyle) unmarshaller.unmarshal(xmlFile); 093 // list of file elements in the checkstyle result file 094 List<org.hackystat.sensor.ant.checkstyle.jaxb.File> checkedFiles = checkstyle 095 .getFile(); 096 097 int codeIssueCount = 0; 098 for (org.hackystat.sensor.ant.checkstyle.jaxb.File file : checkedFiles) { 099 // Fully qualified name of the file checked 100 String fileName = file.getName(); 101 102 // Base unique timestamp off of the runtime (which is when it start running) 103 long uniqueTstamp = this.tstampSet.getUniqueTstamp(this.runtime); 104 105 // Get altered time as XMLGregorianCalendar 106 XMLGregorianCalendar uniqueTstampGregorian = LongTimeConverter 107 .convertLongToGregorian(uniqueTstamp); 108 109 // Add required information to the sensor key-val map 110 Map<String, String> keyValMap = new HashMap<String, String>(); 111 keyValMap.put("Tool", "Checkstyle"); 112 keyValMap.put("SensorDataType", "CodeIssue"); 113 keyValMap.put("Timestamp", uniqueTstampGregorian.toString()); 114 keyValMap.put("Runtime", runtimeGregorian.toString()); 115 keyValMap.put("Resource", fileName); 116 117 Map<String, Integer> issueCounts = new HashMap<String, Integer>(); 118 119 // gets all error elements for the file 120 List<Error> errors = file.getError(); 121 122 // file has errors, send one entry per error 123 for (Error error : errors) { 124 String source = error.getSource(); 125 126 String[] tokens = source.split("\\."); 127 String rule = tokens[tokens.length - 1]; 128 129 if (issueCounts.containsKey(rule)) { 130 Integer count = issueCounts.get(rule); 131 issueCounts.put(rule, ++count); 132 } 133 else { 134 // no mapping, first occurrence 135 issueCounts.put(rule, 1); 136 } 137 } 138 139 // Add the issue counts to the key-val map 140 for (Entry<String, Integer> entry : issueCounts.entrySet()) { 141 String typeKey = TYPE + entry.getKey(); 142 keyValMap.put(typeKey, entry.getValue().toString()); 143 } 144 145 this.sensorShell.add(keyValMap); 146 codeIssueCount++; 147 } 148 return codeIssueCount; 149 } 150 catch (JAXBException e) { 151 throw new BuildException(errMsgPrefix + "Failure in JAXB " + xmlFile, e); 152 } 153 catch (SensorShellException f) { 154 throw new BuildException(errMsgPrefix + "Failure in SensorShell " + xmlFile, f); 155 } 156 } 157 }