All Downloads are FREE. Search and download functionalities are using the official Maven repository.

se.bjurr.violations.lib.parsers.FindbugsParser Maven / Gradle / Ivy

package se.bjurr.violations.lib.parsers;

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.logging.Level.SEVERE;
import static se.bjurr.violations.lib.model.Violation.violationBuilder;
import static se.bjurr.violations.lib.reports.Parser.FINDBUGS;
import static se.bjurr.violations.lib.util.Utils.isNullOrEmpty;
import static se.bjurr.violations.lib.util.ViolationParserUtils.findIntegerAttribute;
import static se.bjurr.violations.lib.util.ViolationParserUtils.getAttribute;
import static se.bjurr.violations.lib.util.ViolationParserUtils.getIntegerAttribute;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import se.bjurr.violations.lib.ViolationsLogger;
import se.bjurr.violations.lib.model.SEVERITY;
import se.bjurr.violations.lib.model.Violation;
import se.bjurr.violations.lib.util.Utils;
import se.bjurr.violations.lib.util.ViolationParserUtils;

public class FindbugsParser implements ViolationsParser {
  /** Severity rank. */
  public static final String FINDBUGS_SPECIFIC_RANK = "RANK";

  private static String findbugsMessagesXml;
  private static String findSecurityBugsMessagesXml;

  public static void setFindbugsMessagesXml(final String findbugsMessagesXml) {
    FindbugsParser.findbugsMessagesXml = findbugsMessagesXml;
  }

  public static void setFindSecurityBugsMessagesXml(final String findSecurityBugsMessagesXml) {
    FindbugsParser.findSecurityBugsMessagesXml = findSecurityBugsMessagesXml;
  }

  private Map getMessagesPerType(
      final ViolationsLogger violationsLogger, final String messagesXml) throws Exception {
    final Map messagesPerType = new HashMap<>();
    try {

      try (InputStream input = new ByteArrayInputStream(messagesXml.getBytes(UTF_8))) {
        final XMLStreamReader xmlr = ViolationParserUtils.createXmlReader(input);
        String type = "";
        String shortDescription = "";
        String details = "";
        while (xmlr.hasNext()) {
          final int eventType = xmlr.next();
          if (eventType == XMLStreamConstants.START_ELEMENT) {
            if (xmlr.getLocalName().equalsIgnoreCase("BugPattern")) {
              type = getAttribute(xmlr, "type");
            }
            if (xmlr.getLocalName().equalsIgnoreCase("ShortDescription")) {
              shortDescription = xmlr.getElementText();
            }
            if (xmlr.getLocalName().equalsIgnoreCase("Details")) {
              details = xmlr.getElementText();
            }
          }
          if (eventType == XMLStreamConstants.END_ELEMENT) {
            if (xmlr.getLocalName().equalsIgnoreCase("BugPattern")) {
              messagesPerType.put(type, shortDescription + "\n\n" + details);
            }
          }
        }
      }
    } catch (final IOException e) {
      violationsLogger.log(SEVERE, e.getMessage(), e);
    }
    return messagesPerType;
  }

  private void parseBugInstance(
      final XMLStreamReader xmlr,
      final Set violations,
      final Map messagesPerType)
      throws XMLStreamException {
    final String type = getAttribute(xmlr, "type");
    final Integer rank = getIntegerAttribute(xmlr, "rank");
    String message = messagesPerType.get(type);
    if (message == null) {
      message = type;
    }
    final SEVERITY severity = this.toSeverity(rank);

    final List candidates = new ArrayList<>();

    while (xmlr.hasNext()) {
      final int eventType = xmlr.next();
      if (eventType == XMLStreamConstants.START_ELEMENT) {
        if (xmlr.getLocalName().equalsIgnoreCase("SourceLine")) {
          final Optional startLine = findIntegerAttribute(xmlr, "start");
          final Optional endLine = findIntegerAttribute(xmlr, "end");
          if (!startLine.isPresent() || !endLine.isPresent()) {
            continue;
          }
          final String filename = getAttribute(xmlr, "sourcepath");
          final String classname = getAttribute(xmlr, "classname");
          candidates.add( //
              violationBuilder() //
                  .setParser(FINDBUGS) //
                  .setMessage(message) //
                  .setFile(filename) //
                  .setStartLine(startLine.get()) //
                  .setEndLine(endLine.get()) //
                  .setRule(type) //
                  .setSeverity(severity) //
                  .setSource(classname) //
                  .setSpecific(FINDBUGS_SPECIFIC_RANK, rank) //
                  .build() //
              );
        }
      }
      if (eventType == XMLStreamConstants.END_ELEMENT) {
        if (xmlr.getLocalName().equalsIgnoreCase("BugInstance")) {
          // End of the bug instance.
          break;
        }
      }
    }

    if (!candidates.isEmpty()) {
      /**
       * Last one is the most specific, first 2 may be class and method when the third is source
       * line.
       */
      violations.add(candidates.get(candidates.size() - 1));
    }
  }

  @Override
  public Set parseReportOutput(
      final String string, final ViolationsLogger violationsLogger) throws Exception {
    final Set violations = new TreeSet<>();

    final Map messagesPerType =
        this.getMessagesPerType(
            violationsLogger, this.getMessagesXml(findbugsMessagesXml, "/findbugs/messages.xml"));
    messagesPerType.putAll(
        this.getMessagesPerType(
            violationsLogger,
            this.getMessagesXml(findSecurityBugsMessagesXml, "/findbugs/fsb-messages.xml")));

    try (InputStream input = new ByteArrayInputStream(string.getBytes(StandardCharsets.UTF_8))) {
      final XMLStreamReader xmlr = ViolationParserUtils.createXmlReader(input);
      while (xmlr.hasNext()) {
        final int eventType = xmlr.next();
        if (eventType == XMLStreamConstants.START_ELEMENT) {
          if (xmlr.getLocalName().equalsIgnoreCase("BugInstance")) {
            this.parseBugInstance(xmlr, violations, messagesPerType);
          }
        }
      }
    }
    return violations;
  }

  private String getMessagesXml(final String staticValue, final String messagesResourceFilename)
      throws IOException {
    String messagesXml;
    if (isNullOrEmpty(staticValue)) {
      final URL resource = FindbugsParser.class.getResource(messagesResourceFilename);
      if (resource == null) {
        throw new RuntimeException("Unable to find resource " + messagesResourceFilename);
      }
      messagesXml = Utils.toString(resource);
    } else {
      messagesXml = staticValue;
    }
    return messagesXml;
  }

  /**
   * Bugs are given a rank 1-20, and grouped into the categories scariest (rank 1-4), scary (rank
   * 5-9), troubling (rank 10-14), and of concern (rank 15-20).
   */
  private SEVERITY toSeverity(final Integer rank) {
    if (rank <= 9) {
      return SEVERITY.ERROR;
    }
    if (rank <= 14) {
      return SEVERITY.WARN;
    }
    return SEVERITY.INFO;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy