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