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