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.List;
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 List<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 = List.of(
62 new DocumentationFormula(),
63 new CommentDensityFormula()
68 public void execute(ComputationStep.Context context) {
69 new PathAwareCrawler<>(
70 FormulaExecutorComponentVisitor.newBuilder(metricRepository, measureRepository).buildFor(formulas))
71 .visit(treeRootHolder.getRoot());
74 private class CommentDensityFormula implements Formula<IntSumCounter> {
76 private final Metric nclocMetric;
78 public CommentDensityFormula() {
79 this.nclocMetric = metricRepository.getByKey(NCLOC_KEY);
83 public IntSumCounter createNewCounter() {
84 return new IntSumCounter(COMMENT_LINES_KEY);
88 public Optional<Measure> createMeasure(IntSumCounter counter, CreateMeasureContext context) {
89 Optional<Measure> measure = createCommentLinesMeasure(counter, context);
90 return measure.isPresent() ? measure : createCommentLinesDensityMeasure(counter, context);
93 private Optional<Measure> createCommentLinesMeasure(SumCounter counter, CreateMeasureContext context) {
94 Optional<Integer> commentLines = counter.getValue();
95 if (COMMENT_LINES_KEY.equals(context.getMetric().getKey())
96 && commentLines.isPresent()
97 && CrawlerDepthLimit.LEAVES.isDeeperThan(context.getComponent().getType())) {
98 return Optional.of(Measure.newMeasureBuilder().create(commentLines.get()));
100 return Optional.empty();
103 private Optional<Measure> createCommentLinesDensityMeasure(SumCounter counter, CreateMeasureContext context) {
104 if (COMMENT_LINES_DENSITY_KEY.equals(context.getMetric().getKey())) {
105 Optional<Measure> nclocsOpt = measureRepository.getRawMeasure(context.getComponent(), nclocMetric);
106 Optional<Integer> commentsOpt = counter.getValue();
107 if (nclocsOpt.isPresent() && commentsOpt.isPresent()) {
108 double nclocs = nclocsOpt.get().getIntValue();
109 double comments = commentsOpt.get();
110 double divisor = nclocs + comments;
112 double value = 100d * (comments / divisor);
113 return Optional.of(Measure.newMeasureBuilder().create(value, context.getMetric().getDecimalScale()));
117 return Optional.empty();
121 public String[] getOutputMetricKeys() {
122 return new String[] {COMMENT_LINES_KEY, COMMENT_LINES_DENSITY_KEY};
126 private static class DocumentationFormula implements Formula<DocumentationCounter> {
129 public DocumentationCounter createNewCounter() {
130 return new DocumentationCounter();
134 public Optional<Measure> createMeasure(DocumentationCounter counter, CreateMeasureContext context) {
135 Optional<Measure> measure = getMeasure(context, counter.getPublicApiValue(), PUBLIC_API_KEY);
136 if (measure.isPresent()) {
139 measure = getMeasure(context, counter.getPublicUndocumentedApiValue(), PUBLIC_UNDOCUMENTED_API_KEY);
140 return measure.isPresent() ? measure : getDensityMeasure(counter, context);
143 private static Optional<Measure> getMeasure(CreateMeasureContext context, Optional<Integer> metricValue, String metricKey) {
144 if (context.getMetric().getKey().equals(metricKey) && metricValue.isPresent()
145 && CrawlerDepthLimit.LEAVES.isDeeperThan(context.getComponent().getType())) {
146 return Optional.of(Measure.newMeasureBuilder().create(metricValue.get()));
148 return Optional.empty();
151 private static Optional<Measure> getDensityMeasure(DocumentationCounter counter, CreateMeasureContext context) {
152 if (context.getMetric().getKey().equals(PUBLIC_DOCUMENTED_API_DENSITY_KEY) && counter.getPublicApiValue().isPresent()
153 && counter.getPublicUndocumentedApiValue().isPresent()) {
154 double publicApis = counter.getPublicApiValue().get();
155 double publicUndocumentedApis = counter.getPublicUndocumentedApiValue().get();
156 if (publicApis > 0d) {
157 double documentedAPI = publicApis - publicUndocumentedApis;
158 double value = 100d * (documentedAPI / publicApis);
159 return Optional.of(Measure.newMeasureBuilder().create(value, context.getMetric().getDecimalScale()));
162 return Optional.empty();
166 public String[] getOutputMetricKeys() {
167 return new String[] {PUBLIC_API_KEY, PUBLIC_UNDOCUMENTED_API_KEY, PUBLIC_DOCUMENTED_API_DENSITY_KEY};
171 private static class DocumentationCounter implements Counter<DocumentationCounter> {
173 private final SumCounter publicApiCounter;
174 private final SumCounter publicUndocumentedApiCounter;
176 public DocumentationCounter() {
177 this.publicApiCounter = new IntSumCounter(PUBLIC_API_KEY);
178 this.publicUndocumentedApiCounter = new IntSumCounter(PUBLIC_UNDOCUMENTED_API_KEY);
182 public void aggregate(DocumentationCounter counter) {
183 publicApiCounter.aggregate(counter.publicApiCounter);
184 publicUndocumentedApiCounter.aggregate(counter.publicUndocumentedApiCounter);
188 public void initialize(CounterInitializationContext context) {
189 publicApiCounter.initialize(context);
190 publicUndocumentedApiCounter.initialize(context);
193 public Optional<Integer> getPublicApiValue() {
194 return publicApiCounter.getValue();
197 public Optional<Integer> getPublicUndocumentedApiValue() {
198 return publicUndocumentedApiCounter.getValue();
203 public String getDescription() {
204 return "Compute comment measures";