org.conqat.engine.commons.findings.location.ManualTestCaseTextRegionLocation 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
The newest version!
package org.conqat.engine.commons.findings.location;
import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.conqat.lib.commons.assertion.CCSMAssert;
import org.conqat.lib.commons.collections.CollectionUtils;
import org.conqat.lib.commons.collections.UnmodifiableList;
import org.conqat.lib.commons.string.StringUtils;
import org.conqat.lib.commons.test.IndexValueClass;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
/**
* This class represents a region of text in the test steps table in a manual test case description.
* It specifies
*
* - the indices of the test steps (table rows) where the text region starts and ends
* - whether it starts and ends in the 'action' or the 'check' part (table column) of the
* respective start and end test steps.
* - the locations, if any, of gaps within the overall text region, e.g., for gapped clones.
*
* In principle, there are three kinds of supported locations, which are listed below in decreasing
* order of specificity. Each location kind subsumes the one listed above.
*
* - Single cell locations: The text region starts and ends within the same table cell,
* i.e., start and end test steps (table row) as well as start and end columns are the same.
* - Single step locations: The text region starts and ends within the same table row,
* i.e., start and end test steps are the same but start and end columns are different (start in
* 'action' and end in 'check').
* - Unrestricted locations: The text region starts and ends in different table rows, i.e.,
* start and end steps are different while start and end columns may or may not be different.
*
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", defaultImpl = ManualTestCaseTextRegionLocation.class)
@IndexValueClass(containedInBackup = true)
public class ManualTestCaseTextRegionLocation extends TeamscaleIssueFieldLocation {
@Serial
private static final long serialVersionUID = 1L;
/**
* The table cell where the text region starts.
*/
@JsonProperty("startTestStepIdentifier")
private final @NonNull TestStepCellIdentifier startTestStepIdentifier;
/**
* The table cell where the text region ends.
*/
@JsonProperty("endTestStepIdentifier")
private final @NonNull TestStepCellIdentifier endTestStepIdentifier;
/** The gaps within the overall text region, if any. */
@JsonProperty("gapLocations")
private final @NonNull UnmodifiableList gapLocations;
/**
* Constructs a text region location with potentially different start and end test steps and step
* parts ('action' or 'check').
*/
public ManualTestCaseTextRegionLocation(TeamscaleIssueFieldLocation fieldLocation, int startStepIndex,
boolean startStepAction, int endStepIndex, boolean endStepAction,
@Nullable List gapLocations) {
super(fieldLocation);
CCSMAssert.isTrue(startStepIndex >= 0 && endStepIndex >= 0,
() -> String.format("Test steps cannot be negative (start=%d, end=%d).", startStepIndex, endStepIndex));
CCSMAssert.isTrue(startStepIndex <= endStepIndex, () -> String
.format("Start step (%d) cannot be greater than end step (%d).", startStepIndex, endStepIndex));
if (startStepIndex == endStepIndex) {
CCSMAssert.isTrue(startStepAction == endStepAction || startStepAction,
"Single step locations cannot start in 'check' and end in 'action'.");
}
startTestStepIdentifier = new TestStepCellIdentifier(startStepIndex, startStepAction);
endTestStepIdentifier = new TestStepCellIdentifier(endStepIndex, endStepAction);
if (gapLocations == null) {
this.gapLocations = CollectionUtils.emptyList();
} else {
this.gapLocations = new UnmodifiableList<>(new ArrayList<>(gapLocations));
}
}
/**
* Constructs a text region location with potentially different start and end test steps and step
* parts ('action' or 'check').
*/
public ManualTestCaseTextRegionLocation(String uniformPath, String issueId, RawAndFieldSpecific startOffset,
RawAndFieldSpecific endOffset, RawAndFieldSpecific startLine, RawAndFieldSpecific endLine,
int startStepIndex, boolean startStepAction, int endStepIndex, boolean endStepAction,
String testStepsFieldName, @Nullable List gapLocations) {
this(new TeamscaleIssueFieldLocation(uniformPath, issueId, startOffset, endOffset, startLine, endLine,
testStepsFieldName), startStepIndex, startStepAction, endStepIndex, endStepAction, gapLocations);
}
/**
* Returns the 0-based index of the test step where the text regions starts.
*/
public int getStartStepIndex() {
return startTestStepIdentifier.index;
}
/**
* Returns the 0-based index of the test step where the text regions ends.
*/
public int getEndStepIndex() {
return endTestStepIdentifier.index;
}
/**
* Returns {@code true} if the text region starts in the 'action' part of the start test step and
* {@code false} if it starts in the 'check' part.
*/
public boolean startsInStepAction() {
return startTestStepIdentifier.isAction;
}
/**
* Returns {@code true} if the text region ends in the 'action' part of the end test step and
* {@code false} if it ends in the 'check' part.
*/
public boolean endsInStepAction() {
return endTestStepIdentifier.isAction;
}
/**
* Returns {@code true} if the text region resides within a single test step cell and {@code false}
* otherwise.
*/
public boolean isSingleCellLocation() {
return isSingleStepLocation() && startTestStepIdentifier.isAction == endTestStepIdentifier.isAction;
}
/**
* Returns {@code true} if the text region resides within a single test step and {@code false}
* otherwise.
*/
public boolean isSingleStepLocation() {
return startTestStepIdentifier.index == endTestStepIdentifier.index;
}
/**
* Returns the gaps contained in this location. The returned list may be empty.
*/
public @NonNull UnmodifiableList getGapLocations() {
return gapLocations;
}
@Override
public String toLocationString() {
String fieldPath = String.join(ElementLocation.INTERNAL_PATH_SEPARATOR, getUniformPath(), getAffectedField());
String startStep = String.valueOf(getStartStepIndex() + 1); // User visible step index starts from 1
String startColumn = StringUtils.alternativeOnCondition(startsInStepAction(), "action", "check");
if (isSingleCellLocation()) {
return String.join(ElementLocation.INTERNAL_PATH_SEPARATOR, fieldPath, startStep, startColumn,
getFieldStartLine() + "-" + getFieldEndLine());
} else if (isSingleStepLocation()) {
String endColumn = StringUtils.alternativeOnCondition(endsInStepAction(), "action", "check");
return String.join(ElementLocation.INTERNAL_PATH_SEPARATOR, fieldPath, startStep,
startColumn + "@" + getFieldStartLine() + "-" + endColumn + "@" + getFieldEndLine());
} else {
String endColumn = StringUtils.alternativeOnCondition(endsInStepAction(), "action", "check");
String endStep = String.valueOf(getEndStepIndex() + 1); // User visible step index starts from 1
return String.join(ElementLocation.INTERNAL_PATH_SEPARATOR, fieldPath, startStep + "|" + startColumn + "@"
+ getFieldStartLine() + "-" + endStep + "|" + endColumn + "@" + getFieldEndLine());
}
}
@Override
protected boolean canEqual(Object other) {
return other instanceof ManualTestCaseTextRegionLocation;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ManualTestCaseTextRegionLocation that)) {
return false;
}
return that.canEqual(this) && Objects.equals(startTestStepIdentifier, that.startTestStepIdentifier)
&& Objects.equals(endTestStepIdentifier, that.endTestStepIdentifier)
&& Objects.equals(gapLocations, that.gapLocations) && super.equals(o);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), startTestStepIdentifier, endTestStepIdentifier, gapLocations);
}
/**
* Identifier for a cell in the test steps table. Note that the table row index is 0-based.
*/
@IndexValueClass(containedInBackup = true)
public static final class TestStepCellIdentifier implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* The 0-based index of the test step inside the test case description (table row).
*/
@JsonProperty("index")
public final int index;
/**
* Differentiates between 'action' or 'check' column. {@code true} means 'action', {@code false}
* means 'check'.
*/
@JsonProperty("isAction")
public final boolean isAction;
public TestStepCellIdentifier(int index, boolean isAction) {
this.index = index;
this.isAction = isAction;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof TestStepCellIdentifier that)) {
return false;
}
return index == that.index && isAction == that.isAction;
}
@Override
public int hashCode() {
return Objects.hash(index, isAction);
}
@Override
public String toString() {
return index + "/" + StringUtils.alternativeOnCondition(isAction, "action", "check");
}
}
/**
* A gap location within a text region inside the test step table. Gap locations work in principle
* the same as the {@link ManualTestCaseTextRegionLocation} itself.
*/
@IndexValueClass(containedInBackup = true)
public static final class GapLocation implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** The cell where the gap starts. */
@JsonProperty("startCell")
public final TestStepCellIdentifier startCell;
/** The cell where the gap ends. */
@JsonProperty("endCell")
public final TestStepCellIdentifier endCell;
/** The start offset within {@link #startCell}, 0-based inclusive. */
@JsonProperty("fieldStartOffset")
public final int startOffset;
/** The end offset within {@link #endCell}, 0-based inclusive. */
@JsonProperty("fieldEndOffset")
public final int endOffset;
public GapLocation(TestStepCellIdentifier startCell, TestStepCellIdentifier endCell, int startOffset,
int endOffset) {
this.startCell = startCell;
this.endCell = endCell;
this.startOffset = startOffset;
this.endOffset = endOffset;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof GapLocation that)) {
return false;
}
return Objects.equals(startCell, that.startCell) && Objects.equals(endCell, that.endCell)
&& startOffset == that.startOffset && endOffset == that.endOffset;
}
@Override
public int hashCode() {
return Objects.hash(startCell, endCell, startOffset, endOffset);
}
@Override
public String toString() {
return "GapLocation[" + startCell + "@" + startOffset + "-" + endCell + "@" + endOffset + "]";
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy