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     */
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    
037    import javax.annotation.CheckForNull;
038    import javax.annotation.Nullable;
039    
040    import java.io.Serializable;
041    import java.util.*;
042    
043    import 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     */
050    public 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    }