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.
20 package org.sonar.server.computation.task.projectanalysis.step;
22 import com.google.common.base.Function;
23 import com.google.common.base.Optional;
24 import com.google.common.base.Predicate;
25 import java.util.Collection;
26 import java.util.HashMap;
27 import java.util.List;
30 import javax.annotation.CheckForNull;
31 import javax.annotation.Nonnull;
32 import javax.annotation.Nullable;
33 import org.sonar.db.DbClient;
34 import org.sonar.db.DbSession;
35 import org.sonar.db.measure.PastMeasureDto;
36 import org.sonar.server.computation.task.projectanalysis.component.Component;
37 import org.sonar.server.computation.task.projectanalysis.component.CrawlerDepthLimit;
38 import org.sonar.server.computation.task.projectanalysis.component.DepthTraversalTypeAwareCrawler;
39 import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolder;
40 import org.sonar.server.computation.task.projectanalysis.component.TypeAwareVisitorAdapter;
41 import org.sonar.server.computation.task.projectanalysis.measure.Measure;
42 import org.sonar.server.computation.task.projectanalysis.measure.MeasureKey;
43 import org.sonar.server.computation.task.projectanalysis.measure.MeasureRepository;
44 import org.sonar.server.computation.task.projectanalysis.measure.MeasureVariations;
45 import org.sonar.server.computation.task.projectanalysis.metric.Metric;
46 import org.sonar.server.computation.task.projectanalysis.metric.MetricRepository;
47 import org.sonar.server.computation.task.projectanalysis.period.Period;
48 import org.sonar.server.computation.task.projectanalysis.period.PeriodsHolder;
49 import org.sonar.server.computation.task.step.ComputationStep;
51 import static com.google.common.base.Preconditions.checkArgument;
52 import static com.google.common.collect.FluentIterable.from;
53 import static java.lang.String.format;
54 import static org.sonar.server.computation.task.projectanalysis.component.Component.Type.DIRECTORY;
55 import static org.sonar.server.computation.task.projectanalysis.component.Component.Type.SUBVIEW;
56 import static org.sonar.server.computation.task.projectanalysis.component.ComponentVisitor.Order.PRE_ORDER;
59 * Set variations on all numeric measures found in the repository.
60 * This step MUST be executed after all steps that create some measures
62 * Note that measures on developer are not handle yet.
64 public class ComputeMeasureVariationsStep implements ComputationStep {
66 private final DbClient dbClient;
67 private final TreeRootHolder treeRootHolder;
68 private final PeriodsHolder periodsHolder;
69 private final MetricRepository metricRepository;
70 private final MeasureRepository measureRepository;
72 private final Function<PastMeasureDto, MeasureKey> pastMeasureToMeasureKey = new Function<PastMeasureDto, MeasureKey>() {
75 public MeasureKey apply(@Nonnull PastMeasureDto input) {
76 Metric metric = metricRepository.getById((long)input.getMetricId());
77 return new MeasureKey(metric.getKey(), null);
81 public ComputeMeasureVariationsStep(DbClient dbClient, TreeRootHolder treeRootHolder, PeriodsHolder periodsHolder, MetricRepository metricRepository,
82 MeasureRepository measureRepository) {
83 this.dbClient = dbClient;
84 this.treeRootHolder = treeRootHolder;
85 this.periodsHolder = periodsHolder;
86 this.metricRepository = metricRepository;
87 this.measureRepository = measureRepository;
91 public void execute() {
92 DbSession dbSession = dbClient.openSession(false);
94 List<Metric> metrics = from(metricRepository.getAll()).filter(NumericMetric.INSTANCE).toList();
95 new DepthTraversalTypeAwareCrawler(new VariationMeasuresVisitor(dbSession, metrics))
96 .visit(treeRootHolder.getRoot());
98 dbClient.closeSession(dbSession);
102 private class VariationMeasuresVisitor extends TypeAwareVisitorAdapter {
104 private final DbSession session;
105 private final Set<Integer> metricIds;
106 private final List<Metric> metrics;
108 VariationMeasuresVisitor(DbSession session, List<Metric> metrics) {
109 // measures on files are currently purged, so past measures are not available on files
110 super(CrawlerDepthLimit.reportMaxDepth(DIRECTORY).withViewsMaxDepth(SUBVIEW), PRE_ORDER);
111 this.session = session;
112 this.metricIds = from(metrics).transform(MetricDtoToMetricId.INSTANCE).toSet();
113 this.metrics = metrics;
117 public void visitAny(Component component) {
118 MeasuresWithVariationRepository measuresWithVariationRepository = computeMeasuresWithVariations(component);
119 processMeasuresWithVariation(component, measuresWithVariationRepository);
122 private MeasuresWithVariationRepository computeMeasuresWithVariations(Component component) {
123 MeasuresWithVariationRepository measuresWithVariationRepository = new MeasuresWithVariationRepository();
124 for (Period period : periodsHolder.getPeriods()) {
125 List<PastMeasureDto> pastMeasures = dbClient.measureDao()
126 .selectPastMeasures(session, component.getUuid(), period.getAnalysisUuid(), metricIds);
127 setVariationMeasures(component, pastMeasures, period.getIndex(), measuresWithVariationRepository);
129 return measuresWithVariationRepository;
132 private void setVariationMeasures(Component component, List<PastMeasureDto> pastMeasures, int period, MeasuresWithVariationRepository measuresWithVariationRepository) {
133 Map<MeasureKey, PastMeasureDto> pastMeasuresByMeasureKey = from(pastMeasures).uniqueIndex(pastMeasureToMeasureKey);
134 for (Metric metric : metrics) {
135 Optional<Measure> measure = measureRepository.getRawMeasure(component, metric);
136 if (measure.isPresent() && !measure.get().hasVariations()) {
137 PastMeasureDto pastMeasure = pastMeasuresByMeasureKey.get(new MeasureKey(metric.getKey(), null));
138 double pastValue = (pastMeasure != null && pastMeasure.hasValue()) ? pastMeasure.getValue() : 0d;
139 measuresWithVariationRepository.add(metric, measure.get(), period, computeVariation(measure.get(), pastValue));
144 private double computeVariation(Measure measure, double pastValue) {
145 switch (measure.getValueType()) {
147 return measure.getIntValue() - pastValue;
149 return measure.getLongValue() - pastValue;
151 return measure.getDoubleValue() - pastValue;
153 return (measure.getBooleanValue() ? 1d : 0d) - pastValue;
155 throw new IllegalArgumentException(format("Unsupported Measure.ValueType on measure '%s'", measure));
159 private void processMeasuresWithVariation(Component component, MeasuresWithVariationRepository measuresWithVariationRepository) {
160 for (MeasureWithVariations measureWithVariations : measuresWithVariationRepository.measures()) {
161 Metric metric = measureWithVariations.getMetric();
162 Measure measure = Measure.updatedMeasureBuilder(measureWithVariations.getMeasure())
163 .setVariations(new MeasureVariations(
164 measureWithVariations.getVariation(1),
165 measureWithVariations.getVariation(2),
166 measureWithVariations.getVariation(3),
167 measureWithVariations.getVariation(4),
168 measureWithVariations.getVariation(5)))
170 measureRepository.update(component, metric, measure);
175 private static final class MeasuresWithVariationRepository {
177 private final Map<MeasureKey, MeasureWithVariations> measuresWithVariations = new HashMap<>();
179 public void add(Metric metric, final Measure measure, int variationIndex, double variationValue) {
180 checkArgument(measure.getDeveloper() == null, "%s does not support computing variations of Measures for Developer", getClass().getSimpleName());
181 MeasureKey measureKey = new MeasureKey(metric.getKey(), null);
182 MeasureWithVariations measureWithVariations = measuresWithVariations.get(measureKey);
183 if (measureWithVariations == null) {
184 measureWithVariations = new MeasureWithVariations(metric, measure);
185 measuresWithVariations.put(measureKey, measureWithVariations);
187 measureWithVariations.setVariation(variationIndex, variationValue);
190 public Collection<MeasureWithVariations> measures() {
191 return measuresWithVariations.values();
195 private static final class MeasureWithVariations {
196 private final Metric metric;
197 private final Measure measure;
198 private final Double[] variations = new Double[5];
200 MeasureWithVariations(Metric metric, Measure measure) {
201 this.metric = metric;
202 this.measure = measure;
205 public Measure getMeasure() {
209 public Metric getMetric() {
213 public void setVariation(int index, @Nullable Double value) {
214 variations[index - 1] = value;
218 public Double getVariation(int index) {
219 return variations[index - 1];
223 private enum MetricDtoToMetricId implements Function<Metric, Integer> {
228 public Integer apply(@Nonnull Metric metric) {
229 return metric.getId();
233 private enum NumericMetric implements Predicate<Metric> {
237 public boolean apply(@Nonnull Metric metric) {
238 Measure.ValueType valueType = metric.getType().getValueType();
239 return Measure.ValueType.INT.equals(valueType)
240 || Measure.ValueType.LONG.equals(valueType)
241 || Measure.ValueType.DOUBLE.equals(valueType)
242 || Measure.ValueType.BOOLEAN.equals(valueType);
247 public String getDescription() {
248 return "Compute measure variations";