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.formula;
22 import com.google.common.base.Optional;
23 import com.google.common.collect.ImmutableList;
24 import java.util.HashMap;
25 import java.util.List;
27 import javax.annotation.CheckForNull;
28 import org.sonar.server.computation.component.Component;
29 import org.sonar.server.computation.component.ComponentVisitor;
30 import org.sonar.server.computation.component.CrawlerDepthLimit;
31 import org.sonar.server.computation.component.PathAwareVisitorAdapter;
32 import org.sonar.server.computation.measure.Measure;
33 import org.sonar.server.computation.measure.MeasureRepository;
34 import org.sonar.server.computation.metric.Metric;
35 import org.sonar.server.computation.metric.MetricRepository;
36 import org.sonar.server.computation.period.Period;
37 import org.sonar.server.computation.period.PeriodsHolder;
39 import static java.util.Objects.requireNonNull;
41 public class FormulaExecutorComponentVisitor extends PathAwareVisitorAdapter<FormulaExecutorComponentVisitor.Counters> {
42 private static final SimpleStackElementFactory<Counters> COUNTERS_FACTORY = new SimpleStackElementFactory<Counters>() {
45 public Counters createForAny(Component component) {
46 return new Counters();
50 public Counters createForFile(Component component) {
51 // No need to create a counter on leaf levels
56 public Counters createForProjectView(Component projectView) {
57 // No need to create a counter on leaf levels
63 private final PeriodsHolder periodsHolder;
64 private final MetricRepository metricRepository;
65 private final MeasureRepository measureRepository;
66 private final List<Formula> formulas;
68 private FormulaExecutorComponentVisitor(Builder builder, Iterable<Formula> formulas) {
69 super(CrawlerDepthLimit.LEAVES, ComponentVisitor.Order.POST_ORDER, COUNTERS_FACTORY);
70 this.periodsHolder = builder.periodsHolder;
71 this.measureRepository = builder.measureRepository;
72 this.metricRepository = builder.metricRepository;
73 this.formulas = ImmutableList.copyOf(formulas);
76 public static Builder newBuilder(MetricRepository metricRepository, MeasureRepository measureRepository) {
77 return new Builder(metricRepository, measureRepository);
80 public static class Builder {
81 private final MetricRepository metricRepository;
82 private final MeasureRepository measureRepository;
84 private PeriodsHolder periodsHolder;
86 private Builder(MetricRepository metricRepository, MeasureRepository measureRepository) {
87 this.metricRepository = requireNonNull(metricRepository);
88 this.measureRepository = requireNonNull(measureRepository);
91 public Builder create(MetricRepository metricRepository, MeasureRepository measureRepository) {
92 return new Builder(metricRepository, measureRepository);
95 public Builder withVariationSupport(PeriodsHolder periodsHolder) {
96 this.periodsHolder = requireNonNull(periodsHolder);
100 public FormulaExecutorComponentVisitor buildFor(Iterable<Formula> formulas) {
101 return new FormulaExecutorComponentVisitor(this, formulas);
106 public void visitProject(Component project, Path<FormulaExecutorComponentVisitor.Counters> path) {
107 process(project, path);
111 public void visitModule(Component module, Path<FormulaExecutorComponentVisitor.Counters> path) {
112 process(module, path);
116 public void visitDirectory(Component directory, Path<FormulaExecutorComponentVisitor.Counters> path) {
117 process(directory, path);
121 public void visitFile(Component file, Path<FormulaExecutorComponentVisitor.Counters> path) {
126 public void visitView(Component view, Path<Counters> path) {
131 public void visitSubView(Component subView, Path<Counters> path) {
132 process(subView, path);
136 public void visitProjectView(Component projectView, Path<Counters> path) {
137 process(projectView, path);
140 private void process(Component component, Path<FormulaExecutorComponentVisitor.Counters> path) {
141 if (component.getChildren().isEmpty()) {
142 processLeaf(component, path);
144 processNotLeaf(component, path);
148 private void processNotLeaf(Component component, Path<FormulaExecutorComponentVisitor.Counters> path) {
149 for (Formula formula : formulas) {
150 Counter counter = path.current().getCounter(formula);
151 // If there were no file under this node, the counter won't be initialized
152 if (counter != null) {
153 for (String metricKey : formula.getOutputMetricKeys()) {
154 addNewMeasure(component, metricKey, formula, counter);
156 aggregateToParent(path, formula, counter);
161 private void processLeaf(Component file, Path<FormulaExecutorComponentVisitor.Counters> path) {
162 CounterInitializationContext counterContext = new CounterInitializationContextImpl(file);
163 for (Formula formula : formulas) {
164 Counter counter = formula.createNewCounter();
165 counter.initialize(counterContext);
166 for (String metricKey : formula.getOutputMetricKeys()) {
167 addNewMeasure(file, metricKey, formula, counter);
169 aggregateToParent(path, formula, counter);
173 private void addNewMeasure(Component component, String metricKey, Formula formula, Counter counter) {
174 // no new measure can be created by formulas for PROJECT_VIEW components, their measures are the copy
175 if (component.getType() == Component.Type.PROJECT_VIEW) {
178 Metric metric = metricRepository.getByKey(metricKey);
179 Optional<Measure> measure = formula.createMeasure(counter, new CreateMeasureContextImpl(component, metric));
180 if (measure.isPresent()) {
181 measureRepository.add(component, metric, measure.get());
185 private void aggregateToParent(Path<FormulaExecutorComponentVisitor.Counters> path, Formula formula, Counter currentCounter) {
186 if (!path.isRoot()) {
187 path.parent().aggregate(formula, currentCounter);
191 private class CounterInitializationContextImpl implements CounterInitializationContext {
192 private final Component file;
194 public CounterInitializationContextImpl(Component file) {
199 public Component getLeaf() {
204 public Optional<Measure> getMeasure(String metricKey) {
205 return measureRepository.getRawMeasure(file, metricRepository.getByKey(metricKey));
209 public List<Period> getPeriods() {
210 return periodsHolder.getPeriods();
214 public static class Counters {
215 Map<Formula, Counter> countersByFormula = new HashMap<>();
217 public void aggregate(Formula formula, Counter childCounter) {
218 Counter counter = countersByFormula.get(formula);
219 if (counter == null) {
220 countersByFormula.put(formula, childCounter);
222 counter.aggregate(childCounter);
227 * Counter can be null on a level when it has not been fed by children levels
230 public Counter getCounter(Formula formula) {
231 return countersByFormula.get(formula);
235 private class CreateMeasureContextImpl implements CreateMeasureContext {
236 private final Component component;
237 private final Metric metric;
239 public CreateMeasureContextImpl(Component component, Metric metric) {
240 this.component = component;
241 this.metric = metric;
245 public Component getComponent() {
250 public Metric getMetric() {
255 public List<Period> getPeriods() {
256 return periodsHolder.getPeriods();