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