001/* 002 * SonarQube, open source software quality management tool. 003 * Copyright (C) 2008-2014 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.ImmutableSet; 028import com.google.common.collect.Maps; 029import org.apache.commons.lang.StringUtils; 030import org.apache.commons.lang.builder.ToStringBuilder; 031import org.apache.commons.lang.builder.ToStringStyle; 032import org.apache.commons.lang.time.DateUtils; 033import org.sonar.api.issue.Issue; 034import org.sonar.api.issue.IssueComment; 035import org.sonar.api.rule.RuleKey; 036import org.sonar.api.rule.Severity; 037import org.sonar.api.utils.Duration; 038 039import javax.annotation.CheckForNull; 040import javax.annotation.Nullable; 041 042import java.io.Serializable; 043import java.util.*; 044 045import static com.google.common.collect.Lists.newArrayList; 046 047/** 048 * PLUGINS MUST NOT BE USED THIS CLASS, EXCEPT FOR UNIT TESTING. 049 * 050 * @since 3.6 051 */ 052public class DefaultIssue implements Issue { 053 054 private String key; 055 056 private String componentUuid; 057 private String componentKey; 058 059 private String moduleUuid; 060 private String moduleUuidPath; 061 062 private String projectUuid; 063 private String projectKey; 064 065 private RuleKey ruleKey; 066 private String language; 067 private String severity; 068 private boolean manualSeverity = false; 069 private String message; 070 private Integer line; 071 private Double effortToFix; 072 private Duration debt; 073 private String status; 074 private String resolution; 075 private String reporter; 076 private String assignee; 077 private String checksum; 078 private Map<String, String> attributes = null; 079 private String authorLogin = null; 080 private String actionPlanKey; 081 private List<IssueComment> comments = null; 082 private Set<String> tags = null; 083 084 // FUNCTIONAL DATES 085 private Date creationDate; 086 private Date updateDate; 087 private Date closeDate; 088 089 // FOLLOWING FIELDS ARE AVAILABLE ONLY DURING SCAN 090 091 // Current changes 092 private FieldDiffs currentChange = null; 093 094 // all changes 095 private List<FieldDiffs> changes = null; 096 097 // true if the the issue did not exist in the previous scan. 098 private boolean isNew = true; 099 100 // True if the the issue did exist in the previous scan but not in the current one. That means 101 // that this issue should be closed. 102 private boolean endOfLife = false; 103 104 private boolean onDisabledRule = false; 105 106 // true if some fields have been changed since the previous scan 107 private boolean isChanged = false; 108 109 // true if notifications have to be sent 110 private boolean sendNotifications = false; 111 112 // Date when issue was loaded from db (only when isNew=false) 113 private Long selectedAt; 114 115 @Override 116 public String key() { 117 return key; 118 } 119 120 public DefaultIssue setKey(String key) { 121 this.key = key; 122 return this; 123 } 124 125 /** 126 * Can be null on Views or Devs 127 */ 128 @Override 129 @CheckForNull 130 public String componentUuid() { 131 return componentUuid; 132 } 133 134 public DefaultIssue setComponentUuid(@CheckForNull String componentUuid) { 135 this.componentUuid = componentUuid; 136 return this; 137 } 138 139 @Override 140 public String componentKey() { 141 return componentKey; 142 } 143 144 public DefaultIssue setComponentKey(String s) { 145 this.componentKey = s; 146 return this; 147 } 148 149 @CheckForNull 150 public String moduleUuid() { 151 return moduleUuid; 152 } 153 154 public DefaultIssue setModuleUuid(@Nullable String moduleUuid) { 155 this.moduleUuid = moduleUuid; 156 return this; 157 } 158 159 @CheckForNull 160 public String moduleUuidPath() { 161 return moduleUuidPath; 162 } 163 164 public DefaultIssue setModuleUuidPath(@Nullable String moduleUuidPath) { 165 this.moduleUuidPath = moduleUuidPath; 166 return this; 167 } 168 169 /** 170 * Can be null on Views or Devs 171 */ 172 @Override 173 @CheckForNull 174 public String projectUuid() { 175 return projectUuid; 176 } 177 178 public DefaultIssue setProjectUuid(@Nullable String projectUuid) { 179 this.projectUuid = projectUuid; 180 return this; 181 } 182 183 @Override 184 public String projectKey() { 185 return projectKey; 186 } 187 188 public DefaultIssue setProjectKey(String projectKey) { 189 this.projectKey = projectKey; 190 return this; 191 } 192 193 @Override 194 public RuleKey ruleKey() { 195 return ruleKey; 196 } 197 198 public DefaultIssue setRuleKey(RuleKey k) { 199 this.ruleKey = k; 200 return this; 201 } 202 203 @Override 204 public String language() { 205 return language; 206 } 207 208 public DefaultIssue setLanguage(String l) { 209 this.language = l; 210 return this; 211 } 212 213 @Override 214 public String severity() { 215 return severity; 216 } 217 218 public DefaultIssue setSeverity(@Nullable String s) { 219 Preconditions.checkArgument(s == null || Severity.ALL.contains(s), "Not a valid severity: " + s); 220 this.severity = s; 221 return this; 222 } 223 224 public boolean manualSeverity() { 225 return manualSeverity; 226 } 227 228 public DefaultIssue setManualSeverity(boolean b) { 229 this.manualSeverity = b; 230 return this; 231 } 232 233 @Override 234 @CheckForNull 235 public String message() { 236 return message; 237 } 238 239 public DefaultIssue setMessage(@Nullable String s) { 240 this.message = StringUtils.abbreviate(StringUtils.trim(s), MESSAGE_MAX_SIZE); 241 return this; 242 } 243 244 @Override 245 @CheckForNull 246 public Integer line() { 247 return line; 248 } 249 250 public DefaultIssue setLine(@Nullable Integer l) { 251 Preconditions.checkArgument(l == null || l > 0, "Line must be null or greater than zero (got " + l + ")"); 252 this.line = l; 253 return this; 254 } 255 256 @Override 257 @CheckForNull 258 public Double effortToFix() { 259 return effortToFix; 260 } 261 262 public DefaultIssue setEffortToFix(@Nullable Double d) { 263 Preconditions.checkArgument(d == null || d >= 0, "Effort to fix must be greater than or equal 0 (got " + d + ")"); 264 this.effortToFix = d; 265 return this; 266 } 267 268 /** 269 * Elapsed time to fix the issue 270 */ 271 @Override 272 @CheckForNull 273 public Duration debt() { 274 return debt; 275 } 276 277 @CheckForNull 278 public Long debtInMinutes() { 279 return debt != null ? debt.toMinutes() : null; 280 } 281 282 public DefaultIssue setDebt(@Nullable Duration t) { 283 this.debt = t; 284 return this; 285 } 286 287 @Override 288 public String status() { 289 return status; 290 } 291 292 public DefaultIssue setStatus(String s) { 293 Preconditions.checkArgument(!Strings.isNullOrEmpty(s), "Status must be set"); 294 this.status = s; 295 return this; 296 } 297 298 @Override 299 @CheckForNull 300 public String resolution() { 301 return resolution; 302 } 303 304 public DefaultIssue setResolution(@Nullable String s) { 305 this.resolution = s; 306 return this; 307 } 308 309 @Override 310 @CheckForNull 311 public String reporter() { 312 return reporter; 313 } 314 315 public DefaultIssue setReporter(@Nullable String s) { 316 this.reporter = s; 317 return this; 318 } 319 320 @Override 321 @CheckForNull 322 public String assignee() { 323 return assignee; 324 } 325 326 public DefaultIssue setAssignee(@Nullable String s) { 327 this.assignee = s; 328 return this; 329 } 330 331 @Override 332 public Date creationDate() { 333 return creationDate; 334 } 335 336 public DefaultIssue setCreationDate(Date d) { 337 // d is not marked as Nullable but we still allow null parameter for unit testing. 338 this.creationDate = (d != null ? DateUtils.truncate(d, Calendar.SECOND) : null); 339 return this; 340 } 341 342 @Override 343 @CheckForNull 344 public Date updateDate() { 345 return updateDate; 346 } 347 348 public DefaultIssue setUpdateDate(@Nullable Date d) { 349 this.updateDate = (d != null ? DateUtils.truncate(d, Calendar.SECOND) : null); 350 return this; 351 } 352 353 @Override 354 @CheckForNull 355 public Date closeDate() { 356 return closeDate; 357 } 358 359 public DefaultIssue setCloseDate(@Nullable Date d) { 360 this.closeDate = (d != null ? DateUtils.truncate(d, Calendar.SECOND) : null); 361 return this; 362 } 363 364 @CheckForNull 365 public String checksum() { 366 return checksum; 367 } 368 369 public DefaultIssue setChecksum(@Nullable String s) { 370 this.checksum = s; 371 return this; 372 } 373 374 @Override 375 public boolean isNew() { 376 return isNew; 377 } 378 379 public DefaultIssue setNew(boolean b) { 380 isNew = b; 381 return this; 382 } 383 384 /** 385 * True when one of the following conditions is true : 386 * <ul> 387 * <li>the related component has been deleted or renamed</li> 388 * <li>the rule has been deleted (eg. on plugin uninstall)</li> 389 * <li>the rule has been disabled in the Quality profile</li> 390 * </ul> 391 */ 392 public boolean isEndOfLife() { 393 return endOfLife; 394 } 395 396 public DefaultIssue setEndOfLife(boolean b) { 397 endOfLife = b; 398 return this; 399 } 400 401 public boolean isOnDisabledRule() { 402 return onDisabledRule; 403 } 404 405 public DefaultIssue setOnDisabledRule(boolean b) { 406 onDisabledRule = b; 407 return this; 408 } 409 410 public boolean isChanged() { 411 return isChanged; 412 } 413 414 public DefaultIssue setChanged(boolean b) { 415 isChanged = b; 416 return this; 417 } 418 419 public boolean mustSendNotifications() { 420 return sendNotifications; 421 } 422 423 public DefaultIssue setSendNotifications(boolean b) { 424 sendNotifications = b; 425 return this; 426 } 427 428 @Override 429 @CheckForNull 430 public String attribute(String key) { 431 return attributes == null ? null : attributes.get(key); 432 } 433 434 public DefaultIssue setAttribute(String key, @Nullable String value) { 435 if (attributes == null) { 436 attributes = Maps.newHashMap(); 437 } 438 if (value == null) { 439 attributes.remove(key); 440 } else { 441 attributes.put(key, value); 442 } 443 return this; 444 } 445 446 @Override 447 public Map<String, String> attributes() { 448 return attributes == null ? Collections.<String, String>emptyMap() : ImmutableMap.copyOf(attributes); 449 } 450 451 public DefaultIssue setAttributes(@Nullable Map<String, String> map) { 452 if (map != null) { 453 if (attributes == null) { 454 attributes = Maps.newHashMap(); 455 } 456 attributes.putAll(map); 457 } 458 return this; 459 } 460 461 @Override 462 @CheckForNull 463 public String authorLogin() { 464 return authorLogin; 465 } 466 467 public DefaultIssue setAuthorLogin(@Nullable String s) { 468 this.authorLogin = s; 469 return this; 470 } 471 472 @Override 473 @CheckForNull 474 public String actionPlanKey() { 475 return actionPlanKey; 476 } 477 478 public DefaultIssue setActionPlanKey(@Nullable String actionPlanKey) { 479 this.actionPlanKey = actionPlanKey; 480 return this; 481 } 482 483 public DefaultIssue setFieldChange(IssueChangeContext context, String field, @Nullable Serializable oldValue, @Nullable Serializable newValue) { 484 if (!Objects.equal(oldValue, newValue)) { 485 if (currentChange == null) { 486 currentChange = new FieldDiffs(); 487 currentChange.setUserLogin(context.login()); 488 currentChange.setCreationDate(context.date()); 489 } 490 currentChange.setDiff(field, oldValue, newValue); 491 } 492 addChange(currentChange); 493 return this; 494 } 495 496 public DefaultIssue setCurrentChange(FieldDiffs currentChange) { 497 this.currentChange = currentChange; 498 addChange(currentChange); 499 return this; 500 } 501 502 @CheckForNull 503 public FieldDiffs currentChange() { 504 return currentChange; 505 } 506 507 public DefaultIssue addChange(FieldDiffs change) { 508 if (changes == null) { 509 changes = newArrayList(); 510 } 511 changes.add(change); 512 return this; 513 } 514 515 public DefaultIssue setChanges(List<FieldDiffs> changes) { 516 this.changes = changes; 517 return this; 518 } 519 520 public List<FieldDiffs> changes() { 521 if (changes == null) { 522 return Collections.emptyList(); 523 } 524 return ImmutableList.copyOf(changes); 525 } 526 527 public DefaultIssue addComment(DefaultIssueComment comment) { 528 if (comments == null) { 529 comments = newArrayList(); 530 } 531 comments.add(comment); 532 return this; 533 } 534 535 @Override 536 public List<IssueComment> comments() { 537 if (comments == null) { 538 return Collections.emptyList(); 539 } 540 return ImmutableList.copyOf(comments); 541 } 542 543 @CheckForNull 544 public Long selectedAt() { 545 return selectedAt; 546 } 547 548 public DefaultIssue setSelectedAt(@Nullable Long d) { 549 this.selectedAt = d; 550 return this; 551 } 552 553 @Override 554 public boolean equals(Object o) { 555 if (this == o) { 556 return true; 557 } 558 if (o == null || getClass() != o.getClass()) { 559 return false; 560 } 561 DefaultIssue that = (DefaultIssue) o; 562 if (key != null ? !key.equals(that.key) : that.key != null) { 563 return false; 564 } 565 return true; 566 } 567 568 @Override 569 public int hashCode() { 570 return key != null ? key.hashCode() : 0; 571 } 572 573 @Override 574 public String toString() { 575 return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); 576 } 577 578 @Override 579 public Collection<String> tags() { 580 if (tags == null) { 581 return ImmutableSet.of(); 582 } else { 583 return ImmutableSet.copyOf(tags); 584 } 585 } 586 587 public DefaultIssue setTags(Collection<String> tags) { 588 this.tags = new LinkedHashSet<>(tags); 589 return this; 590 } 591}