Skip to content

Commit

Permalink
Refactor parse API so that report properties are automatically set.
Browse files Browse the repository at this point in the history
  • Loading branch information
uhafner committed Dec 13, 2024
1 parent a6c367c commit d0792af
Show file tree
Hide file tree
Showing 194 changed files with 382 additions and 298 deletions.
2 changes: 1 addition & 1 deletion src/main/java/edu/hm/hafner/analysis/Issue.java
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ public static Predicate<Issue> byType(final String type) {
}

/**
* Called after de-serialization to improve the memory usage.
* Called after deserialization to improve the memory usage.
*
* @return this
*/
Expand Down
76 changes: 55 additions & 21 deletions src/main/java/edu/hm/hafner/analysis/IssueParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import org.apache.commons.lang3.StringUtils;

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

Expand All @@ -19,7 +19,7 @@
@SuppressWarnings("checkstyle:JavadocVariable")
public abstract class IssueParser implements Serializable {
@Serial
private static final long serialVersionUID = 200992696185460268L;
private static final long serialVersionUID = 5L; // release 13.0.0

protected static final String ADDITIONAL_PROPERTIES = "additionalProperties";
protected static final String CATEGORY = "category";
Expand All @@ -41,44 +41,78 @@ public abstract class IssueParser implements Serializable {
protected static final String SEVERITY = "severity";
protected static final String TYPE = "type";

private String defaultId = StringUtils.EMPTY;
private String defaultName = StringUtils.EMPTY;

/**
* Parses the specified file for issues.
* Parses a report (given by the reader factory) for issues.
*
* @param readerFactory
* provides a reader to the reports
* factory to read input reports with a specific locale
*
* @return the issues
* @return the report containing the found issues
* @throws ParsingException
* Signals that during parsing a non-recoverable error has been occurred
* signals that during parsing a non-recoverable error has been occurred
* @throws ParsingCanceledException
* Signals that the user has aborted the parsing
* signals that the user has aborted the parsing
*/
public abstract Report parse(ReaderFactory readerFactory) throws ParsingException, ParsingCanceledException;

public Type getType() {
return Type.WARNING;
public Report parse(final ReaderFactory readerFactory) throws ParsingException, ParsingCanceledException {
return parse(readerFactory, defaultId, defaultName);
}

/**
* Parses the specified file for issues. Invokes the parser using {@link #parse(ReaderFactory)} and sets the file
* name of the report.
* Parses a report (given by the reader factory) for issues.
*
* @param readerFactory
* provides a reader to the reports
* factory to read input reports with a specific locale
* @param id
* the ID for the returned report
* @param name
* a human-readable name for the returned report
*
* @return the issues
* @return the report containing the found issues
* @throws ParsingException
* Signals that during parsing a non-recoverable error has been occurred
* signals that during parsing a non-recoverable error has been occurred
* @throws ParsingCanceledException
* Signals that the user has aborted the parsing
* signals that the user has aborted the parsing
*/
public Report parseFile(final ReaderFactory readerFactory) throws ParsingException, ParsingCanceledException {
var report = parse(readerFactory);
public Report parse(final ReaderFactory readerFactory, final String id, final String name) throws ParsingException, ParsingCanceledException {
var report = parseReport(readerFactory);

report.setOriginReportFile(readerFactory.getFileName());
report.setType(getType());
report.setElementType(getType());
report.setOrigin(id, name);

return report;
}

/**
* Parses a report (given by the reader factory) for issues.
*
* @param readerFactory
* factory to read input reports with a specific locale
*
* @return the report containing the found issues
* @throws ParsingException
* signals that during parsing a non-recoverable error has been occurred
* @throws ParsingCanceledException
* signals that the user has aborted the parsing
*/
protected abstract Report parseReport(ReaderFactory readerFactory) throws ParsingException, ParsingCanceledException;

public final void setDefaultId(final String defaultId) {
this.defaultId = defaultId;
}

public final void setDefaultName(final String defaultName) {
this.defaultName = defaultName;
}

// FIXME: back to descriptor?
public IssueType getType() {
return IssueType.WARNING;
}

/**
* Returns whether this parser accepts the specified file as valid input. Parsers may reject a file if it is in the
* wrong format to avoid exceptions during parsing.
Expand Down Expand Up @@ -134,7 +168,7 @@ protected boolean isXmlFile(final ReaderFactory readerFactory) {
* @return {@code true} if the CharSequences are equal (case-insensitive), or both {@code null}
*/
public static boolean equalsIgnoreCase(@CheckForNull final String a, @CheckForNull final String b) {
return StringUtils.equals(normalize(a), normalize(b));
return StringUtils.equalsIgnoreCase(normalize(a), normalize(b));
}

private static String normalize(@CheckForNull final String input) {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/edu/hm/hafner/analysis/LookaheadParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ protected LookaheadParser(final String pattern) {
}

@Override
public Report parse(final ReaderFactory readerFactory) throws ParsingException, ParsingCanceledException {
public Report parseReport(final ReaderFactory readerFactory) throws ParsingException, ParsingCanceledException {
var report = new Report();
try (Stream<String> lines = readerFactory.readStream()) {
try (var lookahead = new LookaheadStream(lines, readerFactory.getFileName())) {
Expand Down
89 changes: 62 additions & 27 deletions src/main/java/edu/hm/hafner/analysis/Report.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

import static edu.hm.hafner.analysis.Severity.*;

/**
* 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
Expand All @@ -69,7 +71,7 @@ public class Report implements Iterable<Issue>, Serializable {
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 IssueType elementType = IssueType.WARNING; // since 13.0.0

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

Expand Down Expand Up @@ -251,16 +253,16 @@ public Set<String> getOriginReportFiles() {
}

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

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

/**
Expand Down Expand Up @@ -424,7 +426,7 @@ protected Object readResolve() {
if (parserId == null) { // release 13.0.0
parserId = DEFAULT_ID;
icon = DEFAULT_ID;
type = Type.WARNING;
elementType = IssueType.WARNING;
}
return this;
}
Expand Down Expand Up @@ -649,8 +651,47 @@ public int getSizeOf(final Severity severity) {

@Override
public String toString() {
return String.format(Locale.ENGLISH, "%s (%s): %s%s", getEffectiveName(), getEffectiveId(),
getItemName(size()), getDuplicates());
var summary = String.format(Locale.ENGLISH, "%s%s%s", getNamePrefix(), getSummary(), getDuplicates());

var details = getPredefinedValues()
.stream()
.map(this::reportSeverity)
.flatMap(Optional::stream)
.collect(Collectors.joining(", "));
if (StringUtils.isEmpty(details)) {
return summary;
}
return summary + " (" + details + ")";
}

/**
* Returns a string representation of this report that shows the number of issues and their distribution.
*
* @return a string representation of this report
*/
public String getSummary() {
return getItemName(size());
}

private String getNamePrefix() {
if (isEmptyOrDefault(getName()) && isEmptyOrDefault(getId())) {
return StringUtils.EMPTY;
}
else {
return String.format(Locale.ENGLISH, "%s (%s): ", getName(), getId());
}
}

private boolean isEmptyOrDefault(final String value) {
return StringUtils.isEmpty(value) || DEFAULT_ID.equals(value);
}

private Optional<String> reportSeverity(final Severity severity) {
var size = getSizeOf(severity);
if (size > 0) {
return Optional.of(String.format(Locale.ENGLISH, "%s: %d", StringUtils.lowerCase(severity.getName()), size));
}
return Optional.empty();
}

private String getItemName(final int size) {
Expand All @@ -664,14 +705,15 @@ private String getItemName(final int size) {
// Open as API?
private String getItemCount(final int count) {
if (count == 1) {
return switch (getType()) {
return switch (getElementType()) {
case WARNING -> "warning";
case BUG -> "bug";
case DUPLICATION -> "duplication";
case VULNERABILITY -> "vulnerability";
};
} else {
return switch (getType()) {
}
else {
return switch (getElementType()) {
case WARNING -> "warnings";
case BUG -> "bugs";
case DUPLICATION -> "duplications";
Expand All @@ -687,13 +729,6 @@ private String getDuplicates() {
return StringUtils.EMPTY;
}

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

/**
* Prints all issues of the report.
*
Expand Down Expand Up @@ -1077,7 +1112,7 @@ public boolean equals(final Object o) {
&& Objects.equals(id, issues.id)
&& Objects.equals(name, issues.name)
&& Objects.equals(icon, issues.icon)
&& Objects.equals(type, issues.type)
&& Objects.equals(elementType, issues.elementType)
&& Objects.equals(parserId, issues.parserId)
&& Objects.equals(originReportFile, issues.originReportFile)
&& Objects.equals(subReports, issues.subReports)
Expand All @@ -1090,7 +1125,7 @@ public boolean equals(final Object o) {
@Override
@Generated
public int hashCode() {
return Objects.hash(id, name, icon, type, parserId, originReportFile, subReports, elements,
return Objects.hash(id, name, icon, elementType, parserId, originReportFile, subReports, elements,
infoMessages, errorMessages, countersByKey, duplicatesSize);
}

Expand All @@ -1109,7 +1144,7 @@ private void writeObject(final ObjectOutputStream output) throws IOException {
output.writeUTF(name);
output.writeUTF(icon);
output.writeUTF(parserId);
output.writeObject(type);
output.writeObject(elementType);

output.writeUTF(originReportFile);
output.writeInt(subReports.size());
Expand Down Expand Up @@ -1167,7 +1202,7 @@ private void readObject(final ObjectInputStream input) throws IOException, Class

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

originReportFile = input.readUTF();
subReports = new ArrayList<>();
Expand Down Expand Up @@ -1345,7 +1380,7 @@ public static class IssueFilterBuilder {
private final Collection<Predicate<Issue>> includeFilters = new ArrayList<>();
private final Collection<Predicate<Issue>> excludeFilters = new ArrayList<>();

/** Type of the filter: include or exclude elements. */
/** IssueType of the filter: include or exclude elements. */
enum FilterType {
INCLUDE,
EXCLUDE
Expand Down Expand Up @@ -1625,7 +1660,7 @@ public IssueFilterBuilder setExcludeCategoryFilter(final String... patterns) {

//</editor-fold>

//<editor-fold desc="Type">
//<editor-fold desc="IssueType">

/**
* Add a new filter for {@code Issue::getCategory}.
Expand Down Expand Up @@ -1749,7 +1784,7 @@ private void addMessageFilter(final Collection<String> patterns, final FilterTyp
/**
* Returns the type of the issues. The type is used to customize reports in the UI.
*/
public enum Type {
public enum IssueType {
/** 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. */
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/edu/hm/hafner/analysis/parser/AjcParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public class AjcParser extends IssueParser {
static final String ADVICE = "Advice";

@Override
public Report parse(final ReaderFactory reader) throws ParsingException {
public Report parseReport(final ReaderFactory reader) throws ParsingException {
try (Stream<String> lines = reader.readStream()) {
return parse(lines);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public class CargoCheckParser extends IssueParser {
private static final String MESSAGE_SPAN_COLUMN_END = "column_end";

@Override
public Report parse(final ReaderFactory readerFactory) throws ParsingException, ParsingCanceledException {
public Report parseReport(final ReaderFactory readerFactory) throws ParsingException, ParsingCanceledException {
var report = new Report();

try (Stream<String> lines = readerFactory.readStream(); var issueBuilder = new IssueBuilder()) {
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/edu/hm/hafner/analysis/parser/ClairParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +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.Report.IssueType;
import edu.hm.hafner.analysis.Severity;
import edu.umd.cs.findbugs.annotations.CheckForNull;

Expand All @@ -23,8 +23,8 @@ public class ClairParser extends JsonIssueParser {
private static final long serialVersionUID = 371390072777545322L;

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

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public boolean accepts(final ReaderFactory readerFactory) {
}

@Override
public Report parse(final ReaderFactory readerFactory) throws ParsingException {
public Report parseReport(final ReaderFactory readerFactory) throws ParsingException {
try (var issueBuilder = new IssueBuilder()) {
var doc = readerFactory.readDocument();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ private String getCategory(final String line) {
else if (line.contains("does not support multiword aliases")) {
return "Multiword Aliases not Supported by Code Generation";
}
else if (line.contains("Unnecessary Data Type Conversion")) {
return "Unnecessary Data Type Conversion";
else if (line.contains("Unnecessary Data IssueType Conversion")) {
return "Unnecessary Data IssueType Conversion";
}
else if (line.contains("Cannot close the model")) {
return "Model Cannot be Closed";
Expand Down
Loading

0 comments on commit d0792af

Please sign in to comment.