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