org.conqat.engine.commons.findings.DetachedFinding Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of teamscale-commons Show documentation
Show all versions of teamscale-commons Show documentation
Provides common DTOs for Teamscale
/*
* Copyright (c) CQSE GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.conqat.engine.commons.findings;
import java.io.Serializable;
import java.io.UTFDataFormatException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.conqat.engine.commons.findings.location.ElementLocation;
import org.conqat.lib.commons.assertion.CCSMAssert;
import org.conqat.lib.commons.assessment.ETrafficLightColor;
import org.conqat.lib.commons.collections.CollectionUtils;
import org.conqat.lib.commons.collections.SetMap;
import org.conqat.lib.commons.collections.UnmodifiableMap;
import org.conqat.lib.commons.js_export.ExportAsType;
import org.conqat.lib.commons.js_export.ExportToTypeScript;
import org.conqat.lib.commons.string.StringUtils;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* This class describes orphaned finding that is attached to a node or a
* findings report. This is useful if e.g. findings have been filtered but
* certain operations should still be carried out on the findings.
*
* This class is used as DTO during communication with IDE clients via
* com.teamscale.ide.commons.client.IIdeServiceClient, special care has to be
* taken when changing its signature!
*/
@ExportToTypeScript
public class DetachedFinding implements Serializable {
// Class argument necessary due to
// and
// .
private static final Logger LOGGER = LogManager.getLogger(DetachedFinding.class);
/** Serial version UID. */
private static final long serialVersionUID = 1;
/** The maximum length of the {@link #message}. */
public static final int MAX_MESSAGE_LENGTH = 1000;
/** The name of the JSON property name for {@link #location}. */
protected static final String LOCATION_PROPERTY = "location";
/** The name of the JSON property name for {@link #groupName}. */
protected static final String GROUP_NAME_PROPERTY = "groupName";
/** The name of the JSON property name for {@link #categoryName}. */
protected static final String CATEGORY_NAME_PROPERTY = "categoryName";
/** The name of the JSON property name for {@link #message}. */
protected static final String MESSAGE_PROPERTY = "message";
/** The name of the JSON property name for {@link #assessment}. */
private static final String ASSESSMENT_PROPERTY = "assessment";
/** The location. */
@JsonProperty(LOCATION_PROPERTY)
@ExportAsType("ElementLocation | TextRegionLocation | QualifiedNameLocation")
private ElementLocation location;
/** The group name. */
@JsonProperty(GROUP_NAME_PROPERTY)
private String groupName;
/** The category name. */
@JsonProperty(CATEGORY_NAME_PROPERTY)
private String categoryName;
/** The message. */
@JsonProperty(MESSAGE_PROPERTY)
private String message;
/** The assessment color of the finding (may be null). */
@JsonProperty(ASSESSMENT_PROPERTY)
@Nullable
private ETrafficLightColor assessment;
/**
* The locations of other findings that are considered siblings. This is, e.g.,
* used to find other clone instances in the same clone class. As this is
* commonly empty, we keep this attribute null in this case to save space for
* serialization. The access methods, however, handle this transparently.
*/
@JsonProperty("siblingLocations")
@Nullable
@ExportAsType("Array")
private List siblingLocations;
/**
* Caches the sibling locations as strings. Used to avoid adding duplicate
* sibling locations. Initialized lazily.
*/
private transient Set siblingLocationCache = null;
/**
* Next to the primary location {@link #location}, a finding may optionally have
* secondary locations. For instance, an architecture finding may contain the
* individual source code locations of the violating identifiers.
*/
@JsonProperty("secondaryLocations")
@Nullable
@ExportAsType("Array")
private List secondaryLocations;
/**
* Properties for this finding. Each finding can be associated with one or more
* properties describing details of the finding (e.g. length of long method,
* etc.). These properties are also displayed in the UI and can be used for
* sorting findings (in which case the value class must be comparable).
*/
@JsonProperty("properties")
private final Map properties = new HashMap<>();
/**
* @see #getStatementPath()
*/
@JsonProperty("statementPath")
@Nullable
private List statementPath = null;
/**
* The guidelines (e.g. AUTOSAR C++14) the check for this finding is part of as
* a mapping from guideline name to rules.
*/
@JsonProperty("guidelineMapping")
@Nullable
private SetMap guidelineMapping = null;
/** Constructor. */
public DetachedFinding(String groupName, String categoryName, String message, ElementLocation location) {
this(groupName, categoryName, message, location, null);
}
/**
* Constructor. Adds the given finding properties to the finding. The assessment
* may be null
.
*/
public DetachedFinding(String groupName, String categoryName, String message, ElementLocation location,
ETrafficLightColor assessment, Map findingProperties) {
this(groupName, categoryName, message, location, assessment);
properties.putAll(findingProperties);
}
/** Constructor. The assessment may be null
*/
@JsonCreator
public DetachedFinding(@JsonProperty(GROUP_NAME_PROPERTY) String groupName,
@JsonProperty(CATEGORY_NAME_PROPERTY) String categoryName, @JsonProperty(MESSAGE_PROPERTY) String message,
@JsonProperty(LOCATION_PROPERTY) ElementLocation location,
@JsonProperty(ASSESSMENT_PROPERTY) ETrafficLightColor assessment) {
CCSMAssert.isNotNull(location);
this.groupName = groupName;
this.categoryName = categoryName;
this.location = location;
this.assessment = assessment;
setMessage(message);
}
/** Copy constructor. */
protected DetachedFinding(DetachedFinding other) {
this(other.groupName, other.categoryName, other.message, other.location, other.assessment);
if (other.hasSiblings()) {
this.siblingLocations = new ArrayList<>(other.siblingLocations);
}
if (other.secondaryLocations != null) {
this.secondaryLocations = new ArrayList<>(other.secondaryLocations);
}
properties.putAll(other.properties);
statementPath = other.statementPath;
guidelineMapping = other.guidelineMapping;
}
/** Get group name. */
public String getGroupName() {
return groupName;
}
/** Sets group name. */
public void setGroupName(String groupName) {
this.groupName = groupName;
}
/** Get category name. */
public String getCategoryName() {
return categoryName;
}
/** Sets category name. */
public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}
/** Get location. */
public ElementLocation getLocation() {
return location;
}
/** Sets location. */
public void setLocation(ElementLocation location) {
CCSMAssert.isNotNull(location);
this.location = location;
}
/**
* Get Location Path string. (e.g src/main/java/org/foo/bar/Foo.java:189-204)
*/
public String getLocationPathString() {
return location.toString();
}
/** Get location string. (e.g src/main/java/org/foo/bar/Foo.java) */
public String getLocationString() {
return location.toLocationString();
}
/** Get message. */
public String getMessage() {
return message;
}
/**
* Setter for the {@link #message} that ensures findings messages are not too
* long for serialization causing a {@link UTFDataFormatException} in the
* FindingSerializationUtils.
*/
public void setMessage(String message) {
if (message.length() > MAX_MESSAGE_LENGTH) {
this.message = StringUtils.truncateWithEllipsis(message, MAX_MESSAGE_LENGTH);
LOGGER.error("Shortening overly long finding message of length {} for finding {}", message::length,
this::toString);
return;
}
this.message = message;
}
/** Returns whether this findings has siblings. */
public boolean hasSiblings() {
return siblingLocations != null && !siblingLocations.isEmpty();
}
/** Returns the locations of sibling findings. */
public List getSiblingLocations() {
if (siblingLocations == null) {
return CollectionUtils.emptyList();
}
return CollectionUtils.asUnmodifiable(siblingLocations);
}
/** Removes all sibling locations matching the given filter. */
public void removeMatchingSiblingLocations(Predicate super ElementLocation> filter) {
siblingLocations.removeIf(filter);
}
/**
* @see #secondaryLocations
*/
public List getSecondaryLocations() {
if (secondaryLocations == null) {
return CollectionUtils.emptyList();
}
return CollectionUtils.asUnmodifiable(secondaryLocations);
}
/**
* Adds all previously unknown locations of sibling findings (already known
* locations are skipped).
*/
public void addSiblingLocation(ElementLocation location) {
if (siblingLocations == null) {
siblingLocations = new ArrayList<>();
}
if (siblingLocationCache == null) {
siblingLocationCache = new HashSet<>(
CollectionUtils.map(siblingLocations, ElementLocation::toLocationString));
// This way, a finding can never be a sibling of itself
siblingLocationCache.add(getLocationString());
}
String locationString = location.toLocationString();
if (!siblingLocationCache.contains(locationString)) {
siblingLocations.add(location);
siblingLocationCache.add(locationString);
}
}
/**
* Adds the given list of locations as sibling findings.
*
* @param findings
* a collection of {@link DetachedFinding} that will be added as
* siblings.
*/
public void addSiblingFindings(Collection findings) {
findings.forEach(this::addSiblingFinding);
}
/** Add the given sibling locations */
public void addSiblingLocations(Collection siblingLocations) {
siblingLocations.forEach(this::addSiblingLocation);
}
/** Adds the given {@link DetachedFinding} to the list of sibling findings. */
public void addSiblingFinding(DetachedFinding sibling) {
addSiblingLocation(sibling.getLocation());
}
/** Adds a secondary location to this finding. */
public void addSecondaryLocation(ElementLocation location) {
if (secondaryLocations == null) {
secondaryLocations = new ArrayList<>();
}
secondaryLocations.add(location);
}
/** Adds the given locations as secondary locations. */
public void addSecondaryLocations(Collection locations) {
if (secondaryLocations == null) {
secondaryLocations = new ArrayList<>();
}
secondaryLocations.addAll(locations);
}
/** Returns the properties of this finding. */
public UnmodifiableMap getProperties() {
return CollectionUtils.asUnmodifiable(properties);
}
/** Sets a property for this finding. */
public void setProperty(String name, Object value) {
properties.put(name, value);
}
/**
* Sets the properties of this finding, overwriting any previously existing ones
*/
public void setProperties(Map properties) {
this.properties.clear();
addProperties(properties);
}
/**
* Adds a list of properties to this finding, in addition to any previously
* existing ones.
*/
public void addProperties(Map properties) {
this.properties.putAll(properties);
}
/**
* Get the property value for a given key. Returns null if no such property
* exists.
*/
public Object getProperty(String name) {
return properties.get(name);
}
/** Returns assessment. */
public ETrafficLightColor getAssessment() {
return assessment;
}
/** Sets the assessment. */
public void setAssessment(ETrafficLightColor assessment) {
this.assessment = assessment;
}
/**
* Returns a string representation that contains the, message, the group and and
* location hint.
*/
@Override
public String toString() {
return getMessage() + " (" + getGroupName() + ") @ " + location.toLocationString();
}
/**
* Returns the statement path if there is one attached to this finding.
*
* A statementPath is a control-flow path through the actual program that leads
* to this finding, comparable to a stacktrace, however, the path may not only
* be a simple chain, but could be a DAG.
*
* Each entry in the list refers to the indices of its predecessor statements.
* Contract for the setup of this structure:
*
* - An element might have more than one predecessor.
* - Multiple elements can have the same predecessor.
* - There are no cycles in the path.
*
*/
public List getStatementPath() {
if (statementPath == null) {
return CollectionUtils.emptyList();
}
return statementPath;
}
/** @see #getStatementPath() */
public void setStatementPath(List path) {
this.statementPath = path;
}
/** @see #guidelineMapping */
public void setGuidelineMapping(SetMap guidelineMapping) {
this.guidelineMapping = guidelineMapping;
}
/** @see #guidelineMapping */
public SetMap getGuidelineMapping() {
if (guidelineMapping == null) {
return new SetMap<>();
}
return guidelineMapping;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy