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    }