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