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.ImmutableList;
25 import com.google.common.collect.Iterables;
26 import java.util.HashSet;
28 import java.util.stream.IntStream;
29 import java.util.stream.StreamSupport;
30 import org.sonar.server.computation.task.projectanalysis.component.Component;
31 import org.sonar.server.computation.task.projectanalysis.component.PathAwareCrawler;
32 import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolder;
33 import org.sonar.server.computation.task.projectanalysis.duplication.Duplication;
34 import org.sonar.server.computation.task.projectanalysis.duplication.DuplicationRepository;
35 import org.sonar.server.computation.task.projectanalysis.duplication.InnerDuplicate;
36 import org.sonar.server.computation.task.projectanalysis.duplication.TextBlock;
37 import org.sonar.server.computation.task.projectanalysis.formula.Counter;
38 import org.sonar.server.computation.task.projectanalysis.formula.CounterInitializationContext;
39 import org.sonar.server.computation.task.projectanalysis.formula.CreateMeasureContext;
40 import org.sonar.server.computation.task.projectanalysis.formula.Formula;
41 import org.sonar.server.computation.task.projectanalysis.formula.FormulaExecutorComponentVisitor;
42 import org.sonar.server.computation.task.projectanalysis.formula.counter.IntValue;
43 import org.sonar.server.computation.task.projectanalysis.measure.Measure;
44 import org.sonar.server.computation.task.projectanalysis.measure.MeasureRepository;
45 import org.sonar.server.computation.task.projectanalysis.metric.MetricRepository;
46 import org.sonar.server.computation.task.projectanalysis.period.Period;
47 import org.sonar.server.computation.task.projectanalysis.period.PeriodsHolder;
48 import org.sonar.server.computation.task.projectanalysis.scm.Changeset;
49 import org.sonar.server.computation.task.projectanalysis.scm.ScmInfo;
50 import org.sonar.server.computation.task.projectanalysis.scm.ScmInfoRepository;
51 import org.sonar.server.computation.task.step.ComputationStep;
53 import static org.sonar.api.measures.CoreMetrics.NEW_BLOCKS_DUPLICATED_KEY;
54 import static org.sonar.api.measures.CoreMetrics.NEW_DUPLICATED_LINES_DENSITY_KEY;
55 import static org.sonar.api.measures.CoreMetrics.NEW_DUPLICATED_LINES_KEY;
56 import static org.sonar.api.measures.CoreMetrics.NEW_LINES_KEY;
59 * Computes measures on new code related to the size
61 public class NewSizeMeasuresStep implements ComputationStep {
63 private final TreeRootHolder treeRootHolder;
64 private final PeriodsHolder periodsHolder;
65 private final MetricRepository metricRepository;
66 private final MeasureRepository measureRepository;
67 private final NewDuplicationFormula duplicationFormula;
69 public NewSizeMeasuresStep(TreeRootHolder treeRootHolder, PeriodsHolder periodsHolder, MetricRepository metricRepository, MeasureRepository measureRepository,
70 ScmInfoRepository scmInfoRepository, DuplicationRepository duplicationRepository) {
71 this.treeRootHolder = treeRootHolder;
72 this.periodsHolder = periodsHolder;
73 this.metricRepository = metricRepository;
74 this.measureRepository = measureRepository;
75 this.duplicationFormula = new NewDuplicationFormula(scmInfoRepository, duplicationRepository);
79 public String getDescription() {
80 return "Compute size measures on new code";
84 public void execute() {
85 new PathAwareCrawler<>(
86 FormulaExecutorComponentVisitor.newBuilder(metricRepository, measureRepository)
87 .withVariationSupport(periodsHolder)
88 .buildFor(ImmutableList.of(duplicationFormula)))
89 .visit(treeRootHolder.getRoot());
92 private static class NewSizeCounter implements Counter<NewSizeCounter> {
93 private final DuplicationRepository duplicationRepository;
94 private final ScmInfoRepository scmInfoRepository;
95 private final IntValue newLines = new IntValue();
96 private final IntValue newDuplicatedLines = new IntValue();
97 private final IntValue newDuplicatedBlocks = new IntValue();
99 private NewSizeCounter(DuplicationRepository duplicationRepository,
100 ScmInfoRepository scmInfoRepository) {
101 this.duplicationRepository = duplicationRepository;
102 this.scmInfoRepository = scmInfoRepository;
106 public void aggregate(NewSizeCounter counter) {
107 this.newDuplicatedLines.increment(counter.newDuplicatedLines);
108 this.newDuplicatedBlocks.increment(counter.newDuplicatedBlocks);
109 this.newLines.increment(counter.newLines);
113 public void initialize(CounterInitializationContext context) {
114 Component leaf = context.getLeaf();
115 Optional<ScmInfo> scmInfo = scmInfoRepository.getScmInfo(leaf);
116 if (!scmInfo.isPresent() || !context.hasPeriod()) {
120 newLines.increment(0);
121 if (leaf.getType() != Component.Type.FILE) {
122 newDuplicatedLines.increment(0);
123 newDuplicatedBlocks.increment(0);
127 initNewLines(scmInfo.get(), context.getPeriod());
128 initNewDuplicated(leaf, scmInfo.get(), context.getPeriod());
131 private void initNewLines(ScmInfo scmInfo, Period period) {
132 StreamSupport.stream(scmInfo.getAllChangesets().spliterator(), false)
133 .filter(changeset -> isLineInPeriod(changeset, period))
134 .forEach(changeset -> newLines.increment(1));
137 private void initNewDuplicated(Component component, ScmInfo scmInfo, Period period) {
138 DuplicationCounters duplicationCounters = new DuplicationCounters(scmInfo, period);
139 Iterable<Duplication> duplications = duplicationRepository.getDuplications(component);
140 for (Duplication duplication : duplications) {
141 duplicationCounters.addBlock(duplication.getOriginal());
142 duplication.getDuplicates().stream()
143 .filter(InnerDuplicate.class::isInstance)
144 .map(duplicate -> (InnerDuplicate) duplicate)
145 .forEach(duplicate -> duplicationCounters.addBlock(duplicate.getTextBlock()));
148 newDuplicatedLines.increment(duplicationCounters.getNewLinesDuplicated());
149 newDuplicatedBlocks.increment(duplicationCounters.getNewBlocksDuplicated());
152 private static boolean isLineInPeriod(Changeset changeset, Period period) {
153 return changeset.getDate() > period.getSnapshotDate();
157 private static class DuplicationCounters {
158 private final ScmInfo scmInfo;
159 private final Period period;
160 private final Set<Integer> lineCounts;
161 private int blockCounts;
163 private DuplicationCounters(ScmInfo scmInfo, Period period) {
164 this.scmInfo = scmInfo;
165 this.period = period;
166 this.lineCounts = new HashSet<>(Iterables.size(scmInfo.getAllChangesets()));
169 void addBlock(TextBlock textBlock) {
170 Boolean[] newBlock = new Boolean[] {false};
171 IntStream.rangeClosed(textBlock.getStart(), textBlock.getEnd())
172 .filter(line -> isLineInPeriod(line, period))
174 lineCounts.add(line);
182 int getNewLinesDuplicated() {
183 return lineCounts.size();
186 int getNewBlocksDuplicated() {
190 private boolean isLineInPeriod(int lineNumber, Period period) {
191 return scmInfo.getChangesetForLine(lineNumber).getDate() > period.getSnapshotDate();
195 private static final class NewDuplicationFormula implements Formula<NewSizeCounter> {
196 private final DuplicationRepository duplicationRepository;
197 private final ScmInfoRepository scmInfoRepository;
199 private NewDuplicationFormula(ScmInfoRepository scmInfoRepository,
200 DuplicationRepository duplicationRepository) {
201 this.duplicationRepository = duplicationRepository;
202 this.scmInfoRepository = scmInfoRepository;
206 public NewSizeCounter createNewCounter() {
207 return new NewSizeCounter(duplicationRepository, scmInfoRepository);
211 public Optional<Measure> createMeasure(NewSizeCounter counter, CreateMeasureContext context) {
212 String metricKey = context.getMetric().getKey();
215 return createMeasure(counter.newLines);
216 case NEW_DUPLICATED_LINES_KEY:
217 return createMeasure(counter.newDuplicatedLines);
218 case NEW_DUPLICATED_LINES_DENSITY_KEY:
219 return createNewDuplicatedLinesDensityMeasure(counter);
220 case NEW_BLOCKS_DUPLICATED_KEY:
221 return createMeasure(counter.newDuplicatedBlocks);
223 throw new IllegalArgumentException("Unsupported metric " + context.getMetric());
227 private static Optional<Measure> createMeasure(IntValue intValue) {
228 return intValue.isSet()
229 ? Optional.of(Measure.newMeasureBuilder().setVariation(intValue.getValue()).createNoValue())
233 private static Optional<Measure> createNewDuplicatedLinesDensityMeasure(NewSizeCounter counter) {
234 IntValue newLines = counter.newLines;
235 IntValue newDuplicatedLines = counter.newDuplicatedLines;
236 if (newLines.isSet() && newDuplicatedLines.isSet()) {
237 int newLinesVariations = newLines.getValue();
238 int newDuplicatedLinesVariations = newDuplicatedLines.getValue();
239 if (newLinesVariations > 0d) {
240 double density = Math.min(100d, 100d * newDuplicatedLinesVariations / newLinesVariations);
241 return Optional.of(Measure.newMeasureBuilder().setVariation(density).createNoValue());
244 return Optional.absent();
248 public String[] getOutputMetricKeys() {
249 return new String[] {NEW_LINES_KEY, NEW_DUPLICATED_LINES_KEY, NEW_DUPLICATED_LINES_DENSITY_KEY, NEW_BLOCKS_DUPLICATED_KEY};