Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

NewMaintainabilityMeasuresVisitor.java 9.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2024 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program 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. * This program 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. package org.sonar.ce.task.projectanalysis.qualitymodel;
  21. import java.util.Map;
  22. import java.util.Optional;
  23. import java.util.Set;
  24. import org.slf4j.Logger;
  25. import org.slf4j.LoggerFactory;
  26. import org.sonar.api.measures.CoreMetrics;
  27. import org.sonar.api.utils.KeyValueFormat;
  28. import org.sonar.ce.task.projectanalysis.component.Component;
  29. import org.sonar.ce.task.projectanalysis.component.CrawlerDepthLimit;
  30. import org.sonar.ce.task.projectanalysis.component.PathAwareVisitorAdapter;
  31. import org.sonar.ce.task.projectanalysis.formula.counter.LongValue;
  32. import org.sonar.ce.task.projectanalysis.issue.IntegrateIssuesVisitor;
  33. import org.sonar.ce.task.projectanalysis.measure.Measure;
  34. import org.sonar.ce.task.projectanalysis.measure.MeasureRepository;
  35. import org.sonar.ce.task.projectanalysis.metric.Metric;
  36. import org.sonar.ce.task.projectanalysis.metric.MetricRepository;
  37. import org.sonar.ce.task.projectanalysis.source.NewLinesRepository;
  38. import static org.sonar.api.measures.CoreMetrics.NCLOC_DATA_KEY;
  39. import static org.sonar.api.measures.CoreMetrics.NEW_DEVELOPMENT_COST_KEY;
  40. import static org.sonar.api.measures.CoreMetrics.NEW_MAINTAINABILITY_RATING_KEY;
  41. import static org.sonar.api.measures.CoreMetrics.NEW_SQALE_DEBT_RATIO_KEY;
  42. import static org.sonar.api.measures.CoreMetrics.NEW_TECHNICAL_DEBT_KEY;
  43. import static org.sonar.api.utils.KeyValueFormat.newIntegerConverter;
  44. import static org.sonar.ce.task.projectanalysis.component.ComponentVisitor.Order.POST_ORDER;
  45. import static org.sonar.ce.task.projectanalysis.measure.Measure.newMeasureBuilder;
  46. /**
  47. * This visitor depends on {@link IntegrateIssuesVisitor} for the computation of
  48. * metric {@link CoreMetrics#NEW_TECHNICAL_DEBT}.
  49. * Compute following measure :
  50. * {@link CoreMetrics#NEW_SQALE_DEBT_RATIO_KEY}
  51. * {@link CoreMetrics#NEW_MAINTAINABILITY_RATING_KEY}
  52. */
  53. public class NewMaintainabilityMeasuresVisitor extends PathAwareVisitorAdapter<NewMaintainabilityMeasuresVisitor.Counter> {
  54. private static final Logger LOG = LoggerFactory.getLogger(NewMaintainabilityMeasuresVisitor.class);
  55. private final MeasureRepository measureRepository;
  56. private final NewLinesRepository newLinesRepository;
  57. private final RatingSettings ratingSettings;
  58. private final Metric newDebtMetric;
  59. private final Metric nclocDataMetric;
  60. private final Metric newDevelopmentCostMetric;
  61. private final Metric newDebtRatioMetric;
  62. private final Metric newMaintainabilityRatingMetric;
  63. public NewMaintainabilityMeasuresVisitor(MetricRepository metricRepository, MeasureRepository measureRepository, NewLinesRepository newLinesRepository,
  64. RatingSettings ratingSettings) {
  65. super(CrawlerDepthLimit.FILE, POST_ORDER, CounterFactory.INSTANCE);
  66. this.measureRepository = measureRepository;
  67. this.newLinesRepository = newLinesRepository;
  68. this.ratingSettings = ratingSettings;
  69. // computed by NewDebtAggregator which is executed by IntegrateIssuesVisitor
  70. this.newDebtMetric = metricRepository.getByKey(NEW_TECHNICAL_DEBT_KEY);
  71. // which line is ncloc and which isn't
  72. this.nclocDataMetric = metricRepository.getByKey(NCLOC_DATA_KEY);
  73. // output metrics
  74. this.newDevelopmentCostMetric = metricRepository.getByKey(NEW_DEVELOPMENT_COST_KEY);
  75. this.newDebtRatioMetric = metricRepository.getByKey(NEW_SQALE_DEBT_RATIO_KEY);
  76. this.newMaintainabilityRatingMetric = metricRepository.getByKey(NEW_MAINTAINABILITY_RATING_KEY);
  77. }
  78. @Override
  79. public void visitProject(Component project, Path<Counter> path) {
  80. computeAndSaveNewDebtRatioMeasure(project, path);
  81. }
  82. @Override
  83. public void visitDirectory(Component directory, Path<Counter> path) {
  84. computeAndSaveNewDebtRatioMeasure(directory, path);
  85. increaseNewDebtAndDevCostOfParent(path);
  86. }
  87. @Override
  88. public void visitFile(Component file, Path<Counter> path) {
  89. initNewDebtRatioCounter(file, path);
  90. computeAndSaveNewDebtRatioMeasure(file, path);
  91. increaseNewDebtAndDevCostOfParent(path);
  92. }
  93. private void computeAndSaveNewDebtRatioMeasure(Component component, Path<Counter> path) {
  94. if (!newLinesRepository.newLinesAvailable()) {
  95. return;
  96. }
  97. double density = computeDensity(path.current());
  98. double newDebtRatio = 100.0 * density;
  99. int newMaintainability = ratingSettings.getDebtRatingGrid().getRatingForDensity(density).getIndex();
  100. float newDevelopmentCost = path.current().getDevCost().getValue();
  101. measureRepository.add(component, this.newDevelopmentCostMetric, newMeasureBuilder().create(newDevelopmentCost));
  102. measureRepository.add(component, this.newDebtRatioMetric, newMeasureBuilder().create(newDebtRatio));
  103. measureRepository.add(component, this.newMaintainabilityRatingMetric, newMeasureBuilder().create(newMaintainability));
  104. }
  105. private static double computeDensity(Counter counter) {
  106. LongValue newDebt = counter.getNewDebt();
  107. if (newDebt.isSet()) {
  108. long developmentCost = counter.getDevCost().getValue();
  109. if (developmentCost != 0L) {
  110. return newDebt.getValue() / (double) developmentCost;
  111. }
  112. }
  113. return 0D;
  114. }
  115. private static long getLongValue(Optional<Measure> measure) {
  116. return measure.map(NewMaintainabilityMeasuresVisitor::getLongValue).orElse(0L);
  117. }
  118. private static long getLongValue(Measure measure) {
  119. return measure.getLongValue();
  120. }
  121. private void initNewDebtRatioCounter(Component file, Path<Counter> path) {
  122. // first analysis, no period, no differential value to compute, save processing time and return now
  123. if (!newLinesRepository.newLinesAvailable()) {
  124. return;
  125. }
  126. Optional<Set<Integer>> changedLines = newLinesRepository.getNewLines(file);
  127. if (!changedLines.isPresent()) {
  128. LOG.trace(String.format("No information about changed lines is available for file '%s'. Dev cost will be zero.", file.getKey()));
  129. return;
  130. }
  131. Optional<Measure> nclocDataMeasure = measureRepository.getRawMeasure(file, this.nclocDataMetric);
  132. if (!nclocDataMeasure.isPresent()) {
  133. return;
  134. }
  135. initNewDebtRatioCounter(path.current(), file, nclocDataMeasure.get(), changedLines.get());
  136. }
  137. private void initNewDebtRatioCounter(Counter devCostCounter, Component file, Measure nclocDataMeasure, Set<Integer> changedLines) {
  138. boolean hasDevCost = false;
  139. long lineDevCost = ratingSettings.getDevCost();
  140. for (Integer nclocLineIndex : nclocLineIndexes(nclocDataMeasure)) {
  141. if (changedLines.contains(nclocLineIndex)) {
  142. devCostCounter.incrementDevCost(lineDevCost);
  143. hasDevCost = true;
  144. }
  145. }
  146. if (hasDevCost) {
  147. long newDebt = getLongValue(measureRepository.getRawMeasure(file, this.newDebtMetric));
  148. devCostCounter.incrementNewDebt(newDebt);
  149. }
  150. }
  151. private static void increaseNewDebtAndDevCostOfParent(Path<Counter> path) {
  152. path.parent().add(path.current());
  153. }
  154. /**
  155. * NCLOC_DATA contains Key-value pairs, where key - is a number of line, and value - is an indicator of whether line
  156. * contains code (1) or not (0).
  157. * This method parses the value of the NCLOC_DATA measure and return the line numbers of lines which contain code.
  158. */
  159. private static Iterable<Integer> nclocLineIndexes(Measure nclocDataMeasure) {
  160. Map<Integer, Integer> parsedNclocData = KeyValueFormat.parse(nclocDataMeasure.getData(), newIntegerConverter(), newIntegerConverter());
  161. return parsedNclocData.entrySet()
  162. .stream()
  163. .filter(entry -> entry.getValue() == 1)
  164. .map(Map.Entry::getKey)
  165. .toList();
  166. }
  167. public static final class Counter {
  168. private final LongValue newDebt = new LongValue();
  169. private final LongValue devCost = new LongValue();
  170. public void add(Counter counter) {
  171. this.newDebt.increment(counter.newDebt);
  172. this.devCost.increment(counter.devCost);
  173. }
  174. LongValue incrementNewDebt(long value) {
  175. return newDebt.increment(value);
  176. }
  177. LongValue incrementDevCost(long value) {
  178. return devCost.increment(value);
  179. }
  180. LongValue getNewDebt() {
  181. return this.newDebt;
  182. }
  183. LongValue getDevCost() {
  184. return this.devCost;
  185. }
  186. }
  187. private static class CounterFactory extends SimpleStackElementFactory<Counter> {
  188. public static final CounterFactory INSTANCE = new CounterFactory();
  189. @Override
  190. public Counter createForAny(Component component) {
  191. return new Counter();
  192. }
  193. }
  194. }