2 * SonarQube, open source software quality management tool.
3 * Copyright (C) 2008-2013 SonarSource
4 * mailto:contact AT sonarsource DOT com
6 * SonarQube is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * SonarQube is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 package org.sonar.plugins.core.technicaldebt;
23 import com.google.common.annotations.VisibleForTesting;
24 import com.google.common.collect.ArrayListMultimap;
25 import com.google.common.collect.ImmutableList;
26 import com.google.common.collect.ListMultimap;
27 import org.slf4j.LoggerFactory;
28 import org.sonar.api.CoreProperties;
29 import org.sonar.api.PropertyType;
30 import org.sonar.api.batch.*;
31 import org.sonar.api.component.ResourcePerspectives;
32 import org.sonar.api.config.PropertyDefinition;
33 import org.sonar.api.issue.Issuable;
34 import org.sonar.api.issue.Issue;
35 import org.sonar.api.issue.internal.DefaultIssue;
36 import org.sonar.api.measures.*;
37 import org.sonar.api.resources.Project;
38 import org.sonar.api.resources.Resource;
39 import org.sonar.api.resources.ResourceUtils;
40 import org.sonar.api.technicaldebt.batch.Characteristic;
41 import org.sonar.api.technicaldebt.batch.Requirement;
42 import org.sonar.api.technicaldebt.batch.TechnicalDebtModel;
43 import org.sonar.api.utils.WorkDuration;
44 import org.sonar.api.utils.WorkDurationFactory;
46 import java.util.Arrays;
47 import java.util.Collection;
48 import java.util.List;
51 import static com.google.common.collect.Lists.newArrayList;
52 import static com.google.common.collect.Maps.newHashMap;
55 * Decorator that computes the technical debt metric
57 @DependsUpon(DecoratorBarriers.ISSUES_TRACKED)
58 public final class TechnicalDebtDecorator implements Decorator {
60 private static final int DECIMALS_PRECISION = 5;
62 private final ResourcePerspectives perspectives;
63 private final TechnicalDebtModel model;
64 private final WorkDurationFactory workDurationFactory;
66 public TechnicalDebtDecorator(ResourcePerspectives perspectives, TechnicalDebtModel model, WorkDurationFactory workDurationFactory) {
67 this.perspectives = perspectives;
69 this.workDurationFactory = workDurationFactory;
72 public boolean shouldExecuteOnProject(Project project) {
77 public List<Metric> generatesMetrics() {
78 return Arrays.asList(CoreMetrics.TECHNICAL_DEBT);
81 public void decorate(Resource resource, DecoratorContext context) {
82 Issuable issuable = perspectives.as(Issuable.class, resource);
83 if (issuable != null && shouldSaveMeasure(context)) {
84 List<Issue> issues = newArrayList(issuable.issues());
85 saveMeasures(context, issues);
89 private void saveMeasures(DecoratorContext context, List<Issue> issues) {
90 // group issues by requirement
91 ListMultimap<Requirement, Issue> issuesByRequirement = issuesByRequirement(issues);
94 Map<Characteristic, Double> characteristicCosts = newHashMap();
95 Map<Requirement, Double> requirementCosts = newHashMap();
97 for (Requirement requirement : model.requirements()) {
98 List<Issue> requirementIssues = issuesByRequirement.get(requirement);
99 double value = computeTechnicalDebt(CoreMetrics.TECHNICAL_DEBT, context, requirement, requirementIssues);
101 requirementCosts.put(requirement, value);
103 propagateTechnicalDebtInParents(requirement.characteristic(), value, characteristicCosts);
106 context.saveMeasure(new Measure(CoreMetrics.TECHNICAL_DEBT, total, DECIMALS_PRECISION));
107 saveOnCharacteristic(context, characteristicCosts);
108 saveOnRequirement(context, requirementCosts);
111 private void saveOnCharacteristic(DecoratorContext context, Map<Characteristic, Double> characteristicCosts) {
112 for (Map.Entry<Characteristic, Double> entry : characteristicCosts.entrySet()) {
113 saveTechnicalDebt(context, entry.getKey(), entry.getValue(), false);
117 private void saveOnRequirement(DecoratorContext context, Map<Requirement, Double> requirementCosts) {
118 for (Map.Entry<Requirement, Double> entry : requirementCosts.entrySet()) {
119 saveTechnicalDebt(context, entry.getKey(), entry.getValue(), ResourceUtils.isEntity(context.getResource()));
124 void saveTechnicalDebt(DecoratorContext context, Characteristic characteristic, Double value, boolean inMemory) {
125 // we need the value on projects (root or module) even if value==0 in order to display correctly the SQALE history chart (see SQALE-122)
126 // BUT we don't want to save zero-values for non top-characteristics (see SQALE-147)
127 if (value > 0.0 || (ResourceUtils.isProject(context.getResource()) && characteristic.isRoot())) {
128 Measure measure = new Measure(CoreMetrics.TECHNICAL_DEBT);
129 measure.setCharacteristic(characteristic);
130 measure.setValue(value, DECIMALS_PRECISION);
132 measure.setPersistenceMode(PersistenceMode.MEMORY);
134 context.saveMeasure(measure);
139 void saveTechnicalDebt(DecoratorContext context, Requirement requirement, Double value, boolean inMemory) {
140 // we need the value on projects (root or module) even if value==0 in order to display correctly the SQALE history chart (see SQALE-122)
141 // BUT we don't want to save zero-values for non top-characteristics (see SQALE-147)
143 Measure measure = new Measure(CoreMetrics.TECHNICAL_DEBT);
144 measure.setRequirement(requirement);
145 measure.setValue(value, DECIMALS_PRECISION);
147 measure.setPersistenceMode(PersistenceMode.MEMORY);
149 context.saveMeasure(measure);
154 ListMultimap<Requirement, Issue> issuesByRequirement(List<Issue> issues) {
155 ListMultimap<Requirement, Issue> issuesByRequirement = ArrayListMultimap.create();
156 for (Issue issue : issues) {
157 String repositoryKey = issue.ruleKey().repository();
158 String key = issue.ruleKey().rule();
159 Requirement requirement = model.requirementsByRule(issue.ruleKey());
160 if (requirement == null) {
161 LoggerFactory.getLogger(getClass()).debug("No technical debt requirement for: " + repositoryKey + "/" + key);
163 issuesByRequirement.put(requirement, issue);
166 return issuesByRequirement;
169 private double computeTechnicalDebt(Metric metric, DecoratorContext context, Requirement requirement, Collection<Issue> issues) {
170 WorkDuration debt = workDurationFactory.createFromWorkingLong(0l);
171 // double value = 0d;
172 if (issues != null) {
173 for (Issue issue : issues) {
174 // if (debt != null) {
175 // value += debt.toWorkingDays();
177 debt = debt.add(((DefaultIssue) issue).technicalDebt());
181 double value = debt.toWorkingDays();
182 for (Measure measure : context.getChildrenMeasures(MeasuresFilters.requirement(metric, requirement))) {
183 Requirement measureRequirement = measure.getRequirement();
184 if (measureRequirement != null && measureRequirement.equals(requirement) && measure.getValue() != null) {
185 value += measure.getValue();
191 private void propagateTechnicalDebtInParents(Characteristic characteristic, double value, Map<Characteristic, Double> characteristicCosts) {
192 if (characteristic != null) {
193 Double parentCost = characteristicCosts.get(characteristic);
194 if (parentCost == null) {
195 characteristicCosts.put(characteristic, value);
197 characteristicCosts.put(characteristic, value + parentCost);
199 propagateTechnicalDebtInParents(characteristic.parent(), value, characteristicCosts);
203 private boolean shouldSaveMeasure(DecoratorContext context) {
204 return context.getMeasure(CoreMetrics.TECHNICAL_DEBT) == null;
207 public static List<PropertyDefinition> definitions() {
208 return ImmutableList.of(
209 PropertyDefinition.builder(CoreProperties.HOURS_IN_DAY)
210 .name("Number of working hours in a day")
211 .type(PropertyType.INTEGER)
213 .category(CoreProperties.CATEGORY_TECHNICAL_DEBT)
214 .deprecatedKey("sqale.hoursInDay")