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