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    }