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