Skip to content

Commit

Permalink
Refactor type: make the report aware of parser properties.
Browse files Browse the repository at this point in the history
  • Loading branch information
uhafner committed Nov 27, 2024
1 parent a814e87 commit a6c367c
Show file tree
Hide file tree
Showing 29 changed files with 209 additions and 111 deletions.
8 changes: 7 additions & 1 deletion src/main/java/edu/hm/hafner/analysis/IssueParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import org.apache.commons.lang3.StringUtils;

import edu.hm.hafner.analysis.Report.Type;
import edu.hm.hafner.util.SecureXmlParserFactory;
import edu.umd.cs.findbugs.annotations.CheckForNull;

Expand Down Expand Up @@ -54,6 +55,10 @@ public abstract class IssueParser implements Serializable {
*/
public abstract Report parse(ReaderFactory readerFactory) throws ParsingException, ParsingCanceledException;

public Type getType() {
return Type.WARNING;
}

/**
* Parses the specified file for issues. Invokes the parser using {@link #parse(ReaderFactory)} and sets the file
* name of the report.
Expand All @@ -70,6 +75,7 @@ public abstract class IssueParser implements Serializable {
public Report parseFile(final ReaderFactory readerFactory) throws ParsingException, ParsingCanceledException {
var report = parse(readerFactory);
report.setOriginReportFile(readerFactory.getFileName());
report.setType(getType());
return report;
}

Expand Down Expand Up @@ -110,7 +116,7 @@ protected boolean isXmlFile(final ReaderFactory readerFactory) {
* equal sequences of characters, ignoring case.
*
* <p>{@code null}s are handled without exceptions. Two {@code null}
* references are considered equal. The comparison is <strong>case insensitive</strong>.</p>
* references are considered equal. The comparison is <strong>case-insensitive</strong>.</p>
*
* <pre>
* equalsIgnoreCase(null, null) = true
Expand Down
130 changes: 113 additions & 17 deletions src/main/java/edu/hm/hafner/analysis/Report.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,35 +40,36 @@
import edu.hm.hafner.util.PathUtil;
import edu.hm.hafner.util.TreeStringBuilder;
import edu.hm.hafner.util.VisibleForTesting;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/**
* A report contains a set of unique {@link Issue issues}: it contains no duplicate elements, i.e. it models the
* A report contains a set of unique {@link Issue issues}: it contains no duplicate elements, i.e., it models the
* mathematical <i>set</i> abstraction. This report provides a <i>total ordering</i> on its elements. I.e., the issues
* in this report are ordered by their index: the first added issue is at position 0, the second added issues is at
* in this report are ordered by their index: the first added issue is at position 0, the second added issue is at
* position 1, and so on.
*
* <p>
* Additionally, this report provides methods to find and filter issues based on different properties. In order to
* Additionally, this report provides methods to find and filter issues based on different properties. To
* create issues use the provided {@link IssueBuilder builder} class.
* </p>
*
* @author Ullrich Hafner
*/
@SuppressWarnings({"PMD.ExcessivePublicCount", "PMD.ExcessiveClassLength", "PMD.GodClass", "PMD.CognitiveComplexity", "PMD.CyclomaticComplexity", "checkstyle:ClassFanOutComplexity"})
// TODO: provide a readResolve method to check the instance and improve the performance (TreeString, etc.)
public class Report implements Iterable<Issue>, Serializable {
@Serial
private static final long serialVersionUID = 4L; // release 10.0.0
private static final long serialVersionUID = 5L; // release 13.0.0

@VisibleForTesting
static final String DEFAULT_ID = "-";

private String id;
private String name;
private String originReportFile;
private String icon = StringUtils.EMPTY; // since 13.0.0
private String parserId = DEFAULT_ID; // since 13.0.0
private Type type = Type.WARNING; // since 13.0.0

private List<Report> subReports = new ArrayList<>(); // almost final

Expand All @@ -77,11 +78,6 @@ public class Report implements Iterable<Issue>, Serializable {
private List<String> errorMessages = new ArrayList<>();
private Map<String, Integer> countersByKey = new HashMap<>();

@CheckForNull @SuppressWarnings({"all", "UnusedVariable"})
private transient Set<String> fileNames; // Not needed anymore since 10.0.0
@CheckForNull @SuppressWarnings({"all", "UnusedVariable"})
private transient Map<String, String> namesByOrigin; // Not needed anymore since 10.0.0

private int duplicatesSize;

/**
Expand Down Expand Up @@ -120,7 +116,6 @@ public Report(final String id, final String name, final String originReportFile)
this.originReportFile = originReportFile;
}


/**
* Creates a new {@link Report} that is an aggregation of the specified {@link Report reports}. The created report
* will contain the issues of all specified reports, in the same order. The properties of the specified reports will
Expand Down Expand Up @@ -255,6 +250,32 @@ public Set<String> getOriginReportFiles() {
return files;
}

/**
* Returns the type of the report. The type might be used to customize reports in the UI.
*
* @return the type of the parser
*/
public Type getType() {
return type;
}

public void setType(final Type type) {
this.type = type;
}

/**
* Returns the icon of the report. The icon might be used to customize reports in the UI.
*
* @return the name of te icon
*/
public String getIcon() {
return icon;
}

public void setIcon(final String icon) {
this.icon = icon;
}

Check warning on line 277 in src/main/java/edu/hm/hafner/analysis/Report.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 272-277 are not covered by tests

/**
* Appends the specified issue to the end of this report. Duplicates will be skipped (the number of skipped elements
* is available using the method {@link #getDuplicatesSize()}.
Expand Down Expand Up @@ -383,11 +404,12 @@ List<Report> getSubReports() {
}

/**
* Called after de-serialization to improve the memory usage and to initialize fields that have been introduced
* Called after deserialization to improve the memory usage and to initialize fields that have been introduced
* after the first release.
*
* @return this
*/
@Serial
@SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", justification = "Deserialization of instances that do not have all fields yet")
protected Object readResolve() {
if (countersByKey == null) {
Expand All @@ -399,6 +421,11 @@ protected Object readResolve() {
name = DEFAULT_ID;
originReportFile = DEFAULT_ID;
}
if (parserId == null) { // release 13.0.0

Check warning on line 424 in src/main/java/edu/hm/hafner/analysis/Report.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 424 is only partially covered, one branch is missing
parserId = DEFAULT_ID;
icon = DEFAULT_ID;
type = Type.WARNING;

Check warning on line 427 in src/main/java/edu/hm/hafner/analysis/Report.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 425-427 are not covered by tests
}
return this;
}

Expand Down Expand Up @@ -622,8 +649,49 @@ public int getSizeOf(final Severity severity) {

@Override
public String toString() {
return String.format(Locale.ENGLISH, "%s (%s): %d issues (%d duplicates)", getEffectiveName(), getEffectiveId(),
size(), getDuplicatesSize());
return String.format(Locale.ENGLISH, "%s (%s): %s%s", getEffectiveName(), getEffectiveId(),
getItemName(size()), getDuplicates());
}

private String getItemName(final int size) {
var items = getItemCount(size);
if (size == 0) {
return String.format("No %s", items);
}
return String.format(Locale.ENGLISH, "%d %s", size, items);
}

// Open as API?
private String getItemCount(final int count) {
if (count == 1) {
return switch (getType()) {

Check warning on line 667 in src/main/java/edu/hm/hafner/analysis/Report.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 667 is only partially covered, 3 branches are missing
case WARNING -> "warning";
case BUG -> "bug";
case DUPLICATION -> "duplication";
case VULNERABILITY -> "vulnerability";

Check warning on line 671 in src/main/java/edu/hm/hafner/analysis/Report.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 669-671 are not covered by tests
};
} else {
return switch (getType()) {

Check warning on line 674 in src/main/java/edu/hm/hafner/analysis/Report.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 674 is only partially covered, 3 branches are missing
case WARNING -> "warnings";
case BUG -> "bugs";
case DUPLICATION -> "duplications";
case VULNERABILITY -> "vulnerabilities";

Check warning on line 678 in src/main/java/edu/hm/hafner/analysis/Report.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 676-678 are not covered by tests
};
}
}

private String getDuplicates() {
if (duplicatesSize > 0) {
return String.format(Locale.ENGLISH, " (%d duplicates)", duplicatesSize);
}
return StringUtils.EMPTY;
}

private static String plural(final int score) {
if (score == 1) {
return StringUtils.EMPTY;
}
return "s";

Check warning on line 694 in src/main/java/edu/hm/hafner/analysis/Report.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 691-694 are not covered by tests
}

/**
Expand Down Expand Up @@ -1008,6 +1076,9 @@ public boolean equals(final Object o) {
return duplicatesSize == issues.duplicatesSize
&& Objects.equals(id, issues.id)
&& Objects.equals(name, issues.name)
&& Objects.equals(icon, issues.icon)
&& Objects.equals(type, issues.type)
&& Objects.equals(parserId, issues.parserId)
&& Objects.equals(originReportFile, issues.originReportFile)
&& Objects.equals(subReports, issues.subReports)
&& Objects.equals(elements, issues.elements)
Expand All @@ -1019,10 +1090,11 @@ public boolean equals(final Object o) {
@Override
@Generated
public int hashCode() {
return Objects.hash(id, name, originReportFile, subReports, elements, infoMessages, errorMessages,
countersByKey, duplicatesSize);
return Objects.hash(id, name, icon, type, parserId, originReportFile, subReports, elements,
infoMessages, errorMessages, countersByKey, duplicatesSize);
}

@Serial
private void writeObject(final ObjectOutputStream output) throws IOException {
output.writeInt(elements.size());
writeIssues(output);
Expand All @@ -1035,6 +1107,10 @@ private void writeObject(final ObjectOutputStream output) throws IOException {

output.writeUTF(id);
output.writeUTF(name);
output.writeUTF(icon);
output.writeUTF(parserId);
output.writeObject(type);

output.writeUTF(originReportFile);
output.writeInt(subReports.size());
for (Report subReport : subReports) {
Expand Down Expand Up @@ -1075,6 +1151,7 @@ private void writeLongString(final ObjectOutputStream output, final String value
@SuppressWarnings("unchecked")
@SuppressFBWarnings(value = "MC_OVERRIDABLE_METHOD_CALL_IN_READ_OBJECT",
justification = "False positive, the overridden method is in already initialized objects")
@Serial
private void readObject(final ObjectInputStream input) throws IOException, ClassNotFoundException {
elements = new LinkedHashSet<>();
readIssues(input, input.readInt());
Expand All @@ -1087,6 +1164,11 @@ private void readObject(final ObjectInputStream input) throws IOException, Class

id = input.readUTF();
name = input.readUTF();

icon = input.readUTF();
parserId = input.readUTF();
type = (Type) input.readObject();

originReportFile = input.readUTF();
subReports = new ArrayList<>();

Expand Down Expand Up @@ -1663,4 +1745,18 @@ private void addMessageFilter(final Collection<String> patterns, final FilterTyp
}
//</editor-fold>
}

/**
* Returns the type of the issues. The type is used to customize reports in the UI.
*/
public enum Type {
/** A parser that scans the output of a build tool to find warnings. */
WARNING,
/** A parser that scans the output of a build tool to find bugs. */
BUG,
/** A parser that scans the output of a build tool to find vulnerabilities. */
VULNERABILITY,
/** A parser that scans the output of a build tool to find vulnerabilities. */
DUPLICATION
}
}
6 changes: 6 additions & 0 deletions src/main/java/edu/hm/hafner/analysis/parser/ClairParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import edu.hm.hafner.analysis.Issue;
import edu.hm.hafner.analysis.IssueBuilder;
import edu.hm.hafner.analysis.Report;
import edu.hm.hafner.analysis.Report.Type;
import edu.hm.hafner.analysis.Severity;
import edu.umd.cs.findbugs.annotations.CheckForNull;

Expand All @@ -21,6 +22,11 @@ public class ClairParser extends JsonIssueParser {
@Serial
private static final long serialVersionUID = 371390072777545322L;

@Override
public Type getType() {
return Type.VULNERABILITY;
}

@Override
protected void parseJsonObject(final Report report, final JSONObject jsonReport, final IssueBuilder issueBuilder) {
var image = optStringIgnoreCase(jsonReport, "image");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import edu.hm.hafner.analysis.IssueBuilder;
import edu.hm.hafner.analysis.LookaheadParser;
import edu.hm.hafner.analysis.ParsingException;
import edu.hm.hafner.analysis.Report.Type;
import edu.hm.hafner.util.LookaheadStream;

import static j2html.TagCreator.*;
Expand Down Expand Up @@ -42,6 +43,11 @@ public ErrorProneParser() {
super(WARNINGS_PATTERN);
}

@Override
public Type getType() {
return Type.BUG;
}

@Override
protected Optional<Issue> createIssue(final Matcher matcher, final LookaheadStream lookahead,
final IssueBuilder builder) throws ParsingException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import edu.hm.hafner.analysis.Issue;
import edu.hm.hafner.analysis.IssueBuilder;
import edu.hm.hafner.analysis.LookaheadParser;
import edu.hm.hafner.analysis.Report.Type;
import edu.hm.hafner.analysis.Severity;
import edu.hm.hafner.analysis.util.IntegerParser;
import edu.hm.hafner.util.LookaheadStream;
Expand All @@ -33,6 +34,11 @@ public FlawfinderParser() {
super(FLAWFINDER_WARNING_PATTERN);
}

@Override
public Type getType() {
return Type.VULNERABILITY;
}

@Override
protected Optional<Issue> createIssue(final Matcher matcher, final LookaheadStream lookahead,
final IssueBuilder builder) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import edu.hm.hafner.analysis.Issue;
import edu.hm.hafner.analysis.IssueBuilder;
import edu.hm.hafner.analysis.Report;
import edu.hm.hafner.analysis.Report.Type;
import edu.hm.hafner.analysis.Severity;
import edu.umd.cs.findbugs.annotations.CheckForNull;

Expand All @@ -30,6 +31,11 @@ public class OwaspDependencyCheckParser extends JsonIssueParser {
private static final String ATTACK_VECTOR = "attackVector";
private static final String DESCRIPTION = "description";

@Override
public Type getType() {
return Type.VULNERABILITY;
}

@Override
protected void parseJsonObject(final Report report, final JSONObject jsonReport, final IssueBuilder issueBuilder) {
final var dependencies = jsonReport.getJSONArray(DEPENDENCIES);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import edu.hm.hafner.analysis.Issue;
import edu.hm.hafner.analysis.IssueBuilder;
import edu.hm.hafner.analysis.Report;
import edu.hm.hafner.analysis.Report.Type;
import edu.hm.hafner.analysis.Severity;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

Expand Down Expand Up @@ -39,6 +40,11 @@ public class PnpmAuditParser extends JsonIssueParser {
@Serial
private static final long serialVersionUID = 4140706319863200922L;

@Override
public Type getType() {
return Type.VULNERABILITY;
}

@Override
protected void parseJsonObject(final Report report, final JSONObject jsonReport, final IssueBuilder issueBuilder) {
var results = jsonReport.optJSONObject("advisories");
Expand Down
Loading

0 comments on commit a6c367c

Please sign in to comment.