org.conqat.engine.commons.findings.location.TextRegionLocation Maven / Gradle / Ivy
Show all versions of teamscale-commons Show documentation
/*
* 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.location;
import java.io.Serial;
import java.util.Objects;
import org.conqat.lib.commons.assertion.CCSMAssert;
import org.conqat.lib.commons.test.IndexValueClass;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* This class denotes a region of text in an element.
*
* Context: Due to the way ConQAT deals with text, the class is a little bit more complex
* than expected. First, in ConQAT all text is normalized to use Unix style line endings (regardless
* of the line endings in the file). Second, ConQAT may apply filters, i.e. the internal (filtered)
* text representation may be different from the (raw) text in the file. Additionally, a location is
* best described by character offsets into the string, while a user typically expects line numbers.
* Conversion between all these representations is easy, as long as ConQAT internal representation
* is available. Without it, conversion is not possible.
*
* Rationale: When findings are reported to a user, the raw offsets and/or lines should be
* used, as these are more meaningful (visible in other editors as well). Also for persisting in a
* report, the raw positions are preferred, as the filtered ones depend on the ConQAT configuration,
* while raw offsets are independent of filter configuration. When working with findings within
* ConQAT, typically the filtered positions are needed, as most processors also work on the filtered
* representation. However, in such a case, the corresponding element is typically available and
* thus conversion to the filtered representation is easy.
*
* Implementation: The finding (as well as the findings report) only stores raw positions.
* While the offsets would be sufficient, we also store line numbers to be able to provide
* meaningful user output. Filtered positions are not stored, but are made available via utility
* methods in the resource bundle. All fields are mandatory, i.e., it is not allowed to fill any
* position entry with invalid data (contrary to the old CodeRegionLocation, where -1 could be used
* to denote missing information).
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", defaultImpl = TextRegionLocation.class)
@JsonSubTypes(@JsonSubTypes.Type(value = TeamscaleIssueFieldLocation.class, name = "TeamscaleIssueFieldLocation"))
@IndexValueClass(containedInBackup = true)
// For some reason, the OpenApi generator does not recognize the superclass,
// so add it here again
@Schema(allOf = ElementLocation.class)
public class TextRegionLocation extends ElementLocation {
/**
* Version used for serialization.
*/
@Serial
private static final long serialVersionUID = 1;
/**
* An unknown offset used to indicate that the line information is to be used to calculate character
* offset from.
*/
public static final int UNKNOWN_TEXT_REGION_OFFSET = -1;
/**
* The name of the JSON property name for {@link #rawStartOffset}.
*/
private static final String RAW_START_OFFSET_PROPERTY = "rawStartOffset";
/**
* The name of the JSON property name for {@link #rawEndOffset}.
*/
private static final String RAW_END_OFFSET_PROPERTY = "rawEndOffset";
/**
* The name of the JSON property name for {@link #rawStartLine}.
*/
private static final String RAW_START_LINE_PROPERTY = "rawStartLine";
/**
* The name of the JSON property name for {@link #rawEndLine}.
*/
private static final String RAW_END_LINE_PROPERTY = "rawEndLine";
/**
* The absolute start position of the region in the (raw) text (zero based, inclusive).
*/
@JsonProperty(RAW_START_OFFSET_PROPERTY)
private final int rawStartOffset;
/**
* The absolute end position in the (raw) text (zero based, inclusive).
*/
@JsonProperty(RAW_END_OFFSET_PROPERTY)
private final int rawEndOffset;
/**
* The line corresponding to {@link #rawStartOffset} (one-based, inclusive).
*/
@JsonProperty(RAW_START_LINE_PROPERTY)
private final int rawStartLine;
/**
* The line corresponding to {@link #rawEndOffset} (one-based, inclusive).
*/
@JsonProperty(RAW_END_LINE_PROPERTY)
private final int rawEndLine;
public TextRegionLocation(TextRegionLocation location) {
super(location.getUniformPath());
rawStartOffset = location.rawStartOffset;
rawEndOffset = location.rawEndOffset;
rawStartLine = location.rawStartLine;
rawEndLine = location.rawEndLine;
}
/**
* Creates a new location in a text-based file.
*
* Offsets are 0-based character offsets from the start of the file (in a normalized file, i.e.,
* "\r\n" has been replaced by "\n").
*
* Line numbers are 1-based (i.e., code in the first line has line number 1).
*/
@JsonCreator
public TextRegionLocation(@JsonProperty(UNIFORM_PATH_PROPERTY) String uniformPath,
@JsonProperty(RAW_START_OFFSET_PROPERTY) int rawStartOffset,
@JsonProperty(RAW_END_OFFSET_PROPERTY) int rawEndOffset,
@JsonProperty(RAW_START_LINE_PROPERTY) int rawStartLine,
@JsonProperty(RAW_END_LINE_PROPERTY) int rawEndLine) {
super(uniformPath);
CCSMAssert.isTrue(rawStartOffset <= rawEndOffset, "Start offset may not be after end offset.");
CCSMAssert.isTrue(rawStartLine <= rawEndLine, "Start line may not be after end line.");
this.rawStartOffset = rawStartOffset;
this.rawEndOffset = rawEndOffset;
this.rawStartLine = rawStartLine;
this.rawEndLine = rawEndLine;
}
/**
* Returns the absolute start position of the region in the (raw) text (zero based, inclusive).
*/
public int getRawStartOffset() {
return rawStartOffset;
}
/**
* Returns the absolute end position in the (raw) text (zero based, inclusive).
*/
public int getRawEndOffset() {
return rawEndOffset;
}
/**
* Returns the line corresponding to {@link #getRawStartOffset()} (one-based, inclusive).
*/
public int getRawStartLine() {
return rawStartLine;
}
/**
* Returns the line corresponding to {@link #getRawEndOffset()} (one-based, inclusive).
*/
public int getRawEndLine() {
return rawEndLine;
}
/**
* {@inheritDoc}
*
* This includes the start and end line which is typically sufficient for debugging and showing to a
* user.
*/
@Override
public String toLocationString() {
return super.toLocationString() + ElementLocation.INTERNAL_PATH_SEPARATOR + rawStartLine + "-" + rawEndLine;
}
@Override
public String getLocationKey() {
return toLocationString() + " (offsets: " + rawStartOffset + "-" + rawEndOffset + ")";
}
@Override
protected boolean canEqual(Object other) {
return other instanceof TextRegionLocation;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof TextRegionLocation that)) {
return false;
}
return that.canEqual(this) && rawStartOffset == that.rawStartOffset && rawEndOffset == that.rawEndOffset
&& rawStartLine == that.rawStartLine && rawEndLine == that.rawEndLine && super.equals(that);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), rawStartOffset, rawEndOffset, rawStartLine, rawEndLine);
}
}