Skip to content

Commit

Permalink
add FormulaExecutorComponentVisitor + variation aggregation support
Browse files Browse the repository at this point in the history
FormulaExecutorComponentVisitor allows having any step execute Formula
Formula can now do aggregation of MeasureVariation values
also renamed FormulaRepository to CoreFormulaRepository and ComputeFormulaMeasuresStep to CoreMetricFormulaExecutorStep
  • Loading branch information
sns-seb committed Jul 9, 2015
1 parent 8f502d2 commit d603b0e
Show file tree
Hide file tree
Showing 17 changed files with 952 additions and 443 deletions.
Expand Up @@ -22,7 +22,7 @@


import java.util.List; import java.util.List;


public interface FormulaRepository { public interface CoreFormulaRepository {


List<Formula> getFormulas(); List<Formula> getFormulas();


Expand Down
Expand Up @@ -38,7 +38,7 @@
import static org.sonar.api.measures.CoreMetrics.GENERATED_NCLOC_KEY; import static org.sonar.api.measures.CoreMetrics.GENERATED_NCLOC_KEY;
import static org.sonar.api.measures.CoreMetrics.STATEMENTS_KEY; import static org.sonar.api.measures.CoreMetrics.STATEMENTS_KEY;


public class CoreFormulaRepositoryImpl implements FormulaRepository { public class CoreFormulaRepositoryImpl implements CoreFormulaRepository {


private static final List<Formula> FORMULAS = ImmutableList.<Formula>of( private static final List<Formula> FORMULAS = ImmutableList.<Formula>of(
// TODO When all decorators will be moved to CE, uncomment commented lines to activate all formulas and remove formulas declaration in // TODO When all decorators will be moved to CE, uncomment commented lines to activate all formulas and remove formulas declaration in
Expand Down
Expand Up @@ -21,10 +21,17 @@
package org.sonar.server.computation.formula; package org.sonar.server.computation.formula;


import com.google.common.base.Optional; import com.google.common.base.Optional;
import java.util.List;
import org.sonar.server.computation.measure.Measure; import org.sonar.server.computation.measure.Measure;
import org.sonar.server.computation.period.Period;


public interface CounterContext { public interface CounterContext {


Optional<Measure> getMeasure(String metricKey); Optional<Measure> getMeasure(String metricKey);


/**
* Lists of Periods defined for the current project. They can be used to aggregate values of MeasureVariations.
*/
List<Period> getPeriods();

} }
@@ -0,0 +1,192 @@
/*
* 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 com.google.common.collect.ImmutableList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.CheckForNull;
import org.sonar.server.computation.component.Component;
import org.sonar.server.computation.component.PathAwareVisitor;
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;
import org.sonar.server.computation.period.Period;
import org.sonar.server.computation.period.PeriodsHolder;

import static java.util.Objects.requireNonNull;

public class FormulaExecutorComponentVisitor extends PathAwareVisitor<FormulaExecutorComponentVisitor.Counters> {
private static final PathAwareVisitor.SimpleStackElementFactory<Counters> COUNTERS_FACTORY = new PathAwareVisitor.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;
}
};

@CheckForNull
private final PeriodsHolder periodsHolder;
private final MetricRepository metricRepository;
private final MeasureRepository measureRepository;
private final List<Formula> formulas;

private FormulaExecutorComponentVisitor(Builder builder, List<Formula> formulas) {
super(Component.Type.FILE, Order.POST_ORDER, COUNTERS_FACTORY);
this.periodsHolder = builder.periodsHolder;
this.measureRepository = builder.measureRepository;
this.metricRepository = builder.metricRepository;
this.formulas = ImmutableList.copyOf(formulas);
}

public static Builder newBuilder(MetricRepository metricRepository, MeasureRepository measureRepository) {
return new Builder(metricRepository, measureRepository);
}

public static class Builder {
private final MetricRepository metricRepository;
private final MeasureRepository measureRepository;
@CheckForNull
private PeriodsHolder periodsHolder;

private Builder(MetricRepository metricRepository, MeasureRepository measureRepository) {
this.metricRepository = requireNonNull(metricRepository);
this.measureRepository = requireNonNull(measureRepository);
}

public Builder create(MetricRepository metricRepository, MeasureRepository measureRepository) {
return new Builder(metricRepository, measureRepository);
}

public Builder withVariationSupport(PeriodsHolder periodsHolder) {
this.periodsHolder = requireNonNull(periodsHolder);
return this;
}

public FormulaExecutorComponentVisitor buildFor(List<Formula> formulas) {
return new FormulaExecutorComponentVisitor(this, formulas);
}
}



@Override
protected void visitProject(Component project, Path<FormulaExecutorComponentVisitor.Counters> path) {
processNotFile(project, path);
}

@Override
protected void visitModule(Component module, Path<FormulaExecutorComponentVisitor.Counters> path) {
processNotFile(module, path);
}

@Override
protected void visitDirectory(Component directory, Path<FormulaExecutorComponentVisitor.Counters> path) {
processNotFile(directory, path);
}

@Override
protected void visitFile(Component file, Path<FormulaExecutorComponentVisitor.Counters> path) {
processFile(file, path);
}

private void processNotFile(Component component, Path<FormulaExecutorComponentVisitor.Counters> path) {
for (Formula formula : formulas) {
Counter counter = path.current().getCounter(formula.getOutputMetricKey());
// If there were no file under this node, the counter won't be initialized
if (counter != null) {
addNewMeasure(component, formula, counter);
aggregateToParent(path, formula, counter);
}
}
}

private void processFile(Component file, Path<FormulaExecutorComponentVisitor.Counters> path) {
CounterContext counterContext = new CounterContextImpl(file);
for (Formula formula : formulas) {
Counter counter = formula.createNewCounter();
counter.aggregate(counterContext);
addNewMeasure(file, formula, counter);
aggregateToParent(path, formula, counter);
}
}

private void addNewMeasure(Component component, 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(Path<FormulaExecutorComponentVisitor.Counters> path, Formula formula, Counter currentCounter) {
if (!path.isRoot()) {
path.parent().aggregate(formula.getOutputMetricKey(), currentCounter);
}
}

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));
}

@Override
public List<Period> getPeriods() {
return periodsHolder.getPeriods();
}
}

public 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);
}
}

/**
* Counter can be null on a level when it has not been fed by children levels
*/
@CheckForNull
public Counter getCounter(String metricKey) {
return countersByFormula.get(metricKey);
}
}
}
@@ -0,0 +1,118 @@
/*
* 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.counter;

import com.google.common.base.Optional;
import javax.annotation.Nullable;
import org.sonar.server.computation.formula.Counter;
import org.sonar.server.computation.measure.MeasureVariations;
import org.sonar.server.computation.period.PeriodsHolder;

import static org.sonar.server.computation.period.PeriodsHolder.MAX_NUMBER_OF_PERIODS;

/**
* Convenience class wrapping a double to compute the value of a MeasureVariation as an double and know it is has ever
* been set.
* <p>
* Basically, this class will be used in a {@link Counter} implementation as an array property which can be easily
* creating using method {@link #newArray()}.
* </p>
*/
public class DoubleVariationValue {
private boolean set = false;
private double value = 0L;

public void increment(double increment) {
this.value += increment;
this.set = true;
}

public void increment(@Nullable DoubleVariationValue value) {
if (value != null) {
increment(value.value);
}
}

public boolean isSet() {
return set;
}

public double getValue() {
return value;
}

/**
* Creates a new Array of {@link DoubleVariationValue} of size {@link PeriodsHolder#MAX_NUMBER_OF_PERIODS},
* initialized with newly creates {@link DoubleVariationValue} instances.
*/
public static Array newArray() {
return new Array();
}

public static class Array {
private final DoubleVariationValue[] values;

public Array() {
this.values = new DoubleVariationValue[MAX_NUMBER_OF_PERIODS];
for (int i = 0; i < MAX_NUMBER_OF_PERIODS; i++) {
this.values[i] = new DoubleVariationValue();
}
}

public void increment(int index, double value) {
this.values[index].increment(value);
}

public void incrementAll(Array source) {
for (int i = 0; i < this.values.length; i++) {
if (source.values[i].isSet()) {
this.values[i].increment(source.values[i]);
}
}
}

/**
* Creates a new MeasureVariations from the current array.
*
* @throws IllegalArgumentException if none of the {@link DoubleVariationValue} in the array is set
*/
public Optional<MeasureVariations> toMeasureVariations() {
if (!isAnySet()) {
return Optional.absent();
}
Double[] variations = new Double[values.length];
for (int i = 0; i < values.length; i++) {
if (values[i].isSet()) {
variations[i] = (double) values[i].getValue();
}
}
return Optional.of(new MeasureVariations(variations));
}

private boolean isAnySet() {
for (DoubleVariationValue value : values) {
if (value.isSet()) {
return true;
}
}
return false;
}
}
}

0 comments on commit d603b0e

Please sign in to comment.