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 com.google.common.collect.ImmutableList;
23 import java.util.Optional;
24 import org.sonar.ce.task.projectanalysis.component.CrawlerDepthLimit;
25 import org.sonar.ce.task.projectanalysis.component.PathAwareCrawler;
26 import org.sonar.ce.task.projectanalysis.component.TreeRootHolder;
27 import org.sonar.ce.task.projectanalysis.formula.Counter;
28 import org.sonar.ce.task.projectanalysis.formula.CounterInitializationContext;
29 import org.sonar.ce.task.projectanalysis.formula.CreateMeasureContext;
30 import org.sonar.ce.task.projectanalysis.formula.Formula;
31 import org.sonar.ce.task.projectanalysis.formula.FormulaExecutorComponentVisitor;
32 import org.sonar.ce.task.projectanalysis.formula.counter.IntSumCounter;
33 import org.sonar.ce.task.projectanalysis.formula.counter.SumCounter;
34 import org.sonar.ce.task.projectanalysis.measure.Measure;
35 import org.sonar.ce.task.projectanalysis.measure.MeasureRepository;
36 import org.sonar.ce.task.projectanalysis.metric.Metric;
37 import org.sonar.ce.task.projectanalysis.metric.MetricRepository;
38 import org.sonar.ce.task.step.ComputationStep;
40 import static org.sonar.api.measures.CoreMetrics.COMMENT_LINES_DENSITY_KEY;
41 import static org.sonar.api.measures.CoreMetrics.COMMENT_LINES_KEY;
42 import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY;
43 import static org.sonar.api.measures.CoreMetrics.PUBLIC_API_KEY;
44 import static org.sonar.api.measures.CoreMetrics.PUBLIC_DOCUMENTED_API_DENSITY_KEY;
45 import static org.sonar.api.measures.CoreMetrics.PUBLIC_UNDOCUMENTED_API_KEY;
48 * Computes comments measures on files and then aggregates them on higher components.
50 public class CommentMeasuresStep implements ComputationStep {
52 private final TreeRootHolder treeRootHolder;
53 private final MetricRepository metricRepository;
54 private final MeasureRepository measureRepository;
55 private final ImmutableList<Formula> formulas;
57 public CommentMeasuresStep(TreeRootHolder treeRootHolder, MetricRepository metricRepository, MeasureRepository measureRepository) {
58 this.treeRootHolder = treeRootHolder;
59 this.metricRepository = metricRepository;
60 this.measureRepository = measureRepository;
61 this.formulas = ImmutableList.of(
62 new DocumentationFormula(),
63 new CommentDensityFormula());
67 public void execute(ComputationStep.Context context) {
68 new PathAwareCrawler<>(
69 FormulaExecutorComponentVisitor.newBuilder(metricRepository, measureRepository).buildFor(formulas))
70 .visit(treeRootHolder.getRoot());
73 private class CommentDensityFormula implements Formula<IntSumCounter> {
75 private final Metric nclocMetric;
77 public CommentDensityFormula() {
78 this.nclocMetric = metricRepository.getByKey(NCLOC_KEY);
82 public IntSumCounter createNewCounter() {
83 return new IntSumCounter(COMMENT_LINES_KEY);
87 public Optional<Measure> createMeasure(IntSumCounter counter, CreateMeasureContext context) {
88 Optional<Measure> measure = createCommentLinesMeasure(counter, context);
89 return measure.isPresent() ? measure : createCommentLinesDensityMeasure(counter, context);
92 private Optional<Measure> createCommentLinesMeasure(SumCounter counter, CreateMeasureContext context) {
93 Optional<Integer> commentLines = counter.getValue();
94 if (COMMENT_LINES_KEY.equals(context.getMetric().getKey())
95 && commentLines.isPresent()
96 && CrawlerDepthLimit.LEAVES.isDeeperThan(context.getComponent().getType())) {
97 return Optional.of(Measure.newMeasureBuilder().create(commentLines.get()));
99 return Optional.empty();
102 private Optional<Measure> createCommentLinesDensityMeasure(SumCounter counter, CreateMeasureContext context) {
103 if (COMMENT_LINES_DENSITY_KEY.equals(context.getMetric().getKey())) {
104 Optional<Measure> nclocsOpt = measureRepository.getRawMeasure(context.getComponent(), nclocMetric);
105 Optional<Integer> commentsOpt = counter.getValue();
106 if (nclocsOpt.isPresent() && commentsOpt.isPresent()) {
107 double nclocs = nclocsOpt.get().getIntValue();
108 double comments = commentsOpt.get();
109 double divisor = nclocs + comments;
111 double value = 100d * (comments / divisor);
112 return Optional.of(Measure.newMeasureBuilder().create(value, context.getMetric().getDecimalScale()));
116 return Optional.empty();
120 public String[] getOutputMetricKeys() {
121 return new String[] {COMMENT_LINES_KEY, COMMENT_LINES_DENSITY_KEY};
125 private static class DocumentationFormula implements Formula<DocumentationCounter> {
128 public DocumentationCounter createNewCounter() {
129 return new DocumentationCounter();
133 public Optional<Measure> createMeasure(DocumentationCounter counter, CreateMeasureContext context) {
134 Optional<Measure> measure = getMeasure(context, counter.getPublicApiValue(), PUBLIC_API_KEY);
135 if (measure.isPresent()) {
138 measure = getMeasure(context, counter.getPublicUndocumentedApiValue(), PUBLIC_UNDOCUMENTED_API_KEY);
139 return measure.isPresent() ? measure : getDensityMeasure(counter, context);
142 private static Optional<Measure> getMeasure(CreateMeasureContext context, Optional<Integer> metricValue, String metricKey) {
143 if (context.getMetric().getKey().equals(metricKey) && metricValue.isPresent()
144 && CrawlerDepthLimit.LEAVES.isDeeperThan(context.getComponent().getType())) {
145 return Optional.of(Measure.newMeasureBuilder().create(metricValue.get()));
147 return Optional.empty();
150 private static Optional<Measure> getDensityMeasure(DocumentationCounter counter, CreateMeasureContext context) {
151 if (context.getMetric().getKey().equals(PUBLIC_DOCUMENTED_API_DENSITY_KEY) && counter.getPublicApiValue().isPresent()
152 && counter.getPublicUndocumentedApiValue().isPresent()) {
153 double publicApis = counter.getPublicApiValue().get();
154 double publicUndocumentedApis = counter.getPublicUndocumentedApiValue().get();
155 if (publicApis > 0d) {
156 double documentedAPI = publicApis - publicUndocumentedApis;
157 double value = 100d * (documentedAPI / publicApis);
158 return Optional.of(Measure.newMeasureBuilder().create(value, context.getMetric().getDecimalScale()));
161 return Optional.empty();
165 public String[] getOutputMetricKeys() {
166 return new String[] {PUBLIC_API_KEY, PUBLIC_UNDOCUMENTED_API_KEY, PUBLIC_DOCUMENTED_API_DENSITY_KEY};
170 private static class DocumentationCounter implements Counter<DocumentationCounter> {
172 private final SumCounter publicApiCounter;
173 private final SumCounter publicUndocumentedApiCounter;
175 public DocumentationCounter() {
176 this.publicApiCounter = new IntSumCounter(PUBLIC_API_KEY);
177 this.publicUndocumentedApiCounter = new IntSumCounter(PUBLIC_UNDOCUMENTED_API_KEY);
181 public void aggregate(DocumentationCounter counter) {
182 publicApiCounter.aggregate(counter.publicApiCounter);
183 publicUndocumentedApiCounter.aggregate(counter.publicUndocumentedApiCounter);
187 public void initialize(CounterInitializationContext context) {
188 publicApiCounter.initialize(context);
189 publicUndocumentedApiCounter.initialize(context);
192 public Optional<Integer> getPublicApiValue() {
193 return publicApiCounter.getValue();
196 public Optional<Integer> getPublicUndocumentedApiValue() {
197 return publicUndocumentedApiCounter.getValue();
202 public String getDescription() {
203 return "Compute comment measures";