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}