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