001/*-------------------------------------------------------------------------+ 002| | 003| Copyright 2005-2011 the ConQAT Project | 004| | 005| Licensed under the Apache License, Version 2.0 (the "License"); | 006| you may not use this file except in compliance with the License. | 007| You may obtain a copy of the License at | 008| | 009| http://www.apache.org/licenses/LICENSE-2.0 | 010| | 011| Unless required by applicable law or agreed to in writing, software | 012| distributed under the License is distributed on an "AS IS" BASIS, | 013| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 014| See the License for the specific language governing permissions and | 015| limitations under the License. | 016+-------------------------------------------------------------------------*/ 017package org.conqat.engine.commons.findings; 018 019import java.io.Serializable; 020import java.util.ArrayList; 021import java.util.Collection; 022import java.util.HashMap; 023import java.util.HashSet; 024import java.util.List; 025import java.util.Map; 026import java.util.Set; 027import java.util.function.Predicate; 028 029import javax.annotation.Nullable; 030 031import org.conqat.engine.commons.findings.location.ElementLocation; 032import org.conqat.lib.commons.assertion.CCSMAssert; 033import org.conqat.lib.commons.assessment.ETrafficLightColor; 034import org.conqat.lib.commons.collections.CollectionUtils; 035import org.conqat.lib.commons.collections.UnmodifiableMap; 036import org.conqat.lib.commons.js_export.ExportAsType; 037import org.conqat.lib.commons.js_export.ExportToJavaScript; 038 039import com.fasterxml.jackson.annotation.JsonCreator; 040import com.fasterxml.jackson.annotation.JsonProperty; 041 042/** 043 * This class describes orphaned finding that is attached to a node or a 044 * findings report. This is useful if e.g. findings have been filtered but 045 * certain operations should still be carried out on the findings. 046 */ 047@ExportToJavaScript 048public class DetachedFinding implements Serializable { 049 050 /** Serial version UID. */ 051 private static final long serialVersionUID = 1; 052 053 /** The name of the JSON property name for {@link #location}. */ 054 protected static final String LOCATION_PROPERTY = "location"; 055 056 /** The name of the JSON property name for {@link #groupName}. */ 057 protected static final String GROUP_NAME_PROPERTY = "groupName"; 058 059 /** The name of the JSON property name for {@link #categoryName}. */ 060 protected static final String CATEGORY_NAME_PROPERTY = "categoryName"; 061 062 /** The name of the JSON property name for {@link #message}. */ 063 protected static final String MESSAGE_PROPERTY = "message"; 064 065 /** The name of the JSON property name for {@link #assessment}. */ 066 private static final String ASSESSMENT_PROPERTY = "assessment"; 067 068 /** The location. */ 069 @JsonProperty(LOCATION_PROPERTY) 070 @ExportAsType("!ts.data.ElementLocation|!ts.data.TextRegionLocation|!ts.data.QualifiedNameLocation") 071 private ElementLocation location; 072 073 /** The group name. */ 074 @JsonProperty(GROUP_NAME_PROPERTY) 075 private String groupName; 076 077 /** The category name. */ 078 @JsonProperty(CATEGORY_NAME_PROPERTY) 079 private String categoryName; 080 081 /** The message. */ 082 @JsonProperty(MESSAGE_PROPERTY) 083 private String message; 084 085 /** The assessment color of the finding (may be null). */ 086 @JsonProperty(ASSESSMENT_PROPERTY) 087 @Nullable 088 private ETrafficLightColor assessment; 089 090 /** 091 * The locations of other findings that are considered siblings. This is, e.g., 092 * used to find other clone instances in the same clone class. As this is 093 * commonly empty, we keep this attribute null in this case to save space for 094 * serialization. The access methods, however, handle this transparently. 095 */ 096 @JsonProperty("siblingLocations") 097 @Nullable 098 private List<ElementLocation> siblingLocations; 099 100 /** 101 * Caches the sibling locations as strings. Used to avoid adding duplicate 102 * sibling locations. Initialized lazily. 103 */ 104 private transient Set<String> siblingLocationCache = null; 105 106 /** 107 * Next to the primary location {@link #location}, a finding may optionally have 108 * secondary locations. For instance, an architecture finding may contain the 109 * individual source code locations of the violating identifiers. 110 */ 111 @JsonProperty("secondaryLocations") 112 @Nullable 113 private List<ElementLocation> secondaryLocations; 114 115 /** 116 * Properties for this finding. Each finding can be associated with one or more 117 * properties describing details of the finding (e.g. length of long method, 118 * etc.). These properties are also displayed in the UI and can be used for 119 * sorting findings (in which case the value class must be comparable). 120 */ 121 @JsonProperty("properties") 122 @ExportAsType("!Object<string,!Object|string|number|boolean>") 123 private final Map<String, Object> properties = new HashMap<>(); 124 125 /** 126 * @see #getStatementPath() 127 */ 128 @JsonProperty("statementPath") 129 @Nullable 130 private List<StatementPathElement> statementPath = null; 131 132 /** Constructor. */ 133 public DetachedFinding(String groupName, String categoryName, String message, ElementLocation location) { 134 this(groupName, categoryName, message, location, null); 135 } 136 137 /** 138 * Constructor. Adds the given finding properties to the finding. The assessment 139 * may be <code>null</code>. 140 */ 141 public DetachedFinding(String groupName, String categoryName, String message, ElementLocation location, 142 ETrafficLightColor assessment, Map<String, Object> findingProperties) { 143 this(groupName, categoryName, message, location, assessment); 144 properties.putAll(findingProperties); 145 } 146 147 /** Constructor. The assessment may be <code>null</code> */ 148 @JsonCreator 149 public DetachedFinding(@JsonProperty(GROUP_NAME_PROPERTY) String groupName, 150 @JsonProperty(CATEGORY_NAME_PROPERTY) String categoryName, @JsonProperty(MESSAGE_PROPERTY) String message, 151 @JsonProperty(LOCATION_PROPERTY) ElementLocation location, 152 @JsonProperty(ASSESSMENT_PROPERTY) ETrafficLightColor assessment) { 153 CCSMAssert.isNotNull(location); 154 155 this.groupName = groupName; 156 this.categoryName = categoryName; 157 this.message = message; 158 this.location = location; 159 this.assessment = assessment; 160 } 161 162 /** Copy constructor. */ 163 protected DetachedFinding(DetachedFinding other) { 164 this(other.groupName, other.categoryName, other.message, other.location, other.assessment); 165 166 if (other.hasSiblings()) { 167 this.siblingLocations = new ArrayList<>(other.siblingLocations); 168 } 169 170 if (other.secondaryLocations != null) { 171 this.secondaryLocations = new ArrayList<>(other.secondaryLocations); 172 } 173 174 properties.putAll(other.properties); 175 176 this.statementPath = other.statementPath; 177 } 178 179 /** Get group name. */ 180 public String getGroupName() { 181 return groupName; 182 } 183 184 /** Sets group name. */ 185 public void setGroupName(String groupName) { 186 this.groupName = groupName; 187 } 188 189 /** Get category name. */ 190 public String getCategoryName() { 191 return categoryName; 192 } 193 194 /** Sets category name. */ 195 public void setCategoryName(String categoryName) { 196 this.categoryName = categoryName; 197 } 198 199 /** Get location. */ 200 public ElementLocation getLocation() { 201 return location; 202 } 203 204 /** Sets location. */ 205 public void setLocation(ElementLocation location) { 206 CCSMAssert.isNotNull(location); 207 this.location = location; 208 } 209 210 /** Get location string. */ 211 public String getLocationString() { 212 return location.toLocationString(); 213 } 214 215 /** Get message. */ 216 public String getMessage() { 217 return message; 218 } 219 220 /** @see #message */ 221 public void setMessage(String message) { 222 this.message = message; 223 } 224 225 /** Returns whether this findings has siblings. */ 226 public boolean hasSiblings() { 227 return siblingLocations != null && !siblingLocations.isEmpty(); 228 } 229 230 /** Returns the locations of sibling findings. */ 231 public List<ElementLocation> getSiblingLocations() { 232 if (siblingLocations == null) { 233 return CollectionUtils.emptyList(); 234 } 235 return CollectionUtils.asUnmodifiable(siblingLocations); 236 } 237 238 /** Removes all sibling locations matching the given filter. */ 239 public void removeMatchingSiblingLocations(Predicate<? super ElementLocation> filter) { 240 siblingLocations.removeIf(filter); 241 } 242 243 /** 244 * @see #secondaryLocations 245 */ 246 public List<ElementLocation> getSecondaryLocations() { 247 if (secondaryLocations == null) { 248 return CollectionUtils.emptyList(); 249 } 250 return CollectionUtils.asUnmodifiable(secondaryLocations); 251 } 252 253 /** 254 * Adds all previously unknown locations of sibling findings (already known 255 * locations are skipped). 256 */ 257 public void addSiblingLocation(ElementLocation location) { 258 if (siblingLocations == null) { 259 siblingLocations = new ArrayList<>(); 260 } 261 if (siblingLocationCache == null) { 262 siblingLocationCache = new HashSet<>( 263 CollectionUtils.map(siblingLocations, ElementLocation::toLocationString)); 264 // This way, a finding can never be a sibling of itself 265 siblingLocationCache.add(getLocationString()); 266 } 267 String locationString = location.toLocationString(); 268 if (!siblingLocationCache.contains(locationString)) { 269 siblingLocations.add(location); 270 siblingLocationCache.add(locationString); 271 } 272 } 273 274 /** 275 * Adds the given list of locations as sibling findings. 276 * 277 * @param findings 278 * a collection of {@link DetachedFinding} that will be added as 279 * siblings. 280 */ 281 public void addSiblingFindings(Collection<DetachedFinding> findings) { 282 findings.forEach(this::addSiblingFinding); 283 } 284 285 /** Add the given sibling locations */ 286 public void addSiblingLocations(Collection<ElementLocation> siblingLocations) { 287 siblingLocations.forEach(this::addSiblingLocation); 288 } 289 290 /** Adds the given {@link DetachedFinding} to the list of sibling findings. */ 291 public void addSiblingFinding(DetachedFinding sibling) { 292 addSiblingLocation(sibling.getLocation()); 293 } 294 295 /** Adds a secondary location to this finding. */ 296 public void addSecondaryLocation(ElementLocation location) { 297 if (secondaryLocations == null) { 298 secondaryLocations = new ArrayList<>(); 299 } 300 secondaryLocations.add(location); 301 } 302 303 /** Adds the given locations as secondary locations. */ 304 public void addSecondaryLocations(Collection<ElementLocation> locations) { 305 if (secondaryLocations == null) { 306 secondaryLocations = new ArrayList<>(); 307 } 308 secondaryLocations.addAll(locations); 309 } 310 311 /** Returns the properties of this finding. */ 312 public UnmodifiableMap<String, Object> getProperties() { 313 return CollectionUtils.asUnmodifiable(properties); 314 } 315 316 /** Sets a property for this finding. */ 317 public void setProperty(String name, Object value) { 318 properties.put(name, value); 319 } 320 321 /** Returns assessment. */ 322 public ETrafficLightColor getAssessment() { 323 return assessment; 324 } 325 326 /** Sets the assessment. */ 327 public void setAssessment(ETrafficLightColor assessment) { 328 this.assessment = assessment; 329 } 330 331 /** 332 * Returns a string representation that contains the, message, the group and and 333 * location hint. 334 */ 335 @Override 336 public String toString() { 337 return getMessage() + " (" + getGroupName() + ") @ " + location.toLocationString(); 338 } 339 340 /** 341 * Returns the statement path if there is one attached to this finding. 342 * 343 * A statementPath is a path through the program that leads to this finding. The 344 * path may not be a simple chain, but could be a DAG. 345 * 346 * Each entry in the list refers to the indices of its predecessor statements. 347 * Contract for the setup of this structure: 348 * <ul> 349 * <li>An element might have more than one predecessor.</li> 350 * <li>Multiple elements can have the same predecessor.</li> 351 * <li>There are no cycles in the path.</li> 352 * </ul> 353 */ 354 public List<StatementPathElement> getStatementPath() { 355 if (statementPath == null) { 356 return CollectionUtils.emptyList(); 357 } 358 return statementPath; 359 } 360 361 /** @see #getStatementPath() */ 362 public void setStatementPath(List<StatementPathElement> path) { 363 this.statementPath = path; 364 } 365 366}