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}