
edu.hm.hafner.analysis.Issue Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of analysis-model Show documentation
Show all versions of analysis-model Show documentation
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).
package edu.hm.hafner.analysis;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Predicate;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import edu.hm.hafner.util.Ensure;
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.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/**
* An issue reported by a static analysis tool. Use the provided {@link IssueBuilder builder} to create new instances.
*
* @author Ullrich Hafner
*/
@SuppressWarnings({"PMD.TooManyFields", "PMD.GodClass", "PMD.CyclomaticComplexity", "NoFunctionalReturnType"})
public class Issue implements Serializable {
private static final long serialVersionUID = 1L; // release 1.0.0
private static final PathUtil PATH_UTIL = new PathUtil();
static final String UNDEFINED = "-";
/**
* Returns the value of the property with the specified name for a given issue instance.
*
* @param issue
* the issue to get the property for
* @param propertyName
* the name of the property
*
* @return the function that obtains the value
*/
public static String getPropertyValueAsString(final Issue issue, final String propertyName) {
try {
return PropertyUtils.getProperty(issue, propertyName).toString();
}
catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException ignored) {
return propertyName;
}
}
/**
* Returns a function that can dynamically obtain the value of the property with the specified name of an issue
* instance.
*
* @param propertyName
* the name of the property
*
* @return the function that obtains the value
*/
public static Function getPropertyValueGetter(final String propertyName) {
return issue -> Issue.getPropertyValueAsString(issue, propertyName);
}
/**
* Returns a predicate that checks if the package name of an issue is equal to the specified package name.
*
* @param packageName
* the package name to match
*
* @return the predicate
*/
public static Predicate byPackageName(final String packageName) {
return issue -> issue.getPackageName().equals(packageName);
}
/**
* Returns a predicate that checks if the module name of an issue is equal to the specified module name.
*
* @param moduleName
* the module name to match
*
* @return the predicate
*/
public static Predicate byModuleName(final String moduleName) {
return issue -> issue.getModuleName().equals(moduleName);
}
/**
* Returns a predicate that checks if the file name of an issue is equal to the specified file name.
*
* @param fileName
* the file name to match
*
* @return the predicate
*/
public static Predicate byFileName(final String fileName) {
return issue -> issue.getFileName().equals(fileName);
}
/**
* Returns a predicate that checks if the folder of an issue is equal to the specified folder.
*
* @param folder
* the folder to match
*
* @return the predicate
*/
public static Predicate byFolder(final String folder) {
return issue -> issue.getFolder().equals(folder);
}
/**
* Returns a predicate that checks if the severity of an issue is equal to the specified severity.
*
* @param severity
* the severity to match
*
* @return the predicate
*/
public static Predicate bySeverity(final Severity severity) {
return issue -> issue.getSeverity().equals(severity);
}
/**
* Returns a predicate that checks if the category of an issue is equal to the specified category.
*
* @param category
* the category to match
*
* @return the predicate
*/
public static Predicate byCategory(final String category) {
return issue -> issue.getCategory().equals(category);
}
/**
* Returns a predicate that checks if the origin of an issue is equal to the specified origin.
*
* @param origin
* the origin to match
*
* @return the predicate
*/
public static Predicate byOrigin(final String origin) {
return issue -> issue.getOrigin().equals(origin);
}
/**
* Returns a predicate that checks if the type of an issue is equal to the specified type.
*
* @param type
* the type to match
*
* @return the predicate
*/
public static Predicate byType(final String type) {
return issue -> issue.getType().equals(type);
}
private String category; // almost final
private String type; // almost final
private final Severity severity;
private final int lineStart; // fixed
private final int lineEnd; // fixed
private final int columnStart; // fixed
private final int columnEnd; // fixed
private final LineRangeList lineRanges; // fixed
private final UUID id; // fixed
@CheckForNull
private final Serializable additionalProperties; // fixed
private String reference; // mutable, not part of equals
private String origin; // mutable
private String originName; // mutable
private String moduleName; // mutable
private TreeString packageName; // mutable
private String pathName; // mutable, not part of equals, @since 8.0.0
private TreeString fileName; // mutable
private final TreeString message; // fixed
private String description; // fixed
private String fingerprint; // mutable, not part of equals
private boolean partOfModifiedCode; // mutable, not part of equals
/**
* Creates a new instance of {@link Issue} using the properties of the other issue instance. The new issue has the
* same ID as the copy.
*
* @param copy
* the other issue to copy the properties from
*/
@SuppressWarnings("CopyConstructorMissesField")
Issue(final Issue copy) {
this(copy.getPath(), copy.getFileNameTreeString(), copy.getLineStart(), copy.getLineEnd(),
copy.getColumnStart(),
copy.getColumnEnd(), copy.getLineRanges(), copy.getCategory(), copy.getType(),
copy.getPackageNameTreeString(), copy.getModuleName(), copy.getSeverity(), copy.getMessageTreeString(),
copy.getDescription(), copy.getOrigin(), copy.getOriginName(), copy.getReference(), copy.getFingerprint(),
copy.getAdditionalProperties(), copy.getId());
}
/**
* Creates a new instance of {@link Issue} using the specified properties. The new issue will get a new generated
* ID.
*
* @param pathName
* the path that contains the affected file
* @param fileName
* the name of the file that contains this issue
* @param lineStart
* the first line of this issue (lines start at 1; 0 indicates the whole file)
* @param lineEnd
* the last line of this issue (lines start at 1)
* @param columnStart
* the first column of this issue (columns start at 1, 0 indicates the whole line)
* @param columnEnd
* the last column of this issue (columns start at 1)
* @param lineRanges
* additional line ranges of this issue
* @param category
* the category of this issue (depends on the available categories of the static analysis tool)
* @param type
* the type of this issue (depends on the available types of the static analysis tool)
* @param packageName
* the name of the package (or name space) that contains this issue
* @param moduleName
* the name of the moduleName (or project) that contains this issue
* @param severity
* the severity of this issue
* @param message
* the detail message of this issue
* @param description
* the description for this issue
* @param origin
* the ID of the tool that did report this issue
* @param originName
* the name of the tool that did report this issue
* @param reference
* an arbitrary reference to the execution of the static analysis tool (build ID, timestamp, etc.)
* @param fingerprint
* the fingerprint for this issue
* @param additionalProperties
* additional properties from the statical analysis tool
*/
@SuppressWarnings("ParameterNumber")
Issue(final String pathName, final TreeString fileName,
final int lineStart, final int lineEnd, final int columnStart, final int columnEnd,
@CheckForNull final Iterable extends LineRange> lineRanges,
@CheckForNull final String category, @CheckForNull final String type,
final TreeString packageName, @CheckForNull final String moduleName,
@CheckForNull final Severity severity,
final TreeString message, final String description,
@CheckForNull final String origin, @CheckForNull final String originName, @CheckForNull
final String reference, @CheckForNull final String fingerprint,
@CheckForNull final Serializable additionalProperties) {
this(pathName, fileName, lineStart, lineEnd, columnStart, columnEnd, lineRanges, category, type,
packageName, moduleName, severity, message, description, origin, originName, reference,
fingerprint, additionalProperties, UUID.randomUUID());
}
/**
* Creates a new instance of {@link Issue} using the specified properties.
*
* @param pathName
* the path that contains the affected file
* @param fileName
* the name of the file that contains this issue
* @param lineStart
* the first line of this issue (lines start at 1; 0 indicates the whole file)
* @param lineEnd
* the last line of this issue (lines start at 1)
* @param columnStart
* the first column of this issue (columns start at 1, 0 indicates the whole line)
* @param columnEnd
* the last column of this issue (columns start at 1)
* @param lineRanges
* additional line ranges of this issue
* @param category
* the category of this issue (depends on the available categories of the static analysis tool)
* @param type
* the type of this issue (depends on the available types of the static analysis tool)
* @param packageName
* the name of the package (or name space) that contains this issue
* @param moduleName
* the name of the moduleName (or project) that contains this issue
* @param severity
* the severity of this issue
* @param message
* the detail message of this issue
* @param description
* the description for this issue
* @param origin
* the ID of the tool that did report this issue
* @param originName
* the name of the tool that did report this issue
* @param reference
* an arbitrary reference to the execution of the static analysis tool (build ID, timestamp, etc.)
* @param fingerprint
* the fingerprint for this issue
* @param additionalProperties
* additional properties from the statical analysis tool
* @param id
* the ID of this issue
*/
@SuppressWarnings("ParameterNumber")
Issue(@CheckForNull final String pathName, final TreeString fileName, final int lineStart, final int lineEnd,
final int columnStart,
final int columnEnd, @CheckForNull final Iterable extends LineRange> lineRanges,
@CheckForNull final String category,
@CheckForNull final String type, final TreeString packageName,
@CheckForNull final String moduleName, @CheckForNull final Severity severity,
final TreeString message, final String description,
@CheckForNull final String origin, @CheckForNull final String originName,
@CheckForNull final String reference, @CheckForNull final String fingerprint,
@CheckForNull final Serializable additionalProperties,
final UUID id) {
this.pathName = normalizeFileName(pathName);
this.fileName = fileName;
int providedLineStart = defaultInteger(lineStart);
int providedLineEnd = defaultInteger(lineEnd) == 0 ? providedLineStart : defaultInteger(lineEnd);
if (providedLineStart == 0) {
this.lineStart = providedLineEnd;
this.lineEnd = providedLineEnd;
}
else {
this.lineStart = Math.min(providedLineStart, providedLineEnd);
this.lineEnd = Math.max(providedLineStart, providedLineEnd);
}
int providedColumnStart = defaultInteger(columnStart);
int providedColumnEnd = defaultInteger(columnEnd) == 0 ? providedColumnStart : defaultInteger(columnEnd);
if (providedColumnStart == 0) {
this.columnStart = providedColumnEnd;
this.columnEnd = providedColumnEnd;
}
else {
this.columnStart = Math.min(providedColumnStart, providedColumnEnd);
this.columnEnd = Math.max(providedColumnStart, providedColumnEnd);
}
this.lineRanges = new LineRangeList();
if (lineRanges != null) {
this.lineRanges.addAll(lineRanges);
}
this.category = StringUtils.defaultString(category).intern();
this.type = defaultString(type);
this.packageName = packageName;
this.moduleName = defaultString(moduleName);
this.severity = severity == null ? Severity.WARNING_NORMAL : severity;
this.message = message;
this.description = description.intern();
this.origin = stripToEmpty(origin);
this.originName = stripToEmpty(originName);
this.reference = stripToEmpty(reference);
this.fingerprint = defaultString(fingerprint);
this.additionalProperties = additionalProperties;
this.id = id;
}
/**
* Called after de-serialization to improve the memory usage.
*
* @return this
*/
@SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", justification = "Deserialization of instances that do not have all fields yet")
protected Object readResolve() {
category = category.intern();
type = type.intern();
moduleName = moduleName.intern();
origin = origin.intern();
reference = reference.intern();
if (pathName == null) { // new in version 8.0.0
pathName = UNDEFINED;
}
else {
pathName = pathName.intern();
}
if (description == null) { // String in version 8.0.0
description = UNDEFINED;
}
else {
description = description.intern();
}
if (originName == null) { // new in version 10.0.0
originName = StringUtils.EMPTY;
}
else {
originName = originName.intern();
}
return this;
}
private String normalizeFileName(@CheckForNull final String platformFileName) {
if (platformFileName == null || UNDEFINED.equals(platformFileName) || StringUtils.isBlank(platformFileName)) {
return UNDEFINED;
}
return PATH_UTIL.getAbsolutePath(platformFileName);
}
/**
* Creates a default Integer representation for undefined input parameters.
*
* @param integer
* the integer to check
*
* @return the valid integer value or 0 if the specified {@link Integer} is {@code null} or less than 0
*/
private int defaultInteger(final int integer) {
return Math.max(integer, 0);
}
/**
* 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 String defaultString(@CheckForNull final String string) {
return StringUtils.defaultIfEmpty(string, UNDEFINED).intern();
}
/**
* Strips whitespace from the start and end of a String returning an empty String if {@code null} input.
*
* @param string
* the string to check
*
* @return the stripped string or the empty string if the specified string is {@code null}
*/
private String stripToEmpty(@CheckForNull final String string) {
return StringUtils.stripToEmpty(string).intern();
}
/**
* Returns the unique ID of this issue.
*
* @return the unique ID
*/
public UUID getId() {
return id;
}
/**
* Returns the name of the affected file. This file name is a path relative to the path of the affected files
* (returned by {@link #getPath()}).
*
* @return the name of the file that contains this issue
* @see #getPath()
*/
public String getFileName() {
return fileName.toString();
}
/**
* Returns the tree-string containing the name of the affected file. This file name is a path relative to the path
* of the affected files (returned by {@link #getPath()}).
*
* @return the cached tree-string containing the name of the file that contains this issue
*/
TreeString getFileNameTreeString() {
return fileName;
}
/**
* Returns the folder that contains the affected file of this issue. Note that this path is not an absolute path, it
* is relative to the path of the affected files (returned by {@link #getPath()}).
*
* @return the folder of the file that contains this issue
*/
public String getFolder() {
try {
String folder = FilenameUtils.getPath(getFileName());
if (StringUtils.isBlank(folder)) {
return UNDEFINED;
}
return PATH_UTIL.getRelativePath(folder);
}
catch (IllegalArgumentException ignore) {
return UNDEFINED; // fallback
}
}
/**
* Returns the base name of the file that contains this issue (i.e. the file name without the full path).
*
* @return the base name of the file that contains this issue
*/
public String getBaseName() {
try {
return FilenameUtils.getName(getFileName());
}
catch (IllegalArgumentException ignore) {
return getFileName(); // fallback
}
}
/**
* Returns the absolute path of the affected file.
*
* @return the base name of the file that contains this issue
*/
public String getAbsolutePath() {
if (UNDEFINED.equals(pathName)) {
return getFileName();
}
else {
return PATH_UTIL.createAbsolutePath(pathName, getFileName());
}
}
/**
* Returns 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 of the affected files of a {@link Report}. If this path is not defined, then
* the default value {@link #UNDEFINED} is returned.
*
* @return the base name of the file that contains this issue
*/
public String getPath() {
return pathName;
}
/**
* Sets the name of the file that contains this issue.
*
* @param pathName
* the path that contains the affected file
* @param fileName
* the file name to set
*/
@SuppressWarnings("checkstyle:HiddenField")
@SuppressFBWarnings("NM")
void setFileName(final String pathName, final TreeString fileName) {
this.pathName = normalizeFileName(pathName);
this.fileName = fileName;
}
/**
* Returns whether this issue has a file name set.
*
* @return {@code true} if this issue has a file name set
* @see #getFileName()
*/
public boolean hasFileName() {
return !UNDEFINED.equals(getFileName());
}
/**
* Returns the category of this issue (depends on the available categories of the static analysis tool). Examples
* for categories are "Deprecation", "Design", or "JavaDoc".
*
* @return the category
*/
public String getCategory() {
return category;
}
/**
* Returns 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.
*
* @return the type
*/
public String getType() {
return type;
}
/**
* Returns the severity of this issue.
*
* @return the severity
*/
public Severity getSeverity() {
return severity;
}
/**
* Returns the detailed message for this issue.
*
* @return the message
*/
public String getMessage() {
return message.toString();
}
/**
* Returns the tree-string containing the detailed message for this issue.
*
* @return the message
*/
TreeString getMessageTreeString() {
return message;
}
/**
* Returns an additional description for this issue. Static analysis tools might provide some additional information
* about this issue. This description may contain valid HTML.
*
* @return the description
*/
public String getDescription() {
return description;
}
/**
* Returns the first line of this issue (lines start at 1; 0 indicates the whole file).
*
* @return the first line
*/
public int getLineStart() {
return lineStart;
}
/**
* Returns the last line of this issue (lines start at 1).
*
* @return the last line
*/
public int getLineEnd() {
return lineEnd;
}
/**
* Returns additional line ranges for this issue. Not that the primary range given by {@code lineStart} and {@code
* lineEnd} is not included.
*
* @return the last line
*/
// TODO: actually we need a list of locations since a warning may involve several files
public Iterable extends LineRange> getLineRanges() {
return new LineRangeList(lineRanges);
}
/**
* Returns whether this issue line ranges contain the specified line. If this issue has no lines defined, then this
* method will return {@code true}.
*
* @param line
* the line to check
*
* @return {@code true} if the specified line is within the line ranges of this issue, {@code false} otherwise
*/
public boolean affectsLine(final int line) {
if (lineStart == 0 || line == 0) {
return true; // the whole file is marked, so every line is affected
}
if (lineStart <= line && line <= lineEnd) {
return true; // the line is within the primary range of this issue
}
for (LineRange lineRange : lineRanges) {
if (lineRange.contains(line)) {
return true; // the line is within an additional line range of this issue
}
}
return false;
}
/**
* Returns the first column of this issue (columns start at 1, 0 indicates the whole line).
*
* @return the first column
*/
public int getColumnStart() {
return columnStart;
}
/**
* Returns the last column of this issue (columns start at 1).
*
* @return the last column
*/
public int getColumnEnd() {
return columnEnd;
}
/**
* Returns the name of the package or name space (or similar concept) that contains this issue.
*
* @return the package name
*/
public String getPackageName() {
return packageName.toString();
}
/**
* Returns the tree-string containing the name of the package or name space (or similar concept) that contains this
* issue.
*
* @return the package name
*/
TreeString getPackageNameTreeString() {
return packageName;
}
/**
* Sets the name of the package or name space (or similar concept) that contains this issue.
*
* @param packageName
* the name of the package
*/
void setPackageName(final TreeString packageName) {
this.packageName = packageName;
}
/**
* Returns whether this issue has a package name set.
*
* @return {@code true} if this issue has a package name set
* @see #getPackageName()
*/
public boolean hasPackageName() {
return !UNDEFINED.equals(getPackageName());
}
/**
* Returns the name of the module or project (or similar concept) that contains this issue.
*
* @return the module
*/
public String getModuleName() {
return moduleName;
}
/**
* Sets the name of the module or project (or similar concept) that contains this issue.
*
* @param moduleName
* the module name to set
*/
void setModuleName(@CheckForNull final String moduleName) {
this.moduleName = stripToEmpty(moduleName);
}
/**
* Returns whether this issue has a module name set.
*
* @return {@code true} if this issue has a module name set
* @see #getModuleName()
*/
public boolean hasModuleName() {
return !UNDEFINED.equals(getModuleName());
}
/**
* Returns the ID of the tool that did report this issue.
*
* @return the ID of the origin
*/
public String getOrigin() {
return origin;
}
boolean hasOrigin() {
return StringUtils.isNoneBlank(origin);
}
/**
* Returns the name of the tool that did report this issue.
*
* @return the name of the origin
*/
public String getOriginName() {
return originName;
}
/**
* Sets the ID of the tool that did report this issue.
*
* @param origin
* the origin
*/
void setOrigin(final String origin) {
Ensure.that(origin).isNotBlank("Issue origin ID '%s' must be not blank (%s)", id, toString());
this.origin = origin.intern();
}
/**
* Sets the ID and the name of the tool that did report this issue.
*
* @param originId
* the ID of the origin
* @param name
* the name of the origin
*/
void setOrigin(final String originId, final String name) {
setOrigin(originId);
Ensure.that(name).isNotBlank("Issue origin name '%s' must be not blank (%s)", name, toString());
this.originName = name.intern();
}
/**
* Returns a reference to the execution of the static analysis tool (build ID, timestamp, etc.).
*
* @return the reference
*/
public String getReference() {
return reference;
}
/**
* Sets a reference to the execution of the static analysis tool (build ID, timestamp, etc.). This property should
* not be set by parsers as it is overwritten by the {@link IssueDifference differencing engine} while computing new
* and fixed issues.
*
* @param reference
* the reference
*/
void setReference(@CheckForNull final String reference) {
this.reference = stripToEmpty(reference);
}
/**
* Returns 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.
*
* Note: the fingerprint is not part of the equals method since the fingerprint might change due to an unrelated
* refactoring of the source code.
*
*
* @return the fingerprint of this issue
*/
public String getFingerprint() {
return fingerprint;
}
/**
* Sets the fingerprint for this issue to the given value.
*
* @param fingerprint
* the fingerprint to set
*
* @see #getFingerprint()
*/
void setFingerprint(@CheckForNull final String fingerprint) {
this.fingerprint = StringUtils.stripToEmpty(fingerprint);
}
/**
* Returns whether this issue already has a fingerprint set.
*
* @return {@code true} if this issue already has a fingerprint set
*/
public boolean hasFingerprint() {
return !UNDEFINED.equals(fingerprint);
}
/**
* Returns whether this issue affects a code line that has been modified recently.
*
* @return {@code true} if this issue affects a code line that has been modified recently.
* @see IssuesInModifiedCodeMarker
*/
public boolean isPartOfModifiedCode() {
return partOfModifiedCode;
}
/**
* Marks the issue as part of a source control diff.
*/
void markAsPartOfModifiedCode() {
partOfModifiedCode = true;
}
/**
* Returns additional properties for this issue. A static analysis tool may store additional properties in this
* untyped object. This object will be serialized and is used in {@code equals} and {@code hashCode}.
*
* @return the additional properties
*/
@CheckForNull
public Serializable getAdditionalProperties() {
return additionalProperties;
}
@SuppressWarnings("all")
@Override
public boolean equals(@CheckForNull final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Issue issue = (Issue) o;
if (lineStart != issue.lineStart) {
return false;
}
if (lineEnd != issue.lineEnd) {
return false;
}
if (columnStart != issue.columnStart) {
return false;
}
if (columnEnd != issue.columnEnd) {
return false;
}
if (!category.equals(issue.category)) {
return false;
}
if (!type.equals(issue.type)) {
return false;
}
if (!severity.equals(issue.severity)) {
return false;
}
if (!message.equals(issue.message)) {
return false;
}
if (!lineRanges.equals(issue.lineRanges)) {
return false;
}
if (!description.equals(issue.description)) {
return false;
}
if (additionalProperties != null ? !additionalProperties.equals(issue.additionalProperties) :
issue.additionalProperties != null) {
return false;
}
if (!origin.equals(issue.origin)) {
return false;
}
if (!originName.equals(issue.originName)) {
return false;
}
if (!moduleName.equals(issue.moduleName)) {
return false;
}
if (!packageName.equals(issue.packageName)) {
return false;
}
return fileName.equals(issue.fileName);
}
@Override
public int hashCode() {
int result = category.hashCode();
result = 31 * result + type.hashCode();
result = 31 * result + severity.hashCode();
result = 31 * result + message.hashCode();
result = 31 * result + lineStart;
result = 31 * result + lineEnd;
result = 31 * result + columnStart;
result = 31 * result + columnEnd;
result = 31 * result + lineRanges.hashCode();
result = 31 * result + description.hashCode();
result = 31 * result + (additionalProperties == null ? 0 : additionalProperties.hashCode());
result = 31 * result + origin.hashCode();
result = 31 * result + originName.hashCode();
result = 31 * result + moduleName.hashCode();
result = 31 * result + packageName.hashCode();
result = 31 * result + fileName.hashCode();
return result;
}
@Override
public String toString() {
return String.format("%s%s(%d,%d): %s: %s: %s", isPartOfModifiedCode() ? "*" : StringUtils.EMPTY, getBaseName(), lineStart, columnStart, type, category, message);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy