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