aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJulien Lancelot <julien.lancelot@sonarsource.com>2015-07-01 18:28:51 +0200
committerJulien Lancelot <julien.lancelot@sonarsource.com>2015-07-02 13:46:26 +0200
commitca28293be85ab105f03a0ed770cdc396dab5aaf5 (patch)
treefe2b6acdd6a9d3813efea56a5276453b9d6740f4
parent2e146c20ff74e1ca901be39a191bebcad4ddf9cb (diff)
downloadsonarqube-ca28293be85ab105f03a0ed770cdc396dab5aaf5.tar.gz
sonarqube-ca28293be85ab105f03a0ed770cdc396dab5aaf5.zip
SONAR-6605 Create step computing formula measures
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/formula/Counter.java40
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/formula/CounterContext.java30
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/formula/Formula.java47
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/formula/FormulaRepository.java29
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java1
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputeFormulaMeasuresStep.java172
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputeFormulaMeasuresStepTest.java174
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();
+ }
+ }
+ }
+}