3 * Copyright (C) 2009-2021 SonarSource SA
4 * mailto:info AT sonarsource DOT com
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.
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.
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.
20 package org.sonar.ce.task.projectanalysis.step;
22 import java.util.Arrays;
23 import java.util.HashSet;
24 import java.util.List;
25 import java.util.Optional;
27 import java.util.stream.IntStream;
28 import org.sonar.ce.task.projectanalysis.component.Component;
29 import org.sonar.ce.task.projectanalysis.component.PathAwareCrawler;
30 import org.sonar.ce.task.projectanalysis.component.TreeRootHolder;
31 import org.sonar.ce.task.projectanalysis.duplication.Duplication;
32 import org.sonar.ce.task.projectanalysis.duplication.DuplicationRepository;
33 import org.sonar.ce.task.projectanalysis.duplication.InnerDuplicate;
34 import org.sonar.ce.task.projectanalysis.duplication.TextBlock;
35 import org.sonar.ce.task.projectanalysis.formula.Counter;
36 import org.sonar.ce.task.projectanalysis.formula.CounterInitializationContext;
37 import org.sonar.ce.task.projectanalysis.formula.CreateMeasureContext;
38 import org.sonar.ce.task.projectanalysis.formula.Formula;
39 import org.sonar.ce.task.projectanalysis.formula.FormulaExecutorComponentVisitor;
40 import org.sonar.ce.task.projectanalysis.formula.counter.IntValue;
41 import org.sonar.ce.task.projectanalysis.measure.Measure;
42 import org.sonar.ce.task.projectanalysis.measure.MeasureRepository;
43 import org.sonar.ce.task.projectanalysis.metric.MetricRepository;
44 import org.sonar.ce.task.projectanalysis.source.NewLinesRepository;
45 import org.sonar.ce.task.step.ComputationStep;
47 import static org.sonar.api.measures.CoreMetrics.NEW_BLOCKS_DUPLICATED_KEY;
48 import static org.sonar.api.measures.CoreMetrics.NEW_DUPLICATED_LINES_DENSITY_KEY;
49 import static org.sonar.api.measures.CoreMetrics.NEW_DUPLICATED_LINES_KEY;
50 import static org.sonar.api.measures.CoreMetrics.NEW_LINES_KEY;
53 * Computes measures on new code related to the size
55 public class NewSizeMeasuresStep implements ComputationStep {
56 private final TreeRootHolder treeRootHolder;
57 private final MetricRepository metricRepository;
58 private final MeasureRepository measureRepository;
59 private final NewDuplicationFormula duplicationFormula;
61 public NewSizeMeasuresStep(TreeRootHolder treeRootHolder, MetricRepository metricRepository, MeasureRepository measureRepository,
62 NewLinesRepository newLinesRepository, DuplicationRepository duplicationRepository) {
63 this.treeRootHolder = treeRootHolder;
64 this.metricRepository = metricRepository;
65 this.measureRepository = measureRepository;
66 this.duplicationFormula = new NewDuplicationFormula(newLinesRepository, duplicationRepository);
70 public String getDescription() {
71 return "Compute size measures on new code";
75 public void execute(ComputationStep.Context context) {
76 new PathAwareCrawler<>(
77 FormulaExecutorComponentVisitor.newBuilder(metricRepository, measureRepository)
78 .buildFor(List.of(duplicationFormula)))
79 .visit(treeRootHolder.getRoot());
82 private static class NewSizeCounter implements Counter<NewSizeCounter> {
83 private final DuplicationRepository duplicationRepository;
84 private final NewLinesRepository newLinesRepository;
86 private final IntValue newLines = new IntValue();
87 private final IntValue newDuplicatedLines = new IntValue();
88 private final IntValue newDuplicatedBlocks = new IntValue();
90 private NewSizeCounter(DuplicationRepository duplicationRepository, NewLinesRepository newLinesRepository) {
91 this.duplicationRepository = duplicationRepository;
92 this.newLinesRepository = newLinesRepository;
96 public void aggregate(NewSizeCounter counter) {
97 this.newDuplicatedLines.increment(counter.newDuplicatedLines);
98 this.newDuplicatedBlocks.increment(counter.newDuplicatedBlocks);
99 this.newLines.increment(counter.newLines);
103 public void initialize(CounterInitializationContext context) {
104 Component leaf = context.getLeaf();
105 if (leaf.getType() != Component.Type.FILE) {
108 Optional<Set<Integer>> changedLines = newLinesRepository.getNewLines(leaf);
109 if (!changedLines.isPresent()) {
113 newLines.increment(0);
114 if (leaf.getType() != Component.Type.FILE) {
115 newDuplicatedLines.increment(0);
116 newDuplicatedBlocks.increment(0);
118 initNewLines(changedLines.get());
119 initNewDuplicated(leaf, changedLines.get());
123 private void initNewLines(Set<Integer> changedLines) {
124 newLines.increment(changedLines.size());
127 private void initNewDuplicated(Component component, Set<Integer> changedLines) {
128 DuplicationCounters duplicationCounters = new DuplicationCounters(changedLines);
129 Iterable<Duplication> duplications = duplicationRepository.getDuplications(component);
130 for (Duplication duplication : duplications) {
131 duplicationCounters.addBlock(duplication.getOriginal());
132 Arrays.stream(duplication.getDuplicates())
133 .filter(InnerDuplicate.class::isInstance)
134 .map(duplicate -> (InnerDuplicate) duplicate)
135 .forEach(duplicate -> duplicationCounters.addBlock(duplicate.getTextBlock()));
138 newDuplicatedLines.increment(duplicationCounters.getNewLinesDuplicated());
139 newDuplicatedBlocks.increment(duplicationCounters.getNewBlocksDuplicated());
143 private static class DuplicationCounters {
144 private final Set<Integer> changedLines;
145 private final Set<Integer> lineCounts;
146 private int blockCounts;
148 private DuplicationCounters(Set<Integer> changedLines) {
149 this.changedLines = changedLines;
150 this.lineCounts = new HashSet<>(changedLines.size());
153 void addBlock(TextBlock textBlock) {
154 Boolean[] newBlock = new Boolean[] {false};
155 IntStream.rangeClosed(textBlock.getStart(), textBlock.getEnd())
156 .filter(changedLines::contains)
158 lineCounts.add(line);
166 int getNewLinesDuplicated() {
167 return lineCounts.size();
170 int getNewBlocksDuplicated() {
175 private static final class NewDuplicationFormula implements Formula<NewSizeCounter> {
176 private final DuplicationRepository duplicationRepository;
177 private final NewLinesRepository newLinesRepository;
179 private NewDuplicationFormula(NewLinesRepository newLinesRepository, DuplicationRepository duplicationRepository) {
180 this.duplicationRepository = duplicationRepository;
181 this.newLinesRepository = newLinesRepository;
185 public NewSizeCounter createNewCounter() {
186 return new NewSizeCounter(duplicationRepository, newLinesRepository);
190 public Optional<Measure> createMeasure(NewSizeCounter counter, CreateMeasureContext context) {
191 String metricKey = context.getMetric().getKey();
194 return createMeasure(counter.newLines);
195 case NEW_DUPLICATED_LINES_KEY:
196 return createMeasure(counter.newDuplicatedLines);
197 case NEW_DUPLICATED_LINES_DENSITY_KEY:
198 return createNewDuplicatedLinesDensityMeasure(counter);
199 case NEW_BLOCKS_DUPLICATED_KEY:
200 return createMeasure(counter.newDuplicatedBlocks);
202 throw new IllegalArgumentException("Unsupported metric " + context.getMetric());
206 private static Optional<Measure> createMeasure(IntValue intValue) {
207 return intValue.isSet()
208 ? Optional.of(Measure.newMeasureBuilder().setVariation(intValue.getValue()).createNoValue())
212 private static Optional<Measure> createNewDuplicatedLinesDensityMeasure(NewSizeCounter counter) {
213 IntValue newLines = counter.newLines;
214 IntValue newDuplicatedLines = counter.newDuplicatedLines;
215 if (newLines.isSet() && newDuplicatedLines.isSet()) {
216 int newLinesVariations = newLines.getValue();
217 int newDuplicatedLinesVariations = newDuplicatedLines.getValue();
218 if (newLinesVariations > 0d) {
219 double density = Math.min(100d, 100d * newDuplicatedLinesVariations / newLinesVariations);
220 return Optional.of(Measure.newMeasureBuilder().setVariation(density).createNoValue());
223 return Optional.empty();
227 public String[] getOutputMetricKeys() {
228 return new String[] {NEW_LINES_KEY, NEW_DUPLICATED_LINES_KEY, NEW_DUPLICATED_LINES_DENSITY_KEY, NEW_BLOCKS_DUPLICATED_KEY};