001 package org.hackystat.sensor.ant.findbugs; 002 003 import java.io.File; 004 import java.util.ArrayList; 005 import java.util.Date; 006 import java.util.HashMap; 007 import java.util.HashSet; 008 import java.util.List; 009 import java.util.Map; 010 import java.util.Set; 011 import java.util.Map.Entry; 012 013 import javax.xml.bind.JAXBContext; 014 import javax.xml.bind.JAXBException; 015 import javax.xml.bind.Unmarshaller; 016 import javax.xml.datatype.XMLGregorianCalendar; 017 018 import org.apache.tools.ant.BuildException; 019 import org.hackystat.sensor.ant.findbugs.jaxb.BugCollection; 020 import org.hackystat.sensor.ant.findbugs.jaxb.BugInstance; 021 import org.hackystat.sensor.ant.findbugs.jaxb.SourceLine; 022 import org.hackystat.sensor.ant.task.HackystatSensorTask; 023 import org.hackystat.sensor.ant.util.LongTimeConverter; 024 import org.hackystat.sensorshell.SensorShellException; 025 026 /** 027 * Implements an Ant task that parses the XML files generated by FindBugs and sends the test case 028 * results to the Hackystat server. 029 * 030 * @author Philip Johnson, Hongbing Kou, Joy Agustin, Julie Ann Sakuda, Aaron A. Kagawa 031 */ 032 public class FindBugsSensor extends HackystatSensorTask { 033 034 /** The name of this tool. */ 035 private static String tool = "FindBugs"; 036 037 /** Initialize a new instance of a FindBugsSensor. */ 038 public FindBugsSensor() { 039 super(tool); 040 } 041 042 /** 043 * Initialize a new instance of a FindBugsSensor, passing the host email, and password directly. 044 * This supports testing. Note that when this constructor is called, offline data recovery by the 045 * sensor is disabled. 046 * 047 * @param host The hackystat host URL. 048 * @param email The Hackystat email to use. 049 * @param password The Hackystat password to use. 050 */ 051 public FindBugsSensor(String host, String email, String password) { 052 super(host, email, password, tool); 053 } 054 055 /** 056 * Parses the FindBugs XML files and sends the resulting FindBugs test case results to the 057 * hackystat server. This method is invoked automatically by Ant. 058 * 059 * @throws BuildException If there is an error. 060 */ 061 @Override 062 public void executeInternal() throws BuildException { 063 setupSensorShell(); 064 int numberOfTests = 0; 065 Date startTime = new Date(); 066 // Iterate though each file, extract the FindBugs data, send to sensorshell. 067 for (File dataFile : getDataFiles()) { 068 try { 069 verboseInfo("Processing FindBugs file: " + dataFile); 070 numberOfTests += processFindBugsXmlFile(dataFile); 071 } 072 catch (Exception e) { 073 signalError("Failure processing: " + dataFile, e); 074 } 075 } 076 this.sendAndQuit(); 077 summaryInfo(startTime, "CodeIssues", numberOfTests); 078 } 079 080 /** 081 * Parses a FindBugs XML file and sends the FindBugsEntry instances to the shell. 082 * 083 * @param xmlFile The XML file name to be processed. 084 * @exception BuildException if any error. 085 * @return The number of test cases in this XML file. 086 */ 087 public int processFindBugsXmlFile(File xmlFile) throws BuildException { 088 XMLGregorianCalendar runtimeGregorian = LongTimeConverter.convertLongToGregorian(new Date() 089 .getTime()); 090 // The start time for all entries will be approximated by the XML file's last mod time. 091 // The shell will ensure that it's unique by tweaking the millisecond field. 092 long startTime = xmlFile.lastModified(); 093 try { 094 JAXBContext context = JAXBContext 095 .newInstance(org.hackystat.sensor.ant.findbugs.jaxb.ObjectFactory.class); 096 Unmarshaller unmarshaller = context.createUnmarshaller(); 097 098 BugCollection bugCollection = (BugCollection) unmarshaller.unmarshal(xmlFile); 099 Set<String> allSrcFiles = new HashSet<String>(bugCollection.getProject().getSrcDir()); 100 101 List<BugInstance> bugInstanceCollection = bugCollection.getBugInstance(); 102 103 // Sort all the bugs by the file they are from 104 HashMap<String, List<BugInstance>> fileToBugs = new HashMap<String, List<BugInstance>>(); 105 for (BugInstance bugInstance : bugInstanceCollection) { 106 // Discard this bugInstance if we can't figure out the source path. 107 SourceLine sourceLine = bugInstance.getSourceLine(); 108 if (sourceLine == null) { 109 continue; 110 } 111 String abstractSourcePath = sourceLine.getSourcepath(); 112 if (abstractSourcePath == null) { 113 continue; 114 } 115 String fullSourcePath = this.findSrcFile(allSrcFiles, abstractSourcePath); 116 // Discard this bugInstance if we can't figure out the full source path. 117 if (fullSourcePath == null) { 118 continue; 119 } 120 121 if (fileToBugs.containsKey(fullSourcePath)) { 122 List<BugInstance> bugs = fileToBugs.get(fullSourcePath); 123 bugs.add(bugInstance); 124 } 125 else { 126 List<BugInstance> bugs = new ArrayList<BugInstance>(); 127 bugs.add(bugInstance); 128 fileToBugs.put(fullSourcePath, bugs); 129 } 130 } 131 132 // now process all files with bugs 133 int codeIssueCount = 0; 134 for (Entry<String, List<BugInstance>> entry : fileToBugs.entrySet()) { 135 // Alter startTime to guarantee uniqueness. 136 long uniqueTstamp = this.tstampSet.getUniqueTstamp(startTime); 137 138 // Get altered start time as XMLGregorianCalendar 139 XMLGregorianCalendar timestamp = LongTimeConverter.convertLongToGregorian(uniqueTstamp); 140 141 Map<String, String> keyValMap = new HashMap<String, String>(); 142 keyValMap.put("Tool", "FindBugs"); 143 keyValMap.put("SensorDataType", "CodeIssue"); 144 keyValMap.put("Runtime", runtimeGregorian.toString()); 145 keyValMap.put("Timestamp", timestamp.toString()); 146 keyValMap.put("Resource", entry.getKey()); 147 148 HashMap<String, Integer> issueCounts = new HashMap<String, Integer>(); 149 for (BugInstance bugInstance : entry.getValue()) { 150 String category = bugInstance.getCategory(); 151 String type = bugInstance.getType(); 152 String key = category + "_" + type; 153 154 if (issueCounts.containsKey(key)) { 155 Integer count = issueCounts.get(key); 156 issueCounts.put(key, ++count); 157 } 158 else { 159 // no previous mapping, add 1st issue to map 160 issueCounts.put(key, 1); 161 } 162 } 163 for (Entry<String, Integer> issueCountEntry : issueCounts.entrySet()) { 164 String typeKey = "Type_" + issueCountEntry.getKey(); 165 keyValMap.put(typeKey, issueCountEntry.getValue().toString()); 166 } 167 168 this.sensorShell.add(keyValMap); 169 codeIssueCount++; 170 } 171 172 // process the zero issues 173 allSrcFiles.removeAll(fileToBugs.keySet()); 174 for (String srcFile : allSrcFiles) { 175 // Alter startTime to guarantee uniqueness. 176 long uniqueTstamp = this.tstampSet.getUniqueTstamp(startTime); 177 178 // Get altered start time as XMLGregorianCalendar 179 XMLGregorianCalendar timestamp = LongTimeConverter.convertLongToGregorian(uniqueTstamp); 180 181 Map<String, String> keyValMap = new HashMap<String, String>(); 182 // Required 183 keyValMap.put("Tool", "FindBugs"); 184 keyValMap.put("SensorDataType", "CodeIssue"); 185 keyValMap.put("Runtime", runtimeGregorian.toString()); 186 keyValMap.put("Timestamp", timestamp.toString()); 187 keyValMap.put("Resource", srcFile); 188 189 this.sensorShell.add(keyValMap); // add data to sensorshell 190 codeIssueCount++; 191 } 192 return codeIssueCount; 193 } 194 catch (JAXBException e) { 195 throw new BuildException(errMsgPrefix + "Failure in JAXB " + xmlFile, e); 196 } 197 catch (SensorShellException f) { 198 throw new BuildException(errMsgPrefix + "Failure in SensorShell " + xmlFile, f); 199 } 200 } 201 202 /** 203 * Finds the full file path of the source path within the src files collection. For example, 204 * srcFiles could contain: [c:\foo\src\org\Foo.java, c:\foo\src\org\Bar.java] and the sourcePath 205 * could be org\Foo.java. This method will find and return the full path of the Foo.java file. 206 * 207 * @param srcFiles Contains the full path to the files. 208 * @param sourcePath Contains a trimmed version of a file path. 209 * @return The full file path, or null if the path is not found. 210 */ 211 private String findSrcFile(Set<String> srcFiles, String sourcePath) { 212 for (String srcFile : srcFiles) { 213 if (srcFile == null) { 214 continue; 215 } 216 String alteredSourcePath = sourcePath; 217 if (srcFile.contains("\\")) { 218 alteredSourcePath = sourcePath.replace('/', '\\'); 219 } 220 if (srcFile != null && srcFile.contains(alteredSourcePath)) { 221 return srcFile; 222 } 223 } 224 return null; 225 } 226 }