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

edu.hm.hafner.analysis.IssueBuilder Maven / Gradle / Ivy

Go to download

This library provides a Java object model to read, aggregate, filter, and query static analysis reports. It is used by Jenkins' warnings next generation plug-in to visualize the warnings of individual builds. Additionally, this library is used by a GitHub action to autograde student software projects based on a given set of metrics (unit tests, code and mutation coverage, static analysis warnings).

There is a newer version: 13.3.0
Show newest version
package edu.hm.hafner.analysis;

import java.io.Serializable;
import java.util.Optional;
import java.util.UUID;

import org.apache.commons.lang3.StringUtils;

import com.google.errorprone.annotations.CanIgnoreReturnValue;

import edu.hm.hafner.util.LineRange;
import edu.hm.hafner.util.LineRangeList;
import edu.hm.hafner.util.PathUtil;
import edu.hm.hafner.util.TreeString;
import edu.hm.hafner.util.TreeStringBuilder;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

import static edu.hm.hafner.analysis.util.IntegerParser.*;

/**
 * Creates new {@link Issue issues} using the builder pattern. All properties that have not been set in the builder will
 * be set to their default value.
 * 

Example:

*
 * Issue issue = new IssueBuilder()
 *                      .setFileName("affected.file")
 *                      .setLineStart(0)
 *                      .setCategory("JavaDoc")
 *                      .setMessage("Missing JavaDoc")
 *                      .setSeverity(Severity.WARNING_LOW);
 * 
* * @author Ullrich Hafner */ @SuppressWarnings({"InstanceVariableMayNotBeInitialized", "JavaDocMethod", "PMD.TooManyFields", "PMD.GodClass"}) public class IssueBuilder implements AutoCloseable { private static final String EMPTY = StringUtils.EMPTY; private static final String UNDEFINED = "-"; private static final TreeString UNDEFINED_TREE_STRING = TreeString.valueOf(UNDEFINED); private static final TreeString EMPTY_TREE_STRING = TreeString.valueOf(StringUtils.EMPTY); private static final PathUtil PATH_UTIL = new PathUtil(); private final TreeStringBuilder fileNameBuilder = new TreeStringBuilder(); private final TreeStringBuilder packageNameBuilder = new TreeStringBuilder(); private final TreeStringBuilder messageBuilder = new TreeStringBuilder(); private int lineStart = 0; private int lineEnd = 0; private int columnStart = 0; private int columnEnd = 0; @CheckForNull private LineRangeList lineRanges; @CheckForNull private String pathName; private TreeString fileName = UNDEFINED_TREE_STRING; private TreeString packageName = UNDEFINED_TREE_STRING; @CheckForNull private String directory; @CheckForNull private String category; @CheckForNull private String type; @CheckForNull private Severity severity; private TreeString message = EMPTY_TREE_STRING; private String description = EMPTY; @CheckForNull private String moduleName; @CheckForNull private String origin; @CheckForNull private String originName; @CheckForNull private String reference; @CheckForNull private String fingerprint; @CheckForNull private Serializable additionalProperties; private UUID id = UUID.randomUUID(); /** * Sets the unique ID of the issue. If not set, then an ID will be generated. * * @param id * the ID * * @return this */ @CanIgnoreReturnValue public IssueBuilder setId(final UUID id) { this.id = id; return this; } /** * Sets additional properties from the statical analysis tool. This object could be used to store tool-specific * information. * * @param additionalProperties * the instance * * @return this */ @CanIgnoreReturnValue public IssueBuilder setAdditionalProperties(@CheckForNull final Serializable additionalProperties) { this.additionalProperties = additionalProperties; return this; } /** * Sets the fingerprint for this issue. Used to decide if two issues are equal even if the equals method returns * {@code false} since some properties differ due to code refactorings. The fingerprint is created by * analyzing the content of the affected file. * * @param fingerprint * the fingerprint to set * * @return this */ @CanIgnoreReturnValue public IssueBuilder setFingerprint(@CheckForNull final String fingerprint) { this.fingerprint = fingerprint; return this; } /** * Sets the name of the affected file. This file name is a path relative to the path of the affected files (see * {@link #setPathName(String)}). * * @param fileName * the file name * * @return this */ @CanIgnoreReturnValue public IssueBuilder setFileName(@CheckForNull final String fileName) { this.fileName = internFileName(fileName); return this; } TreeString internFileName(@CheckForNull final String unsafeFileName) { if (unsafeFileName == null || StringUtils.isEmpty(unsafeFileName)) { return UNDEFINED_TREE_STRING; } else { if (directory != null && PATH_UTIL.isAbsolute(normalizeFileName(unsafeFileName))) { return fileNameBuilder.intern(normalizeFileName(unsafeFileName)); } return fileNameBuilder.intern(normalizeFileName( PATH_UTIL.createAbsolutePath(directory, unsafeFileName))); } } /** * Sets the current work directory. This directory is used as a prefix for all subsequent issue file names. If the * path is set as well, then the final path of an issue is the concatenation of {@code path}, {@code directory}, and * {@code fileName}. Note that this directory is not visible later on, the issue does only store the path in the * {@code path} and {@code fileName} properties. I.e., the created issue will get a new file name that is composed * of {@code directory} and {@code fileName}. * * @param directory * the directory that contains all affected files * * @return this */ @CanIgnoreReturnValue public IssueBuilder setDirectory(@CheckForNull final String directory) { this.directory = directory; return this; } /** * Sets the path of the affected file. Note that this path is not the parent folder of the affected file. This path * is the folder that contains all the affected files of a {@link Report}. The path of an affected file is stored * in the {@code path} and {@code fileName} properties so that issues can be tracked even if the root folder changes * (due to different build environments). * * @param pathName * the path that contains all affected files * * @return this */ @CanIgnoreReturnValue public IssueBuilder setPathName(@CheckForNull final String pathName) { this.pathName = pathName; return this; } /** * Sets the first line of this issue (lines start at 1; 0 indicates the whole file). * * @param lineStart * the first line * * @return this */ @CanIgnoreReturnValue public IssueBuilder setLineStart(final int lineStart) { this.lineStart = lineStart; return this; } /** * Sets the first line of this issue (lines start at 1; 0 indicates the whole file). * * @param lineStart * the first line * * @return this */ @CanIgnoreReturnValue public IssueBuilder setLineStart(@CheckForNull final String lineStart) { this.lineStart = parseInt(lineStart); return this; } /** * Sets the last line of this issue (lines start at 1). * * @param lineEnd * the last line * * @return this */ @CanIgnoreReturnValue public IssueBuilder setLineEnd(final int lineEnd) { this.lineEnd = lineEnd; return this; } /** * Sets the last line of this issue (lines start at 1). * * @param lineEnd * the last line * * @return this */ @CanIgnoreReturnValue public IssueBuilder setLineEnd(@CheckForNull final String lineEnd) { this.lineEnd = parseInt(lineEnd); return this; } /** * Sets the first column of this issue (columns start at 1, 0 indicates the whole line). * * @param columnStart * the first column * * @return this */ @CanIgnoreReturnValue public IssueBuilder setColumnStart(final int columnStart) { this.columnStart = columnStart; return this; } /** * Sets the first column of this issue (columns start at 1, 0 indicates the whole line). * * @param columnStart * the first column * * @return this */ @CanIgnoreReturnValue public IssueBuilder setColumnStart(@CheckForNull final String columnStart) { this.columnStart = parseInt(columnStart); return this; } /** * Sets the last column of this issue (columns start at 1). * * @param columnEnd * the last column * * @return this */ @CanIgnoreReturnValue public IssueBuilder setColumnEnd(final int columnEnd) { this.columnEnd = columnEnd; return this; } /** * Sets the last column of this issue (columns start at 1). * * @param columnEnd * the last column * * @return this */ @CanIgnoreReturnValue public IssueBuilder setColumnEnd(@CheckForNull final String columnEnd) { this.columnEnd = parseInt(columnEnd); return this; } /** * Sets the category of this issue (depends on the available categories of the static analysis tool). Examples for * categories are "Deprecation", "Design", or "JavaDoc". * * @param category * the category * * @return this */ @CanIgnoreReturnValue public IssueBuilder setCategory(@CheckForNull final String category) { this.category = category; return this; } /** * Sets the type of this issue (depends on the available types of the static analysis tool). The type typically is * the associated rule of the static analysis tool that reported this issue. * * @param type * the type * * @return this */ @CanIgnoreReturnValue public IssueBuilder setType(@CheckForNull final String type) { this.type = type; return this; } /** * Sets the name of the package or name space (or similar concept) that contains this issue. * * @param packageName * the package or namespace name * * @return this */ @CanIgnoreReturnValue public IssueBuilder setPackageName(@CheckForNull final String packageName) { this.packageName = internPackageName(packageName); return this; } TreeString internPackageName(@CheckForNull final String unsafePackageName) { if (unsafePackageName == null || StringUtils.isBlank(unsafePackageName)) { return UNDEFINED_TREE_STRING; } else { return packageNameBuilder.intern(unsafePackageName); } } /** * Sets the name of the module or project (or similar concept) that contains this issue. * * @param moduleName * the module name * * @return this */ @CanIgnoreReturnValue public IssueBuilder setModuleName(@CheckForNull final String moduleName) { this.moduleName = moduleName; return this; } /** * Sets the ID of the tool that did report this issue. * * @param origin * the ID of the originating tool * * @return this */ @CanIgnoreReturnValue public IssueBuilder setOrigin(@CheckForNull final String origin) { this.origin = origin; return this; } /** * Sets the name of the tool that did report this issue. * * @param originName * the ID of the originating tool * * @return this */ @CanIgnoreReturnValue public IssueBuilder setOriginName(@CheckForNull final String originName) { this.originName = originName; return this; } /** * Sets a reference to the execution of the static analysis tool (build ID, timestamp, etc.). * * @param reference * the reference * * @return this */ @CanIgnoreReturnValue public IssueBuilder setReference(@CheckForNull final String reference) { this.reference = reference; return this; } /** * Sets the severity of this issue. * * @param severity * the severity * * @return this */ @CanIgnoreReturnValue public IssueBuilder setSeverity(@CheckForNull final Severity severity) { this.severity = severity; return this; } /** * Guesses a severity for the issues: converts a String severity to one of the predefined severities. If the * provided String does not match (even partly) then the default severity will be returned. * * @param severityString * the severity given as a string representation * * @return this */ @CanIgnoreReturnValue public IssueBuilder guessSeverity(@CheckForNull final String severityString) { severity = Severity.guessFromString(severityString); return this; } /** * Sets the detailed message for this issue. * * @param message * the message * * @return this */ @CanIgnoreReturnValue public IssueBuilder setMessage(@CheckForNull final String message) { if (StringUtils.isBlank(message)) { this.message = EMPTY_TREE_STRING; } else { this.message = messageBuilder.intern(StringUtils.stripToEmpty(message)); } return this; } /** * Sets an additional description for this issue. Static analysis tools might provide some additional information * about this issue. This description may contain valid HTML. * * @param description * the description (as HTML content) * * @return this */ @CanIgnoreReturnValue public IssueBuilder setDescription(@CheckForNull final String description) { this.description = StringUtils.stripToEmpty(description); return this; } /** * Sets additional line ranges for this issue. Not that the primary range given by {@code lineStart} and {@code * * lineEnd} is not included. * * @param lineRanges * the additional line ranges * * @return this */ @CanIgnoreReturnValue public IssueBuilder setLineRanges(final LineRangeList lineRanges) { this.lineRanges = new LineRangeList(lineRanges); return this; } /** * Initializes this builder with an exact copy of all properties of the specified issue. * * @param copy * the issue to copy the properties from * * @return the initialized builder */ public IssueBuilder copy(final Issue copy) { fileName = copy.getFileNameTreeString(); lineStart = copy.getLineStart(); lineEnd = copy.getLineEnd(); columnStart = copy.getColumnStart(); columnEnd = copy.getColumnEnd(); lineRanges = new LineRangeList(); lineRanges.addAll(copy.getLineRanges()); category = copy.getCategory(); type = copy.getType(); severity = copy.getSeverity(); message = copy.getMessageTreeString(); description = copy.getDescription(); packageName = copy.getPackageNameTreeString(); moduleName = copy.getModuleName(); origin = copy.getOrigin(); originName = copy.getOriginName(); reference = copy.getReference(); fingerprint = copy.getFingerprint(); additionalProperties = copy.getAdditionalProperties(); return this; } /** * Creates a new {@link Issue} based on the specified properties. After building the issue, this * {@link IssueBuilder} creates a new ID, but all other {@link Issue} properties will remain unchanged. * * @return the created issue */ public Issue build() { Issue issue = buildWithConstructor(); id = UUID.randomUUID(); // make sure that multiple invocations will create different IDs return issue; } /** * Creates a new {@link Issue} based on the specified properties. After building the issue, this * {@link IssueBuilder} will be reset to its defaults. * * @return the created issue */ public Issue buildAndClean() { Issue issue = buildWithConstructor(); clean(); return issue; } private Issue buildWithConstructor() { cleanupLineRanges(); return new Issue(pathName, fileName, lineStart, lineEnd, columnStart, columnEnd, lineRanges, category, type, packageName, moduleName, severity, message, description, origin, originName, reference, fingerprint, additionalProperties, id); } /** * Sets lineStart and lineEnd if they are not set, and then removes the first element of * lineRanges if its start and end are the same as lineStart and lineEnd. */ @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH", justification = "False positive, `lineRanges != null` avoids a NullPointerException") private void cleanupLineRanges() { if (lineRanges != null && !lineRanges.isEmpty()) { LineRange firstRange = lineRanges.get(0); if (lineStart == 0) { this.lineStart = firstRange.getStart(); this.lineEnd = firstRange.getEnd(); } if (firstRange.getStart() == lineStart && firstRange.getEnd() == lineEnd) { lineRanges.remove(0); } } } /** * Creates a new {@link Issue} based on the specified properties. The returned issue is wrapped in an {@link * Optional}. After building the issue, this {@link IssueBuilder} will be reset to its defaults. * * @return the created issue * @see #buildAndClean() */ public Optional buildOptional() { return Optional.of(buildAndClean()); } @SuppressWarnings("PMD.NullAssignment") private void clean() { id = UUID.randomUUID(); // make sure that multiple invocations will create different IDs lineStart = 0; lineEnd = 0; columnStart = 0; columnEnd = 0; lineRanges = new LineRangeList(); fileName = UNDEFINED_TREE_STRING; packageName = UNDEFINED_TREE_STRING; category = null; type = null; severity = null; message = EMPTY_TREE_STRING; description = EMPTY; moduleName = null; additionalProperties = null; } private static String normalizeFileName(@CheckForNull final String platformFileName) { return defaultString(StringUtils.replace( StringUtils.strip(platformFileName), "\\", "/")); } /** * Creates a default String representation for undefined input parameters. * * @param string * the string to check * * @return the valid string or a default string if the specified string is not valid */ private static String defaultString(@CheckForNull final String string) { return StringUtils.defaultIfEmpty(string, UNDEFINED).intern(); } /** * Reduce the memory print of internal string instances. */ @Override public void close() { fileNameBuilder.dedup(); packageNameBuilder.dedup(); messageBuilder.dedup(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy