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