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.sonar.api.issue.Issue;
033import org.sonar.api.issue.IssueComment;
034import org.sonar.api.rule.RuleKey;
035import org.sonar.api.rule.Severity;
036
037import javax.annotation.CheckForNull;
038import javax.annotation.Nullable;
039import java.io.Serializable;
040import java.util.Collections;
041import java.util.Date;
042import java.util.List;
043import java.util.Map;
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 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
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  public String status() {
192    return status;
193  }
194
195  public DefaultIssue setStatus(String s) {
196    Preconditions.checkArgument(!Strings.isNullOrEmpty(s), "Status must be set");
197    this.status = s;
198    return this;
199  }
200
201  @CheckForNull
202  public String resolution() {
203    return resolution;
204  }
205
206  public DefaultIssue setResolution(@Nullable String s) {
207    this.resolution = s;
208    return this;
209  }
210
211  @CheckForNull
212  public String reporter() {
213    return reporter;
214  }
215
216  public DefaultIssue setReporter(@Nullable String s) {
217    this.reporter = s;
218    return this;
219  }
220
221  @CheckForNull
222  public String assignee() {
223    return assignee;
224  }
225
226  public DefaultIssue setAssignee(@Nullable String s) {
227    this.assignee = s;
228    return this;
229  }
230
231  public Date creationDate() {
232    return creationDate;
233  }
234
235  public DefaultIssue setCreationDate(Date d) {
236    this.creationDate = d;
237    return this;
238  }
239
240  @CheckForNull
241  public Date updateDate() {
242    return updateDate;
243  }
244
245  public DefaultIssue setUpdateDate(@Nullable Date d) {
246    this.updateDate = d;
247    return this;
248  }
249
250  @CheckForNull
251  public Date closeDate() {
252    return closeDate;
253  }
254
255  public DefaultIssue setCloseDate(@Nullable Date d) {
256    this.closeDate = d;
257    return this;
258  }
259
260
261  @CheckForNull
262  public String checksum() {
263    return checksum;
264  }
265
266  public DefaultIssue setChecksum(@Nullable String s) {
267    this.checksum = s;
268    return this;
269  }
270
271  public boolean isNew() {
272    return isNew;
273  }
274
275  public DefaultIssue setNew(boolean b) {
276    isNew = b;
277    return this;
278  }
279
280  /**
281   * True when one of the following conditions is true :
282   * <ul>
283   * <li>the related component has been deleted or renamed</li>
284   * <li>the rule has been deleted (eg. on plugin uninstall)</li>
285   * <li>the rule has been disabled in the Quality profile</li>
286   * </ul>
287   */
288  public boolean isEndOfLife() {
289    return endOfLife;
290  }
291
292  public DefaultIssue setEndOfLife(boolean b) {
293    endOfLife = b;
294    return this;
295  }
296
297  public boolean isOnDisabledRule() {
298    return onDisabledRule;
299  }
300
301  public DefaultIssue setOnDisabledRule(boolean b) {
302    onDisabledRule = b;
303    return this;
304  }
305
306  public boolean isChanged() {
307    return isChanged;
308  }
309
310  public DefaultIssue setChanged(boolean b) {
311    isChanged = b;
312    return this;
313  }
314
315  public boolean mustSendNotifications() {
316    return sendNotifications;
317  }
318
319  public DefaultIssue setSendNotifications(boolean b) {
320    sendNotifications = b;
321    return this;
322  }
323
324  @CheckForNull
325  public String attribute(String key) {
326    return attributes == null ? null : attributes.get(key);
327  }
328
329  public DefaultIssue setAttribute(String key, @Nullable String value) {
330    if (attributes == null) {
331      attributes = Maps.newHashMap();
332    }
333    if (value == null) {
334      attributes.remove(key);
335    } else {
336      attributes.put(key, value);
337    }
338    return this;
339  }
340
341  public Map<String, String> attributes() {
342    return attributes == null ? Collections.<String, String>emptyMap() : ImmutableMap.copyOf(attributes);
343  }
344
345  public DefaultIssue setAttributes(@Nullable Map<String, String> map) {
346    if (map != null) {
347      if (attributes == null) {
348        attributes = Maps.newHashMap();
349      }
350      attributes.putAll(map);
351    }
352    return this;
353  }
354
355  @CheckForNull
356  public String authorLogin() {
357    return authorLogin;
358  }
359
360  public DefaultIssue setAuthorLogin(@Nullable String s) {
361    this.authorLogin = s;
362    return this;
363  }
364
365  @CheckForNull
366  public String actionPlanKey() {
367    return actionPlanKey;
368  }
369
370  public DefaultIssue setActionPlanKey(@Nullable String actionPlanKey) {
371    this.actionPlanKey = actionPlanKey;
372    return this;
373  }
374
375  public DefaultIssue setFieldChange(IssueChangeContext context, String field, @Nullable Serializable oldValue, @Nullable Serializable newValue) {
376    if (!Objects.equal(oldValue, newValue)) {
377      if (currentChange == null) {
378        currentChange = new FieldDiffs();
379        currentChange.setUserLogin(context.login());
380      }
381      currentChange.setDiff(field, oldValue, newValue);
382    }
383    return this;
384  }
385
386  @CheckForNull
387  public FieldDiffs currentChange() {
388    return currentChange;
389  }
390
391  public DefaultIssue addComment(DefaultIssueComment comment) {
392    if (comments == null) {
393      comments = Lists.newArrayList();
394    }
395    comments.add(comment);
396    return this;
397  }
398
399  @SuppressWarnings("unchcked")
400  public List<IssueComment> comments() {
401    if (comments == null) {
402      return Collections.emptyList();
403    }
404    return ImmutableList.copyOf(comments);
405  }
406
407  @CheckForNull
408  public Date selectedAt() {
409    return selectedAt;
410  }
411
412  public DefaultIssue setSelectedAt(@Nullable Date d) {
413    this.selectedAt = d;
414    return this;
415  }
416
417  @Override
418  public boolean equals(Object o) {
419    if (this == o) {
420      return true;
421    }
422    if (o == null || getClass() != o.getClass()) {
423      return false;
424    }
425    DefaultIssue that = (DefaultIssue) o;
426    if (key != null ? !key.equals(that.key) : that.key != null) {
427      return false;
428    }
429    return true;
430  }
431
432  @Override
433  public int hashCode() {
434    return key != null ? key.hashCode() : 0;
435  }
436
437  @Override
438  public String toString() {
439    return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
440  }
441
442
443}