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.batch.issue;
021
022 import com.google.common.base.Objects;
023 import com.google.common.base.Strings;
024 import org.sonar.api.batch.debt.DebtRemediationFunction;
025 import org.sonar.api.batch.rule.ActiveRule;
026 import org.sonar.api.batch.rule.ActiveRules;
027 import org.sonar.api.batch.rule.Rule;
028 import org.sonar.api.batch.rule.Rules;
029 import org.sonar.api.issue.internal.DefaultIssue;
030 import org.sonar.api.resources.Project;
031 import org.sonar.api.rule.RuleKey;
032 import org.sonar.api.rules.Violation;
033 import org.sonar.api.utils.Duration;
034 import org.sonar.api.utils.MessageException;
035 import org.sonar.core.issue.DefaultIssueBuilder;
036
037 import javax.annotation.Nullable;
038
039 /**
040 * Initialize the issues raised during scan.
041 */
042 public class ModuleIssues {
043
044 private final ActiveRules activeRules;
045 private final Rules rules;
046 private final IssueCache cache;
047 private final Project project;
048 private final IssueFilters filters;
049
050 public ModuleIssues(ActiveRules activeRules, Rules rules, IssueCache cache, @Nullable Project project, IssueFilters filters) {
051 this.activeRules = activeRules;
052 this.rules = rules;
053 this.cache = cache;
054 this.project = project;
055 this.filters = filters;
056 }
057
058 /**
059 * Used by scan2
060 */
061 public ModuleIssues(ActiveRules activeRules, Rules rules, IssueCache cache, IssueFilters filters) {
062 this(activeRules, rules, cache, null, filters);
063 }
064
065 public boolean initAndAddIssue(DefaultIssue issue) {
066 return initAndAddIssue(issue, null);
067 }
068
069 public boolean initAndAddViolation(Violation violation) {
070 DefaultIssue issue = newIssue(violation);
071 return initAndAddIssue(issue, violation);
072 }
073
074 private DefaultIssue newIssue(Violation violation) {
075 return (DefaultIssue) new DefaultIssueBuilder()
076 .componentKey(violation.getResource().getEffectiveKey())
077 // Project can be null but Violation not used by scan2
078 .projectKey(project.getRoot().getEffectiveKey())
079 .ruleKey(RuleKey.of(violation.getRule().getRepositoryKey(), violation.getRule().getKey()))
080 .effortToFix(violation.getCost())
081 .line(violation.getLineId())
082 .message(violation.getMessage())
083 .severity(violation.getSeverity() != null ? violation.getSeverity().name() : null)
084 .build();
085 }
086
087 private boolean initAndAddIssue(DefaultIssue issue, @Nullable Violation violation) {
088 RuleKey ruleKey = issue.ruleKey();
089 Rule rule = rules.find(ruleKey);
090 validateRule(issue, rule);
091 ActiveRule activeRule = activeRules.find(ruleKey);
092 if (activeRule == null) {
093 // rule does not exist or is not enabled -> ignore the issue
094 return false;
095 }
096 updateIssue(issue, rule, activeRule);
097 if (filters.accept(issue, violation)) {
098 cache.put(issue);
099 return true;
100 }
101 return false;
102 }
103
104 private void validateRule(DefaultIssue issue, Rule rule) {
105 RuleKey ruleKey = issue.ruleKey();
106 if (rule == null) {
107 throw MessageException.of(String.format("The rule '%s' does not exist.", ruleKey));
108 }
109 if (Strings.isNullOrEmpty(rule.name()) && Strings.isNullOrEmpty(issue.message())) {
110 throw MessageException.of(String.format("The rule '%s' has no name and the related issue has no message.", ruleKey));
111 }
112 }
113
114 private void updateIssue(DefaultIssue issue, Rule rule, ActiveRule activeRule) {
115 if (Strings.isNullOrEmpty(issue.message())) {
116 issue.setMessage(rule.name());
117 }
118 if (project != null) {
119 issue.setCreationDate(project.getAnalysisDate());
120 issue.setUpdateDate(project.getAnalysisDate());
121 }
122 if (issue.severity() == null) {
123 issue.setSeverity(activeRule.severity());
124 }
125 DebtRemediationFunction function = rule.debtRemediationFunction();
126 if (rule.debtSubCharacteristic() != null && function != null) {
127 issue.setDebt(calculateDebt(function, issue.effortToFix(), rule.key()));
128 }
129 }
130
131 private Duration calculateDebt(DebtRemediationFunction function, @Nullable Double effortToFix, RuleKey ruleKey) {
132 if (DebtRemediationFunction.Type.CONSTANT_ISSUE.equals(function.type()) && effortToFix != null) {
133 throw new IllegalArgumentException("Rule '" + ruleKey + "' can not use 'Constant/issue' remediation function " +
134 "because this rule does not have a fixed remediation cost.");
135 }
136 Duration result = Duration.create(0);
137 Duration factor = function.coefficient();
138 Duration offset = function.offset();
139
140 if (factor != null) {
141 int effortToFixValue = Objects.firstNonNull(effortToFix, 1).intValue();
142 result = factor.multiply(effortToFixValue);
143 }
144 if (offset != null) {
145 result = result.add(offset);
146 }
147 return result;
148 }
149
150 }