]> source.dussan.org Git - sonarqube.git/blob
54c3a77d7cd433856b511e5873664799e2e07f0f
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2016 SonarSource SA
4  * mailto:contact 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
21 package org.sonar.server.computation.task.projectanalysis.step;
22
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;
33 import java.util.Map;
34 import java.util.Set;
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;
62
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;
68
69 /**
70  * Computes measures on new code related to the size
71  */
72 public class NewSizeMeasuresStep implements ComputationStep {
73
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;
79
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);
87   }
88
89   @Override
90   public String getDescription() {
91     return "Compute size measures on new code";
92   }
93
94   @Override
95   public void execute() {
96     new PathAwareCrawler<>(
97       FormulaExecutorComponentVisitor.newBuilder(metricRepository, measureRepository)
98         .withVariationSupport(periodsHolder)
99         .buildFor(ImmutableList.of(duplicationFormula)))
100           .visit(treeRootHolder.getRoot());
101   }
102
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();
109
110     private NewSizeCounter(DuplicationRepository duplicationRepository,
111       ScmInfoRepository scmInfoRepository) {
112       this.duplicationRepository = duplicationRepository;
113       this.scmInfoRepository = scmInfoRepository;
114     }
115
116     @Override
117     public void aggregate(NewSizeCounter counter) {
118       this.newDuplicatedLines.incrementAll(counter.newDuplicatedLines);
119       this.newDuplicatedBlocks.incrementAll(counter.newDuplicatedBlocks);
120       this.newLines.incrementAll(counter.newLines);
121     }
122
123     @Override
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);
131         });
132         return;
133       }
134       Optional<ScmInfo> scmInfo = scmInfoRepository.getScmInfo(leaf);
135       if (!scmInfo.isPresent()) {
136         return;
137       }
138
139       initNewLines(scmInfo.get(), context.getPeriods());
140       initNewDuplicated(leaf, scmInfo.get(), context.getPeriods());
141     }
142
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)));
147     }
148
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()));
158       }
159
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));
164       });
165     }
166
167     private static boolean isLineInPeriod(Changeset changeset, Period period) {
168       return changeset.getDate() > period.getSnapshotDate();
169     }
170   }
171
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;
177
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();
183     }
184
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))
190           .forEach(period -> {
191             lineCounts.put(period, line);
192             periodWithNewCode.add(period);
193           }));
194       blockCounts.addAll(periodWithNewCode);
195     }
196
197     Map<Period, Integer> getNewLinesDuplicated() {
198       return ImmutableMap.copyOf(lineCounts.keySet().stream().collect(toMap(Function.identity(), period -> lineCounts.get(period).size())));
199     }
200
201     Map<Period, Integer> getNewBlocksDuplicated() {
202       return blockCounts.entrySet().stream().collect(Collectors.toMap(Multiset.Entry::getElement, Multiset.Entry::getCount));
203     }
204
205     private boolean isLineInPeriod(int lineNumber, Period period) {
206       return scmInfo.getChangesetForLine(lineNumber).getDate() > period.getSnapshotDate();
207     }
208   }
209
210   private static final class NewDuplicationFormula implements Formula<NewSizeCounter> {
211     private final DuplicationRepository duplicationRepository;
212     private final ScmInfoRepository scmInfoRepository;
213
214     private NewDuplicationFormula(ScmInfoRepository scmInfoRepository,
215       DuplicationRepository duplicationRepository) {
216       this.duplicationRepository = duplicationRepository;
217       this.scmInfoRepository = scmInfoRepository;
218     }
219
220     @Override
221     public NewSizeCounter createNewCounter() {
222       return new NewSizeCounter(duplicationRepository, scmInfoRepository);
223     }
224
225     @Override
226     public Optional<Measure> createMeasure(NewSizeCounter counter, CreateMeasureContext context) {
227       String metricKey = context.getMetric().getKey();
228       switch (metricKey) {
229         case NEW_LINES_KEY:
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());
237         default:
238           throw new IllegalArgumentException("Unsupported metric " + context.getMetric());
239       }
240     }
241
242     private static Optional<Measure> createMeasure(Optional<MeasureVariations> measure) {
243       return measure.isPresent()
244         ? Optional.of(Measure.newMeasureBuilder().setVariations(measure.get()).createNoValue())
245         : Optional.absent();
246     }
247
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);
260           }
261         }
262       }
263       return createMeasure(newDuplicatedLinesDensity.toMeasureVariations());
264     }
265
266     @Override
267     public String[] getOutputMetricKeys() {
268       return new String[] {NEW_LINES_KEY, NEW_DUPLICATED_LINES_KEY, NEW_DUPLICATED_LINES_DENSITY_KEY, NEW_BLOCKS_DUPLICATED_KEY};
269     }
270   }
271 }