diff options
author | Julien Lancelot <julien.lancelot@sonarsource.com> | 2015-07-01 18:28:51 +0200 |
---|---|---|
committer | Julien Lancelot <julien.lancelot@sonarsource.com> | 2015-07-02 13:46:26 +0200 |
commit | ca28293be85ab105f03a0ed770cdc396dab5aaf5 (patch) | |
tree | fe2b6acdd6a9d3813efea56a5276453b9d6740f4 | |
parent | 2e146c20ff74e1ca901be39a191bebcad4ddf9cb (diff) | |
download | sonarqube-ca28293be85ab105f03a0ed770cdc396dab5aaf5.tar.gz sonarqube-ca28293be85ab105f03a0ed770cdc396dab5aaf5.zip |
SONAR-6605 Create step computing formula measures
7 files changed, 493 insertions, 0 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/formula/Counter.java b/server/sonar-server/src/main/java/org/sonar/server/computation/formula/Counter.java new file mode 100644 index 00000000000..8d147410a6f --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/formula/Counter.java @@ -0,0 +1,40 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.computation.formula; + +import static org.sonar.server.computation.component.Component.Type; + +/** + * A counter is used to aggregate some data + */ +public interface Counter<T extends Counter> { + + /** + * This method is used on not {@link Type#FILE} levels, to aggregate the value of counter from a child + */ + void aggregate(T counter); + + /** + * This method is called on {@link Type#FILE} levels, in order to populate the counter with one or more {@link Type#FILE} measures. + */ + void aggregate(CounterContext counterContext); + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/formula/CounterContext.java b/server/sonar-server/src/main/java/org/sonar/server/computation/formula/CounterContext.java new file mode 100644 index 00000000000..c34ffdac8ff --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/formula/CounterContext.java @@ -0,0 +1,30 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.computation.formula; + +import com.google.common.base.Optional; +import org.sonar.server.computation.measure.Measure; + +public interface CounterContext { + + Optional<Measure> getMeasure(String metricKey); + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/formula/Formula.java b/server/sonar-server/src/main/java/org/sonar/server/computation/formula/Formula.java new file mode 100644 index 00000000000..e32c12637f0 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/formula/Formula.java @@ -0,0 +1,47 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.computation.formula; + +import com.google.common.base.Optional; +import org.sonar.server.computation.component.Component.Type; +import org.sonar.server.computation.measure.Measure; + +/** + * A formula is used to aggregated data on all nodes of a component tree + */ +public interface Formula<COUNTER extends Counter> { + + COUNTER createNewCounter(); + + /** + * This method is used to create a measure on each node, using the value of the counter + * If {@link Optional#absent()} is returned, no measure will be created + * + * @param componentType can be used for instance to not create a measure on {@link Type#FILE} + */ + Optional<Measure> createMeasure(COUNTER counter, Type componentType); + + /** + * The metric associated to the measure + */ + String getOutputMetricKey(); + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/formula/FormulaRepository.java b/server/sonar-server/src/main/java/org/sonar/server/computation/formula/FormulaRepository.java new file mode 100644 index 00000000000..a59334abb7a --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/formula/FormulaRepository.java @@ -0,0 +1,29 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.computation.formula; + +import java.util.List; + +public interface FormulaRepository { + + List<Formula> getFormulas(); + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java index a40b9e94fda..f22c71b0d5b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java @@ -54,6 +54,7 @@ public class ComputationSteps { FeedPeriodsStep.class, // data computation + ComputeFormulaMeasuresStep.class, CustomMeasuresCopyStep.class, ComputeIssueMeasuresStep.class, SqaleMeasuresStep.class, diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputeFormulaMeasuresStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputeFormulaMeasuresStep.java new file mode 100644 index 00000000000..37f64e6c62f --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputeFormulaMeasuresStep.java @@ -0,0 +1,172 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.computation.step; + +import com.google.common.base.Optional; +import java.util.HashMap; +import java.util.Map; +import org.sonar.server.computation.component.Component; +import org.sonar.server.computation.component.ComponentVisitor; +import org.sonar.server.computation.component.PathAwareVisitor; +import org.sonar.server.computation.component.TreeRootHolder; +import org.sonar.server.computation.formula.Counter; +import org.sonar.server.computation.formula.CounterContext; +import org.sonar.server.computation.formula.Formula; +import org.sonar.server.computation.formula.FormulaRepository; +import org.sonar.server.computation.measure.Measure; +import org.sonar.server.computation.measure.MeasureRepository; +import org.sonar.server.computation.metric.Metric; +import org.sonar.server.computation.metric.MetricRepository; + +public class ComputeFormulaMeasuresStep implements ComputationStep { + + private final TreeRootHolder treeRootHolder; + private final MeasureRepository measureRepository; + private final MetricRepository metricRepository; + private final FormulaRepository formulaRepository; + + public ComputeFormulaMeasuresStep(TreeRootHolder treeRootHolder, MeasureRepository measureRepository, MetricRepository metricRepository, FormulaRepository formulaRepository) { + this.treeRootHolder = treeRootHolder; + this.measureRepository = measureRepository; + this.metricRepository = metricRepository; + this.formulaRepository = formulaRepository; + } + + @Override + public void execute() { + new ComputeFormulaMeasureVisitor().visit(treeRootHolder.getRoot()); + } + + private class ComputeFormulaMeasureVisitor extends PathAwareVisitor<Counters> { + + public ComputeFormulaMeasureVisitor() { + super(Component.Type.FILE, ComponentVisitor.Order.POST_ORDER, new SimpleStackElementFactory<Counters>() { + + @Override + public Counters createForAny(Component component) { + return new Counters(); + } + + @Override + public Counters createForFile(Component component) { + // No need to create a counter on file levels + return null; + } + }); + } + + @Override + protected void visitProject(Component project, Path<Counters> path) { + processNotFile(project, path); + } + + @Override + protected void visitModule(Component module, Path<Counters> path) { + processNotFile(module, path); + } + + @Override + protected void visitDirectory(Component directory, Path<Counters> path) { + processNotFile(directory, path); + } + + @Override + protected void visitFile(Component file, Path<Counters> path) { + processFile(file, path); + } + + private void processNotFile(Component component, PathAwareVisitor.Path<Counters> path) { + for (Formula formula : formulaRepository.getFormulas()) { + Counter counter = path.current().getCounter(formula.getOutputMetricKey()); + addNewMeasure(component, path, formula, counter); + aggregateToParent(path, formula, counter); + } + } + + private void processFile(Component file, PathAwareVisitor.Path<Counters> path) { + CounterContext counterContext = new CounterContextImpl(file); + for (Formula formula : formulaRepository.getFormulas()) { + Counter counter = newCounter(formula); + counter.aggregate(counterContext); + addNewMeasure(file, path, formula, counter); + aggregateToParent(path, formula, counter); + } + } + + private void addNewMeasure(Component component, PathAwareVisitor.Path<Counters> path, Formula formula, Counter counter) { + Metric metric = metricRepository.getByKey(formula.getOutputMetricKey()); + Optional<Measure> measure = formula.createMeasure(counter, component.getType()); + if (measure.isPresent()) { + measureRepository.add(component, metric, measure.get()); + } + } + + private void aggregateToParent(PathAwareVisitor.Path<Counters> path, Formula formula, Counter currentCounter) { + if (!path.isRoot()) { + path.parent().aggregate(formula.getOutputMetricKey(), currentCounter); + } + } + } + + private static Counter newCounter(Formula formula) { + return formula.createNewCounter(); + } + + @Override + public String getDescription() { + return "Compute formula measures"; + } + + private static class Counters { + Map<String, Counter> countersByFormula = new HashMap<>(); + + public void aggregate(String metricKey, Counter childCounter) { + Counter counter = countersByFormula.get(metricKey); + if (counter == null) { + countersByFormula.put(metricKey, childCounter); + } else { + counter.aggregate(childCounter); + } + } + + public Counter getCounter(String metricKey) { + Counter counter = countersByFormula.get(metricKey); + if (counter == null) { + throw new IllegalStateException(String.format("No counter found on metric '%s'", metricKey)); + } + return counter; + } + } + + private class CounterContextImpl implements CounterContext { + + private final Component file; + + public CounterContextImpl(Component file) { + this.file = file; + } + + @Override + public Optional<Measure> getMeasure(String metricKey) { + return measureRepository.getRawMeasure(file, metricRepository.getByKey(metricKey)); + } + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputeFormulaMeasuresStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputeFormulaMeasuresStepTest.java new file mode 100644 index 00000000000..02ffc18dfd4 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputeFormulaMeasuresStepTest.java @@ -0,0 +1,174 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.computation.step; + +import com.google.common.base.Optional; +import com.google.common.collect.Lists; +import org.assertj.guava.api.Assertions; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.server.computation.batch.TreeRootHolderRule; +import org.sonar.server.computation.component.Component; +import org.sonar.server.computation.component.DumbComponent; +import org.sonar.server.computation.formula.Counter; +import org.sonar.server.computation.formula.CounterContext; +import org.sonar.server.computation.formula.Formula; +import org.sonar.server.computation.formula.FormulaRepository; +import org.sonar.server.computation.measure.Measure; +import org.sonar.server.computation.measure.MeasureRepositoryRule; +import org.sonar.server.computation.metric.MetricRepositoryRule; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.server.computation.component.Component.Type.DIRECTORY; +import static org.sonar.server.computation.component.Component.Type.MODULE; +import static org.sonar.server.computation.component.Component.Type.PROJECT; +import static org.sonar.server.computation.component.DumbComponent.builder; +import static org.sonar.server.computation.measure.Measure.newMeasureBuilder; +import static org.sonar.server.computation.measure.MeasureRepoEntry.entryOf; +import static org.sonar.server.computation.measure.MeasureRepoEntry.toEntries; + +public class ComputeFormulaMeasuresStepTest { + + @Rule + public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule(); + + @Rule + public MetricRepositoryRule metricRepository = new MetricRepositoryRule() + .add(CoreMetrics.LINES) + .add(CoreMetrics.NCLOC); + + @Rule + public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository); + + ComputeFormulaMeasuresStep sut; + + @Before + public void setUp() throws Exception { + FormulaRepository formulaRepository = mock(FormulaRepository.class); + when(formulaRepository.getFormulas()).thenReturn(Lists.<Formula>newArrayList(new FakeFormula())); + sut = new ComputeFormulaMeasuresStep(treeRootHolder, measureRepository, metricRepository, formulaRepository); + } + + @Test + public void add_measures() throws Exception { + DumbComponent project = DumbComponent.builder(PROJECT, 1) + .addChildren( + DumbComponent.builder(MODULE, 11) + .addChildren( + DumbComponent.builder(DIRECTORY, 111) + .addChildren( + builder(Component.Type.FILE, 1111).build(), + builder(Component.Type.FILE, 1112).build() + ).build() + ).build(), + DumbComponent.builder(MODULE, 12) + .addChildren( + DumbComponent.builder(DIRECTORY, 121) + .addChildren( + builder(Component.Type.FILE, 1211).build() + ).build() + ).build() + ).build(); + + treeRootHolder.setRoot(project); + + measureRepository.addRawMeasure(1111, CoreMetrics.LINES_KEY, newMeasureBuilder().create(10)); + measureRepository.addRawMeasure(1112, CoreMetrics.LINES_KEY, newMeasureBuilder().create(8)); + measureRepository.addRawMeasure(1211, CoreMetrics.LINES_KEY, newMeasureBuilder().create(2)); + + sut.execute(); + + assertThat(toEntries(measureRepository.getNewRawMeasures(1))).containsOnly(entryOf(CoreMetrics.NCLOC_KEY, newMeasureBuilder().create(20))); + assertThat(toEntries(measureRepository.getNewRawMeasures(11))).containsOnly(entryOf(CoreMetrics.NCLOC_KEY, newMeasureBuilder().create(18))); + assertThat(toEntries(measureRepository.getNewRawMeasures(111))).containsOnly(entryOf(CoreMetrics.NCLOC_KEY, newMeasureBuilder().create(18))); + assertThat(toEntries(measureRepository.getNewRawMeasures(1111))).containsOnly(entryOf(CoreMetrics.NCLOC_KEY, newMeasureBuilder().create(10))); + assertThat(toEntries(measureRepository.getNewRawMeasures(1112))).containsOnly(entryOf(CoreMetrics.NCLOC_KEY, newMeasureBuilder().create(8))); + assertThat(toEntries(measureRepository.getNewRawMeasures(12))).containsOnly(entryOf(CoreMetrics.NCLOC_KEY, newMeasureBuilder().create(2))); + assertThat(toEntries(measureRepository.getNewRawMeasures(121))).containsOnly(entryOf(CoreMetrics.NCLOC_KEY, newMeasureBuilder().create(2))); + assertThat(toEntries(measureRepository.getNewRawMeasures(1211))).containsOnly(entryOf(CoreMetrics.NCLOC_KEY, newMeasureBuilder().create(2))); + } + + @Test + public void add_no_measure() throws Exception { + DumbComponent project = DumbComponent.builder(PROJECT, 1) + .addChildren( + DumbComponent.builder(MODULE, 11) + .addChildren( + DumbComponent.builder(DIRECTORY, 111) + .addChildren( + builder(Component.Type.FILE, 1111).build() + ).build() + ).build() + ).build(); + + treeRootHolder.setRoot(project); + + sut.execute(); + + Assertions.assertThat(measureRepository.getNewRawMeasures(1)).isEmpty(); + Assertions.assertThat(measureRepository.getNewRawMeasures(11)).isEmpty(); + Assertions.assertThat(measureRepository.getNewRawMeasures(111)).isEmpty(); + Assertions.assertThat(measureRepository.getNewRawMeasures(1111)).isEmpty(); + } + + private static class FakeFormula implements Formula<FakeCounter> { + + @Override + public FakeCounter createNewCounter() { + return new FakeCounter(); + } + + @Override + public Optional<Measure> createMeasure(FakeCounter counter, Component.Type componentType) { + if (counter.value <= 0) { + return Optional.absent(); + } + return Optional.of(Measure.newMeasureBuilder().create(counter.value)); + } + + @Override + public String getOutputMetricKey() { + return CoreMetrics.NCLOC_KEY; + } + } + + private static class FakeCounter implements Counter<FakeCounter> { + + private int value = 0; + + @Override + public void aggregate(FakeCounter counter) { + this.value += counter.value; + } + + @Override + public void aggregate(CounterContext counterContext) { + Optional<Measure> measureOptional = counterContext.getMeasure(CoreMetrics.LINES_KEY); + if (measureOptional.isPresent()) { + value += measureOptional.get().getIntValue(); + } + } + } +} |