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 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 Duration debt; 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 Duration debt() { 214 return debt; 215 } 216 217 @CheckForNull 218 public Long debtInMinutes(){ 219 return debt != null ? debt.toMinutes() : null; 220 } 221 222 public DefaultIssue setDebt(@Nullable Duration t) { 223 this.debt = t; 224 return this; 225 } 226 227 public String status() { 228 return status; 229 } 230 231 public DefaultIssue setStatus(String s) { 232 Preconditions.checkArgument(!Strings.isNullOrEmpty(s), "Status must be set"); 233 this.status = s; 234 return this; 235 } 236 237 @CheckForNull 238 public String resolution() { 239 return resolution; 240 } 241 242 public DefaultIssue setResolution(@Nullable String s) { 243 this.resolution = s; 244 return this; 245 } 246 247 @CheckForNull 248 public String reporter() { 249 return reporter; 250 } 251 252 public DefaultIssue setReporter(@Nullable String s) { 253 this.reporter = s; 254 return this; 255 } 256 257 @CheckForNull 258 public String assignee() { 259 return assignee; 260 } 261 262 public DefaultIssue setAssignee(@Nullable String s) { 263 this.assignee = s; 264 return this; 265 } 266 267 public Date creationDate() { 268 return creationDate; 269 } 270 271 public DefaultIssue setCreationDate(Date d) { 272 // d is not marked as Nullable but we still allow null parameter for unit testing. 273 this.creationDate = (d != null ? DateUtils.truncate(d, Calendar.SECOND) : null); 274 return this; 275 } 276 277 @CheckForNull 278 public Date updateDate() { 279 return updateDate; 280 } 281 282 public DefaultIssue setUpdateDate(@Nullable Date d) { 283 this.updateDate = (d != null ? DateUtils.truncate(d, Calendar.SECOND) : null); 284 return this; 285 } 286 287 @CheckForNull 288 public Date closeDate() { 289 return closeDate; 290 } 291 292 public DefaultIssue setCloseDate(@Nullable Date d) { 293 this.closeDate = (d != null ? DateUtils.truncate(d, Calendar.SECOND) : null); 294 return this; 295 } 296 297 @CheckForNull 298 public String checksum() { 299 return checksum; 300 } 301 302 public DefaultIssue setChecksum(@Nullable String s) { 303 this.checksum = s; 304 return this; 305 } 306 307 @Override 308 public boolean isNew() { 309 return isNew; 310 } 311 312 public DefaultIssue setNew(boolean b) { 313 isNew = b; 314 return this; 315 } 316 317 /** 318 * True when one of the following conditions is true : 319 * <ul> 320 * <li>the related component has been deleted or renamed</li> 321 * <li>the rule has been deleted (eg. on plugin uninstall)</li> 322 * <li>the rule has been disabled in the Quality profile</li> 323 * </ul> 324 */ 325 public boolean isEndOfLife() { 326 return endOfLife; 327 } 328 329 public DefaultIssue setEndOfLife(boolean b) { 330 endOfLife = b; 331 return this; 332 } 333 334 public boolean isOnDisabledRule() { 335 return onDisabledRule; 336 } 337 338 public DefaultIssue setOnDisabledRule(boolean b) { 339 onDisabledRule = b; 340 return this; 341 } 342 343 public boolean isChanged() { 344 return isChanged; 345 } 346 347 public DefaultIssue setChanged(boolean b) { 348 isChanged = b; 349 return this; 350 } 351 352 public boolean mustSendNotifications() { 353 return sendNotifications; 354 } 355 356 public DefaultIssue setSendNotifications(boolean b) { 357 sendNotifications = b; 358 return this; 359 } 360 361 @CheckForNull 362 public String attribute(String key) { 363 return attributes == null ? null : attributes.get(key); 364 } 365 366 public DefaultIssue setAttribute(String key, @Nullable String value) { 367 if (attributes == null) { 368 attributes = Maps.newHashMap(); 369 } 370 if (value == null) { 371 attributes.remove(key); 372 } else { 373 attributes.put(key, value); 374 } 375 return this; 376 } 377 378 public Map<String, String> attributes() { 379 return attributes == null ? Collections.<String, String>emptyMap() : ImmutableMap.copyOf(attributes); 380 } 381 382 public DefaultIssue setAttributes(@Nullable Map<String, String> map) { 383 if (map != null) { 384 if (attributes == null) { 385 attributes = Maps.newHashMap(); 386 } 387 attributes.putAll(map); 388 } 389 return this; 390 } 391 392 @CheckForNull 393 public String authorLogin() { 394 return authorLogin; 395 } 396 397 public DefaultIssue setAuthorLogin(@Nullable String s) { 398 this.authorLogin = s; 399 return this; 400 } 401 402 @CheckForNull 403 public String actionPlanKey() { 404 return actionPlanKey; 405 } 406 407 public DefaultIssue setActionPlanKey(@Nullable String actionPlanKey) { 408 this.actionPlanKey = actionPlanKey; 409 return this; 410 } 411 412 public DefaultIssue setFieldChange(IssueChangeContext context, String field, @Nullable Serializable oldValue, @Nullable Serializable newValue) { 413 if (!Objects.equal(oldValue, newValue)) { 414 if (currentChange == null) { 415 currentChange = new FieldDiffs(); 416 currentChange.setUserLogin(context.login()); 417 currentChange.setCreationDate(context.date()); 418 } 419 currentChange.setDiff(field, oldValue, newValue); 420 } 421 addChange(currentChange); 422 return this; 423 } 424 425 @CheckForNull 426 public FieldDiffs currentChange() { 427 return currentChange; 428 } 429 430 public DefaultIssue addChange(FieldDiffs change) { 431 if (changes == null) { 432 changes = newArrayList(); 433 } 434 changes.add(change); 435 return this; 436 } 437 438 public DefaultIssue setChanges(List<FieldDiffs> changes) { 439 this.changes = changes; 440 return this; 441 } 442 443 public List<FieldDiffs> changes() { 444 if (changes == null) { 445 return Collections.emptyList(); 446 } 447 return ImmutableList.copyOf(changes); 448 } 449 450 public DefaultIssue addComment(DefaultIssueComment comment) { 451 if (comments == null) { 452 comments = newArrayList(); 453 } 454 comments.add(comment); 455 return this; 456 } 457 458 @SuppressWarnings("unchcked") 459 public List<IssueComment> comments() { 460 if (comments == null) { 461 return Collections.emptyList(); 462 } 463 return ImmutableList.copyOf(comments); 464 } 465 466 @CheckForNull 467 public Date selectedAt() { 468 return selectedAt; 469 } 470 471 public DefaultIssue setSelectedAt(@Nullable Date d) { 472 this.selectedAt = d; 473 return this; 474 } 475 476 @Override 477 public boolean equals(Object o) { 478 if (this == o) { 479 return true; 480 } 481 if (o == null || getClass() != o.getClass()) { 482 return false; 483 } 484 DefaultIssue that = (DefaultIssue) o; 485 if (key != null ? !key.equals(that.key) : that.key != null) { 486 return false; 487 } 488 return true; 489 } 490 491 @Override 492 public int hashCode() { 493 return key != null ? key.hashCode() : 0; 494 } 495 496 @Override 497 public String toString() { 498 return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); 499 } 500 501 }