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