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