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