All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.conqat.engine.commons.findings.DetachedFinding Maven / Gradle / Ivy

There is a newer version: 2025.1.0-rc2
Show newest version
/*
 * 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.engine.commons.findings.location.ManualTestCaseTextRegionLocation;
import org.conqat.engine.commons.findings.location.QualifiedNameLocation;
import org.conqat.engine.commons.findings.location.TeamscaleIssueFieldLocation;
import org.conqat.engine.commons.findings.location.TeamscaleIssueLocation;
import org.conqat.engine.commons.findings.location.TextRegionLocation;
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.string.StringUtils;
import org.conqat.lib.commons.test.IndexValueClass;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Schema;

/**
 * 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!
 */
@IndexValueClass(containedInBackup = true)
public class DetachedFinding implements Serializable {

	// Class argument necessary due to
	// .
	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)
	@Schema(implementation = ElementLocationSubtype.class)
	private ElementLocation location;

	/** The group name. */
	@JsonProperty(GROUP_NAME_PROPERTY)
	private String groupName;

	/** The category name. */
	@JsonProperty(CATEGORY_NAME_PROPERTY)
	private String categoryName;

	/**
	 * The finding message (the title line in the findings detail page).
	 * 

* This text is in markdown format (e.g., __bold__ will be displayed in bold font). To * avoid that markdown symbols are interpreted, use * {@link org.conqat.lib.commons.markup.MarkupUtils#escapeMarkdownRelevantSymbols(String)} before * creating the {@link DetachedFinding}. */ @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 @Schema(implementation = ElementLocations.class) 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 @Schema(implementation = ElementLocations.class) 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") @Schema(additionalProperties = Schema.AdditionalPropertiesValue.USE_ADDITIONAL_PROPERTIES_ANNOTATION, oneOf = { String.class, Number.class }) 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 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::getLocationKey)); // This way, a finding can never be a sibling of itself siblingLocationCache.add(this.location.getLocationKey()); } String locationString = location.getLocationKey(); 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 Map 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 a 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; } /* * The following interfaces are required to correctly generate the OpenAPI spec. Otherwise, it is * not possible to set the correct value type of the finding location. * * See: https://github.com/swagger-api/swagger-core/issues/3080 * * See: https://stackoverflow.com/a/68752621 */ @Schema(oneOf = { ElementLocation.class, TextRegionLocation.class, QualifiedNameLocation.class, TeamscaleIssueLocation.class, ManualTestCaseTextRegionLocation.class, TeamscaleIssueFieldLocation.class }) private interface ElementLocationSubtype { } @ArraySchema(schema = @Schema(implementation = ElementLocationSubtype.class)) private interface ElementLocations extends List { } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy