001/*-----------------------------------------------------------------------+ 002 | com.teamscale.index 003 | | 004 $Id$ 005 | | 006 | Copyright (c) 2009-2012 CQSE GmbH | 007 +-----------------------------------------------------------------------*/ 008package org.conqat.engine.index.shared; 009 010import java.io.Serializable; 011import java.util.Comparator; 012 013import javax.annotation.Nullable; 014 015import org.conqat.engine.commons.findings.DetachedFinding; 016import org.conqat.engine.commons.findings.location.ElementLocation; 017import org.conqat.engine.commons.findings.location.TextRegionLocation; 018import org.conqat.lib.commons.js_export.ExportToJavaScript; 019 020import com.fasterxml.jackson.annotation.JsonCreator; 021import com.fasterxml.jackson.annotation.JsonIgnore; 022import com.fasterxml.jackson.annotation.JsonProperty; 023import com.thoughtworks.xstream.annotations.XStreamOmitField; 024 025/** 026 * A tracked finding extends the {@link DetachedFinding} by additional 027 * attributes that are calculated via tracking. 028 * 029 * <strong>This class is used for communication with IDE clients (via the 030 * {@link org.conqat.engine.service.shared.client.IdeServiceClient}), so special 031 * care has to be taken when changing its signature!</strong> 032 */ 033@ExportToJavaScript 034public class TrackedFinding extends IndexFinding implements Serializable, Comparable<TrackedFinding> { 035 036 private static final String EQUALS_HASHCODE_NOT_SUPPORTED_MESSAGE = "Tracked findings should never be used in HashSets, " 037 + "as equality should rely on the id, but the id must be mutable (which can cause havok when the object is part of a HashSet). " 038 + "Please use a map with id as key instead!"; 039 040 /** Version used for serialization. */ 041 private static final long serialVersionUID = 1; 042 043 /** The name of the JSON property name for {@link #id}. */ 044 private static final String ID_PROPERTY = "id"; 045 046 /** The name of the JSON property name for {@link #birth}. */ 047 private static final String BIRTH_PROPERTY = "birth"; 048 049 /** The name of the JSON property name for {@link #death}. */ 050 private static final String DEATH_PROPERTY = "death"; 051 052 /** 053 * The partition in the finding index of this finding. We have to store it in 054 * the index (hence it is not transient, to include it in Java serialization), 055 * but we do not want our clients to see it (hence the JsonIgnore annotation). 056 */ 057 @XStreamOmitField 058 @JsonIgnore 059 private final String findingIndexPartition; 060 061 /** 062 * The id of this finding. This is a hexadecimal representation of a MD5 hash 063 * code. 064 */ 065 @JsonProperty(ID_PROPERTY) 066 private String id; 067 068 /** Commit of the birth of this finding. */ 069 @JsonProperty(BIRTH_PROPERTY) 070 private final CommitDescriptor birth; 071 072 /** 073 * Commit of the death of this finding, i.e. the first commit where this did not 074 * exist anymore. While this is <code>null</code> the finding is still alive. 075 */ 076 @JsonProperty(DEATH_PROPERTY) 077 @Nullable 078 private CommitDescriptor death = null; 079 080 public TrackedFinding(IndexFinding finding, String id, CommitDescriptor birthCommit, String findingIndexPartition) { 081 super(finding); 082 this.id = id; 083 this.birth = birthCommit; 084 this.findingIndexPartition = findingIndexPartition; 085 } 086 087 public TrackedFinding(IndexFinding finding, String id, CommitDescriptor birth, String findingIndexPartition, 088 CommitDescriptor death) { 089 this(finding, id, birth, findingIndexPartition); 090 this.death = death; 091 } 092 093 /** Copy constructor. */ 094 protected TrackedFinding(TrackedFinding other) { 095 super(other); 096 this.id = other.id; 097 this.birth = other.birth; 098 this.death = other.death; 099 this.findingIndexPartition = other.findingIndexPartition; 100 } 101 102 @JsonCreator 103 public TrackedFinding(@JsonProperty(GROUP_NAME_PROPERTY) String groupName, 104 @JsonProperty(CATEGORY_NAME_PROPERTY) String categoryName, @JsonProperty(MESSAGE_PROPERTY) String message, 105 @JsonProperty(LOCATION_PROPERTY) ElementLocation location, @JsonProperty(ID_PROPERTY) String id, 106 @JsonProperty(BIRTH_PROPERTY) CommitDescriptor birthCommit, 107 @JsonProperty(DEATH_PROPERTY) CommitDescriptor deathCommit) { 108 super(groupName, categoryName, message, location); 109 this.id = id; 110 this.birth = birthCommit; 111 this.death = deathCommit; 112 this.findingIndexPartition = null; 113 } 114 115 /** Returns the id of this finding. */ 116 public String getId() { 117 return id; 118 } 119 120 public void setId(String id) { 121 this.id = id; 122 } 123 124 /** Returns the birth commit. */ 125 public CommitDescriptor getBirthCommit() { 126 return birth; 127 } 128 129 /** Returns the partition in the findings index of this finding. */ 130 public String getFindingIndexPartition() { 131 return findingIndexPartition; 132 } 133 134 /** 135 * Returns the death commit (or null for findings that are still alive). 136 */ 137 public CommitDescriptor getDeathCommit() { 138 return death; 139 } 140 141 /** 142 * Sets the death commit. Use <code>null</code> to indicate that it is still 143 * alive. 144 */ 145 public void setDeathCommit(CommitDescriptor death) { 146 this.death = death; 147 } 148 149 /** Returns whether this finding is still alive. */ 150 public boolean isAlive() { 151 return death == null; 152 } 153 154 /** 155 * {@inheritDoc} 156 * <p> 157 * Sorts by ID. 158 */ 159 @Override 160 public int compareTo(TrackedFinding other) { 161 return id.compareTo(other.id); 162 } 163 164 /** 165 * Returns the qualified group name, i.e. the concatenation of the category and 166 * group separated by / 167 */ 168 public String getQualifiedGroupName() { 169 return getCategoryName() + "/" + getGroupName(); 170 } 171 172 /** A basic comparator used for TrackedFindings. */ 173 public static class TrackedFindingComparator implements Comparator<TrackedFinding> { 174 175 @Override 176 public int compare(TrackedFinding f1, TrackedFinding f2) { 177 String p1 = f1.getLocation().getUniformPath(); 178 String p2 = f2.getLocation().getUniformPath(); 179 int pathComparison = p1.compareTo(p2); 180 if (pathComparison != 0) { 181 return pathComparison; 182 } 183 184 if (f1.getLocation() instanceof TextRegionLocation && f2.getLocation() instanceof TextRegionLocation) { 185 TextRegionLocation l1 = (TextRegionLocation) f1.getLocation(); 186 TextRegionLocation l2 = (TextRegionLocation) f2.getLocation(); 187 int startDiff = l1.getRawStartLine() - l2.getRawStartLine(); 188 if (startDiff == 0) { 189 return l1.getRawEndLine() - l2.getRawEndLine(); 190 } 191 return startDiff; 192 } 193 if (f1.getLocation() instanceof TextRegionLocation) { 194 return -1; 195 } 196 if (f2.getLocation() instanceof TextRegionLocation) { 197 return 1; 198 } 199 return 0; 200 } 201 } 202 203 @Override 204 public boolean equals(Object obj) { 205 throw new UnsupportedOperationException(EQUALS_HASHCODE_NOT_SUPPORTED_MESSAGE); 206 } 207 208 @Override 209 public int hashCode() { 210 throw new UnsupportedOperationException(EQUALS_HASHCODE_NOT_SUPPORTED_MESSAGE); 211 } 212}