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