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 }