3 * Copyright (C) 2009-2016 SonarSource SA
4 * mailto:contact 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.
21 package org.sonar.server.computation.task.projectanalysis.step;
23 import com.google.common.base.Optional;
24 import com.google.common.collect.HashMultiset;
25 import com.google.common.collect.ImmutableList;
26 import com.google.common.collect.ImmutableMap;
27 import com.google.common.collect.Iterables;
28 import com.google.common.collect.LinkedHashMultimap;
29 import com.google.common.collect.Multiset;
30 import com.google.common.collect.SetMultimap;
31 import java.util.HashSet;
32 import java.util.List;
35 import java.util.function.Function;
36 import java.util.stream.Collectors;
37 import java.util.stream.IntStream;
38 import org.sonar.server.computation.task.projectanalysis.component.Component;
39 import org.sonar.server.computation.task.projectanalysis.component.PathAwareCrawler;
40 import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolder;
41 import org.sonar.server.computation.task.projectanalysis.duplication.Duplication;
42 import org.sonar.server.computation.task.projectanalysis.duplication.DuplicationRepository;
43 import org.sonar.server.computation.task.projectanalysis.duplication.InnerDuplicate;
44 import org.sonar.server.computation.task.projectanalysis.duplication.TextBlock;
45 import org.sonar.server.computation.task.projectanalysis.formula.Counter;
46 import org.sonar.server.computation.task.projectanalysis.formula.CounterInitializationContext;
47 import org.sonar.server.computation.task.projectanalysis.formula.CreateMeasureContext;
48 import org.sonar.server.computation.task.projectanalysis.formula.Formula;
49 import org.sonar.server.computation.task.projectanalysis.formula.FormulaExecutorComponentVisitor;
50 import org.sonar.server.computation.task.projectanalysis.formula.counter.DoubleVariationValue;
51 import org.sonar.server.computation.task.projectanalysis.formula.counter.IntVariationValue;
52 import org.sonar.server.computation.task.projectanalysis.measure.Measure;
53 import org.sonar.server.computation.task.projectanalysis.measure.MeasureRepository;
54 import org.sonar.server.computation.task.projectanalysis.measure.MeasureVariations;
55 import org.sonar.server.computation.task.projectanalysis.metric.MetricRepository;
56 import org.sonar.server.computation.task.projectanalysis.period.Period;
57 import org.sonar.server.computation.task.projectanalysis.period.PeriodsHolder;
58 import org.sonar.server.computation.task.projectanalysis.scm.Changeset;
59 import org.sonar.server.computation.task.projectanalysis.scm.ScmInfo;
60 import org.sonar.server.computation.task.projectanalysis.scm.ScmInfoRepository;
61 import org.sonar.server.computation.task.step.ComputationStep;
63 import static java.util.stream.Collectors.toMap;
64 import static org.sonar.api.measures.CoreMetrics.NEW_BLOCKS_DUPLICATED_KEY;
65 import static org.sonar.api.measures.CoreMetrics.NEW_DUPLICATED_LINES_DENSITY_KEY;
66 import static org.sonar.api.measures.CoreMetrics.NEW_DUPLICATED_LINES_KEY;
67 import static org.sonar.api.measures.CoreMetrics.NEW_LINES_KEY;
70 * Computes measures on new code related to the size
72 public class NewSizeMeasuresStep implements ComputationStep {
74 private final TreeRootHolder treeRootHolder;
75 private final PeriodsHolder periodsHolder;
76 private final MetricRepository metricRepository;
77 private final MeasureRepository measureRepository;
78 private final NewDuplicationFormula duplicationFormula;
80 public NewSizeMeasuresStep(TreeRootHolder treeRootHolder, PeriodsHolder periodsHolder, MetricRepository metricRepository, MeasureRepository measureRepository,
81 ScmInfoRepository scmInfoRepository, DuplicationRepository duplicationRepository) {
82 this.treeRootHolder = treeRootHolder;
83 this.periodsHolder = periodsHolder;
84 this.metricRepository = metricRepository;
85 this.measureRepository = measureRepository;
86 this.duplicationFormula = new NewDuplicationFormula(scmInfoRepository, duplicationRepository);
90 public String getDescription() {
91 return "Compute size measures on new code";
95 public void execute() {
96 new PathAwareCrawler<>(
97 FormulaExecutorComponentVisitor.newBuilder(metricRepository, measureRepository)
98 .withVariationSupport(periodsHolder)
99 .buildFor(ImmutableList.of(duplicationFormula)))
100 .visit(treeRootHolder.getRoot());
103 private static class NewSizeCounter implements Counter<NewSizeCounter> {
104 private final DuplicationRepository duplicationRepository;
105 private final ScmInfoRepository scmInfoRepository;
106 private final IntVariationValue.Array newLines = IntVariationValue.newArray();
107 private final IntVariationValue.Array newDuplicatedLines = IntVariationValue.newArray();
108 private final IntVariationValue.Array newDuplicatedBlocks = IntVariationValue.newArray();
110 private NewSizeCounter(DuplicationRepository duplicationRepository,
111 ScmInfoRepository scmInfoRepository) {
112 this.duplicationRepository = duplicationRepository;
113 this.scmInfoRepository = scmInfoRepository;
117 public void aggregate(NewSizeCounter counter) {
118 this.newDuplicatedLines.incrementAll(counter.newDuplicatedLines);
119 this.newDuplicatedBlocks.incrementAll(counter.newDuplicatedBlocks);
120 this.newLines.incrementAll(counter.newLines);
124 public void initialize(CounterInitializationContext context) {
125 Component leaf = context.getLeaf();
126 context.getPeriods().forEach(period -> newLines.increment(period, 0));
127 if (leaf.getType() != Component.Type.FILE) {
128 context.getPeriods().forEach(period -> {
129 newDuplicatedLines.increment(period, 0);
130 newDuplicatedBlocks.increment(period, 0);
134 Optional<ScmInfo> scmInfo = scmInfoRepository.getScmInfo(leaf);
135 if (!scmInfo.isPresent()) {
139 initNewLines(scmInfo.get(), context.getPeriods());
140 initNewDuplicated(leaf, scmInfo.get(), context.getPeriods());
143 private void initNewLines(ScmInfo scmInfo, List<Period> periods) {
144 scmInfo.getAllChangesets().forEach(changeset -> periods.stream()
145 .filter(period -> isLineInPeriod(changeset, period))
146 .forEach(period -> newLines.increment(period, 1)));
149 private void initNewDuplicated(Component component, ScmInfo scmInfo, List<Period> periods) {
150 DuplicationCounters duplicationCounters = new DuplicationCounters(scmInfo, periods);
151 Iterable<Duplication> duplications = duplicationRepository.getDuplications(component);
152 for (Duplication duplication : duplications) {
153 duplicationCounters.addBlock(duplication.getOriginal());
154 duplication.getDuplicates().stream()
155 .filter(InnerDuplicate.class::isInstance)
156 .map(duplicate -> (InnerDuplicate) duplicate)
157 .forEach(duplicate -> duplicationCounters.addBlock(duplicate.getTextBlock()));
160 Map<Period, Integer> newLinesDuplicatedByPeriod = duplicationCounters.getNewLinesDuplicated();
161 periods.forEach(period -> {
162 newDuplicatedLines.increment(period, newLinesDuplicatedByPeriod.getOrDefault(period, 0));
163 newDuplicatedBlocks.increment(period, duplicationCounters.getNewBlocksDuplicated().getOrDefault(period, 0));
167 private static boolean isLineInPeriod(Changeset changeset, Period period) {
168 return changeset.getDate() > period.getSnapshotDate();
172 private static class DuplicationCounters {
173 private final ScmInfo scmInfo;
174 private final List<Period> periods;
175 private final SetMultimap<Period, Integer> lineCounts;
176 private final Multiset<Period> blockCounts;
178 private DuplicationCounters(ScmInfo scmInfo, List<Period> periods) {
179 this.scmInfo = scmInfo;
180 this.periods = periods;
181 this.lineCounts = LinkedHashMultimap.create(periods.size(), Iterables.size(scmInfo.getAllChangesets()));
182 this.blockCounts = HashMultiset.create();
185 void addBlock(TextBlock textBlock) {
186 Set<Period> periodWithNewCode = new HashSet<>();
187 IntStream.rangeClosed(textBlock.getStart(), textBlock.getEnd())
188 .forEach(line -> periods.stream()
189 .filter(period -> isLineInPeriod(line, period))
191 lineCounts.put(period, line);
192 periodWithNewCode.add(period);
194 blockCounts.addAll(periodWithNewCode);
197 Map<Period, Integer> getNewLinesDuplicated() {
198 return ImmutableMap.copyOf(lineCounts.keySet().stream().collect(toMap(Function.identity(), period -> lineCounts.get(period).size())));
201 Map<Period, Integer> getNewBlocksDuplicated() {
202 return blockCounts.entrySet().stream().collect(Collectors.toMap(Multiset.Entry::getElement, Multiset.Entry::getCount));
205 private boolean isLineInPeriod(int lineNumber, Period period) {
206 return scmInfo.getChangesetForLine(lineNumber).getDate() > period.getSnapshotDate();
210 private static final class NewDuplicationFormula implements Formula<NewSizeCounter> {
211 private final DuplicationRepository duplicationRepository;
212 private final ScmInfoRepository scmInfoRepository;
214 private NewDuplicationFormula(ScmInfoRepository scmInfoRepository,
215 DuplicationRepository duplicationRepository) {
216 this.duplicationRepository = duplicationRepository;
217 this.scmInfoRepository = scmInfoRepository;
221 public NewSizeCounter createNewCounter() {
222 return new NewSizeCounter(duplicationRepository, scmInfoRepository);
226 public Optional<Measure> createMeasure(NewSizeCounter counter, CreateMeasureContext context) {
227 String metricKey = context.getMetric().getKey();
230 return createMeasure(counter.newLines.toMeasureVariations());
231 case NEW_DUPLICATED_LINES_KEY:
232 return createMeasure(counter.newDuplicatedLines.toMeasureVariations());
233 case NEW_DUPLICATED_LINES_DENSITY_KEY:
234 return createNewDuplicatedLinesDensityMeasure(counter, context);
235 case NEW_BLOCKS_DUPLICATED_KEY:
236 return createMeasure(counter.newDuplicatedBlocks.toMeasureVariations());
238 throw new IllegalArgumentException("Unsupported metric " + context.getMetric());
242 private static Optional<Measure> createMeasure(Optional<MeasureVariations> measure) {
243 return measure.isPresent()
244 ? Optional.of(Measure.newMeasureBuilder().setVariations(measure.get()).createNoValue())
248 private static Optional<Measure> createNewDuplicatedLinesDensityMeasure(NewSizeCounter counter, CreateMeasureContext context) {
249 Optional<MeasureVariations> newLines = counter.newLines.toMeasureVariations();
250 Optional<MeasureVariations> newDuplicatedLines = counter.newDuplicatedLines.toMeasureVariations();
251 DoubleVariationValue.Array newDuplicatedLinesDensity = DoubleVariationValue.newArray();
252 if (newLines.isPresent() && newDuplicatedLines.isPresent()) {
253 MeasureVariations newLinesVariations = newLines.get();
254 MeasureVariations newDuplicatedLinesVariations = newDuplicatedLines.get();
255 for (Period period : context.getPeriods()) {
256 double currentNewLines = newLinesVariations.getVariation(period.getIndex());
257 if (currentNewLines > 0d) {
258 double density = Math.min(100d, 100d * newDuplicatedLinesVariations.getVariation(period.getIndex()) / currentNewLines);
259 newDuplicatedLinesDensity.increment(period, density);
263 return createMeasure(newDuplicatedLinesDensity.toMeasureVariations());
267 public String[] getOutputMetricKeys() {
268 return new String[] {NEW_LINES_KEY, NEW_DUPLICATED_LINES_KEY, NEW_DUPLICATED_LINES_DENSITY_KEY, NEW_BLOCKS_DUPLICATED_KEY};