cdc.issues.Issue Maven / Gradle / Ivy
package cdc.issues;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import cdc.issues.locations.LocatedItem;
import cdc.issues.locations.Location;
import cdc.issues.rules.Rule;
import cdc.issues.rules.RuleId;
import cdc.util.lang.Checks;
/**
* Base class used to describe an Issue (more exactly an occurrence of
* detection of an issue).
*
* Node: This class can be specialized if necessary.
*
* An issue may have several target locations.
* For example, when there is a compliance issue between several things,
* one can not tell which one is at fault.
*
* WARNNING
* Issue should probably NOT be derived.
* Currently, IO implementations only produce the base Issue class.
* There is no support to build any derived class.
*
* If you however do so, it is highly recommended not to add any attribute.
* If you do that, those attributes won't be retrieved by IO.
*
* @author Damien Carbonne
*/
public class Issue {
private static final String DESCRIPTION = "description";
private static final String LABELS = "labels";
private static final String LOCATION = "location";
private static final String LOCATIONS = "locations";
private static final String METAS = "metas";
private static final String PARAMS = "params";
private static final String SEVERITY = "severity";
/**
* Comparator of Issues using timestamp.
*/
public static final Comparator TIMESTAMP_COMPARATOR =
Comparator.comparing(Issue::getTimestamp);
private final Instant timestamp;
private final IssueId id;
private final String snapshot;
private final IssueSeverity severity;
private final String description;
private final Metas metas;
private final Labels labels;
protected Issue(Builder> builder) {
this.timestamp = builder.timestamp == null ? Instant.now() : builder.timestamp;
this.id = IssueId.builder()
.domain(builder.domain)
.name(builder.name)
.params(builder.params)
.project(builder.project)
.locations(builder.locations)
.build();
this.snapshot = builder.snapshot;
this.severity = Checks.isNotNull(builder.severity, SEVERITY);
this.description = Checks.isNotNull(builder.description, DESCRIPTION);
this.metas = Checks.isNotNull(builder.metas, METAS);
this.labels = Checks.isNotNull(builder.labels, LABELS);
}
/**
* Creates an Issue.
*
* @param timestamp The optional timestamp.
* @param domain The rule domain.
* @param name The rule name.
* @param params The rule parameters.
* @param project The project name.
* @param locations The target locations.
* @param snapshot The issue snapshot.
* @param severity The issue severity.
* @param description The issue description.
* @param metas The issue meta data.
* @deprecated Do not use anymore. Issue will be made final.
*/
@Deprecated(forRemoval = true, since = "2023-10-14")
protected Issue(Instant timestamp,
String domain,
String name,
Params params,
String project,
List extends Location> locations,
String snapshot,
IssueSeverity severity,
String description,
Params metas) {
this.timestamp = timestamp == null
? Instant.now()
: timestamp;
this.id = IssueId.builder()
.domain(domain)
.name(name)
.params(params)
.project(project)
.locations(locations)
.build();
this.snapshot = snapshot;
this.severity = Checks.isNotNull(severity, SEVERITY);
this.description = Checks.isNotNull(description, DESCRIPTION);
this.metas = Checks.isNotNull(Metas.of(metas), METAS);
this.labels = Labels.NO_LABELS;
}
/**
*
* @param domain The rule domain.
* @param name The rule name.
* @param params The rule parameters.
* @param project The project name.
* @param locations The target locations.
* @param snapshot The issue snapshot.
* @param severity The issue severity.
* @param description The issue description.
* @param metas The issue meta data.
* @deprecated Do not use anymore. Issue will be made final.
*/
@Deprecated(forRemoval = true, since = "2023-10-14")
protected Issue(String domain,
String name,
Params params,
String project,
List extends Location> locations,
String snapshot,
IssueSeverity severity,
String description,
Params metas) {
this(null,
domain,
name,
params,
project,
locations,
snapshot,
severity,
description,
metas);
}
/**
*
* @param timestamp The optional timestamp.
* @param domain The rule domain.
* @param name The rule name.
* @param params The rule parameters.
* @param project The project name.
* @param locations The target locations.
* @param snapshot The issue snapshot.
* @param severity The issue severity.
* @param description The issue description.
* @param metas The issue meta data.
* @deprecated Do not use anymore. Issue will be made final.
*/
@Deprecated(forRemoval = true, since = "2023-10-14")
protected Issue(Instant timestamp,
String domain,
Enum> name,
Params params,
String project,
List extends Location> locations,
String snapshot,
IssueSeverity severity,
String description,
Params metas) {
this(timestamp,
domain,
name.name(),
params,
project,
locations,
snapshot,
severity,
description,
metas);
}
/**
*
* @param domain The rule domain.
* @param name The rule name.
* @param params The rule parameters.
* @param project The project name.
* @param locations The target locations.
* @param snapshot The issue snapshot.
* @param severity The issue severity.
* @param description The issue description.
* @param metas The issue meta data.
* @deprecated Do not use anymore. Issue will be made final.
*/
@Deprecated(forRemoval = true, since = "2023-10-14")
protected Issue(String domain,
Enum> name,
Params params,
String project,
List extends Location> locations,
String snapshot,
IssueSeverity severity,
String description,
Params metas) {
this(null,
domain,
name,
params,
project,
locations,
snapshot,
severity,
description,
metas);
}
/**
* @return The Instant at which this issue was created.
*/
public final Instant getTimestamp() {
return timestamp;
}
/**
* @return The id of this issue.
*/
public IssueId getId() {
return id;
}
/**
* @return The RuleId of this issue.
*/
public RuleId getRuleId() {
return id.getRuleId();
}
/**
* @return The domain of the rule of this issue.
*/
public String getDomain() {
return id.getDomain();
}
/**
* @return The name of the rule of this issue.
*/
public String getName() {
return id.getName();
}
public > T getName(Class typeClass) {
return id.getName(typeClass);
}
/**
* @return The rule parameters of this issue.
*/
public Params getParams() {
return id.getParams();
}
/**
* @return The project of this issue.
* May be {@code null}.
*/
public String getProject() {
return id.getProject();
}
/**
* @return The snapshot of this issue.
* May be {@code null}.
*/
public String getSnapshot() {
return snapshot;
}
/**
* @return The severity of this issue.
*/
public final IssueSeverity getSeverity() {
return severity;
}
/**
* @return The description of this issue.
*/
public final String getDescription() {
return description;
}
/**
* @return The meta data associated to this issue.
*/
public Metas getMetas() {
return metas;
}
/**
* @return The labels associated to this issue.
*/
public Labels getLabels() {
return labels;
}
/**
* @return The target locations of this issue.
*/
public Location[] getLocations() {
return id.getLocations();
}
/**
* @return The number of target locations of this issue.
*/
public final int getNumberOfLocations() {
return id.getLocations().length;
}
public Location getLocationAt(int index) {
return id.getLocations()[index];
}
public L getLocationAt(int index,
Class cls) {
return cls.cast(getLocationAt(index));
}
@Override
public int hashCode() {
return Objects.hash(timestamp,
id,
snapshot,
severity,
description,
metas,
labels);
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object == null || getClass() != object.getClass()) {
return false;
}
final Issue other = (Issue) object;
return Objects.equals(this.timestamp, other.timestamp)
&& Objects.equals(this.id, other.id)
&& Objects.equals(this.snapshot, other.snapshot)
&& this.severity == other.severity
&& Objects.equals(this.description, other.description)
&& Objects.equals(this.metas, other.metas)
&& Objects.equals(this.labels, other.labels);
}
@Override
public String toString() {
return getTimestamp()
+ " - " + getDomain()
+ " - " + getName()
+ " - " + getParams()
+ " - " + getProject()
+ " - " + getSnapshot()
+ " - " + getSeverity()
+ " - " + getDescription()
+ " - " + Arrays.toString(getLocations())
+ " - " + getMetas()
+ " - " + getLabels();
}
/**
* @return A new {@link Builder} of {@link Issue}.
*/
public static Builder> builder() {
return new Builder<>();
}
/**
* Builder of {@link Issue}.
*
* This class should be derived to construct specializations of {@link Issue}.
*
* @param The builder type.
*/
public static class Builder> {
protected Instant timestamp = null;
protected String domain;
protected String name;
protected Params params = Params.NO_PARAMS;
protected String project;
protected String snapshot;
protected IssueSeverity severity;
protected String description = "";
protected final List locations = new ArrayList<>();
protected Metas metas = Metas.NO_METAS;
protected Labels labels = Labels.NO_LABELS;
protected Builder() {
}
@SuppressWarnings("unchecked")
protected B self() {
return (B) this;
}
public B accept(Consumer super B> consumer) {
consumer.accept(self());
return self();
}
/**
* Sets the issue timestamp.
*
* WARNING: this should only be used to reconstruct an issue -from a file, stream, ...).
*
* @param timestamp The timestamp.
* @return This builder.
*/
public B timestamp(Instant timestamp) {
this.timestamp = timestamp;
return self();
}
/**
* Sets the issue Rule.
*
* This is equivalent to setting its domain, name, and severity.
*
* @param rule The rule.
* @return This builder.
*/
public B rule(Rule rule) {
this.domain = rule.getDomain();
this.name = rule.getName();
this.severity = rule.getSeverity();
return self();
}
/**
* Sets the issue RuleId.
*
* This is equivalent to setting its domain and name.
*
* @param ruleId The rule id.
* @return This builder.
*/
public B ruleId(RuleId ruleId) {
this.domain = ruleId.getDomain();
this.name = ruleId.getName();
return self();
}
/**
* Sets the issue domain.
*
* @param domain The domain.
* @return This builder.
*/
public B domain(String domain) {
this.domain = domain;
return self();
}
/**
* Sets the issue name.
*
* @param name The name.
* @return This builder.
*/
public B name(String name) {
this.name = name;
return self();
}
/**
* Sets the issue name, and optionally its severity if the {@code name} implements
* {@link IssueSeverityItem} and current severity is {@code null}.
*
* @param name The name.
* @return This builder.
*/
public B name(Enum> name) {
this.name = name.name();
if (severity == null && name instanceof final IssueSeverityItem isi) {
final IssueSeverity s = isi.getSeverity();
if (s != null) {
severity(s);
}
}
return self();
}
public B params(Params params) {
Checks.isNotNull(params, PARAMS);
this.params = params;
return self();
}
public B labels(Labels labels) {
Checks.isNotNull(labels, LABELS);
this.labels = labels;
return self();
}
/**
* Sets the project name (may be {@code null}).
*
* @param project The project name.
* @return This builder.
*/
public B project(String project) {
this.project = project;
return self();
}
/**
* Adds an issue location.
*
* @param location The location.
* @return This builder.
*/
public B addLocation(Location location) {
Checks.isNotNull(location, LOCATION);
this.locations.add(location);
return self();
}
/**
* Sets the issue location.
*
* @param location The location.
* @return This builder.
*/
public B location(Location location) {
Checks.isNotNull(location, LOCATION);
this.locations.clear();
this.locations.add(location);
return self();
}
/**
* Sets the issue location.
*
* @param item The {@link LocatedItem} whose location is used.
* @return This builder.
*/
public B location(LocatedItem item) {
return location(item.getLocation());
}
/**
* Sets the issue locations.
*
* @param locations The locations
* @return This builder.
*/
public B locations(Location... locations) {
Checks.isNotNull(locations, LOCATIONS);
this.locations.clear();
Collections.addAll(this.locations, locations);
return self();
}
/**
* Sets the issue locations.
*
* @param locations The locations
* @return This builder.
*/
public B locations(List locations) {
Checks.isNotNull(locations, LOCATIONS);
this.locations.clear();
this.locations.addAll(locations);
return self();
}
/**
* Sets the issue snapshot.
*
* @param snapshot The snapshot identifier.
* @return This builder.
*/
public B snapshot(String snapshot) {
this.snapshot = snapshot;
return self();
}
/**
* Sets the issue severity.
*
* @param severity The severity.
* @return This builder.
*/
public B severity(IssueSeverity severity) {
Checks.isNotNull(severity, SEVERITY);
this.severity = severity;
return self();
}
/**
* Sets the issue description.
*
* @param description The description.
* @return This builder.
*/
public B description(String description) {
Checks.isNotNull(description, DESCRIPTION);
this.description = description;
return self();
}
/**
* Sets the issue description.
*
* The passed builder is built to a {@link StructuredDescription} which
* is then converted to String.
*
* @param description The description.
* @return This builder.
*/
public B description(StructuredDescription.Builder> description) {
Checks.isNotNull(description, DESCRIPTION);
this.description = description.build().toString();
return self();
}
/**
* Sets the issue description.
*
* {@link Object#toString()} is used.
*
* @param description The description.
* @return This builder.
*/
public B description(Object description) {
Checks.isNotNull(description, DESCRIPTION);
return description(description.toString());
}
/**
* Sets the issue meta data.
*
* @param metas The meta data.
* @return This builder.
* @deprecated Use {@link Builder#metas(Metas)}.
*/
@Deprecated(since = "2023-03-30", forRemoval = true)
public B metas(Params metas) {
Checks.isNotNull(metas, METAS);
this.metas = Metas.of(metas);
return self();
}
public B metas(Metas metas) {
Checks.isNotNull(metas, METAS);
this.metas = metas;
return self();
}
public Issue build() {
return new Issue(this);
}
}
}