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