package hudson.plugins.violations.types.findbugs; import java.io.IOException; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import hudson.plugins.violations.model.Severity; import hudson.plugins.violations.util.AbsoluteFileFinder; import hudson.plugins.violations.util.StringUtil; import hudson.plugins.violations.util.HashMapWithDefault; import hudson.plugins.violations.model.Violation; import hudson.plugins.violations.parse.AbstractTypeParser; /** * Parses a find bugs xml report file. */ public class FindBugsParser extends AbstractTypeParser { private static final Logger LOG = Logger.getLogger( FindBugsParser.class.getName()); private boolean debug = false; private AbsoluteFileFinder absoluteFileFinder = new AbsoluteFileFinder(); /** * Parse the findbugs xml file. * @throws IOException if there is a problem reading the file. * @throws XmlPullParserException if there is a problem parsing the file. */ protected void execute() throws IOException, XmlPullParserException { // Ensure that the top level tag is "BugCollection" expectNextTag("BugCollection"); getParser().next(); // consume the "BugCollection" tag absoluteFileFinder.addSourcePaths(getSourcePaths()); // Top level tags: // file: from the maven findbugs plugin // Project: from the ant findbugs tag // BugInstance: from the ant findbugs tag // others - ignore while (true) { String tag = getSibTag(); if (tag == null) { break; } if (tag.equals("Project")) { getSourceDirs(); } else if (tag.equals("BugInstance")) { getBugInstance(); } else if (tag.equals("file")) { getMavenFileInstance(); } else { skipTag(); } } } private String resolveClassName(String classname) { if (classname == null) { return null; } String filename = classname.replace('.', '/'); int pos = filename.indexOf('$'); if (pos != -1) { filename = filename.substring(0, pos); } return filename + ".java"; } private String getRelativeName(String name, File file) { if (file != null && file.exists()) { String absolute = file.getAbsolutePath(); String relative = resolveName(absolute); if (!relative.equals(absolute)) { return relative; } } return name; } private void getMavenFileInstance() throws IOException, XmlPullParserException { // FIXME: I have no definition - just an example file // with no source files String classname = checkNotBlank("classname"); String name = resolveClassName(classname); File file = absoluteFileFinder.getFileForName(name); name = getRelativeName(name, file); getParser().next(); while (true) { String tag = getSibTag(); if (tag == null) { break; } if (tag.equals("BugInstance")) { Violation v = new Violation(); v.setType("findbugs"); v.setLine(getInt("lineNumber")); v.setSource(checkNotBlank("type")); v.setSeverity(normalizeSeverity( checkNotBlank("priority"))); v.setSeverityLevel( getSeverityLevel(v.getSeverity())); v.setMessage(checkNotBlank("message")); getFileModel(name, file).addViolation(v); } skipTag(); } endElement(); } private void getSourceDirs() throws IOException, XmlPullParserException { expectStartTag("Project"); getParser().next(); while (skipToTag("SrcDir")) { getSourceDir(); } endElement(); } private void getSourceDir() throws IOException, XmlPullParserException { checkNextEvent(XmlPullParser.TEXT, "Expecting text"); absoluteFileFinder.addSourcePath(getParser().getText()); endElement(); } private void getBugInstances() throws IOException, XmlPullParserException { while (skipToTag("BugInstance")) { getBugInstance(); } } private String convertType(String x) { String y = FindBugsDescriptor.getMessageMap().get(x); return (y == null) ? x : y; } /** * Parse the BugInstance element. * -- 1.2.1 -- * This is something like: * BugInstance { * Class [classname] { SourceLine? (of the class) } ?, * Method { SourceLine? (of the method) } ?, * Field ?, * Type*, * LocalVariable* * SourceLine* (of the line in error) * } * This code will look classname and the SourceLine element. * The last source line element will be used for the line number. */ private String classname; private String path; private String lineNumber; private void getSourceLine() throws IOException, XmlPullParserException { if (StringUtil.isBlank(path)) { path = getParser().getAttributeValue("", "sourcepath"); } if (StringUtil.isBlank(classname)) { classname = getParser().getAttributeValue("", "classname"); } String l = getParser().getAttributeValue("", "start"); if (!StringUtil.isBlank(l)) { lineNumber = l; } } private void getSourceLines() throws IOException, XmlPullParserException { while (true) { String tag = getSibTag(); if (tag == null) { return; } if ("SourceLine".equals(tag)) { getSourceLine(); } skipTag(); } } private boolean sameClassname(String currentClassname) { if (currentClassname == null) { return true; } String thisClassname = getParser().getAttributeValue("", "classname"); if (StringUtil.isBlank(thisClassname)) { return true; } return thisClassname.equals(currentClassname); } private void getBugInstance() throws IOException, XmlPullParserException { String type = getParser().getAttributeValue("", "type"); String priority = getParser().getAttributeValue("", "priority"); String category = getParser().getAttributeValue("", "category"); getParser().next(); classname = null; path = null; lineNumber = null; while (true) { String tag = getSibTag(); if (tag == null) { break; } if (("Class".equals(tag) || "Method".equals(tag) || "Field".equals(tag)) && sameClassname(classname)) { if (StringUtil.isBlank(classname)) { classname = getParser().getAttributeValue("", "classname"); } getParser().next(); getSourceLines(); endElement(); continue; } else if ("SourceLine".equals(tag)) { getSourceLine(); } skipTag(); } String name = path == null ? resolveClassName(classname) : path; if (StringUtil.isBlank(name)) { LOG.info("Unable to decode BugInstance element"); endElement(); return; } File file = absoluteFileFinder.getFileForName(name); name = getRelativeName(name, file); Violation v = new Violation(); v.setType("findbugs"); v.setLine(lineNumber); v.setSource(type); v.setSeverity(normalizeSeverity(priority)); v.setSeverityLevel( getSeverityLevel(v.getSeverity())); v.setMessage(convertType(type)); getFileModel(name, file).addViolation(v); endElement(); } private static final HashMapWithDefault<String, String> SEVERITIES = new HashMapWithDefault<String, String>("High"); static { // From findbugs SEVERITIES.put("1", "High"); SEVERITIES.put("2", "Medium"); SEVERITIES.put("3", "Low"); // From maven SEVERITIES.put("High", "High"); SEVERITIES.put("Medium", "Medium"); SEVERITIES.put("Normal", "Medium"); SEVERITIES.put("Low", "Low"); } /** Convert a severity in the xml to a name */ private String normalizeSeverity(String name) { return SEVERITIES.get(name); } /** Convert a normalized severity to a severity level */ private int getSeverityLevel(String severity) { return Severity.getSeverityLevel( severity); } }