123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227 |
- /*
- * SonarQube
- * Copyright (C) 2009-2024 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
- package org.sonar.ce.task.projectanalysis.qualitymodel;
-
- import java.util.Map;
- import java.util.Optional;
- import java.util.Set;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.sonar.api.measures.CoreMetrics;
- import org.sonar.api.utils.KeyValueFormat;
- import org.sonar.ce.task.projectanalysis.component.Component;
- import org.sonar.ce.task.projectanalysis.component.CrawlerDepthLimit;
- import org.sonar.ce.task.projectanalysis.component.PathAwareVisitorAdapter;
- import org.sonar.ce.task.projectanalysis.formula.counter.LongValue;
- import org.sonar.ce.task.projectanalysis.issue.IntegrateIssuesVisitor;
- import org.sonar.ce.task.projectanalysis.measure.Measure;
- import org.sonar.ce.task.projectanalysis.measure.MeasureRepository;
- import org.sonar.ce.task.projectanalysis.metric.Metric;
- import org.sonar.ce.task.projectanalysis.metric.MetricRepository;
- import org.sonar.ce.task.projectanalysis.source.NewLinesRepository;
-
- import static org.sonar.api.measures.CoreMetrics.NCLOC_DATA_KEY;
- import static org.sonar.api.measures.CoreMetrics.NEW_DEVELOPMENT_COST_KEY;
- import static org.sonar.api.measures.CoreMetrics.NEW_MAINTAINABILITY_RATING_KEY;
- import static org.sonar.api.measures.CoreMetrics.NEW_SQALE_DEBT_RATIO_KEY;
- import static org.sonar.api.measures.CoreMetrics.NEW_TECHNICAL_DEBT_KEY;
- import static org.sonar.api.utils.KeyValueFormat.newIntegerConverter;
- import static org.sonar.ce.task.projectanalysis.component.ComponentVisitor.Order.POST_ORDER;
- import static org.sonar.ce.task.projectanalysis.measure.Measure.newMeasureBuilder;
-
- /**
- * This visitor depends on {@link IntegrateIssuesVisitor} for the computation of
- * metric {@link CoreMetrics#NEW_TECHNICAL_DEBT}.
- * Compute following measure :
- * {@link CoreMetrics#NEW_SQALE_DEBT_RATIO_KEY}
- * {@link CoreMetrics#NEW_MAINTAINABILITY_RATING_KEY}
- */
- public class NewMaintainabilityMeasuresVisitor extends PathAwareVisitorAdapter<NewMaintainabilityMeasuresVisitor.Counter> {
- private static final Logger LOG = LoggerFactory.getLogger(NewMaintainabilityMeasuresVisitor.class);
-
- private final MeasureRepository measureRepository;
- private final NewLinesRepository newLinesRepository;
- private final RatingSettings ratingSettings;
-
- private final Metric newDebtMetric;
- private final Metric nclocDataMetric;
-
- private final Metric newDevelopmentCostMetric;
- private final Metric newDebtRatioMetric;
- private final Metric newMaintainabilityRatingMetric;
-
- public NewMaintainabilityMeasuresVisitor(MetricRepository metricRepository, MeasureRepository measureRepository, NewLinesRepository newLinesRepository,
- RatingSettings ratingSettings) {
- super(CrawlerDepthLimit.FILE, POST_ORDER, CounterFactory.INSTANCE);
- this.measureRepository = measureRepository;
- this.newLinesRepository = newLinesRepository;
- this.ratingSettings = ratingSettings;
-
- // computed by NewDebtAggregator which is executed by IntegrateIssuesVisitor
- this.newDebtMetric = metricRepository.getByKey(NEW_TECHNICAL_DEBT_KEY);
- // which line is ncloc and which isn't
- this.nclocDataMetric = metricRepository.getByKey(NCLOC_DATA_KEY);
-
- // output metrics
- this.newDevelopmentCostMetric = metricRepository.getByKey(NEW_DEVELOPMENT_COST_KEY);
- this.newDebtRatioMetric = metricRepository.getByKey(NEW_SQALE_DEBT_RATIO_KEY);
- this.newMaintainabilityRatingMetric = metricRepository.getByKey(NEW_MAINTAINABILITY_RATING_KEY);
- }
-
- @Override
- public void visitProject(Component project, Path<Counter> path) {
- computeAndSaveNewDebtRatioMeasure(project, path);
- }
-
- @Override
- public void visitDirectory(Component directory, Path<Counter> path) {
- computeAndSaveNewDebtRatioMeasure(directory, path);
- increaseNewDebtAndDevCostOfParent(path);
- }
-
- @Override
- public void visitFile(Component file, Path<Counter> path) {
- initNewDebtRatioCounter(file, path);
- computeAndSaveNewDebtRatioMeasure(file, path);
- increaseNewDebtAndDevCostOfParent(path);
- }
-
- private void computeAndSaveNewDebtRatioMeasure(Component component, Path<Counter> path) {
- if (!newLinesRepository.newLinesAvailable()) {
- return;
- }
- double density = computeDensity(path.current());
- double newDebtRatio = 100.0 * density;
- int newMaintainability = ratingSettings.getDebtRatingGrid().getRatingForDensity(density).getIndex();
- float newDevelopmentCost = path.current().getDevCost().getValue();
- measureRepository.add(component, this.newDevelopmentCostMetric, newMeasureBuilder().create(newDevelopmentCost));
- measureRepository.add(component, this.newDebtRatioMetric, newMeasureBuilder().create(newDebtRatio));
- measureRepository.add(component, this.newMaintainabilityRatingMetric, newMeasureBuilder().create(newMaintainability));
- }
-
- private static double computeDensity(Counter counter) {
- LongValue newDebt = counter.getNewDebt();
- if (newDebt.isSet()) {
- long developmentCost = counter.getDevCost().getValue();
- if (developmentCost != 0L) {
- return newDebt.getValue() / (double) developmentCost;
- }
- }
- return 0D;
- }
-
- private static long getLongValue(Optional<Measure> measure) {
- return measure.map(NewMaintainabilityMeasuresVisitor::getLongValue).orElse(0L);
- }
-
- private static long getLongValue(Measure measure) {
- return measure.getLongValue();
- }
-
- private void initNewDebtRatioCounter(Component file, Path<Counter> path) {
- // first analysis, no period, no differential value to compute, save processing time and return now
- if (!newLinesRepository.newLinesAvailable()) {
- return;
- }
-
- Optional<Set<Integer>> changedLines = newLinesRepository.getNewLines(file);
-
- if (!changedLines.isPresent()) {
- LOG.trace(String.format("No information about changed lines is available for file '%s'. Dev cost will be zero.", file.getKey()));
- return;
- }
-
- Optional<Measure> nclocDataMeasure = measureRepository.getRawMeasure(file, this.nclocDataMetric);
- if (!nclocDataMeasure.isPresent()) {
- return;
- }
-
- initNewDebtRatioCounter(path.current(), file, nclocDataMeasure.get(), changedLines.get());
- }
-
- private void initNewDebtRatioCounter(Counter devCostCounter, Component file, Measure nclocDataMeasure, Set<Integer> changedLines) {
- boolean hasDevCost = false;
-
- long lineDevCost = ratingSettings.getDevCost();
- for (Integer nclocLineIndex : nclocLineIndexes(nclocDataMeasure)) {
- if (changedLines.contains(nclocLineIndex)) {
- devCostCounter.incrementDevCost(lineDevCost);
- hasDevCost = true;
- }
- }
- if (hasDevCost) {
- long newDebt = getLongValue(measureRepository.getRawMeasure(file, this.newDebtMetric));
- devCostCounter.incrementNewDebt(newDebt);
- }
- }
-
- private static void increaseNewDebtAndDevCostOfParent(Path<Counter> path) {
- path.parent().add(path.current());
- }
-
- /**
- * NCLOC_DATA contains Key-value pairs, where key - is a number of line, and value - is an indicator of whether line
- * contains code (1) or not (0).
- * This method parses the value of the NCLOC_DATA measure and return the line numbers of lines which contain code.
- */
- private static Iterable<Integer> nclocLineIndexes(Measure nclocDataMeasure) {
- Map<Integer, Integer> parsedNclocData = KeyValueFormat.parse(nclocDataMeasure.getData(), newIntegerConverter(), newIntegerConverter());
- return parsedNclocData.entrySet()
- .stream()
- .filter(entry -> entry.getValue() == 1)
- .map(Map.Entry::getKey)
- .toList();
- }
-
- public static final class Counter {
- private final LongValue newDebt = new LongValue();
- private final LongValue devCost = new LongValue();
-
- public void add(Counter counter) {
- this.newDebt.increment(counter.newDebt);
- this.devCost.increment(counter.devCost);
- }
-
- LongValue incrementNewDebt(long value) {
- return newDebt.increment(value);
- }
-
- LongValue incrementDevCost(long value) {
- return devCost.increment(value);
- }
-
- LongValue getNewDebt() {
- return this.newDebt;
- }
-
- LongValue getDevCost() {
- return this.devCost;
- }
- }
-
- private static class CounterFactory extends SimpleStackElementFactory<Counter> {
- public static final CounterFactory INSTANCE = new CounterFactory();
-
- @Override
- public Counter createForAny(Component component) {
- return new Counter();
- }
- }
- }
|