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