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;
040import java.io.Serializable;
041import java.util.*;
042
043/**
044 * PLUGINS MUST NOT BE USED THIS CLASS, EXCEPT FOR UNIT TESTING.
045 *
046 * @since 3.6
047 */
048public 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}