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.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.
*/
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
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
TextRegionLocation that = (TextRegionLocation) o;
return super.equals(that) && rawStartOffset == that.rawStartOffset && rawEndOffset == that.rawEndOffset
&& rawStartLine == that.rawStartLine && rawEndLine == that.rawEndLine;
}
@Override
public int hashCode() {
return super.hashCode() * 13 + Objects.hash(rawStartOffset, rawEndOffset, rawStartLine, rawEndLine);
}
}