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