org.conqat.engine.index.shared.TrackedFinding 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.index.shared;
import java.util.Comparator;
import java.util.Optional;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.conqat.engine.commons.findings.DetachedFinding;
import org.conqat.engine.commons.findings.location.ElementLocation;
import org.conqat.engine.commons.findings.location.QualifiedNameLocation;
import org.conqat.engine.commons.findings.location.TextRegionLocation;
import org.conqat.lib.commons.js_export.ExportToTypeScript;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Preconditions;
/**
* A tracked finding extends the {@link DetachedFinding} by additional
* attributes that are calculated via tracking. The class is intended to be
* immutable, especially regarding the ID attribute. The
* {@link #setDeathCommit(CommitDescriptor)} method is exposed due to historical
* reasons, but is expected to be called only once in the lifecycle of a tracked
* finding.
*
* This class is used as DTO during communication with IDE clients via
* {@link com.teamscale.ide.commons.client.IIdeServiceClient}, special care has
* to be taken when changing its signature!
*/
@ExportToTypeScript
public class TrackedFinding extends IndexFinding implements Comparable {
/** Version used for serialization. */
private static final long serialVersionUID = 1;
/** The name of the JSON property name for {@link #id}. */
protected static final String ID_PROPERTY = "id";
/** The name of the JSON property name for {@link #birth}. */
protected static final String BIRTH_PROPERTY = "birth";
/** The name of the JSON property name for {@link #death}. */
protected static final String DEATH_PROPERTY = "death";
/**
* The partition in the finding index of this finding. We have to store it in
* the index (hence it is not transient, to include it in Java serialization),
* but we do not want our clients to see it (hence the JsonIgnore annotation).
*/
@JsonIgnore
private final String findingIndexPartition;
/**
* The ID of this finding. This is a hexadecimal representation of a MD5 hash
* code.
*/
@JsonProperty(ID_PROPERTY)
private final String id;
/** Commit of the birth of this finding. */
@JsonProperty(BIRTH_PROPERTY)
private final CommitDescriptor birth;
/**
* Commit of the death of this finding, i.e. the first commit where this did not
* exist anymore. While this is null
the finding is still alive.
*/
@JsonProperty(DEATH_PROPERTY)
@Nullable
private CommitDescriptor death;
public TrackedFinding(IndexFinding finding, String id, CommitDescriptor birth, String findingIndexPartition) {
this(finding, id, birth, findingIndexPartition, null);
}
public TrackedFinding(IndexFinding finding, String id, CommitDescriptor birth, String findingIndexPartition,
CommitDescriptor death) {
super(finding);
this.id = id;
this.birth = birth;
this.findingIndexPartition = findingIndexPartition;
this.death = death;
}
/** Copy constructor. */
protected TrackedFinding(TrackedFinding other) {
super(other);
this.id = other.id;
this.birth = other.birth;
this.death = other.death;
this.findingIndexPartition = other.findingIndexPartition;
}
@JsonCreator
public TrackedFinding(@JsonProperty(GROUP_NAME_PROPERTY) String groupName,
@JsonProperty(CATEGORY_NAME_PROPERTY) String categoryName, @JsonProperty(MESSAGE_PROPERTY) String message,
@JsonProperty(LOCATION_PROPERTY) ElementLocation location, @JsonProperty(ID_PROPERTY) String id,
@JsonProperty(BIRTH_PROPERTY) CommitDescriptor birth,
@JsonProperty(DEATH_PROPERTY) CommitDescriptor death) {
super(groupName, categoryName, message, location);
this.id = id;
this.birth = birth;
this.death = death;
this.findingIndexPartition = null;
}
/** Returns the ID of this finding. */
public String getId() {
return id;
}
/**
* Adds the finding id to the toString method of the super class.
*/
public String toStringWithId() {
return super.toString() + " (" + id + ")";
}
/** Returns the birth commit. */
public CommitDescriptor getBirthCommit() {
return birth;
}
/** Returns the partition in the findings index of this finding. */
public String getFindingIndexPartition() {
return findingIndexPartition;
}
/**
* Returns the death commit (or null for findings that are still alive).
*/
public CommitDescriptor getDeathCommit() {
return death;
}
/**
* Sets the death commit. Use null
to indicate that it is still
* alive.
*/
public void setDeathCommit(CommitDescriptor death) {
this.death = death;
}
/** Returns whether this finding is still alive. */
public boolean isAlive() {
return death == null;
}
/**
* {@inheritDoc}
*
* Sorts by ID.
*/
@Override
public int compareTo(TrackedFinding other) {
return id.compareTo(other.id);
}
/**
* Returns the qualified group name, i.e. the concatenation of the category and
* group separated by /
*/
public String getQualifiedGroupName() {
return getCategoryName() + "/" + getGroupName();
}
/** A basic comparator used for TrackedFindings. */
public static class TrackedFindingComparator implements Comparator {
@Override
public int compare(TrackedFinding f1, TrackedFinding f2) {
String p1 = f1.getLocation().getUniformPath();
String p2 = f2.getLocation().getUniformPath();
if (f1.getLocation() instanceof QualifiedNameLocation) {
p1 = f1.getLocation().toString();
}
if (f2.getLocation() instanceof QualifiedNameLocation) {
p2 = f2.getLocation().toString();
}
int pathComparison = p1.compareTo(p2);
if (pathComparison != 0) {
return pathComparison;
}
if (f1.getLocation() instanceof TextRegionLocation && f2.getLocation() instanceof TextRegionLocation) {
TextRegionLocation l1 = (TextRegionLocation) f1.getLocation();
TextRegionLocation l2 = (TextRegionLocation) f2.getLocation();
int startDiff = l1.getRawStartLine() - l2.getRawStartLine();
if (startDiff == 0) {
return l1.getRawEndLine() - l2.getRawEndLine();
}
return startDiff;
}
if (f1.getLocation() instanceof TextRegionLocation) {
return -1;
}
if (f2.getLocation() instanceof TextRegionLocation) {
return 1;
}
return 0;
}
}
/** Equality only depends on the ID attribute of the finding. */
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
TrackedFinding that = (TrackedFinding) o;
return id.equals(that.id);
}
/** Hash code only depends on the ID attribute of the finding. */
@Override
public int hashCode() {
return id.hashCode();
}
/**
* A builder for immutable {@link TrackedFinding}s. Requires an index finding as
* a starting point.
*/
public static class Builder {
/**
* The index finding used as a starting point for building. May be
* null
, in this case {@link #originalTrackedFinding} is always
* set.
*/
private IndexFinding originalIndexFinding;
/**
* The tracked finding used as a starting point for building. May be
* null
, in this case {@link #originalIndexFinding} is always set.
*/
private TrackedFinding originalTrackedFinding;
private String id;
private String findingIndexPartition;
private CommitDescriptor birthCommit;
private CommitDescriptor deathCommit;
/** Hidden constructor, use {@link #from} instead. */
private Builder() {
// empty
}
/** Returns a new builder from the given index finding. */
public static Builder from(IndexFinding indexFinding) {
Builder builder = new Builder();
builder.originalIndexFinding = indexFinding;
return builder;
}
/** Returns a new builder from the given tracked finding. */
public static Builder from(TrackedFinding trackedFinding) {
Builder builder = new Builder();
builder.originalTrackedFinding = trackedFinding;
builder.id = trackedFinding.id;
builder.findingIndexPartition = trackedFinding.findingIndexPartition;
builder.birthCommit = trackedFinding.birth;
builder.deathCommit = trackedFinding.death;
return builder;
}
/** Adds an ID to this builder. */
public Builder withId(String id) {
this.id = id;
return this;
}
/** Adds a finding index partition to this builder. */
public Builder withFindingIndexPartition(String findingIndexPartition) {
this.findingIndexPartition = findingIndexPartition;
return this;
}
/** Adds a birth commit to this builder. */
public Builder withBirthCommit(CommitDescriptor birthCommit) {
this.birthCommit = birthCommit;
return this;
}
/** Adds a death commit to this builder. */
public Builder withDeathCommit(CommitDescriptor deathCommit) {
this.deathCommit = deathCommit;
return this;
}
/**
* Builds and returns an immutable tracked finding from the values stored in
* this builder.
*/
public TrackedFinding build() {
IndexFinding originalFinding = Optional.ofNullable(originalIndexFinding).orElse(originalTrackedFinding);
Preconditions.checkNotNull(id);
Preconditions.checkNotNull(birthCommit);
Preconditions.checkNotNull(findingIndexPartition);
return new TrackedFinding(originalFinding, id, birthCommit, findingIndexPartition, deathCommit);
}
}
}