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