]> source.dussan.org Git - sonarqube.git/blob
6813e1f197101298b3f2886d868e622c2dd14c3a
[sonarqube.git] /
1 /*
2  * SonarQube, open source software quality management tool.
3  * Copyright (C) 2008-2013 SonarSource
4  * mailto:contact AT sonarsource DOT com
5  *
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.
10  *
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.
15  *
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.
19  */
20
21 package org.sonar.plugins.core.technicaldebt;
22
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;
45
46 import java.util.Arrays;
47 import java.util.Collection;
48 import java.util.List;
49 import java.util.Map;
50
51 import static com.google.common.collect.Lists.newArrayList;
52 import static com.google.common.collect.Maps.newHashMap;
53
54 /**
55  * Decorator that computes the technical debt metric
56  */
57 @DependsUpon(DecoratorBarriers.ISSUES_TRACKED)
58 public final class TechnicalDebtDecorator implements Decorator {
59
60   private static final int DECIMALS_PRECISION = 5;
61
62   private final ResourcePerspectives perspectives;
63   private final TechnicalDebtModel model;
64   private final WorkDurationFactory workDurationFactory;
65
66   public TechnicalDebtDecorator(ResourcePerspectives perspectives, TechnicalDebtModel model, WorkDurationFactory workDurationFactory) {
67     this.perspectives = perspectives;
68     this.model = model;
69     this.workDurationFactory = workDurationFactory;
70   }
71
72   public boolean shouldExecuteOnProject(Project project) {
73     return true;
74   }
75
76   @DependedUpon
77   public List<Metric> generatesMetrics() {
78     return Arrays.asList(CoreMetrics.TECHNICAL_DEBT);
79   }
80
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);
86     }
87   }
88
89   private void saveMeasures(DecoratorContext context, List<Issue> issues) {
90     // group issues by requirement
91     ListMultimap<Requirement, Issue> issuesByRequirement = issuesByRequirement(issues);
92
93     double total = 0.0;
94     Map<Characteristic, Double> characteristicCosts = newHashMap();
95     Map<Requirement, Double> requirementCosts = newHashMap();
96
97     for (Requirement requirement : model.requirements()) {
98       List<Issue> requirementIssues = issuesByRequirement.get(requirement);
99       double value = computeTechnicalDebt(CoreMetrics.TECHNICAL_DEBT, context, requirement, requirementIssues);
100
101       requirementCosts.put(requirement, value);
102       total += value;
103       propagateTechnicalDebtInParents(requirement.characteristic(), value, characteristicCosts);
104     }
105
106     context.saveMeasure(new Measure(CoreMetrics.TECHNICAL_DEBT, total, DECIMALS_PRECISION));
107     saveOnCharacteristic(context, characteristicCosts);
108     saveOnRequirement(context, requirementCosts);
109   }
110
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);
114     }
115   }
116
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()));
120     }
121   }
122
123   @VisibleForTesting
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);
131       if (inMemory) {
132         measure.setPersistenceMode(PersistenceMode.MEMORY);
133       }
134       context.saveMeasure(measure);
135     }
136   }
137
138   @VisibleForTesting
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)
142     if (value > 0.0) {
143       Measure measure = new Measure(CoreMetrics.TECHNICAL_DEBT);
144       measure.setRequirement(requirement);
145       measure.setValue(value, DECIMALS_PRECISION);
146       if (inMemory) {
147         measure.setPersistenceMode(PersistenceMode.MEMORY);
148       }
149       context.saveMeasure(measure);
150     }
151   }
152
153   @VisibleForTesting
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);
162       } else {
163         issuesByRequirement.put(requirement, issue);
164       }
165     }
166     return issuesByRequirement;
167   }
168
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();
176 //        }
177         debt = debt.add(((DefaultIssue) issue).technicalDebt());
178       }
179     }
180
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();
186       }
187     }
188     return value;
189   }
190
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);
196       } else {
197         characteristicCosts.put(characteristic, value + parentCost);
198       }
199       propagateTechnicalDebtInParents(characteristic.parent(), value, characteristicCosts);
200     }
201   }
202
203   private boolean shouldSaveMeasure(DecoratorContext context) {
204     return context.getMeasure(CoreMetrics.TECHNICAL_DEBT) == null;
205   }
206
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)
212         .defaultValue("8")
213         .category(CoreProperties.CATEGORY_TECHNICAL_DEBT)
214         .deprecatedKey("sqale.hoursInDay")
215         .build()
216     );
217   }
218 }