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