001/* 002 * SonarQube, open source software quality management tool. 003 * Copyright (C) 2008-2013 SonarSource 004 * mailto:contact AT sonarsource DOT com 005 * 006 * SonarQube is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 3 of the License, or (at your option) any later version. 010 * 011 * SonarQube is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public License 017 * along with this program; if not, write to the Free Software Foundation, 018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 019 */ 020package org.sonar.api.issue.internal; 021 022import com.google.common.base.Objects; 023import com.google.common.base.Preconditions; 024import com.google.common.base.Strings; 025import com.google.common.collect.ImmutableList; 026import com.google.common.collect.ImmutableMap; 027import com.google.common.collect.Maps; 028import org.apache.commons.lang.StringUtils; 029import org.apache.commons.lang.builder.ToStringBuilder; 030import org.apache.commons.lang.builder.ToStringStyle; 031import org.apache.commons.lang.time.DateUtils; 032import org.sonar.api.issue.Issue; 033import org.sonar.api.issue.IssueComment; 034import org.sonar.api.rule.RuleKey; 035import org.sonar.api.rule.Severity; 036import org.sonar.api.utils.internal.WorkDuration; 037 038import javax.annotation.CheckForNull; 039import javax.annotation.Nullable; 040 041import java.io.Serializable; 042import java.util.*; 043 044import static com.google.common.collect.Lists.newArrayList; 045 046/** 047 * PLUGINS MUST NOT BE USED THIS CLASS, EXCEPT FOR UNIT TESTING. 048 * 049 * @since 3.6 050 */ 051public class DefaultIssue implements Issue { 052 053 private String key; 054 private String componentKey; 055 private Long componentId; 056 private String projectKey; 057 private RuleKey ruleKey; 058 private String severity; 059 private boolean manualSeverity = false; 060 private String message; 061 private Integer line; 062 private Double effortToFix; 063 private WorkDuration technicalDebt; 064 private String status; 065 private String resolution; 066 private String reporter; 067 private String assignee; 068 private String checksum; 069 private Map<String, String> attributes = null; 070 private String authorLogin = null; 071 private String actionPlanKey; 072 private List<IssueComment> comments = null; 073 074 // FUNCTIONAL DATES 075 private Date creationDate; 076 private Date updateDate; 077 private Date closeDate; 078 079 // FOLLOWING FIELDS ARE AVAILABLE ONLY DURING SCAN 080 081 // Current changes 082 private FieldDiffs currentChange = null; 083 084 // all changes 085 private List<FieldDiffs> changes = null; 086 087 // true if the the issue did not exist in the previous scan. 088 private boolean isNew = true; 089 090 // True if the the issue did exist in the previous scan but not in the current one. That means 091 // that this issue should be closed. 092 private boolean endOfLife = false; 093 094 private boolean onDisabledRule = false; 095 096 // true if some fields have been changed since the previous scan 097 private boolean isChanged = false; 098 099 // true if notifications have to be sent 100 private boolean sendNotifications = false; 101 102 // Date when issue was loaded from db (only when isNew=false) 103 private Date selectedAt; 104 105 public String key() { 106 return key; 107 } 108 109 public DefaultIssue setKey(String key) { 110 this.key = key; 111 return this; 112 } 113 114 public String componentKey() { 115 return componentKey; 116 } 117 118 public DefaultIssue setComponentKey(String s) { 119 this.componentKey = s; 120 return this; 121 } 122 123 /** 124 * The component id not populated on batch side 125 */ 126 @CheckForNull 127 public Long componentId() { 128 return componentId; 129 } 130 131 public DefaultIssue setComponentId(@Nullable Long s) { 132 this.componentId = s; 133 return this; 134 } 135 136 /** 137 * The project key is not always populated, that's why it's not present in the Issue API 138 */ 139 @CheckForNull 140 public String projectKey() { 141 return projectKey; 142 } 143 144 public DefaultIssue setProjectKey(String projectKey) { 145 this.projectKey = projectKey; 146 return this; 147 } 148 149 public RuleKey ruleKey() { 150 return ruleKey; 151 } 152 153 public DefaultIssue setRuleKey(RuleKey k) { 154 this.ruleKey = k; 155 return this; 156 } 157 158 public String severity() { 159 return severity; 160 } 161 162 public DefaultIssue setSeverity(@Nullable String s) { 163 Preconditions.checkArgument(s == null || Severity.ALL.contains(s), "Not a valid severity: " + s); 164 this.severity = s; 165 return this; 166 } 167 168 public boolean manualSeverity() { 169 return manualSeverity; 170 } 171 172 public DefaultIssue setManualSeverity(boolean b) { 173 this.manualSeverity = b; 174 return this; 175 } 176 177 @CheckForNull 178 public String message() { 179 return message; 180 } 181 182 public DefaultIssue setMessage(@Nullable String s) { 183 this.message = StringUtils.abbreviate(StringUtils.trim(s), MESSAGE_MAX_SIZE); 184 return this; 185 } 186 187 @CheckForNull 188 public Integer line() { 189 return line; 190 } 191 192 public DefaultIssue setLine(@Nullable Integer l) { 193 Preconditions.checkArgument(l == null || l > 0, "Line must be null or greater than zero (got " + l + ")"); 194 this.line = l; 195 return this; 196 } 197 198 @CheckForNull 199 public Double effortToFix() { 200 return effortToFix; 201 } 202 203 public DefaultIssue setEffortToFix(@Nullable Double d) { 204 Preconditions.checkArgument(d == null || d >= 0, "Effort to fix must be greater than or equal 0 (got " + d + ")"); 205 this.effortToFix = d; 206 return this; 207 } 208 209 /** 210 * Elapsed time to fix the issue 211 */ 212 @CheckForNull 213 public WorkDuration technicalDebt() { 214 return technicalDebt; 215 } 216 217 public DefaultIssue setTechnicalDebt(@Nullable WorkDuration t) { 218 this.technicalDebt = t; 219 return this; 220 } 221 222 public String status() { 223 return status; 224 } 225 226 public DefaultIssue setStatus(String s) { 227 Preconditions.checkArgument(!Strings.isNullOrEmpty(s), "Status must be set"); 228 this.status = s; 229 return this; 230 } 231 232 @CheckForNull 233 public String resolution() { 234 return resolution; 235 } 236 237 public DefaultIssue setResolution(@Nullable String s) { 238 this.resolution = s; 239 return this; 240 } 241 242 @CheckForNull 243 public String reporter() { 244 return reporter; 245 } 246 247 public DefaultIssue setReporter(@Nullable String s) { 248 this.reporter = s; 249 return this; 250 } 251 252 @CheckForNull 253 public String assignee() { 254 return assignee; 255 } 256 257 public DefaultIssue setAssignee(@Nullable String s) { 258 this.assignee = s; 259 return this; 260 } 261 262 public Date creationDate() { 263 return creationDate; 264 } 265 266 public DefaultIssue setCreationDate(Date d) { 267 // d is not marked as Nullable but we still allow null parameter for unit testing. 268 this.creationDate = (d != null ? DateUtils.truncate(d, Calendar.SECOND) : null); 269 return this; 270 } 271 272 @CheckForNull 273 public Date updateDate() { 274 return updateDate; 275 } 276 277 public DefaultIssue setUpdateDate(@Nullable Date d) { 278 this.updateDate = (d != null ? DateUtils.truncate(d, Calendar.SECOND) : null); 279 return this; 280 } 281 282 @CheckForNull 283 public Date closeDate() { 284 return closeDate; 285 } 286 287 public DefaultIssue setCloseDate(@Nullable Date d) { 288 this.closeDate = (d != null ? DateUtils.truncate(d, Calendar.SECOND) : null); 289 return this; 290 } 291 292 @CheckForNull 293 public String checksum() { 294 return checksum; 295 } 296 297 public DefaultIssue setChecksum(@Nullable String s) { 298 this.checksum = s; 299 return this; 300 } 301 302 @Override 303 public boolean isNew() { 304 return isNew; 305 } 306 307 public DefaultIssue setNew(boolean b) { 308 isNew = b; 309 return this; 310 } 311 312 /** 313 * True when one of the following conditions is true : 314 * <ul> 315 * <li>the related component has been deleted or renamed</li> 316 * <li>the rule has been deleted (eg. on plugin uninstall)</li> 317 * <li>the rule has been disabled in the Quality profile</li> 318 * </ul> 319 */ 320 public boolean isEndOfLife() { 321 return endOfLife; 322 } 323 324 public DefaultIssue setEndOfLife(boolean b) { 325 endOfLife = b; 326 return this; 327 } 328 329 public boolean isOnDisabledRule() { 330 return onDisabledRule; 331 } 332 333 public DefaultIssue setOnDisabledRule(boolean b) { 334 onDisabledRule = b; 335 return this; 336 } 337 338 public boolean isChanged() { 339 return isChanged; 340 } 341 342 public DefaultIssue setChanged(boolean b) { 343 isChanged = b; 344 return this; 345 } 346 347 public boolean mustSendNotifications() { 348 return sendNotifications; 349 } 350 351 public DefaultIssue setSendNotifications(boolean b) { 352 sendNotifications = b; 353 return this; 354 } 355 356 @CheckForNull 357 public String attribute(String key) { 358 return attributes == null ? null : attributes.get(key); 359 } 360 361 public DefaultIssue setAttribute(String key, @Nullable String value) { 362 if (attributes == null) { 363 attributes = Maps.newHashMap(); 364 } 365 if (value == null) { 366 attributes.remove(key); 367 } else { 368 attributes.put(key, value); 369 } 370 return this; 371 } 372 373 public Map<String, String> attributes() { 374 return attributes == null ? Collections.<String, String>emptyMap() : ImmutableMap.copyOf(attributes); 375 } 376 377 public DefaultIssue setAttributes(@Nullable Map<String, String> map) { 378 if (map != null) { 379 if (attributes == null) { 380 attributes = Maps.newHashMap(); 381 } 382 attributes.putAll(map); 383 } 384 return this; 385 } 386 387 @CheckForNull 388 public String authorLogin() { 389 return authorLogin; 390 } 391 392 public DefaultIssue setAuthorLogin(@Nullable String s) { 393 this.authorLogin = s; 394 return this; 395 } 396 397 @CheckForNull 398 public String actionPlanKey() { 399 return actionPlanKey; 400 } 401 402 public DefaultIssue setActionPlanKey(@Nullable String actionPlanKey) { 403 this.actionPlanKey = actionPlanKey; 404 return this; 405 } 406 407 public DefaultIssue setFieldChange(IssueChangeContext context, String field, @Nullable Serializable oldValue, @Nullable Serializable newValue) { 408 if (!Objects.equal(oldValue, newValue)) { 409 if (currentChange == null) { 410 currentChange = new FieldDiffs(); 411 currentChange.setUserLogin(context.login()); 412 currentChange.setCreationDate(context.date()); 413 } 414 currentChange.setDiff(field, oldValue, newValue); 415 } 416 addChange(currentChange); 417 return this; 418 } 419 420 @CheckForNull 421 public FieldDiffs currentChange() { 422 return currentChange; 423 } 424 425 public DefaultIssue addChange(FieldDiffs change) { 426 if (changes == null) { 427 changes = newArrayList(); 428 } 429 changes.add(change); 430 return this; 431 } 432 433 public DefaultIssue setChanges(List<FieldDiffs> changes) { 434 this.changes = changes; 435 return this; 436 } 437 438 public List<FieldDiffs> changes() { 439 if (changes == null) { 440 return Collections.emptyList(); 441 } 442 return ImmutableList.copyOf(changes); 443 } 444 445 public DefaultIssue addComment(DefaultIssueComment comment) { 446 if (comments == null) { 447 comments = newArrayList(); 448 } 449 comments.add(comment); 450 return this; 451 } 452 453 @SuppressWarnings("unchcked") 454 public List<IssueComment> comments() { 455 if (comments == null) { 456 return Collections.emptyList(); 457 } 458 return ImmutableList.copyOf(comments); 459 } 460 461 @CheckForNull 462 public Date selectedAt() { 463 return selectedAt; 464 } 465 466 public DefaultIssue setSelectedAt(@Nullable Date d) { 467 this.selectedAt = d; 468 return this; 469 } 470 471 @Override 472 public boolean equals(Object o) { 473 if (this == o) { 474 return true; 475 } 476 if (o == null || getClass() != o.getClass()) { 477 return false; 478 } 479 DefaultIssue that = (DefaultIssue) o; 480 if (key != null ? !key.equals(that.key) : that.key != null) { 481 return false; 482 } 483 return true; 484 } 485 486 @Override 487 public int hashCode() { 488 return key != null ? key.hashCode() : 0; 489 } 490 491 @Override 492 public String toString() { 493 return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); 494 } 495 496}